Full Code of BoostIO/BoostNote-Legacy for AI

master 789926bc7606 cached
367 files
4.3 MB
1.2M tokens
982 symbols
1 requests
Download .txt
Showing preview only (4,606K chars total). Download the full file or copy to clipboard to get everything.
Repository: BoostIO/BoostNote-Legacy
Branch: master
Commit: 789926bc7606
Files: 367
Total size: 4.3 MB

Directory structure:
gitextract_1f_tfmxn/

├── .babelrc
├── .boostnoterc.sample
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .prettierrc
├── .snapcraft/
│   └── travis_snapcraft.cfg
├── .travis.yml
├── FAQ.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── PULL_REQUEST_TEMPLATE.md
├── __mocks__/
│   └── electron.js
├── appdmg.json
├── browser/
│   ├── components/
│   │   ├── CodeEditor.js
│   │   ├── CodeEditor.styl
│   │   ├── ColorPicker.js
│   │   ├── ColorPicker.styl
│   │   ├── MarkdownEditor.js
│   │   ├── MarkdownEditor.styl
│   │   ├── MarkdownPreview.js
│   │   ├── MarkdownSplitEditor.js
│   │   ├── MarkdownSplitEditor.styl
│   │   ├── ModalEscButton.js
│   │   ├── ModalEscButton.styl
│   │   ├── NavToggleButton.js
│   │   ├── NavToggleButton.styl
│   │   ├── NoteItem.js
│   │   ├── NoteItem.styl
│   │   ├── NoteItemSimple.js
│   │   ├── NoteItemSimple.styl
│   │   ├── RealtimeNotification.js
│   │   ├── RealtimeNotification.styl
│   │   ├── SideNavFilter.js
│   │   ├── SideNavFilter.styl
│   │   ├── SnippetTab.js
│   │   ├── SnippetTab.styl
│   │   ├── StorageItem.js
│   │   ├── StorageItem.styl
│   │   ├── StorageList.js
│   │   ├── StorageList.styl
│   │   ├── TagListItem.js
│   │   ├── TagListItem.styl
│   │   ├── TodoListPercentage.js
│   │   ├── TodoListPercentage.styl
│   │   ├── TodoProcess.js
│   │   ├── TodoProcess.styl
│   │   ├── markdown.styl
│   │   └── render/
│   │       └── MermaidRender.js
│   ├── lib/
│   │   ├── CMLanguageList.js
│   │   ├── CSSModules.js
│   │   ├── Languages.js
│   │   ├── Mutable.js
│   │   ├── RcParser.js
│   │   ├── SnippetManager.js
│   │   ├── TextEditorInterface.js
│   │   ├── confirmDeleteNote.js
│   │   ├── consts.js
│   │   ├── context.js
│   │   ├── contextMenuBuilder.js
│   │   ├── convertModeName.js
│   │   ├── customMeta.js
│   │   ├── date-formatter.js
│   │   ├── findNoteTitle.js
│   │   ├── findStorage.js
│   │   ├── getTodoStatus.js
│   │   ├── htmlTextHelper.js
│   │   ├── i18n.js
│   │   ├── keygen.js
│   │   ├── markdown-it-deflist.js
│   │   ├── markdown-it-fence.js
│   │   ├── markdown-it-frontmatter.js
│   │   ├── markdown-it-sanitize-html.js
│   │   ├── markdown-toc-generator.js
│   │   ├── markdown.js
│   │   ├── markdown2.js
│   │   ├── markdownTextHelper.js
│   │   ├── newNote.js
│   │   ├── normalizeEditorFontFamily.js
│   │   ├── search.js
│   │   ├── slugify.js
│   │   ├── spellcheck.js
│   │   ├── turndown.js
│   │   ├── ui-themes.js
│   │   ├── utils.js
│   │   └── wakatime-plugin.js
│   ├── main/
│   │   ├── Detail/
│   │   │   ├── Detail.styl
│   │   │   ├── DetailVars.styl
│   │   │   ├── FolderSelect.js
│   │   │   ├── FolderSelect.styl
│   │   │   ├── FromUrlButton.js
│   │   │   ├── FromUrlButton.styl
│   │   │   ├── FullscreenButton.js
│   │   │   ├── FullscreenButton.styl
│   │   │   ├── InfoButton.js
│   │   │   ├── InfoButton.styl
│   │   │   ├── InfoPanel.js
│   │   │   ├── InfoPanel.styl
│   │   │   ├── InfoPanelTrashed.js
│   │   │   ├── MarkdownNoteDetail.js
│   │   │   ├── MarkdownNoteDetail.styl
│   │   │   ├── NoteDetailInfo.styl
│   │   │   ├── PermanentDeleteButton.js
│   │   │   ├── RestoreButton.js
│   │   │   ├── RestoreButton.styl
│   │   │   ├── SnippetNoteDetail.js
│   │   │   ├── SnippetNoteDetail.styl
│   │   │   ├── StarButton.js
│   │   │   ├── StarButton.styl
│   │   │   ├── TagSelect.js
│   │   │   ├── TagSelect.styl
│   │   │   ├── ToggleDirectionButton.js
│   │   │   ├── ToggleDirectionButton.styl
│   │   │   ├── ToggleModeButton.js
│   │   │   ├── ToggleModeButton.styl
│   │   │   ├── TrashButton.js
│   │   │   ├── TrashButton.styl
│   │   │   └── index.js
│   │   ├── DevTools/
│   │   │   ├── index.dev.js
│   │   │   ├── index.js
│   │   │   └── index.prod.js
│   │   ├── Main.js
│   │   ├── Main.styl
│   │   ├── NewNoteButton/
│   │   │   ├── NewNoteButton.styl
│   │   │   └── index.js
│   │   ├── NoteList/
│   │   │   ├── NoteList.styl
│   │   │   └── index.js
│   │   ├── SideNav/
│   │   │   ├── ListButton.js
│   │   │   ├── PreferenceButton.js
│   │   │   ├── PreferenceButton.styl
│   │   │   ├── SearchButton.js
│   │   │   ├── SearchButton.styl
│   │   │   ├── SideNav.styl
│   │   │   ├── StorageItem.js
│   │   │   ├── StorageItem.styl
│   │   │   ├── SwitchButton.styl
│   │   │   ├── TagButton.js
│   │   │   └── index.js
│   │   ├── StatusBar/
│   │   │   ├── StatusBar.styl
│   │   │   └── index.js
│   │   ├── TopBar/
│   │   │   ├── TopBar.styl
│   │   │   └── index.js
│   │   ├── global.styl
│   │   ├── index.js
│   │   ├── lib/
│   │   │   ├── AwsMobileAnalyticsConfig.js
│   │   │   ├── Commander.js
│   │   │   ├── ConfigManager.js
│   │   │   ├── ThemeManager.js
│   │   │   ├── ZoomManager.js
│   │   │   ├── dataApi/
│   │   │   │   ├── addStorage.js
│   │   │   │   ├── attachmentManagement.js
│   │   │   │   ├── copyFile.js
│   │   │   │   ├── createFolder.js
│   │   │   │   ├── createNote.js
│   │   │   │   ├── createNoteFromUrl.js
│   │   │   │   ├── createSnippet.js
│   │   │   │   ├── deleteFolder.js
│   │   │   │   ├── deleteNote.js
│   │   │   │   ├── deleteSnippet.js
│   │   │   │   ├── exportFolder.js
│   │   │   │   ├── exportNote.js
│   │   │   │   ├── exportNoteAs.js
│   │   │   │   ├── exportStorage.js
│   │   │   │   ├── exportTag.js
│   │   │   │   ├── fetchSnippet.js
│   │   │   │   ├── formatHTML.js
│   │   │   │   ├── formatMarkdown.js
│   │   │   │   ├── formatPDF.js
│   │   │   │   ├── getContentFormatter.js
│   │   │   │   ├── getFilename.js
│   │   │   │   ├── index.js
│   │   │   │   ├── init.js
│   │   │   │   ├── migrateFromV5Storage.js
│   │   │   │   ├── migrateFromV6Storage.js
│   │   │   │   ├── moveNote.js
│   │   │   │   ├── removeStorage.js
│   │   │   │   ├── renameStorage.js
│   │   │   │   ├── reorderFolder.js
│   │   │   │   ├── resolveStorageData.js
│   │   │   │   ├── resolveStorageNotes.js
│   │   │   │   ├── toggleStorage.js
│   │   │   │   ├── updateFolder.js
│   │   │   │   ├── updateNote.js
│   │   │   │   └── updateSnippet.js
│   │   │   ├── eventEmitter.js
│   │   │   ├── ipcClient.js
│   │   │   ├── modal.js
│   │   │   ├── notify.js
│   │   │   ├── shortcut.js
│   │   │   └── shortcutManager.js
│   │   ├── modals/
│   │   │   ├── CreateFolderModal.js
│   │   │   ├── CreateFolderModal.styl
│   │   │   ├── CreateMarkdownFromURLModal.js
│   │   │   ├── CreateMarkdownFromURLModal.styl
│   │   │   ├── NewNoteModal.js
│   │   │   ├── NewNoteModal.styl
│   │   │   ├── PreferencesModal/
│   │   │   │   ├── Blog.js
│   │   │   │   ├── ConfigTab.styl
│   │   │   │   ├── Crowdfunding.js
│   │   │   │   ├── Crowdfunding.styl
│   │   │   │   ├── ExportTab.js
│   │   │   │   ├── FolderItem.js
│   │   │   │   ├── FolderItem.styl
│   │   │   │   ├── FolderList.js
│   │   │   │   ├── FolderList.styl
│   │   │   │   ├── HotkeyTab.js
│   │   │   │   ├── InfoTab.js
│   │   │   │   ├── InfoTab.styl
│   │   │   │   ├── PluginsTab.js
│   │   │   │   ├── PreferencesModal.styl
│   │   │   │   ├── SnippetEditor.js
│   │   │   │   ├── SnippetList.js
│   │   │   │   ├── SnippetTab.js
│   │   │   │   ├── SnippetTab.styl
│   │   │   │   ├── StorageItem.js
│   │   │   │   ├── StorageItem.styl
│   │   │   │   ├── StoragesTab.js
│   │   │   │   ├── StoragesTab.styl
│   │   │   │   ├── Tab.styl
│   │   │   │   ├── UiTab.js
│   │   │   │   └── index.js
│   │   │   ├── RenameFolderModal.js
│   │   │   ├── RenameModal.styl
│   │   │   └── RenameTagModal.js
│   │   └── store.js
│   └── styles/
│       ├── Detail/
│       │   └── TagSelect.styl
│       ├── index.styl
│       ├── mixins/
│       │   ├── alert.styl
│       │   ├── btn.styl
│       │   ├── circle.styl
│       │   ├── fullsize.styl
│       │   ├── input.styl
│       │   ├── marked.styl
│       │   ├── tooltip.styl
│       │   └── util.styl
│       ├── shared/
│       │   └── btn.styl
│       ├── theme/
│       │   └── dark.styl
│       └── vars.styl
├── contributing.md
├── dev-scripts/
│   └── dev.js
├── dictionaries/
│   ├── de_DE/
│   │   ├── LICENSE
│   │   ├── de_DE.aff
│   │   └── de_DE.dic
│   ├── en_GB/
│   │   ├── LICENSE
│   │   ├── en_GB.aff
│   │   └── en_GB.dic
│   └── fr_FR/
│       ├── fr_FR.aff
│       └── fr_FR.dic
├── docs/
│   ├── build.md
│   ├── code_style.md
│   ├── de/
│   │   ├── build.md
│   │   └── debug.md
│   ├── debug.md
│   ├── fr/
│   │   ├── build.md
│   │   └── debug.md
│   ├── jp/
│   │   ├── build.md
│   │   └── debug.md
│   ├── ko/
│   │   ├── build.md
│   │   └── debug.md
│   ├── pt_BR/
│   │   ├── build.md
│   │   └── debug.md
│   ├── ru/
│   │   ├── build.md
│   │   └── debug.md
│   ├── zh_CN/
│   │   ├── build.md
│   │   └── debug.md
│   └── zh_TW/
│       └── build.md
├── extra_scripts/
│   ├── boost/
│   │   └── boostNewLineIndentContinueMarkdownList.js
│   └── codemirror/
│       ├── addon/
│       │   ├── edit/
│       │   │   └── closebrackets.js
│       │   └── hyperlink/
│       │       └── hyperlink.js
│       ├── mode/
│       │   ├── bfm/
│       │   │   ├── bfm.css
│       │   │   └── bfm.js
│       │   └── gfm/
│       │       └── gfm.js
│       └── theme/
│           └── nord.css
├── gruntfile.js
├── index.js
├── lib/
│   ├── ipcServer.js
│   ├── main-app.js
│   ├── main-menu.js
│   ├── main-window.js
│   ├── main.development.html
│   ├── main.production.html
│   └── touchbar-menu.js
├── locales/
│   ├── cs.json
│   ├── da.json
│   ├── de.json
│   ├── en.json
│   ├── es-ES.json
│   ├── fa.json
│   ├── fr.json
│   ├── hu.json
│   ├── it.json
│   ├── ja.json
│   ├── ko.json
│   ├── no.json
│   ├── pl.json
│   ├── pt-BR.json
│   ├── pt-PT.json
│   ├── ru.json
│   ├── sq.json
│   ├── th.json
│   ├── tr.json
│   ├── zh-CN.json
│   └── zh-TW.json
├── package.json
├── prettier.config
├── readme.md
├── resources/
│   ├── app.icns
│   └── dmg.icns
├── snap/
│   ├── gui/
│   │   └── boostnote.desktop
│   └── snapcraft.yaml
├── tests/
│   ├── .gitignore
│   ├── components/
│   │   ├── TagListItem.snapshot.test.js
│   │   └── __snapshots__/
│   │       └── TagListItem.snapshot.test.js.snap
│   ├── dataApi/
│   │   ├── addStorage.js
│   │   ├── attachmentManagement.test.js
│   │   ├── copyFile.test.js
│   │   ├── createFolder.test.js
│   │   ├── createNote.test.js
│   │   ├── createNoteFromUrl.test.js
│   │   ├── createSnippet.test.js
│   │   ├── deleteFolder.test.js
│   │   ├── deleteNote-test.js
│   │   ├── deleteSnippet-test.js
│   │   ├── exportFolder-test.js
│   │   ├── exportStorage-test.js
│   │   ├── init.js
│   │   ├── migrateFromV6Storage-test.js
│   │   ├── moveNote-test.js
│   │   ├── removeStorage-test.js
│   │   ├── renameStorage-test.js
│   │   ├── reorderFolder-test.js
│   │   ├── toggleStorage-test.js
│   │   ├── updateFolder-test.js
│   │   ├── updateNote-test.js
│   │   └── updateSnippet-test.js
│   ├── date-formatter-test.js
│   ├── fixtures/
│   │   ├── TestDummy.js
│   │   └── markdowns.js
│   ├── helpers/
│   │   ├── setup-browser-env.js
│   │   └── setup-electron-mock.js
│   ├── jest.js
│   └── lib/
│       ├── __snapshots__/
│       │   └── markdown.test.js.snap
│       ├── boostnoterc/
│       │   ├── .boostnoterc.all
│       │   ├── .boostnoterc.invalid
│       │   └── .boostnoterc.valid
│       ├── contextMenuBuilder.test.js
│       ├── escapeHtmlCharacters.test.js
│       ├── find-storage.test.js
│       ├── find-title.test.js
│       ├── get-todo-status.test.js
│       ├── html-text-helper.test.js
│       ├── markdown-text-helper.test.js
│       ├── markdown-toc-generator.test.js
│       ├── markdown.test.js
│       ├── normalize-editor-font-family.test.js
│       ├── rc-parser.test.js
│       ├── search.test.js
│       ├── slugify.test.js
│       ├── snapshots/
│       │   └── markdown-test.js.md
│       ├── spellcheck.test.js
│       ├── themeManager.test.js
│       └── utils.test.js
├── webpack-production.config.js
├── webpack-skeleton.js
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["react", "es2015"],
  "env": {
    "development": {
      "presets": ["react-hmre"]
    },
    "test": {
      "presets": ["env" ,"react", "es2015"],
      "plugins": [
        [ "babel-plugin-webpack-alias", { "config": "<rootDir>/webpack.config.js" } ]
      ]
    }
  }
}


================================================
FILE: .boostnoterc.sample
================================================
{
    "amaEnabled": true,
    "editor": {
        "fontFamily": "Monaco, Consolas",
        "fontSize": "14",
        "indentSize": "2",
        "indentType": "space",
        "keyMap": "vim",
        "switchPreview": "BLUR",
        "theme": "monokai"
    },
    "hotkey": {
        "toggleMain": "Cmd + Alt + L"
    },
    "isSideNavFolded": false,
    "listStyle": "DEFAULT",
    "listWidth": 174,
    "navWidth": 200,
    "preview": {
        "codeBlockTheme": "dracula",
        "fontFamily": "Lato",
        "fontSize": "14",
        "lineNumber": true
    },
    "sortBy": {
        "default": "UPDATED_AT"
    },
    "sortTagsBy": "ALPHABETICAL",
    "ui": {
        "defaultNote": "ALWAYS_ASK",
        "disableDirectWrite": false,
        "theme": "default"
    },
    "zoom": 1
}


================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# Space indentation
[*]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

# The indent size used in the `package.json` file cannot be changed
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
[{*.yml,*.yaml,package.json}]
indent_style = space
indent_size = 2


================================================
FILE: .eslintignore
================================================
node_modules/
compiled/
dist/
extra_scripts/

================================================
FILE: .eslintrc
================================================
{
  "extends": ["standard", "standard-jsx", "plugin:react/recommended", "prettier"],
  "plugins": ["react", "prettier"],
  "rules": {
    "no-useless-escape": 0,
    "prefer-const": ["warn", {
      "destructuring": "all"
    }],
    "no-unused-vars": "warn",
    "no-undef": "warn",
    "no-lone-blocks": "warn",
    "react/prop-types": 0,
    "react/no-string-refs": 0,
    "react/no-find-dom-node": "warn",
    "react/no-render-return-value": "warn",
    "react/no-deprecated": "warn",
    "prettier/prettier": ["error"]
  },
  "globals": {
    "FileReader": true,
    "localStorage": true,
    "fetch": true,
    "Image": true,
    "MutationObserver": true
  },
  "env": {
    "jest": true
  }
}


================================================
FILE: .github/FUNDING.yml
================================================
issuehunt: BoostIo/Boostnote


================================================
FILE: .gitignore
================================================
.DS_Store
.env
Desktop.ini
Thumbs.db
node_modules/*
!node_modules/boost
/dist
/compiled
/secret
*.log
.idea
.vscode
package-lock.json
config.json


================================================
FILE: .prettierrc
================================================
{
  "singleQuote": true,
  "semi": false,
  "jsxSingleQuote": true
}

================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - 8
script:
  - npm run lint && npm run test
  - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi'
after_success:
  - openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
    -in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
sudo: required
services:
  - docker
deploy:
  'on':
    branch: master
  provider: script
  script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
    && cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
  skip_cleanup: true


================================================
FILE: FAQ.md
================================================
# Frequently Asked Questions


<details><summary>Allowing dangerous HTML tags</summary>

Sometimes it is useful to allow dangerous HTML tags to add interactivity to your notebook. One of the example is to use details/summary as a way to expand/collaps your todo-list.

* How to enable:
  * Go to **Preferences** → **Interface** → **Sanitization** → **Allow dangerous html tags**
* Example note: Multiple todo-list
  * Create new notes
  * Paste the below code, and you'll see that you can expand/collaps the todo-list, and you can have multiple todo-list in your note.
  
```html
<details><summary>What I want to do</summary>

- [x] Create an awesome feature X
- [ ] Do my homework

</details>
```

</details>

## Other questions

You can ask [here][ISSUES]

[ISSUES]: https://github.com/BoostIO/Boostnote/issues


================================================
FILE: ISSUE_TEMPLATE.md
================================================
# Current behavior

<!--
Let us know what is currently happening.

Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.

If your issue is regarding the new Boost Note.next, please open an issue in the new repo 👉 https://github.com/BoostIO/BoostNote.next/issues.
-->

# Expected behavior

<!--
Let us know what you think should happen.
-->

# Steps to reproduce

<!-- 
Please be thorough, issues we can reproduce are easier to fix.
-->

1. 
2. 
3. 

# Environment

- Boostnote version: <!-- 0.x.x -->
- OS version and name: <!-- Windows 10 / Ubuntu 18.04 / etc -->

<!--
Love Boostnote? Please consider supporting us on IssueHunt:
👉  https://issuehunt.io/repos/53266139
-->


================================================
FILE: LICENSE
================================================
GPL-3.0

Boostnote - an open source note-taking app made for programmers just like you.

Copyright (C) 2017 - 2019 BoostIO

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.


================================================
FILE: PULL_REQUEST_TEMPLATE.md
================================================
<!--
Before submitting this PR, please make sure that:
- You have read and understand the contributing.md
- You have checked docs/code_style.md for information on code style
-->

## Description

<!--
Tell us what your PR does.
Please attach a screenshot/ video/gif image describing your PR if possible.
-->

## Issue fixed

<!--
Please list out all issue fixed with this PR here.
-->

<!--
Please make sure you fill in these checkboxes,
your PR will be reviewed faster if we know exactly what it does.

Change :white_circle: to :radio_button: in all the options that apply
-->

## Type of changes

- :white_circle: Bug fix (Change that fixed an issue)
- :white_circle: Breaking change (Change that can cause existing functionality to change)
- :white_circle: Improvement (Change that improves the code. Maybe performance or development improvement)
- :white_circle: Feature (Change that adds new functionality)
- :white_circle: Documentation change (Change that modifies documentation. Maybe typo fixes)

## Checklist:

- :white_circle: My code follows [the project code style](docs/code_style.md)
- :white_circle: I have written test for my code and it has been tested
- :white_circle: All existing tests have been passed
- :white_circle: I have attached a screenshot/video to visualize my change if possible
- :white_circle: This PR will modify the UI or affects the UX
- :white_circle: This PR will add/update/delete a keybinding


================================================
FILE: __mocks__/electron.js
================================================
module.exports = {
  require: jest.genMockFunction(),
  match: jest.genMockFunction(),
  app: jest.genMockFunction(),
  remote: jest.genMockFunction(),
  dialog: jest.genMockFunction()
}


================================================
FILE: appdmg.json
================================================
{
  "title": "Boostnote",
  "icon": "resources/dmg.icns",
  "background": "resources/boostnote-install.png",
  "icon-size": 80,
  "contents": [
    { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
    { "x": 192, "y": 344, "type": "file", "path": "dist/Boostnote-darwin-x64/Boostnote.app" }
  ]

}


================================================
FILE: browser/components/CodeEditor.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import hljs from 'highlight.js'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import { options, TableEditor, Alignment } from '@susisu/mte-kernel'
import TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'

import { isMarkdownTitleURL } from 'browser/lib/utils'
import styles from '../components/CodeEditor.styl'
const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
  .buildEditorContextMenu
import { createTurndownService } from '../lib/turndown'
import { languageMaps } from '../lib/CMLanguageList'
import snippetManager from '../lib/SnippetManager'
import { findStorage } from 'browser/lib/findStorage'
import { sendWakatimeHeartBeat } from 'browser/lib/wakatime-plugin'
import {
  generateInEditor,
  tocExistsInEditor
} from 'browser/lib/markdown-toc-generator'
import markdownlint from 'markdownlint'
import Jsonlint from 'jsonlint-mod'
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
import prettier from 'prettier'

CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'

const buildCMRulers = (rulers, enableRulers) =>
  enableRulers
    ? rulers.map(ruler => ({
        column: ruler
      }))
    : []

function translateHotkey(hotkey) {
  return hotkey
    .replace(/\s*\+\s*/g, '-')
    .replace(/Command/g, 'Cmd')
    .replace(/Control/g, 'Ctrl')
}

export default class CodeEditor extends React.Component {
  constructor(props) {
    super(props)

    this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
      leading: false,
      trailing: true
    })
    this.changeHandler = (editor, changeObject) =>
      this.handleChange(editor, changeObject)
    this.highlightHandler = (editor, changeObject) =>
      this.handleHighlight(editor, changeObject)
    this.focusHandler = () => {
      ipcRenderer.send('editor:focused', true)
    }
    this.debouncedDeletionOfAttachments = _.debounce(
      attachmentManagement.deleteAttachmentsNotPresentInNote,
      30000
    )
    this.blurHandler = (editor, e) => {
      ipcRenderer.send('editor:focused', false)
      if (e == null) return null
      let el = e.relatedTarget
      while (el != null) {
        if (el === this.refs.root) {
          return
        }
        el = el.parentNode
      }
      this.props.onBlur != null && this.props.onBlur(e)
      const { storageKey, noteKey } = this.props
      if (this.props.deleteUnusedAttachments === true) {
        this.debouncedDeletionOfAttachments(
          this.editor.getValue(),
          storageKey,
          noteKey
        )
      }
    }
    this.pasteHandler = (editor, e) => {
      e.preventDefault()

      this.handlePaste(editor, false)
    }
    this.loadStyleHandler = e => {
      this.editor.refresh()
    }
    this.searchHandler = (e, msg) => this.handleSearch(msg)
    this.searchState = null
    this.scrollToLineHandeler = this.scrollToLine.bind(this)
    this.getCodeEditorLintConfig = this.getCodeEditorLintConfig.bind(this)
    this.validatorOfMarkdown = this.validatorOfMarkdown.bind(this)

    this.formatTable = () => this.handleFormatTable()

    if (props.switchPreview !== 'RIGHTCLICK') {
      this.contextMenuHandler = function(editor, event) {
        const menu = buildEditorContextMenu(editor, event)
        if (menu != null) {
          setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
        }
      }
    }

    this.editorActivityHandler = () => this.handleEditorActivity()

    this.turndownService = createTurndownService()

    // wakatime
    const { storageKey, noteKey } = this.props
    const storage = findStorage(storageKey)
    if (storage)
      sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
        isWrite: false,
        hasFileChanges: false,
        isFileChange: true
      })
  }

  handleSearch(msg) {
    const cm = this.editor
    const component = this

    if (component.searchState) cm.removeOverlay(component.searchState)
    if (msg.length < 1) return

    cm.operation(function() {
      component.searchState = makeOverlay(msg, 'searching')
      cm.addOverlay(component.searchState)

      function makeOverlay(query, style) {
        query = new RegExp(
          query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
          'gi'
        )
        return {
          token: function(stream) {
            query.lastIndex = stream.pos
            var match = query.exec(stream.string)
            if (match && match.index === stream.pos) {
              stream.pos += match[0].length || 1
              return style
            } else if (match) {
              stream.pos = match.index
            } else {
              stream.skipToEnd()
            }
          }
        }
      }
    })
  }

  handleFormatTable() {
    this.tableEditor.formatAll(
      options({
        textWidthOptions: {}
      })
    )
  }

  handleEditorActivity() {
    if (this.props.onCursorActivity) {
      this.props.onCursorActivity(this.editor)
    }

    if (!this.textEditorInterface.transaction) {
      this.updateTableEditorState()
    }
  }

  updateDefaultKeyMap() {
    const { hotkey } = this.props
    const self = this
    const expandSnippet = snippetManager.expandSnippet

    this.defaultKeyMap = CodeMirror.normalizeKeyMap({
      Tab: function(cm) {
        const cursor = cm.getCursor()
        const line = cm.getLine(cursor.line)
        const cursorPosition = cursor.ch
        const charBeforeCursor = line.substr(cursorPosition - 1, 1)
        if (cm.somethingSelected()) cm.indentSelection('add')
        else {
          const tabs = cm.getOption('indentWithTabs')
          if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
            cm.execCommand('goLineStart')
            if (tabs) {
              cm.execCommand('insertTab')
            } else {
              cm.execCommand('insertSoftTab')
            }
            cm.execCommand('goLineEnd')
          } else if (
            !charBeforeCursor.match(/\t|\s|\r|\n|\$/) &&
            cursor.ch > 1
          ) {
            // text expansion on tab key if the char before is alphabet
            const wordBeforeCursor = self.getWordBeforeCursor(
              line,
              cursor.line,
              cursor.ch
            )
            if (expandSnippet(wordBeforeCursor, cursor, cm) === false) {
              if (tabs) {
                cm.execCommand('insertTab')
              } else {
                cm.execCommand('insertSoftTab')
              }
            }
          } else {
            if (tabs) {
              cm.execCommand('insertTab')
            } else {
              cm.execCommand('insertSoftTab')
            }
          }
        }
      },
      'Cmd-Left': function(cm) {
        cm.execCommand('goLineLeft')
      },
      'Cmd-T': function(cm) {
        // Do nothing
      },
      [translateHotkey(hotkey.insertDate)]: function(cm) {
        const dateNow = new Date()
        if (self.props.dateFormatISO8601) {
          cm.replaceSelection(dateNow.toISOString().split('T')[0])
        } else {
          cm.replaceSelection(dateNow.toLocaleDateString())
        }
      },
      [translateHotkey(hotkey.insertDateTime)]: function(cm) {
        const dateNow = new Date()
        if (self.props.dateFormatISO8601) {
          cm.replaceSelection(dateNow.toISOString())
        } else {
          cm.replaceSelection(dateNow.toLocaleString())
        }
      },
      Enter: 'boostNewLineAndIndentContinueMarkdownList',
      'Ctrl-C': cm => {
        if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
          document.execCommand('copy')
        }
        return CodeMirror.Pass
      },
      [translateHotkey(hotkey.prettifyMarkdown)]: cm => {
        // Default / User configured prettier options
        const currentConfig = JSON.parse(self.props.prettierConfig)

        // Parser type will always need to be markdown so we override the option before use
        currentConfig.parser = 'markdown'

        // Get current cursor position
        const cursorPos = cm.getCursor()
        currentConfig.cursorOffset = cm.doc.indexFromPos(cursorPos)

        // Prettify contents of editor
        const formattedTextDetails = prettier.formatWithCursor(
          cm.doc.getValue(),
          currentConfig
        )

        const formattedText = formattedTextDetails.formatted
        const formattedCursorPos = formattedTextDetails.cursorOffset
        cm.doc.setValue(formattedText)

        // Reset Cursor position to be at the same markdown as was before prettifying
        const newCursorPos = cm.doc.posFromIndex(formattedCursorPos)
        cm.doc.setCursor(newCursorPos)
      },
      [translateHotkey(hotkey.sortLines)]: cm => {
        const selection = cm.doc.getSelection()
        const appendLineBreak = /\n$/.test(selection)

        const sorted = _.split(selection.trim(), '\n').sort()
        const sortedString =
          _.join(sorted, '\n') + (appendLineBreak ? '\n' : '')

        cm.doc.replaceSelection(sortedString)
      },
      [translateHotkey(hotkey.pasteSmartly)]: cm => {
        this.handlePaste(cm, true)
      }
    })
  }

  updateTableEditorState() {
    const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
    if (active) {
      if (this.extraKeysMode !== 'editor') {
        this.extraKeysMode = 'editor'
        this.editor.setOption('extraKeys', this.editorKeyMap)
      }
    } else {
      if (this.extraKeysMode !== 'default') {
        this.extraKeysMode = 'default'
        this.editor.setOption('extraKeys', this.defaultKeyMap)
        this.tableEditor.resetSmartCursor()
      }
    }
  }

  componentDidMount() {
    const { rulers, enableRulers, enableMarkdownLint, RTL } = this.props
    eventEmitter.on('line:jump', this.scrollToLineHandeler)

    snippetManager.init()
    this.updateDefaultKeyMap()

    this.value = this.props.value
    this.editor = CodeMirror(this.refs.root, {
      rulers: buildCMRulers(rulers, enableRulers),
      value: this.props.value,
      linesHighlighted: this.props.linesHighlighted,
      lineNumbers: this.props.displayLineNumbers,
      lineWrapping: this.props.lineWrapping,
      theme: this.props.theme,
      indentUnit: this.props.indentSize,
      tabSize: this.props.indentSize,
      indentWithTabs: this.props.indentType !== 'space',
      keyMap: this.props.keyMap,
      scrollPastEnd: this.props.scrollPastEnd,
      inputStyle: 'textarea',
      dragDrop: false,
      direction: RTL ? 'rtl' : 'ltr',
      rtlMoveVisually: RTL,
      foldGutter: true,
      lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
      gutters: [
        'CodeMirror-linenumbers',
        'CodeMirror-foldgutter',
        'CodeMirror-lint-markers'
      ],
      autoCloseBrackets: {
        codeBlock: {
          pairs: this.props.codeBlockMatchingPairs,
          closeBefore: this.props.codeBlockMatchingCloseBefore,
          triples: this.props.codeBlockMatchingTriples,
          explode: this.props.codeBlockExplodingPairs
        },
        markdown: {
          pairs: this.props.matchingPairs,
          closeBefore: this.props.matchingCloseBefore,
          triples: this.props.matchingTriples,
          explode: this.props.explodingPairs
        }
      },
      extraKeys: this.defaultKeyMap,
      prettierConfig: this.props.prettierConfig
    })

    document.querySelector(
      '.CodeMirror-lint-markers'
    ).style.display = enableMarkdownLint ? 'inline-block' : 'none'

    if (!this.props.mode && this.props.value && this.props.autoDetect) {
      this.autoDetectLanguage(this.props.value)
    } else {
      this.setMode(this.props.mode)
    }

    this.editor.on('focus', this.focusHandler)
    this.editor.on('blur', this.blurHandler)
    this.editor.on('change', this.changeHandler)
    this.editor.on('gutterClick', this.highlightHandler)
    this.editor.on('paste', this.pasteHandler)
    if (this.props.switchPreview !== 'RIGHTCLICK') {
      this.editor.on('contextmenu', this.contextMenuHandler)
    }
    eventEmitter.on('top:search', this.searchHandler)

    eventEmitter.emit('code:init')
    this.editor.on('scroll', this.scrollHandler)
    this.editor.on('cursorActivity', this.editorActivityHandler)

    const editorTheme = document.getElementById('editorTheme')
    editorTheme.addEventListener('load', this.loadStyleHandler)

    CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
    CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
    CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
    CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
    CodeMirror.Vim.map('ZZ', ':q', 'normal')

    this.textEditorInterface = new TextEditorInterface(this.editor)
    this.tableEditor = new TableEditor(this.textEditorInterface)
    if (this.props.spellCheck) {
      this.editor.addPanel(this.createSpellCheckPanel(), { position: 'bottom' })
    }

    eventEmitter.on('code:format-table', this.formatTable)

    this.tableEditorOptions = options({
      smartCursor: true
    })

    this.editorKeyMap = CodeMirror.normalizeKeyMap({
      Tab: () => {
        this.tableEditor.nextCell(this.tableEditorOptions)
      },
      'Shift-Tab': () => {
        this.tableEditor.previousCell(this.tableEditorOptions)
      },
      Enter: () => {
        this.tableEditor.nextRow(this.tableEditorOptions)
      },
      'Ctrl-Enter': () => {
        this.tableEditor.escape(this.tableEditorOptions)
      },
      'Cmd-Enter': () => {
        this.tableEditor.escape(this.tableEditorOptions)
      },
      'Shift-Ctrl-Left': () => {
        this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
      },
      'Shift-Cmd-Left': () => {
        this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
      },
      'Shift-Ctrl-Right': () => {
        this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
      },
      'Shift-Cmd-Right': () => {
        this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
      },
      'Shift-Ctrl-Up': () => {
        this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
      },
      'Shift-Cmd-Up': () => {
        this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
      },
      'Shift-Ctrl-Down': () => {
        this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
      },
      'Shift-Cmd-Down': () => {
        this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
      },
      'Ctrl-Left': () => {
        this.tableEditor.moveFocus(0, -1, this.tableEditorOptions)
      },
      'Cmd-Left': () => {
        this.tableEditor.moveFocus(0, -1, this.tableEditorOptions)
      },
      'Ctrl-Right': () => {
        this.tableEditor.moveFocus(0, 1, this.tableEditorOptions)
      },
      'Cmd-Right': () => {
        this.tableEditor.moveFocus(0, 1, this.tableEditorOptions)
      },
      'Ctrl-Up': () => {
        this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions)
      },
      'Cmd-Up': () => {
        this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions)
      },
      'Ctrl-Down': () => {
        this.tableEditor.moveFocus(1, 0, this.tableEditorOptions)
      },
      'Cmd-Down': () => {
        this.tableEditor.moveFocus(1, 0, this.tableEditorOptions)
      },
      'Ctrl-K Ctrl-I': () => {
        this.tableEditor.insertRow(this.tableEditorOptions)
      },
      'Cmd-K Cmd-I': () => {
        this.tableEditor.insertRow(this.tableEditorOptions)
      },
      'Ctrl-L Ctrl-I': () => {
        this.tableEditor.deleteRow(this.tableEditorOptions)
      },
      'Cmd-L Cmd-I': () => {
        this.tableEditor.deleteRow(this.tableEditorOptions)
      },
      'Ctrl-K Ctrl-J': () => {
        this.tableEditor.insertColumn(this.tableEditorOptions)
      },
      'Cmd-K Cmd-J': () => {
        this.tableEditor.insertColumn(this.tableEditorOptions)
      },
      'Ctrl-L Ctrl-J': () => {
        this.tableEditor.deleteColumn(this.tableEditorOptions)
      },
      'Cmd-L Cmd-J': () => {
        this.tableEditor.deleteColumn(this.tableEditorOptions)
      },
      'Alt-Shift-Ctrl-Left': () => {
        this.tableEditor.moveColumn(-1, this.tableEditorOptions)
      },
      'Alt-Shift-Cmd-Left': () => {
        this.tableEditor.moveColumn(-1, this.tableEditorOptions)
      },
      'Alt-Shift-Ctrl-Right': () => {
        this.tableEditor.moveColumn(1, this.tableEditorOptions)
      },
      'Alt-Shift-Cmd-Right': () => {
        this.tableEditor.moveColumn(1, this.tableEditorOptions)
      },
      'Alt-Shift-Ctrl-Up': () => {
        this.tableEditor.moveRow(-1, this.tableEditorOptions)
      },
      'Alt-Shift-Cmd-Up': () => {
        this.tableEditor.moveRow(-1, this.tableEditorOptions)
      },
      'Alt-Shift-Ctrl-Down': () => {
        this.tableEditor.moveRow(1, this.tableEditorOptions)
      },
      'Alt-Shift-Cmd-Down': () => {
        this.tableEditor.moveRow(1, this.tableEditorOptions)
      }
    })

    if (this.props.enableTableEditor) {
      this.editor.on('changes', this.editorActivityHandler)
    }

    this.setState({
      clientWidth: this.refs.root.clientWidth
    })

    this.initialHighlighting()
  }

  getWordBeforeCursor(line, lineNumber, cursorPosition) {
    let wordBeforeCursor = ''
    const originCursorPosition = cursorPosition
    const emptyChars = /\t|\s|\r|\n|\$/

    // to prevent the word is long that will crash the whole app
    // the safeStop is there to stop user to expand words that longer than 20 chars
    const safeStop = 20

    while (cursorPosition > 0) {
      const currentChar = line.substr(cursorPosition - 1, 1)
      // if char is not an empty char
      if (!emptyChars.test(currentChar)) {
        wordBeforeCursor = currentChar + wordBeforeCursor
      } else if (wordBeforeCursor.length >= safeStop) {
        throw new Error('Stopped after 20 loops for safety reason !')
      } else {
        break
      }
      cursorPosition--
    }

    return {
      text: wordBeforeCursor,
      range: {
        from: {
          line: lineNumber,
          ch: originCursorPosition
        },
        to: {
          line: lineNumber,
          ch: cursorPosition
        }
      }
    }
  }

  quitEditor() {
    document.querySelector('textarea').blur()
  }

  componentWillUnmount() {
    this.editor.off('focus', this.focusHandler)
    this.editor.off('blur', this.blurHandler)
    this.editor.off('change', this.changeHandler)
    this.editor.off('paste', this.pasteHandler)
    eventEmitter.off('top:search', this.searchHandler)
    this.editor.off('scroll', this.scrollHandler)
    this.editor.off('cursorActivity', this.editorActivityHandler)
    this.editor.off('contextmenu', this.contextMenuHandler)

    const editorTheme = document.getElementById('editorTheme')
    editorTheme.removeEventListener('load', this.loadStyleHandler)

    spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
    eventEmitter.off('code:format-table', this.formatTable)

    if (this.props.enableTableEditor) {
      this.editor.off('changes', this.editorActivityHandler)
    }
  }

  componentDidUpdate(prevProps, prevState) {
    let needRefresh = false
    const {
      rulers,
      enableRulers,
      enableMarkdownLint,
      customMarkdownLintConfig
    } = this.props
    if (prevProps.mode !== this.props.mode) {
      this.setMode(this.props.mode)
    }
    if (prevProps.theme !== this.props.theme) {
      this.editor.setOption('theme', this.props.theme)
      // editor should be refreshed after css loaded
    }
    if (prevProps.fontSize !== this.props.fontSize) {
      needRefresh = true
    }
    if (prevProps.fontFamily !== this.props.fontFamily) {
      needRefresh = true
    }
    if (prevProps.keyMap !== this.props.keyMap) {
      needRefresh = true
    }
    if (prevProps.RTL !== this.props.RTL) {
      this.editor.setOption('direction', this.props.RTL ? 'rtl' : 'ltr')
      this.editor.setOption('rtlMoveVisually', this.props.RTL)
    }
    if (
      prevProps.enableMarkdownLint !== enableMarkdownLint ||
      prevProps.customMarkdownLintConfig !== customMarkdownLintConfig
    ) {
      if (!enableMarkdownLint) {
        this.editor.setOption('lint', { default: false })
        document.querySelector('.CodeMirror-lint-markers').style.display =
          'none'
      } else {
        this.editor.setOption('lint', this.getCodeEditorLintConfig())
        document.querySelector('.CodeMirror-lint-markers').style.display =
          'inline-block'
      }
      needRefresh = true
    }

    if (
      prevProps.enableRulers !== enableRulers ||
      prevProps.rulers !== rulers
    ) {
      this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
    }

    if (prevProps.indentSize !== this.props.indentSize) {
      this.editor.setOption('indentUnit', this.props.indentSize)
      this.editor.setOption('tabSize', this.props.indentSize)
    }
    if (prevProps.indentType !== this.props.indentType) {
      this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
    }

    if (prevProps.displayLineNumbers !== this.props.displayLineNumbers) {
      this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
    }

    if (prevProps.lineWrapping !== this.props.lineWrapping) {
      this.editor.setOption('lineWrapping', this.props.lineWrapping)
    }

    if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
      this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
    }

    if (
      prevProps.matchingPairs !== this.props.matchingPairs ||
      prevProps.matchingCloseBefore !== this.props.matchingCloseBefore ||
      prevProps.matchingTriples !== this.props.matchingTriples ||
      prevProps.explodingPairs !== this.props.explodingPairs ||
      prevProps.codeBlockMatchingPairs !== this.props.codeBlockMatchingPairs ||
      prevProps.codeBlockMatchingCloseBefore !==
        this.props.codeBlockMatchingCloseBefore ||
      prevProps.codeBlockMatchingTriples !==
        this.props.codeBlockMatchingTriples ||
      prevProps.codeBlockExplodingPairs !== this.props.codeBlockExplodingPairs
    ) {
      const autoCloseBrackets = {
        codeBlock: {
          pairs: this.props.codeBlockMatchingPairs,
          closeBefore: this.props.codeBlockMatchingCloseBefore,
          triples: this.props.codeBlockMatchingTriples,
          explode: this.props.codeBlockExplodingPairs
        },
        markdown: {
          pairs: this.props.matchingPairs,
          closeBefore: this.props.matchingCloseBefore,
          triples: this.props.matchingTriples,
          explode: this.props.explodingPairs
        }
      }

      this.editor.setOption('autoCloseBrackets', autoCloseBrackets)
    }

    if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
      if (this.props.enableTableEditor) {
        this.editor.on('cursorActivity', this.editorActivityHandler)
        this.editor.on('changes', this.editorActivityHandler)
      } else {
        this.editor.off('cursorActivity', this.editorActivityHandler)
        this.editor.off('changes', this.editorActivityHandler)
      }

      this.extraKeysMode = 'default'
      this.editor.setOption('extraKeys', this.defaultKeyMap)
    }

    if (prevProps.hotkey !== this.props.hotkey) {
      this.updateDefaultKeyMap()

      if (this.extraKeysMode === 'default') {
        this.editor.setOption('extraKeys', this.defaultKeyMap)
      }
    }

    if (this.state.clientWidth !== this.refs.root.clientWidth) {
      this.setState({
        clientWidth: this.refs.root.clientWidth
      })

      needRefresh = true
    }

    if (prevProps.spellCheck !== this.props.spellCheck) {
      if (this.props.spellCheck === false) {
        spellcheck.setLanguage(this.editor, spellcheck.SPELLCHECK_DISABLED)
        const elem = document.getElementById('editor-bottom-panel')
        elem.parentNode.removeChild(elem)
      } else {
        this.editor.addPanel(this.createSpellCheckPanel(), {
          position: 'bottom'
        })
      }
    }
    if (
      prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments
    ) {
      this.editor.setOption(
        'deleteUnusedAttachments',
        this.props.deleteUnusedAttachments
      )
    }

    if (needRefresh) {
      this.editor.refresh()
    }
  }

  getCodeEditorLintConfig() {
    const { mode } = this.props
    const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'

    return checkMarkdownNoteIsOpen
      ? {
          getAnnotations: this.validatorOfMarkdown,
          async: true
        }
      : false
  }

  validatorOfMarkdown(text, updateLinting) {
    const { customMarkdownLintConfig } = this.props
    let lintConfigJson
    try {
      Jsonlint.parse(customMarkdownLintConfig)
      lintConfigJson = JSON.parse(customMarkdownLintConfig)
    } catch (err) {
      eventEmitter.emit('APP_SETTING_ERROR')
      return
    }
    const lintOptions = {
      strings: {
        content: text
      },
      config: lintConfigJson
    }

    return markdownlint(lintOptions, (err, result) => {
      if (!err) {
        const foundIssues = []
        const splitText = text.split('\n')
        result.content.map(item => {
          let ruleNames = ''
          item.ruleNames.map((ruleName, index) => {
            ruleNames += ruleName
            ruleNames += index === item.ruleNames.length - 1 ? ': ' : '/'
          })
          const lineNumber = item.lineNumber - 1
          foundIssues.push({
            from: CodeMirror.Pos(lineNumber, 0),
            to: CodeMirror.Pos(lineNumber, splitText[lineNumber].length),
            message: ruleNames + item.ruleDescription,
            severity: 'warning'
          })
        })
        updateLinting(foundIssues)
      }
    })
  }

  setMode(mode) {
    let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
    if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')

    this.editor.setOption('mode', syntax.mime)
    CodeMirror.autoLoadMode(this.editor, syntax.mode)
  }

  handleChange(editor, changeObject) {
    this.debouncedDeletionOfAttachments.cancel()

    spellcheck.handleChange(editor, changeObject)

    // The current note contains an toc. We'll check for changes on headlines.
    // origin is undefined when markdownTocGenerator replace the old tod
    if (tocExistsInEditor(editor) && changeObject.origin !== undefined) {
      let requireTocUpdate

      // Check if one of the changed lines contains a headline
      for (let line = 0; line < changeObject.text.length; line++) {
        if (
          this.linePossibleContainsHeadline(
            editor.getLine(changeObject.from.line + line)
          )
        ) {
          requireTocUpdate = true
          break
        }
      }

      if (!requireTocUpdate) {
        // Check if one of the removed lines contains a headline
        for (let line = 0; line < changeObject.removed.length; line++) {
          if (this.linePossibleContainsHeadline(changeObject.removed[line])) {
            requireTocUpdate = true
            break
          }
        }
      }

      if (requireTocUpdate) {
        generateInEditor(editor)
      }
    }

    this.updateHighlight(editor, changeObject)

    this.value = editor.getValue()

    const { storageKey, noteKey } = this.props
    const storage = findStorage(storageKey)
    if (this.props.onChange) {
      this.props.onChange(editor)
    }

    const isWrite = !!this.props.onChange
    const hasFileChanges = isWrite

    if (storage) {
      sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
        isWrite,
        hasFileChanges,
        isFileChange: false
      })
    }
  }

  linePossibleContainsHeadline(currentLine) {
    // We can't check if the line start with # because when some write text before
    // the # we also need to update the toc
    return currentLine.includes('# ')
  }

  incrementLines(start, linesAdded, linesRemoved, editor) {
    const highlightedLines = editor.options.linesHighlighted

    const totalHighlightedLines = highlightedLines.length

    const offset = linesAdded - linesRemoved

    // Store new items to be added as we're changing the lines
    const newLines = []

    let i = totalHighlightedLines

    while (i--) {
      const lineNumber = highlightedLines[i]

      // Interval that will need to be updated
      // Between start and (start + offset) remove highlight
      if (lineNumber >= start) {
        highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)

        // Lines that need to be relocated
        if (lineNumber >= start + linesRemoved) {
          newLines.push(lineNumber + offset)
        }
      }
    }

    // Adding relocated lines
    highlightedLines.push(...newLines)

    if (this.props.onChange) {
      this.props.onChange(editor)
    }
  }

  handleHighlight(editor, changeObject) {
    const lines = editor.options.linesHighlighted

    if (!lines.includes(changeObject)) {
      lines.push(changeObject)
      editor.addLineClass(
        changeObject,
        'text',
        'CodeMirror-activeline-background'
      )
    } else {
      lines.splice(lines.indexOf(changeObject), 1)
      editor.removeLineClass(
        changeObject,
        'text',
        'CodeMirror-activeline-background'
      )
    }
    if (this.props.onChange) {
      this.props.onChange(editor)
    }
  }

  updateHighlight(editor, changeObject) {
    const linesAdded = changeObject.text.length - 1
    const linesRemoved = changeObject.removed.length - 1

    // If no lines added or removed return
    if (linesAdded === 0 && linesRemoved === 0) {
      return
    }

    let start = changeObject.from.line

    switch (changeObject.origin) {
      case '+insert", "undo':
        start += 1
        break

      case 'paste':
      case '+delete':
      case '+input':
        if (changeObject.to.ch !== 0 || changeObject.from.ch !== 0) {
          start += 1
        }
        break

      default:
        return
    }

    this.incrementLines(start, linesAdded, linesRemoved, editor)
  }

  moveCursorTo(row, col) {}

  scrollToLine(event, num) {
    const cursor = {
      line: num,
      ch: 1
    }
    this.editor.setCursor(cursor)
    const top = this.editor.charCoords({ line: num, ch: 0 }, 'local').top
    const middleHeight = this.editor.getScrollerElement().offsetHeight / 2
    this.editor.scrollTo(null, top - middleHeight - 5)
  }

  focus() {
    this.editor.focus()
  }

  blur() {
    this.editor.blur()
  }

  reload() {
    // Change event shouldn't be fired when switch note
    this.editor.off('change', this.changeHandler)
    this.value = this.props.value
    this.editor.setValue(this.props.value)
    this.editor.clearHistory()
    this.restartHighlighting()
    this.editor.on('change', this.changeHandler)
    this.editor.refresh()

    // wakatime
    const { storageKey, noteKey } = this.props
    const storage = findStorage(storageKey)
    if (storage)
      sendWakatimeHeartBeat(storage.path, noteKey, storage.name, {
        isWrite: false,
        hasFileChanges: false,
        isFileChange: true
      })
  }

  setValue(value) {
    const cursor = this.editor.getCursor()
    this.editor.setValue(value)
    this.editor.setCursor(cursor)
  }

  /**
   * Update content of one line
   * @param {Number} lineNumber
   * @param {String} content
   */
  setLineContent(lineNumber, content) {
    const prevContent = this.editor.getLine(lineNumber)
    const prevContentLength = prevContent ? prevContent.length : 0
    this.editor.replaceRange(
      content,
      { line: lineNumber, ch: 0 },
      { line: lineNumber, ch: prevContentLength }
    )
  }

  handleDropImage(dropEvent) {
    dropEvent.preventDefault()
    const { storageKey, noteKey } = this.props
    attachmentManagement.handleAttachmentDrop(
      this,
      storageKey,
      noteKey,
      dropEvent
    )
  }

  insertAttachmentMd(imageMd) {
    this.editor.replaceSelection(imageMd)
  }

  autoDetectLanguage(content) {
    const res = hljs.highlightAuto(content, Object.keys(languageMaps))
    this.setMode(languageMaps[res.language])
  }

  handlePaste(editor, forceSmartPaste) {
    const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props

    const isURL = str =>
      /(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)

    const isInLinkTag = editor => {
      const startCursor = editor.getCursor('start')
      const prevChar = editor.getRange(
        {
          line: startCursor.line,
          ch: startCursor.ch - 2
        },
        {
          line: startCursor.line,
          ch: startCursor.ch
        }
      )
      const endCursor = editor.getCursor('end')
      const nextChar = editor.getRange(
        {
          line: endCursor.line,
          ch: endCursor.ch
        },
        {
          line: endCursor.line,
          ch: endCursor.ch + 1
        }
      )
      return prevChar === '](' && nextChar === ')'
    }

    const isInFencedCodeBlock = editor => {
      const cursor = editor.getCursor()

      let token = editor.getTokenAt(cursor)
      if (token.state.fencedState) {
        return true
      }

      let line = (line = cursor.line - 1)
      while (line >= 0) {
        token = editor.getTokenAt({
          ch: 3,
          line
        })

        if (token.start === token.end) {
          --line
        } else if (token.type === 'comment') {
          if (line > 0) {
            token = editor.getTokenAt({
              ch: 3,
              line: line - 1
            })

            return token.type !== 'comment'
          } else {
            return true
          }
        } else {
          return false
        }
      }

      return false
    }

    const pastedTxt = clipboard.readText()

    if (isInFencedCodeBlock(editor)) {
      this.handlePasteText(editor, pastedTxt)
    } else if (
      fetchUrlTitle &&
      isMarkdownTitleURL(pastedTxt) &&
      !isInLinkTag(editor)
    ) {
      this.handlePasteUrl(editor, pastedTxt)
    } else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
      this.handlePasteUrl(editor, pastedTxt)
    } else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
      attachmentManagement
        .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
        .then(modifiedText => {
          this.editor.replaceSelection(modifiedText)
        })
    } else {
      const image = clipboard.readImage()
      if (!image.isEmpty()) {
        attachmentManagement.handlePasteNativeImage(
          this,
          storageKey,
          noteKey,
          image
        )
      } else if (enableSmartPaste || forceSmartPaste) {
        const pastedHtml = clipboard.readHTML()
        if (pastedHtml.length > 0) {
          this.handlePasteHtml(editor, pastedHtml)
        } else {
          this.handlePasteText(editor, pastedTxt)
        }
      } else {
        this.handlePasteText(editor, pastedTxt)
      }
    }

    if (!this.props.mode && this.props.autoDetect) {
      this.autoDetectLanguage(editor.doc.getValue())
    }
  }

  handleScroll(e) {
    if (this.props.onScroll) {
      this.props.onScroll(e)
    }
  }

  handlePasteUrl(editor, pastedTxt) {
    let taggedUrl = `<${pastedTxt}>`
    let urlToFetch = pastedTxt
    let titleMark = ''

    if (isMarkdownTitleURL(pastedTxt)) {
      const pastedTxtSplitted = pastedTxt.split(' ')
      titleMark = `${pastedTxtSplitted[0]} `
      urlToFetch = pastedTxtSplitted[1]
      taggedUrl = `<${urlToFetch}>`
    }

    editor.replaceSelection(taggedUrl)

    const isImageReponse = response => {
      return (
        response.headers.has('content-type') &&
        response.headers.get('content-type').match(/^image\/.+$/)
      )
    }
    const replaceTaggedUrl = replacement => {
      const value = editor.getValue()
      const cursor = editor.getCursor()
      const newValue = value.replace(taggedUrl, titleMark + replacement)
      const newCursor = Object.assign({}, cursor, {
        ch: cursor.ch + newValue.length - (value.length - titleMark.length)
      })

      editor.setValue(newValue)
      editor.setCursor(newCursor)
    }

    fetch(urlToFetch, {
      method: 'get'
    })
      .then(response => {
        if (isImageReponse(response)) {
          return this.mapImageResponse(response, urlToFetch)
        } else {
          return this.mapNormalResponse(response, urlToFetch)
        }
      })
      .then(replacement => {
        replaceTaggedUrl(replacement)
      })
      .catch(e => {
        replaceTaggedUrl(pastedTxt)
      })
  }

  handlePasteHtml(editor, pastedHtml) {
    const markdown = this.turndownService.turndown(pastedHtml)
    editor.replaceSelection(markdown)
  }

  handlePasteText(editor, pastedTxt) {
    editor.replaceSelection(pastedTxt)
  }

  mapNormalResponse(response, pastedTxt) {
    return this.decodeResponse(response).then(body => {
      return new Promise((resolve, reject) => {
        try {
          const parsedBody = new window.DOMParser().parseFromString(
            body,
            'text/html'
          )
          const escapePipe = str => {
            return str.replace('|', '\\|')
          }
          const linkWithTitle = `[${escapePipe(
            parsedBody.title
          )}](${pastedTxt})`
          resolve(linkWithTitle)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  initialHighlighting() {
    if (this.editor.options.linesHighlighted == null) {
      return
    }

    const totalHighlightedLines = this.editor.options.linesHighlighted.length
    const totalAvailableLines = this.editor.lineCount()

    for (let i = 0; i < totalHighlightedLines; i++) {
      const lineNumber = this.editor.options.linesHighlighted[i]
      if (lineNumber > totalAvailableLines) {
        // make sure that we skip the invalid lines althrough this case should not be happened.
        continue
      }
      this.editor.addLineClass(
        lineNumber,
        'text',
        'CodeMirror-activeline-background'
      )
    }
  }

  restartHighlighting() {
    this.editor.options.linesHighlighted = this.props.linesHighlighted
    this.initialHighlighting()
  }

  mapImageResponse(response, pastedTxt) {
    return new Promise((resolve, reject) => {
      try {
        const url = response.url
        const name = url.substring(url.lastIndexOf('/') + 1)
        const imageLinkWithName = `![${name}](${pastedTxt})`
        resolve(imageLinkWithName)
      } catch (e) {
        reject(e)
      }
    })
  }

  decodeResponse(response) {
    const headers = response.headers
    const _charset = headers.has('content-type')
      ? this.extractContentTypeCharset(headers.get('content-type'))
      : undefined
    return response.arrayBuffer().then(buff => {
      return new Promise((resolve, reject) => {
        try {
          const charset =
            _charset !== undefined && iconv.encodingExists(_charset)
              ? _charset
              : 'utf-8'
          resolve(iconv.decode(Buffer.from(buff), charset).toString())
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  extractContentTypeCharset(contentType) {
    return contentType
      .split(';')
      .filter(str => {
        return str
          .trim()
          .toLowerCase()
          .startsWith('charset')
      })
      .map(str => {
        return str.replace(/['"]/g, '').split('=')[1]
      })[0]
  }

  render() {
    const { className, fontSize, fontFamily, width, height } = this.props
    const normalisedFontFamily = normalizeEditorFontFamily(fontFamily)

    return (
      <div
        className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
        ref='root'
        tabIndex='-1'
        style={{
          fontFamily: normalisedFontFamily,
          fontSize,
          width,
          height
        }}
        onDrop={e => this.handleDropImage(e)}
      />
    )
  }

  createSpellCheckPanel() {
    const panel = document.createElement('div')
    panel.className = 'panel bottom'
    panel.id = 'editor-bottom-panel'
    const dropdown = document.createElement('select')
    dropdown.title = 'Spellcheck'
    dropdown.className = styles['spellcheck-select']
    dropdown.addEventListener('change', e =>
      spellcheck.setLanguage(this.editor, dropdown.value)
    )
    const options = spellcheck.getAvailableDictionaries()
    for (const op of options) {
      const option = document.createElement('option')
      option.value = op.value
      option.innerHTML = op.label
      dropdown.appendChild(option)
    }
    panel.appendChild(dropdown)
    return panel
  }
}

CodeEditor.propTypes = {
  value: PropTypes.string,
  enableRulers: PropTypes.bool,
  rulers: PropTypes.arrayOf(Number),
  mode: PropTypes.string,
  className: PropTypes.string,
  onBlur: PropTypes.func,
  onChange: PropTypes.func,
  readOnly: PropTypes.bool,
  autoDetect: PropTypes.bool,
  spellCheck: PropTypes.bool,
  enableMarkdownLint: PropTypes.bool,
  customMarkdownLintConfig: PropTypes.string,
  deleteUnusedAttachments: PropTypes.bool,
  RTL: PropTypes.bool
}

CodeEditor.defaultProps = {
  readOnly: false,
  theme: 'xcode',
  keyMap: 'sublime',
  fontSize: 14,
  fontFamily: 'Monaco, Consolas',
  indentSize: 4,
  indentType: 'space',
  autoDetect: false,
  spellCheck: false,
  enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint,
  customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig,
  prettierConfig: DEFAULT_CONFIG.editor.prettierConfig,
  deleteUnusedAttachments: DEFAULT_CONFIG.editor.deleteUnusedAttachments,
  RTL: false
}


================================================
FILE: browser/components/CodeEditor.styl
================================================
.codeEditor-typo
  text-decoration underline wavy red

.spellcheck-select
  border: none


================================================
FILE: browser/components/ColorPicker.js
================================================
import React from 'react'
import PropTypes from 'prop-types'
import { SketchPicker } from 'react-color'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ColorPicker.styl'

const componentHeight = 330

class ColorPicker extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      color: this.props.color || '#939395'
    }

    this.onColorChange = this.onColorChange.bind(this)
    this.handleConfirm = this.handleConfirm.bind(this)
  }

  componentWillReceiveProps(nextProps) {
    this.onColorChange(nextProps.color)
  }

  onColorChange(color) {
    this.setState({
      color
    })
  }

  handleConfirm() {
    this.props.onConfirm(this.state.color)
  }

  render() {
    const { onReset, onCancel, targetRect } = this.props
    const { color } = this.state

    const clientHeight = document.body.clientHeight
    const alignX = targetRect.right + 4
    let alignY = targetRect.top
    if (targetRect.top + componentHeight > clientHeight) {
      alignY = targetRect.bottom - componentHeight
    }

    return (
      <div
        styleName='colorPicker'
        style={{ top: `${alignY}px`, left: `${alignX}px` }}
      >
        <div styleName='cover' onClick={onCancel} />
        <SketchPicker color={color} onChange={this.onColorChange} />
        <div styleName='footer'>
          <button styleName='btn-reset' onClick={onReset}>
            Reset
          </button>
          <button styleName='btn-cancel' onClick={onCancel}>
            Cancel
          </button>
          <button styleName='btn-confirm' onClick={this.handleConfirm}>
            Confirm
          </button>
        </div>
      </div>
    )
  }
}

ColorPicker.propTypes = {
  color: PropTypes.string,
  targetRect: PropTypes.object,
  onConfirm: PropTypes.func.isRequired,
  onCancel: PropTypes.func.isRequired,
  onReset: PropTypes.func.isRequired
}

export default CSSModules(ColorPicker, styles)


================================================
FILE: browser/components/ColorPicker.styl
================================================
.colorPicker
  position fixed
  z-index 2
  display flex
  flex-direction column

.cover
  position fixed
  top 0
  right 0
  bottom 0
  left 0

.footer
  display flex
  justify-content center
  z-index 2
  align-items center
  & > button + button
    margin-left 10px

.btn-cancel,
.btn-confirm,
.btn-reset
  vertical-align middle
  height 25px
  margin-top 2.5px
  border-radius 2px
  border none
  padding 0 5px
  background-color $default-button-background
  &:hover
    background-color $default-button-background--hover
.btn-confirm
  background-color #1EC38B
  &:hover
    background-color darken(#1EC38B, 25%)




================================================
FILE: browser/components/MarkdownEditor.js
================================================
/* eslint-disable camelcase */
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './MarkdownEditor.styl'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter'
import { findStorage } from 'browser/lib/findStorage'
import ConfigManager from 'browser/main/lib/ConfigManager'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'

class MarkdownEditor extends React.Component {
  constructor(props) {
    super(props)

    // char codes for ctrl + w
    this.escapeFromEditor = [17, 87]

    // ctrl + shift + ;
    this.supportMdSelectionBold = [16, 17, 186]

    this.state = {
      status:
        props.config.editor.switchPreview === 'RIGHTCLICK'
          ? props.config.editor.delfaultStatus
          : 'CODE',
      renderValue: props.value,
      keyPressed: new Set(),
      isLocked: props.isLocked
    }

    this.lockEditorCode = this.handleLockEditor.bind(this)
    this.focusEditor = this.focusEditor.bind(this)

    this.previewRef = React.createRef()
  }

  componentDidMount() {
    this.value = this.refs.code.value
    eventEmitter.on('editor:lock', this.lockEditorCode)
    eventEmitter.on('editor:focus', this.focusEditor)
  }

  componentDidUpdate() {
    this.value = this.refs.code.value
  }

  UNSAFE_componentWillReceiveProps(props) {
    if (props.value !== this.props.value) {
      this.queueRendering(props.value)
    }
  }

  componentWillUnmount() {
    this.cancelQueue()
    eventEmitter.off('editor:lock', this.lockEditorCode)
    eventEmitter.off('editor:focus', this.focusEditor)
  }

  focusEditor() {
    this.setState(
      {
        status: 'CODE'
      },
      () => {
        if (this.refs.code == null) {
          return
        }
        this.refs.code.focus()
      }
    )
  }

  queueRendering(value) {
    clearTimeout(this.renderTimer)
    this.renderTimer = setTimeout(() => {
      this.renderPreview(value)
    }, 500)
  }

  cancelQueue() {
    clearTimeout(this.renderTimer)
  }

  renderPreview(value) {
    this.setState({
      renderValue: value
    })
  }

  setValue(value) {
    this.refs.code.setValue(value)
  }

  handleChange(e) {
    this.value = this.refs.code.value
    this.props.onChange(e)
  }

  handleContextMenu(e) {
    if (this.state.isLocked) return
    const { config } = this.props
    if (config.editor.switchPreview === 'RIGHTCLICK') {
      const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
      this.setState(
        {
          status: newStatus
        },
        () => {
          if (newStatus === 'CODE') {
            this.refs.code.focus()
          } else {
            this.previewRef.current.focus()
          }
          eventEmitter.emit('topbar:togglelockbutton', this.state.status)

          const newConfig = Object.assign({}, config)
          newConfig.editor.delfaultStatus = newStatus
          ConfigManager.set(newConfig)
        }
      )
    }
  }

  handleBlur(e) {
    if (this.state.isLocked) return
    this.setState({ keyPressed: new Set() })
    const { config } = this.props
    if (
      config.editor.switchPreview === 'BLUR' ||
      (config.editor.switchPreview === 'DBL_CLICK' &&
        this.state.status === 'CODE')
    ) {
      const cursorPosition = this.refs.code.editor.getCursor()
      this.setState(
        {
          status: 'PREVIEW'
        },
        () => {
          this.previewRef.current.focus()
          this.previewRef.current.scrollToLine(cursorPosition.line)
        }
      )
      eventEmitter.emit('topbar:togglelockbutton', this.state.status)
    }
  }

  handleDoubleClick(e) {
    if (this.state.isLocked) return
    this.setState({ keyPressed: new Set() })
    const { config } = this.props
    if (config.editor.switchPreview === 'DBL_CLICK') {
      this.setState(
        {
          status: 'CODE'
        },
        () => {
          this.refs.code.focus()
          eventEmitter.emit('topbar:togglelockbutton', this.state.status)
        }
      )
    }
  }

  handlePreviewMouseDown(e) {
    this.previewMouseDownedAt = new Date()
  }

  handlePreviewMouseUp(e) {
    const { config } = this.props
    if (
      config.editor.switchPreview === 'BLUR' &&
      new Date() - this.previewMouseDownedAt < 200
    ) {
      this.setState(
        {
          status: 'CODE'
        },
        () => {
          this.refs.code.focus()
        }
      )
      eventEmitter.emit('topbar:togglelockbutton', this.state.status)
    }
  }

  handleCheckboxClick(e) {
    e.preventDefault()
    e.stopPropagation()
    const idMatch = /checkbox-([0-9]+)/
    const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
    const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
    const checkReplace = /\[x]/i
    const uncheckReplace = /\[ ]/
    if (idMatch.test(e.target.getAttribute('id'))) {
      const lineIndex =
        parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
      const lines = this.refs.code.value.split('\n')

      const targetLine = lines[lineIndex]
      let newLine = targetLine

      if (targetLine.match(checkedMatch)) {
        newLine = targetLine.replace(checkReplace, '[ ]')
      }
      if (targetLine.match(uncheckedMatch)) {
        newLine = targetLine.replace(uncheckReplace, '[x]')
      }
      this.refs.code.setLineContent(lineIndex, newLine)
    }
  }

  focus() {
    if (this.state.status === 'PREVIEW') {
      this.setState(
        {
          status: 'CODE'
        },
        () => {
          this.refs.code.focus()
        }
      )
    } else {
      this.refs.code.focus()
    }
    eventEmitter.emit('topbar:togglelockbutton', this.state.status)
  }

  reload() {
    this.refs.code.reload()
    this.cancelQueue()
    this.renderPreview(this.props.value)
  }

  handleKeyDown(e) {
    const { config } = this.props
    if (this.state.status !== 'CODE') return false
    const keyPressed = this.state.keyPressed
    keyPressed.add(e.keyCode)
    this.setState({ keyPressed })
    const isNoteHandlerKey = el => {
      return keyPressed.has(el)
    }
    // These conditions are for ctrl-e and ctrl-w
    if (
      keyPressed.size === this.escapeFromEditor.length &&
      !this.state.isLocked &&
      this.state.status === 'CODE' &&
      this.escapeFromEditor.every(isNoteHandlerKey)
    ) {
      this.handleContextMenu()
      if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
    }
    if (
      keyPressed.size === this.supportMdSelectionBold.length &&
      this.supportMdSelectionBold.every(isNoteHandlerKey)
    ) {
      this.addMdAroundWord('**')
    }
  }

  addMdAroundWord(mdElement) {
    if (this.refs.code.editor.getSelection()) {
      return this.addMdAroundSelection(mdElement)
    }
    const currentCaret = this.refs.code.editor.getCursor()
    const word = this.refs.code.editor.findWordAt(currentCaret)
    const cmDoc = this.refs.code.editor.getDoc()
    cmDoc.replaceRange(mdElement, word.anchor)
    cmDoc.replaceRange(mdElement, {
      line: word.head.line,
      ch: word.head.ch + mdElement.length
    })
  }

  addMdAroundSelection(mdElement) {
    this.refs.code.editor.replaceSelection(
      `${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
    )
  }

  handleDropImage(dropEvent) {
    dropEvent.preventDefault()
    const { storageKey, noteKey } = this.props

    this.setState(
      {
        status: 'CODE'
      },
      () => {
        this.refs.code.focus()

        this.refs.code.editor.execCommand('goDocEnd')
        this.refs.code.editor.execCommand('goLineEnd')
        this.refs.code.editor.execCommand('newlineAndIndent')

        attachmentManagement.handleAttachmentDrop(
          this.refs.code,
          storageKey,
          noteKey,
          dropEvent
        )
      }
    )
  }

  handleKeyUp(e) {
    const keyPressed = this.state.keyPressed
    keyPressed.delete(e.keyCode)
    this.setState({ keyPressed })
  }

  handleLockEditor() {
    this.setState({ isLocked: !this.state.isLocked })
  }

  render() {
    const {
      className,
      value,
      config,
      storageKey,
      noteKey,
      linesHighlighted,
      getNote,
      RTL
    } = this.props

    let editorFontSize = parseInt(config.editor.fontSize, 10)
    if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
    let editorIndentSize = parseInt(config.editor.indentSize, 10)
    if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4

    const previewStyle = {}
    if (this.props.ignorePreviewPointerEvents)
      previewStyle.pointerEvents = 'none'

    const storage = findStorage(storageKey)

    return (
      <div
        className={
          className == null ? 'MarkdownEditor' : `MarkdownEditor ${className}`
        }
        onContextMenu={e => this.handleContextMenu(e)}
        tabIndex='-1'
        onKeyDown={e => this.handleKeyDown(e)}
        onKeyUp={e => this.handleKeyUp(e)}
      >
        <CodeEditor
          styleName={
            this.state.status === 'CODE' ? 'codeEditor' : 'codeEditor--hide'
          }
          ref='code'
          mode='Boost Flavored Markdown'
          value={value}
          theme={config.editor.theme}
          keyMap={config.editor.keyMap}
          fontFamily={config.editor.fontFamily}
          fontSize={editorFontSize}
          indentType={config.editor.indentType}
          indentSize={editorIndentSize}
          enableRulers={config.editor.enableRulers}
          rulers={config.editor.rulers}
          displayLineNumbers={config.editor.displayLineNumbers}
          lineWrapping
          matchingPairs={config.editor.matchingPairs}
          matchingCloseBefore={config.editor.matchingCloseBefore}
          matchingTriples={config.editor.matchingTriples}
          explodingPairs={config.editor.explodingPairs}
          codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
          codeBlockMatchingCloseBefore={
            config.editor.codeBlockMatchingCloseBefore
          }
          codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
          codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
          scrollPastEnd={config.editor.scrollPastEnd}
          storageKey={storageKey}
          noteKey={noteKey}
          fetchUrlTitle={config.editor.fetchUrlTitle}
          enableTableEditor={config.editor.enableTableEditor}
          linesHighlighted={linesHighlighted}
          onChange={e => this.handleChange(e)}
          onBlur={e => this.handleBlur(e)}
          spellCheck={config.editor.spellcheck}
          enableSmartPaste={config.editor.enableSmartPaste}
          hotkey={config.hotkey}
          switchPreview={config.editor.switchPreview}
          enableMarkdownLint={config.editor.enableMarkdownLint}
          customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
          dateFormatISO8601={config.editor.dateFormatISO8601}
          prettierConfig={config.editor.prettierConfig}
          deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
          RTL={RTL}
        />
        <MarkdownPreview
          ref={this.previewRef}
          styleName={
            this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
          }
          style={previewStyle}
          theme={config.ui.theme}
          keyMap={config.editor.keyMap}
          fontSize={config.preview.fontSize}
          fontFamily={config.preview.fontFamily}
          codeBlockTheme={config.preview.codeBlockTheme}
          codeBlockFontFamily={config.editor.fontFamily}
          lineNumber={config.preview.lineNumber}
          indentSize={editorIndentSize}
          scrollPastEnd={config.preview.scrollPastEnd}
          smartQuotes={config.preview.smartQuotes}
          smartArrows={config.preview.smartArrows}
          breaks={config.preview.breaks}
          sanitize={config.preview.sanitize}
          mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
          onContextMenu={e => this.handleContextMenu(e)}
          onDoubleClick={e => this.handleDoubleClick(e)}
          tabIndex='0'
          value={this.state.renderValue}
          onMouseUp={e => this.handlePreviewMouseUp(e)}
          onMouseDown={e => this.handlePreviewMouseDown(e)}
          onCheckboxClick={e => this.handleCheckboxClick(e)}
          showCopyNotification={config.ui.showCopyNotification}
          storagePath={storage.path}
          noteKey={noteKey}
          customCSS={config.preview.customCSS}
          allowCustomCSS={config.preview.allowCustomCSS}
          lineThroughCheckbox={config.preview.lineThroughCheckbox}
          getNote={getNote}
          export={config.export}
          onDrop={e => this.handleDropImage(e)}
          RTL={RTL}
        />
      </div>
    )
  }
}

MarkdownEditor.propTypes = {
  className: PropTypes.string,
  value: PropTypes.string,
  onChange: PropTypes.func,
  ignorePreviewPointerEvents: PropTypes.bool
}

export default CSSModules(MarkdownEditor, styles)


================================================
FILE: browser/components/MarkdownEditor.styl
================================================
.root
  position relative

.codeEditor
  absolute top bottom left right

.hide
  z-index 0
  opacity 0
  pointer-events none

.codeEditor--hide
  @extend .codeEditor
  @extend .hide

.preview
  display block
  absolute top bottom left right
  background-color white
  height 100%
  width 100%

.preview--hide
  @extend .preview
  @extend .hide



================================================
FILE: browser/components/MarkdownPreview.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { connect } from 'react-redux'
import Markdown from 'browser/lib/markdown'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import consts from 'browser/lib/consts'
import Raphael from 'raphael'
import flowchart from 'flowchart'
import mermaidRender from './render/MermaidRender'
import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
import Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter'
import config from 'browser/main/lib/ConfigManager'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
import formatMarkdown from 'browser/main/lib/dataApi/formatMarkdown'
import formatHTML, {
  CSS_FILES,
  buildStyle,
  getCodeThemeLink,
  getStyleParams,
  escapeHtmlCharactersInCodeTag
} from 'browser/main/lib/dataApi/formatHTML'
import formatPDF from 'browser/main/lib/dataApi/formatPDF'
import yaml from 'js-yaml'
import i18n from 'browser/lib/i18n'
import path from 'path'
import { remote, shell } from 'electron'
import attachmentManagement from '../main/lib/dataApi/attachmentManagement'
import filenamify from 'filenamify'
import { render } from 'react-dom'
import Carousel from 'react-image-carousel'
import { push } from 'connected-react-router'
import ConfigManager from '../main/lib/ConfigManager'
import uiThemes from 'browser/lib/ui-themes'
import { buildMarkdownPreviewContextMenu } from 'browser/lib/contextMenuBuilder'

const dialog = remote.dialog

const scrollBarStyle = `
::-webkit-scrollbar {
  ${config.get().ui.showScrollBar ? '' : 'display: none;'}
  width: 12px;
}

::-webkit-scrollbar-thumb {
  ${config.get().ui.showScrollBar ? '' : 'display: none;'}
  background-color: rgba(0, 0, 0, 0.15);
}

::-webkit-scrollbar-track-piece {
  background-color: inherit;
}
`
const scrollBarDarkStyle = `
::-webkit-scrollbar {
  ${config.get().ui.showScrollBar ? '' : 'display: none;'}
  width: 12px;
}

::-webkit-scrollbar-thumb {
  ${config.get().ui.showScrollBar ? '' : 'display: none;'}
  background-color: rgba(0, 0, 0, 0.3);
}

::-webkit-scrollbar-track-piece {
  background-color: inherit;
}
`

// return the line number of the line that used to generate the specified element
// return -1 if the line is not found
function getSourceLineNumberByElement(element) {
  let isHasLineNumber = element.dataset.line !== undefined
  let parent = element
  while (!isHasLineNumber && parent.parentElement !== null) {
    parent = parent.parentElement
    isHasLineNumber = parent.dataset.line !== undefined
  }
  return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1
}

class MarkdownPreview extends React.Component {
  constructor(props) {
    super(props)

    this.contextMenuHandler = e => this.handleContextMenu(e)
    this.mouseDownHandler = e => this.handleMouseDown(e)
    this.mouseUpHandler = e => this.handleMouseUp(e)
    this.DoubleClickHandler = e => this.handleDoubleClick(e)
    this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
      leading: false,
      trailing: true
    })
    this.checkboxClickHandler = e => this.handleCheckboxClick(e)
    this.saveAsTextHandler = () => this.handleSaveAsText()
    this.saveAsMdHandler = () => this.handleSaveAsMd()
    this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
    this.saveAsPdfHandler = () => this.handleSaveAsPdf()
    this.printHandler = () => this.handlePrint()
    this.resizeHandler = _.throttle(this.handleResize.bind(this), 100)

    this.linkClickHandler = this.handleLinkClick.bind(this)
    this.initMarkdown = this.initMarkdown.bind(this)
    this.initMarkdown()
  }

  initMarkdown() {
    const { smartQuotes, sanitize, breaks } = this.props
    this.markdown = new Markdown({
      typographer: smartQuotes,
      sanitize,
      breaks
    })
  }

  handleCheckboxClick(e) {
    this.props.onCheckboxClick(e)
  }

  handleScroll(e) {
    if (this.props.onScroll) {
      this.props.onScroll(e)
    }
  }

  handleContextMenu(event) {
    const menu = buildMarkdownPreviewContextMenu(this, event)
    const switchPreview = ConfigManager.get().editor.switchPreview
    if (menu != null && switchPreview !== 'RIGHTCLICK') {
      menu.popup(remote.getCurrentWindow())
    } else if (_.isFunction(this.props.onContextMenu)) {
      this.props.onContextMenu(event)
    }
  }

  handleDoubleClick(e) {
    if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
  }

  handleMouseDown(e) {
    const config = ConfigManager.get()
    const clickElement = e.target
    const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
    const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.

    if (
      config.editor.switchPreview === 'RIGHTCLICK' &&
      e.buttons === 2 &&
      config.editor.type === 'SPLIT'
    ) {
      eventEmitter.emit('topbar:togglemodebutton', 'CODE')
    }
    if (e.ctrlKey) {
      if (config.editor.type === 'SPLIT') {
        if (lineNumber !== -1) {
          eventEmitter.emit('line:jump', lineNumber)
        }
      } else {
        if (lineNumber !== -1) {
          eventEmitter.emit('editor:focus')
          eventEmitter.emit('line:jump', lineNumber)
        }
      }
    }

    if (this.props.onMouseDown != null && targetTag === 'BODY')
      this.props.onMouseDown(e)
  }

  handleMouseUp(e) {
    if (!this.props.onMouseUp) return
    if (e.target != null && e.target.tagName === 'A') {
      return null
    }
    if (this.props.onMouseUp != null) this.props.onMouseUp(e)
  }

  handleSaveAsText() {
    this.exportAsDocument('txt')
  }

  handleSaveAsMd() {
    this.exportAsDocument('md', formatMarkdown(this.props))
  }

  handleSaveAsHtml() {
    this.exportAsDocument('html', formatHTML(this.props))
  }

  handleSaveAsPdf() {
    this.exportAsDocument('pdf', formatPDF(this.props))
  }

  handlePrint() {
    this.refs.root.contentWindow.print()
  }

  exportAsDocument(fileType, contentFormatter) {
    const note = this.props.getNote()

    const options = {
      defaultPath: filenamify(note.title, {
        replacement: '_'
      }),
      filters: [{ name: 'Documents', extensions: [fileType] }],
      properties: ['openFile', 'createDirectory']
    }

    dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
      if (filename) {
        const storagePath = this.props.storagePath

        exportNote(storagePath, note, filename, contentFormatter)
          .then(res => {
            dialog.showMessageBox(remote.getCurrentWindow(), {
              type: 'info',
              message: `Exported to ${filename}`,
              buttons: [i18n.__('Ok')]
            })
          })
          .catch(err => {
            dialog.showErrorBox(
              'Export error',
              err ? err.message || err : 'Unexpected error during export'
            )
            throw err
          })
      }
    })
  }

  fixDecodedURI(node) {
    if (
      node &&
      node.children.length === 1 &&
      typeof node.children[0] === 'string'
    ) {
      const { innerText, href } = node

      node.innerText = mdurl.decode(href) === innerText ? href : innerText
    }
  }

  getScrollBarStyle() {
    const { theme } = this.props

    return uiThemes.some(item => item.name === theme && item.isDark)
      ? scrollBarDarkStyle
      : scrollBarStyle
  }

  componentDidMount() {
    const { onDrop } = this.props

    this.refs.root.setAttribute('sandbox', 'allow-scripts')
    this.refs.root.contentWindow.document.body.addEventListener(
      'contextmenu',
      this.contextMenuHandler
    )

    let styles = `
      <style id='style'></style>
      <link rel="stylesheet" id="codeTheme">
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <style>
        ${this.getScrollBarStyle()}
      </style>
    `

    CSS_FILES.forEach(file => {
      styles += `<link rel="stylesheet" href="${file}">`
    })

    this.refs.root.contentWindow.document.head.innerHTML = styles
    this.rewriteIframe()
    this.applyStyle()

    this.refs.root.contentWindow.document.addEventListener(
      'mousedown',
      this.mouseDownHandler
    )
    this.refs.root.contentWindow.document.addEventListener(
      'mouseup',
      this.mouseUpHandler
    )
    this.refs.root.contentWindow.document.addEventListener(
      'dblclick',
      this.DoubleClickHandler
    )
    this.refs.root.contentWindow.document.addEventListener(
      'drop',
      onDrop || this.preventImageDroppedHandler
    )
    this.refs.root.contentWindow.document.addEventListener(
      'dragover',
      this.preventImageDroppedHandler
    )
    this.refs.root.contentWindow.document.addEventListener(
      'scroll',
      this.scrollHandler
    )
    this.refs.root.contentWindow.addEventListener('resize', this.resizeHandler)
    eventEmitter.on('export:save-text', this.saveAsTextHandler)
    eventEmitter.on('export:save-md', this.saveAsMdHandler)
    eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
    eventEmitter.on('export:save-pdf', this.saveAsPdfHandler)
    eventEmitter.on('print', this.printHandler)
  }

  componentWillUnmount() {
    const { onDrop } = this.props

    this.refs.root.contentWindow.document.body.removeEventListener(
      'contextmenu',
      this.contextMenuHandler
    )
    this.refs.root.contentWindow.document.removeEventListener(
      'mousedown',
      this.mouseDownHandler
    )
    this.refs.root.contentWindow.document.removeEventListener(
      'mouseup',
      this.mouseUpHandler
    )
    this.refs.root.contentWindow.document.removeEventListener(
      'dblclick',
      this.DoubleClickHandler
    )
    this.refs.root.contentWindow.document.removeEventListener(
      'drop',
      onDrop || this.preventImageDroppedHandler
    )
    this.refs.root.contentWindow.document.removeEventListener(
      'dragover',
      this.preventImageDroppedHandler
    )
    this.refs.root.contentWindow.document.removeEventListener(
      'scroll',
      this.scrollHandler
    )
    this.refs.root.contentWindow.removeEventListener(
      'resize',
      this.resizeHandler
    )
    eventEmitter.off('export:save-text', this.saveAsTextHandler)
    eventEmitter.off('export:save-md', this.saveAsMdHandler)
    eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
    eventEmitter.off('export:save-pdf', this.saveAsPdfHandler)
    eventEmitter.off('print', this.printHandler)
  }

  componentDidUpdate(prevProps) {
    // actual rewriteIframe function should be called only once
    let needsRewriteIframe = false
    if (prevProps.value !== this.props.value) needsRewriteIframe = true
    if (
      prevProps.smartQuotes !== this.props.smartQuotes ||
      prevProps.sanitize !== this.props.sanitize ||
      prevProps.mermaidHTMLLabel !== this.props.mermaidHTMLLabel ||
      prevProps.smartArrows !== this.props.smartArrows ||
      prevProps.breaks !== this.props.breaks ||
      prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
    ) {
      this.initMarkdown()
      needsRewriteIframe = true
    }
    if (
      prevProps.fontFamily !== this.props.fontFamily ||
      prevProps.fontSize !== this.props.fontSize ||
      prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
      prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
      prevProps.lineNumber !== this.props.lineNumber ||
      prevProps.showCopyNotification !== this.props.showCopyNotification ||
      prevProps.theme !== this.props.theme ||
      prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
      prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
      prevProps.customCSS !== this.props.customCSS ||
      prevProps.RTL !== this.props.RTL
    ) {
      this.applyStyle()
      needsRewriteIframe = true
    }

    if (needsRewriteIframe) {
      this.rewriteIframe()
    }

    // Should scroll to top after selecting another note
    if (prevProps.noteKey !== this.props.noteKey) {
      this.scrollTo(0, 0)
    }
  }

  applyStyle() {
    const {
      fontFamily,
      fontSize,
      codeBlockFontFamily,
      lineNumber,
      codeBlockTheme,
      scrollPastEnd,
      theme,
      allowCustomCSS,
      customCSS,
      RTL
    } = getStyleParams(this.props)

    this.getWindow().document.getElementById(
      'codeTheme'
    ).href = getCodeThemeLink(codeBlockTheme)

    this.getWindow().document.getElementById('style').innerHTML = buildStyle(
      fontFamily,
      fontSize,
      codeBlockFontFamily,
      lineNumber,
      scrollPastEnd,
      theme,
      allowCustomCSS,
      customCSS,
      RTL
    )
  }

  rewriteIframe() {
    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll(
        'input[type="checkbox"]'
      ),
      el => {
        el.removeEventListener('click', this.checkboxClickHandler)
      }
    )

    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('a'),
      el => {
        el.removeEventListener('click', this.linkClickHandler)
      }
    )

    const {
      theme,
      indentSize,
      showCopyNotification,
      storagePath,
      noteKey,
      sanitize,
      mermaidHTMLLabel
    } = this.props
    let { value, codeBlockTheme } = this.props

    this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
    if (sanitize === 'NONE') {
      const splitWithCodeTag = value.split('```')
      value = escapeHtmlCharactersInCodeTag(splitWithCodeTag)
    }
    const renderedHTML = this.markdown.render(value)
    attachmentManagement.migrateAttachments(value, storagePath, noteKey)
    this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
      renderedHTML,
      storagePath
    )
    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll(
        'input[type="checkbox"]'
      ),
      el => {
        el.addEventListener('click', this.checkboxClickHandler)
      }
    )

    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('a'),
      el => {
        this.fixDecodedURI(el)
        el.addEventListener('click', this.linkClickHandler)
      }
    )

    codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)

    const codeBlockThemeClassName = codeBlockTheme
      ? codeBlockTheme.className
      : 'cm-s-default'

    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('.code code'),
      el => {
        let syntax = CodeMirror.findModeByName(convertModeName(el.className))
        if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
        CodeMirror.requireMode(syntax.mode, () => {
          const content = htmlTextHelper.decodeEntities(el.innerHTML)
          const copyIcon = document.createElement('i')
          copyIcon.innerHTML =
            '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
          copyIcon.onclick = e => {
            e.preventDefault()
            e.stopPropagation()
            copy(content)
            if (showCopyNotification) {
              this.notify('Saved to Clipboard!', {
                body: 'Paste it wherever you want!',
                silent: true
              })
            }
          }

          el.parentNode.appendChild(copyIcon)
          el.innerHTML = ''
          el.parentNode.className += ` ${codeBlockThemeClassName}`

          CodeMirror.runMode(content, syntax.mime, el, {
            tabSize: indentSize
          })
        })
      }
    )

    const opts = {}

    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
      el => {
        Raphael.setWindow(this.getWindow())
        try {
          const diagram = flowchart.parse(
            htmlTextHelper.decodeEntities(el.innerHTML)
          )
          el.innerHTML = ''
          diagram.drawSVG(el, opts)
          _.forEach(el.querySelectorAll('a'), el => {
            el.addEventListener('click', this.linkClickHandler)
          })
        } catch (e) {
          el.className = 'flowchart-error'
          el.innerHTML = 'Flowchart parse error: ' + e.message
        }
      }
    )

    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('.sequence'),
      el => {
        Raphael.setWindow(this.getWindow())
        try {
          const diagram = SequenceDiagram.parse(
            htmlTextHelper.decodeEntities(el.innerHTML)
          )
          el.innerHTML = ''
          diagram.drawSVG(el, { theme: 'simple' })
          _.forEach(el.querySelectorAll('a'), el => {
            el.addEventListener('click', this.linkClickHandler)
          })
        } catch (e) {
          el.className = 'sequence-error'
          el.innerHTML = 'Sequence diagram parse error: ' + e.message
        }
      }
    )

    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('.chart'),
      el => {
        try {
          const format = el.attributes.getNamedItem('data-format').value
          const chartConfig =
            format === 'yaml'
              ? yaml.load(el.innerHTML)
              : JSON.parse(el.innerHTML)
          el.innerHTML = ''

          const canvas = document.createElement('canvas')
          el.appendChild(canvas)

          const height = el.attributes.getNamedItem('data-height')
          if (height && height.value !== 'undefined') {
            el.style.height = height.value + 'vh'
            canvas.height = height.value + 'vh'
          }

          // eslint-disable-next-line no-unused-vars
          const chart = new Chart(canvas, chartConfig)
        } catch (e) {
          el.className = 'chart-error'
          el.innerHTML = 'chartjs diagram parse error: ' + e.message
        }
      }
    )
    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
      el => {
        mermaidRender(
          el,
          htmlTextHelper.decodeEntities(el.innerHTML),
          theme,
          mermaidHTMLLabel
        )
      }
    )

    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('.gallery'),
      el => {
        const images = el.innerHTML.split(/\n/g).filter(i => i.length > 0)
        el.innerHTML = ''

        const height = el.attributes.getNamedItem('data-height')
        if (height && height.value !== 'undefined') {
          el.style.height = height.value + 'vh'
        }

        let autoplay = el.attributes.getNamedItem('data-autoplay')
        if (autoplay && autoplay.value !== 'undefined') {
          autoplay = parseInt(autoplay.value, 10) || 0
        } else {
          autoplay = 0
        }

        render(<Carousel images={images} autoplay={autoplay} />, el)
      }
    )

    const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
    const rect = markdownPreviewIframe.getBoundingClientRect()
    const config = { attributes: true, subtree: true }
    const imgObserver = new MutationObserver(mutationList => {
      for (const mu of mutationList) {
        if (mu.target.className === 'carouselContent-enter-done') {
          this.setImgOnClickEventHelper(mu.target, rect)
          break
        }
      }
    })

    const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
      'img'
    )
    for (const img of imgList) {
      const parentEl = img.parentElement
      this.setImgOnClickEventHelper(img, rect)
      imgObserver.observe(parentEl, config)
    }

    const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
      'a'
    )
    for (const a of aList) {
      a.removeEventListener('click', this.linkClickHandler)
      a.addEventListener('click', this.linkClickHandler)
    }
  }

  setImgOnClickEventHelper(img, rect) {
    img.onclick = () => {
      const widthMagnification = document.body.clientWidth / img.width
      const heightMagnification = document.body.clientHeight / img.height
      const baseOnWidth = widthMagnification < heightMagnification
      const magnification = baseOnWidth
        ? widthMagnification
        : heightMagnification

      const zoomImgWidth = img.width * magnification
      const zoomImgHeight = img.height * magnification
      const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
      const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
      const originalImgTop = img.y + rect.top
      const originalImgLeft = img.x + rect.left
      const originalImgRect = {
        top: `${originalImgTop}px`,
        left: `${originalImgLeft}px`,
        width: `${img.width}px`,
        height: `${img.height}px`
      }
      const zoomInImgRect = {
        top: `${baseOnWidth ? zoomImgTop : 0}px`,
        left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
        width: `${zoomImgWidth}px`,
        height: `${zoomImgHeight}px`
      }
      const animationSpeed = 300

      const zoomImg = document.createElement('img')
      zoomImg.src = img.src
      zoomImg.style = `
        position: absolute;
        top: ${baseOnWidth ? zoomImgTop : 0}px;
        left: ${baseOnWidth ? 0 : zoomImgLeft}px;
        width: ${zoomImgWidth};
        height: ${zoomImgHeight}px;
        `
      zoomImg.animate([originalImgRect, zoomInImgRect], animationSpeed)

      const overlay = document.createElement('div')
      overlay.style = `
        background-color: rgba(0,0,0,0.5);
        cursor: zoom-out;
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: ${document.body.clientHeight}px;
        z-index: 100;
      `
      overlay.onclick = () => {
        zoomImg.style = `
          position: absolute;
          top: ${originalImgTop}px;
          left: ${originalImgLeft}px;
          width: ${img.width}px;
          height: ${img.height}px;
          `
        const zoomOutImgAnimation = zoomImg.animate(
          [zoomInImgRect, originalImgRect],
          animationSpeed
        )
        zoomOutImgAnimation.onfinish = () => overlay.remove()
      }

      overlay.appendChild(zoomImg)
      document.body.appendChild(overlay)
    }
  }

  handleResize() {
    _.forEach(
      this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'),
      el => {
        el.setAttribute('height', el.clientWidth / el.getAttribute('ratio'))
      }
    )
  }

  focus() {
    this.refs.root.focus()
  }

  getWindow() {
    return this.refs.root.contentWindow
  }

  /**
   * @public
   * @param {Number} targetLine
   */
  scrollToLine(targetLine) {
    const blocks = this.getWindow().document.querySelectorAll(
      'body [data-line]'
    )

    for (let index = 0; index < blocks.length; index++) {
      let block = blocks[index]
      const line = parseInt(block.getAttribute('data-line'))

      if (line > targetLine || index === blocks.length - 1) {
        block = blocks[index - 1]
        block != null && this.scrollTo(0, block.offsetTop)
        break
      }
    }
  }

  /**
   * `document.body.scrollTo`
   * @param {Number} x
   * @param {Number} y
   */
  scrollTo(x, y) {
    this.getWindow().document.body.scrollTo(x, y)
  }

  preventImageDroppedHandler(e) {
    e.preventDefault()
    e.stopPropagation()
  }

  notify(title, options) {
    if (global.process.platform === 'win32') {
      options.icon = path.join(
        'file://',
        global.__dirname,
        '../../resources/app.png'
      )
    }
    return new window.Notification(title, options)
  }

  handleLinkClick(e) {
    e.preventDefault()
    e.stopPropagation()

    const el = e.target.closest('a[href]')
    if (!el) return

    const rawHref = el.getAttribute('href')
    const { dispatch } = this.props
    if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()

    const parser = document.createElement('a')
    parser.href = rawHref
    const isStartWithHash = rawHref[0] === '#'
    const { href, hash } = parser

    const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10

    const extractIdRegex = /file:\/\/.*main.?\w*.html#/ // file://path/to/main(.development.)html
    const regexNoteInternalLink = new RegExp(`${extractIdRegex.source}(.+)`)
    if (isStartWithHash || regexNoteInternalLink.test(rawHref)) {
      const posOfHash = linkHash.indexOf('#')
      if (posOfHash > -1) {
        const extractedId = linkHash.slice(posOfHash + 1)
        const targetId = mdurl.encode(extractedId)
        const targetElement = this.getWindow().document.getElementById(targetId)

        if (targetElement != null) {
          this.scrollTo(0, targetElement.offsetTop)
        }
        return
      }
    }

    // this will match the new uuid v4 hash and the old hash
    // e.g.
    // :note:1c211eb7dcb463de6490 and
    // :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
    const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
    if (regexIsNoteLink.test(linkHash)) {
      eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
      return
    }

    const regexIsLine = /^:line:[0-9]/
    if (regexIsLine.test(linkHash)) {
      const numberPattern = /\d+/g

      const lineNumber = parseInt(linkHash.match(numberPattern)[0])
      eventEmitter.emit('line:jump', lineNumber)
      return
    }

    // this will match the old link format storage.key-note.key
    // e.g.
    // 877f99c3268608328037-1c211eb7dcb463de6490
    const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
    if (regexIsLegacyNoteLink.test(linkHash)) {
      eventEmitter.emit('list:jump', linkHash.split('-')[1])
      return
    }

    const regexIsTagLink = /^:tag:([\w]+)$/
    if (regexIsTagLink.test(rawHref)) {
      const tag = rawHref.match(regexIsTagLink)[1]
      dispatch(push(`/tags/${encodeURIComponent(tag)}`))
      return
    }

    // other case
    this.openExternal(href)
  }

  openExternal(href) {
    try {
      const success =
        shell.openExternal(href) || shell.openExternal(decodeURI(href))
      if (!success) console.error('failed to open url ' + href)
    } catch (e) {
      // URI Error threw from decodeURI
      console.error(e)
    }
  }

  render() {
    const { className, style, tabIndex } = this.props
    return (
      <iframe
        className={
          className != null ? 'MarkdownPreview ' + className : 'MarkdownPreview'
        }
        style={style}
        tabIndex={tabIndex}
        ref='root'
      />
    )
  }
}

MarkdownPreview.propTypes = {
  onClick: PropTypes.func,
  onDoubleClick: PropTypes.func,
  onMouseUp: PropTypes.func,
  onMouseDown: PropTypes.func,
  onContextMenu: PropTypes.func,
  className: PropTypes.string,
  value: PropTypes.string,
  showCopyNotification: PropTypes.bool,
  storagePath: PropTypes.string,
  smartQuotes: PropTypes.bool,
  smartArrows: PropTypes.bool,
  breaks: PropTypes.bool
}

export default connect(
  null,
  null,
  null,
  { forwardRef: true }
)(MarkdownPreview)


================================================
FILE: browser/components/MarkdownSplitEditor.js
================================================
import React from 'react'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage'
import _ from 'lodash'

import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules'

class MarkdownSplitEditor extends React.Component {
  constructor(props) {
    super(props)
    this.value = props.value
    this.focus = () => this.refs.code.focus()
    this.reload = () => this.refs.code.reload()
    this.userScroll = props.config.preview.scrollSync
    this.state = {
      isSliderFocused: false,
      codeEditorWidthInPercent: 50,
      codeEditorHeightInPercent: 50
    }
  }

  componentDidUpdate(prevProps) {
    if (
      this.props.config.preview.scrollSync !==
      prevProps.config.preview.scrollSync
    ) {
      this.userScroll = this.props.config.preview.scrollSync
    }
  }

  handleCursorActivity(editor) {
    if (this.userScroll) {
      const previewDoc = _.get(
        this,
        'refs.preview.refs.root.contentWindow.document'
      )
      const previewTop = _.get(previewDoc, 'body.scrollTop')

      const line = editor.doc.getCursor().line
      let top
      if (line === 0) {
        top = 0
      } else {
        const blockElements = previewDoc.querySelectorAll('body [data-line]')
        const blocks = []
        for (const block of blockElements) {
          const l = parseInt(block.getAttribute('data-line'))

          blocks.push({
            line: l,
            top: block.offsetTop
          })

          if (l > line) {
            break
          }
        }

        if (blocks.length === 1) {
          const block = blockElements[blockElements.length - 1]

          blocks.push({
            line: editor.doc.size,
            top: block.offsetTop + block.offsetHeight
          })
        }

        const i = blocks.length - 1

        const ratio =
          (blocks[i].top - blocks[i - 1].top) /
          (blocks[i].line - blocks[i - 1].line)

        const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)

        top =
          blocks[i - 1].top +
          Math.floor((line - blocks[i - 1].line) * ratio) -
          delta
      }

      this.scrollTo(previewTop, top, y =>
        _.set(previewDoc, 'body.scrollTop', y)
      )
    }
  }

  setValue(value) {
    this.refs.code.setValue(value)
  }

  handleOnChange(e) {
    this.value = this.refs.code.value
    this.props.onChange(e)
  }

  handleEditorScroll(e) {
    if (this.userScroll) {
      const previewDoc = _.get(
        this,
        'refs.preview.refs.root.contentWindow.document'
      )
      const codeDoc = _.get(this, 'refs.code.editor.doc')

      const from = codeDoc.cm.coordsChar({ left: 0, top: 0 }).line
      const to = codeDoc.cm.coordsChar({
        left: 0,
        top: codeDoc.cm.display.lastWrapHeight * 1.125
      }).line
      const previewTop = _.get(previewDoc, 'body.scrollTop')

      let top
      if (from === 0) {
        top = 0
      } else if (to === codeDoc.lastLine()) {
        top =
          _.get(previewDoc, 'body.scrollHeight') -
          _.get(previewDoc, 'body.clientHeight')
      } else {
        const line = from + Math.floor((to - from) / 3)

        const blockElements = previewDoc.querySelectorAll('body [data-line]')
        const blocks = []
        for (const block of blockElements) {
          const l = parseInt(block.getAttribute('data-line'))

          blocks.push({
            line: l,
            top: block.offsetTop
          })

          if (l > line) {
            break
          }
        }

        if (blocks.length === 1) {
          const block = blockElements[blockElements.length - 1]

          blocks.push({
            line: codeDoc.size,
            top: block.offsetTop + block.offsetHeight
          })
        }

        const i = blocks.length - 1

        const ratio =
          (blocks[i].top - blocks[i - 1].top) /
          (blocks[i].line - blocks[i - 1].line)

        top =
          blocks[i - 1].top + Math.floor((line - blocks[i - 1].line) * ratio)
      }

      this.scrollTo(previewTop, top, y =>
        _.set(previewDoc, 'body.scrollTop', y)
      )
    }
  }

  handlePreviewScroll(e) {
    if (this.userScroll) {
      const previewDoc = _.get(
        this,
        'refs.preview.refs.root.contentWindow.document'
      )
      const codeDoc = _.get(this, 'refs.code.editor.doc')

      const srcTop = _.get(previewDoc, 'body.scrollTop')
      const editorTop = _.get(codeDoc, 'scrollTop')

      let top
      if (srcTop === 0) {
        top = 0
      } else {
        const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
        const previewTop = srcTop + delta

        const blockElements = previewDoc.querySelectorAll('body [data-line]')
        const blocks = []
        for (const block of blockElements) {
          const top = block.offsetTop

          blocks.push({
            line: parseInt(block.getAttribute('data-line')),
            top
          })

          if (top > previewTop) {
            break
          }
        }

        if (blocks.length === 1) {
          const block = blockElements[blockElements.length - 1]

          blocks.push({
            line: codeDoc.size,
            top: block.offsetTop + block.offsetHeight
          })
        }

        const i = blocks.length - 1

        const from = codeDoc.cm.heightAtLine(blocks[i - 1].line, 'local')
        const to = codeDoc.cm.heightAtLine(blocks[i].line, 'local')

        const ratio =
          (previewTop - blocks[i - 1].top) / (blocks[i].top - blocks[i - 1].top)

        top = from + Math.floor((to - from) * ratio) - delta
      }

      this.scrollTo(editorTop, top, y => codeDoc.cm.scrollTo(0, y))
    }
  }

  handleCheckboxClick(e) {
    e.preventDefault()
    e.stopPropagation()
    const idMatch = /checkbox-([0-9]+)/
    const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
    const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
    const checkReplace = /\[x]/i
    const uncheckReplace = /\[ ]/
    if (idMatch.test(e.target.getAttribute('id'))) {
      const lineIndex =
        parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
      const lines = this.refs.code.value.split('\n')

      const targetLine = lines[lineIndex]
      let newLine = targetLine

      if (targetLine.match(checkedMatch)) {
        newLine = targetLine.replace(checkReplace, '[ ]')
      }
      if (targetLine.match(uncheckedMatch)) {
        newLine = targetLine.replace(uncheckReplace, '[x]')
      }
      this.refs.code.setLineContent(lineIndex, newLine)
    }
  }

  handleMouseMove(e) {
    if (this.state.isSliderFocused) {
      const rootRect = this.refs.root.getBoundingClientRect()
      if (this.props.isStacking) {
        const rootHeight = rootRect.height
        const offset = rootRect.top
        let newCodeEditorHeightInPercent =
          ((e.pageY - offset) / rootHeight) * 100

        // limit minSize to 10%, maxSize to 90%
        if (newCodeEditorHeightInPercent <= 10) {
          newCodeEditorHeightInPercent = 10
        }

        if (newCodeEditorHeightInPercent >= 90) {
          newCodeEditorHeightInPercent = 90
        }

        this.setState({
          codeEditorHeightInPercent: newCodeEditorHeightInPercent
        })
      } else {
        const rootWidth = rootRect.width
        const offset = rootRect.left
        let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100

        // limit minSize to 10%, maxSize to 90%
        if (newCodeEditorWidthInPercent <= 10) {
          newCodeEditorWidthInPercent = 10
        }

        if (newCodeEditorWidthInPercent >= 90) {
          newCodeEditorWidthInPercent = 90
        }

        this.setState({
          codeEditorWidthInPercent: newCodeEditorWidthInPercent
        })
      }
    }
  }

  handleMouseUp(e) {
    e.preventDefault()
    this.setState({
      isSliderFocused: false
    })
  }

  handleMouseDown(e) {
    e.preventDefault()
    this.setState({
      isSliderFocused: true
    })
  }

  scrollTo(from, to, scroller) {
    const distance = to - from
    const framerate = 1000 / 60
    const frames = 20
    const refractory = frames * framerate

    this.userScroll = false

    let frame = 0
    let scrollPos, time
    const timer = setInterval(() => {
      time = frame / frames
      scrollPos =
        time < 0.5
          ? 2 * time * time // ease in
          : -1 + (4 - 2 * time) * time // ease out

      scroller(from + scrollPos * distance)

      if (frame >= frames) {
        clearInterval(timer)
        setTimeout(() => {
          this.userScroll = true
        }, refractory)
      }
      frame++
    }, framerate)
  }

  render() {
    const {
      config,
      value,
      storageKey,
      noteKey,
      linesHighlighted,
      getNote,
      isStacking,
      RTL
    } = this.props
    let storage
    try {
      storage = findStorage(storageKey)
    } catch (e) {
      return <div />
    }

    let editorStyle = {}
    let previewStyle = {}
    let sliderStyle = {}

    let editorFontSize = parseInt(config.editor.fontSize, 10)
    if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
    editorStyle.fontSize = editorFontSize

    let editorIndentSize = parseInt(config.editor.indentSize, 10)
    if (!(editorStyle.fontSize > 0 && editorStyle.fontSize < 132))
      editorIndentSize = 4
    editorStyle.indentSize = editorIndentSize

    editorStyle = Object.assign(
      editorStyle,
      isStacking
        ? {
            width: '100%',
            height: `${this.state.codeEditorHeightInPercent}%`
          }
        : {
            width: `${this.state.codeEditorWidthInPercent}%`,
            height: '100%'
          }
    )

    previewStyle = Object.assign(
      previewStyle,
      isStacking
        ? {
            width: '100%',
            height: `${100 - this.state.codeEditorHeightInPercent}%`
          }
        : {
            width: `${100 - this.state.codeEditorWidthInPercent}%`,
            height: '100%'
          }
    )

    sliderStyle = Object.assign(
      sliderStyle,
      isStacking
        ? {
            left: 0,
            top: `${this.state.codeEditorHeightInPercent}%`
          }
        : {
            left: `${this.state.codeEditorWidthInPercent}%`,
            top: 0
          }
    )

    if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
      previewStyle.pointerEvents = 'none'

    return (
      <div
        styleName='root'
        ref='root'
        onMouseMove={e => this.handleMouseMove(e)}
        onMouseUp={e => this.handleMouseUp(e)}
      >
        <CodeEditor
          ref='code'
          width={editorStyle.width}
          height={editorStyle.height}
          mode='Boost Flavored Markdown'
          value={value}
          theme={config.editor.theme}
          keyMap={config.editor.keyMap}
          fontFamily={config.editor.fontFamily}
          fontSize={editorStyle.fontSize}
          displayLineNumbers={config.editor.displayLineNumbers}
          lineWrapping
          matchingPairs={config.editor.matchingPairs}
          matchingCloseBefore={config.editor.matchingCloseBefore}
          matchingTriples={config.editor.matchingTriples}
          explodingPairs={config.editor.explodingPairs}
          codeBlockMatchingPairs={config.editor.codeBlockMatchingPairs}
          codeBlockMatchingCloseBefore={
            config.editor.codeBlockMatchingCloseBefore
          }
          codeBlockMatchingTriples={config.editor.codeBlockMatchingTriples}
          codeBlockExplodingPairs={config.editor.codeBlockExplodingPairs}
          indentType={config.editor.indentType}
          indentSize={editorStyle.indentSize}
          enableRulers={config.editor.enableRulers}
          rulers={config.editor.rulers}
          scrollPastEnd={config.editor.scrollPastEnd}
          fetchUrlTitle={config.editor.fetchUrlTitle}
          enableTableEditor={config.editor.enableTableEditor}
          storageKey={storageKey}
          noteKey={noteKey}
          linesHighlighted={linesHighlighted}
          onChange={e => this.handleOnChange(e)}
          onScroll={e => this.handleEditorScroll(e)}
          onCursorActivity={e => this.handleCursorActivity(e)}
          spellCheck={config.editor.spellcheck}
          enableSmartPaste={config.editor.enableSmartPaste}
          hotkey={config.hotkey}
          switchPreview={config.editor.switchPreview}
          enableMarkdownLint={config.editor.enableMarkdownLint}
          customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
          dateFormatISO8601={config.editor.dateFormatISO8601}
          deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
          RTL={RTL}
        />
        <div
          styleName={isStacking ? 'slider-hoz' : 'slider'}
          style={{ left: sliderStyle.left, top: sliderStyle.top }}
          onMouseDown={e => this.handleMouseDown(e)}
        >
          <div styleName='slider-hitbox' />
        </div>
        <MarkdownPreview
          ref='preview'
          style={previewStyle}
          theme={config.ui.theme}
          keyMap={config.editor.keyMap}
          fontSize={config.preview.fontSize}
          fontFamily={config.preview.fontFamily}
          codeBlockTheme={config.preview.codeBlockTheme}
          codeBlockFontFamily={config.editor.fontFamily}
          lineNumber={config.preview.lineNumber}
          indentSize={editorIndentSize}
          scrollPastEnd={config.preview.scrollPastEnd}
          smartQuotes={config.preview.smartQuotes}
          smartArrows={config.preview.smartArrows}
          breaks={config.preview.breaks}
          sanitize={config.preview.sanitize}
          mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
          tabInde='0'
          value={value}
          onCheckboxClick={e => this.handleCheckboxClick(e)}
          onScroll={e => this.handlePreviewScroll(e)}
          showCopyNotification={config.ui.showCopyNotification}
          storagePath={storage.path}
          noteKey={noteKey}
          customCSS={config.preview.customCSS}
          allowCustomCSS={config.preview.allowCustomCSS}
          lineThroughCheckbox={config.preview.lineThroughCheckbox}
          getNote={getNote}
          export={config.export}
          RTL={RTL}
        />
      </div>
    )
  }
}

export default CSSModules(MarkdownSplitEditor, styles)


================================================
FILE: browser/components/MarkdownSplitEditor.styl
================================================
.root
  width 100%
  height 100%
  font-size 30px
  display flex
  flex-wrap wrap
  .slider
    absolute top bottom
    top -2px
    width 0
    z-index 0
    border-left 1px solid $ui-borderColor
  .slider-hitbox
    absolute top bottom left right
    width 7px
    left -3px
    z-index 10
    cursor col-resize
  .slider-hoz
    absolute left right
    .slider-hitbox
      absolute left right
      width: 100%
      height 7px
      cursor row-resize


apply-theme(theme)
  body[data-theme={theme}]
    .root
      .slider
        border-left 1px solid get-theme-var(theme, 'borderColor')

for theme in 'dark' 'dracula' 'solarized-dark'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/ModalEscButton.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ModalEscButton.styl'

const ModalEscButton = ({ handleEscButtonClick }) => (
  <button styleName='escButton' onClick={handleEscButtonClick}>
    <div styleName='esc-mark'>×</div>
    <div>esc</div>
  </button>
)

ModalEscButton.propTypes = {
  handleEscButtonClick: PropTypes.func.isRequired
}

export default CSSModules(ModalEscButton, styles)


================================================
FILE: browser/components/ModalEscButton.styl
================================================
.escButton
  height 50px
  position absolute
  background-color transparent
  color $ui-inactive-text-color
  border none
  top 1px
  right 10px
  text-align center
  width top-bar--height
  height top-bar-height

.esc-mark
  font-size 28px
  margin-top -5px
  margin-bottom -7px

================================================
FILE: browser/components/NavToggleButton.js
================================================
/**
 * @fileoverview Micro component for toggle SideNav
 */
import PropTypes from 'prop-types'
import React from 'react'
import styles from './NavToggleButton.styl'
import CSSModules from 'browser/lib/CSSModules'

/**
 * @param {boolean} isFolded
 * @param {Function} handleToggleButtonClick
 */

const NavToggleButton = ({ isFolded, handleToggleButtonClick }) => (
  <button styleName='navToggle' onClick={e => handleToggleButtonClick(e)}>
    {isFolded ? (
      <i className='fa fa-angle-double-right fa-2x' />
    ) : (
      <i className='fa fa-angle-double-left fa-2x' />
    )}
  </button>
)

NavToggleButton.propTypes = {
  isFolded: PropTypes.bool.isRequired,
  handleToggleButtonClick: PropTypes.func.isRequired
}

export default CSSModules(NavToggleButton, styles)


================================================
FILE: browser/components/NavToggleButton.styl
================================================
.navToggle
  navButtonColor()
  display block
  position absolute
  left 5px
  bottom 5px
  border-radius 16.5px
  height 34px
  width 34px
  line-height 100%
  padding 0
  &:hover
    border: 1px solid #1EC38B;
    background-color: alpha(#1EC38B, 30%)
    border-radius: 50%;

body[data-theme="white"]
  navWhiteButtonColor()

apply-theme(theme)
  body[data-theme={theme}]
    .navToggle:hover
      background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
      border 1px solid get-theme-var(theme, 'button--active-backgroundColor')
      transition 0.15s
      color get-theme-var(theme, 'text-color')

for theme in 'dark' 'dracula' 'solarized-dark'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/NoteItem.js
================================================
/**
 * @fileoverview Note item component.
 */
import PropTypes from 'prop-types'
import React from 'react'
import { isArray, sortBy } from 'lodash'
import invertColor from 'invert-color'
import Emoji from 'react-emoji-render'
import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl'
import TodoProcess from './TodoProcess'
import i18n from 'browser/lib/i18n'

/**
 * @description Tag element component.
 * @param {string} tagName
 * @param {string} color
 * @return {React.Component}
 */
const TagElement = ({ tagName, color }) => {
  const style = {}
  if (color) {
    style.backgroundColor = color
    style.color = invertColor(color, {
      black: '#222',
      white: '#f1f1f1',
      threshold: 0.3
    })
  }
  return (
    <span styleName='item-bottom-tagList-item' key={tagName} style={style}>
      #{tagName}
    </span>
  )
}

/**
 * @description Tag element list component.
 * @param {Array|null} tags
 * @param {boolean} showTagsAlphabetically
 * @param {Object} coloredTags
 * @return {React.Component}
 */
const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
  if (!isArray(tags)) {
    return []
  }

  if (showTagsAlphabetically) {
    return sortBy(tags).map(tag =>
      TagElement({ tagName: tag, color: coloredTags[tag] })
    )
  } else {
    return tags.map(tag =>
      TagElement({ tagName: tag, color: coloredTags[tag] })
    )
  }
}

/**
 * @description Note item component when using normal display mode.
 * @param {boolean} isActive
 * @param {Object} note
 * @param {Function} handleNoteClick
 * @param {Function} handleNoteContextMenu
 * @param {Function} handleDragStart
 * @param {Object} coloredTags
 * @param {string} dateDisplay
 */
const NoteItem = ({
  isActive,
  note,
  dateDisplay,
  handleNoteClick,
  handleNoteContextMenu,
  handleDragStart,
  pathname,
  storageName,
  folderName,
  viewType,
  showTagsAlphabetically,
  coloredTags
}) => (
  <div
    styleName={isActive ? 'item--active' : 'item'}
    key={note.key}
    onClick={e => handleNoteClick(e, note.key)}
    onContextMenu={e => handleNoteContextMenu(e, note.key)}
    onDragStart={e => handleDragStart(e, note)}
    draggable='true'
  >
    <div styleName='item-wrapper'>
      {note.type === 'SNIPPET_NOTE' ? (
        <i styleName='item-title-icon' className='fa fa-fw fa-code' />
      ) : (
        <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
      )}
      <div styleName='item-title'>
        {note.title.trim().length > 0 ? (
          <Emoji text={note.title} />
        ) : (
          <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
        )}
      </div>
      <div styleName='item-middle'>
        <div styleName='item-middle-time'>{dateDisplay}</div>
        <div styleName='item-middle-app-meta'>
          <div
            title={
              viewType === 'ALL'
                ? storageName
                : viewType === 'STORAGE'
                ? folderName
                : null
            }
            styleName='item-middle-app-meta-label'
          >
            {viewType === 'ALL' && storageName}
            {viewType === 'STORAGE' && folderName}
          </div>
        </div>
      </div>
      <div styleName='item-bottom'>
        <div styleName='item-bottom-tagList'>
          {note.tags.length > 0 ? (
            TagElementList(note.tags, showTagsAlphabetically, coloredTags)
          ) : (
            <span
              style={{ fontStyle: 'italic', opacity: 0.5 }}
              styleName='item-bottom-tagList-empty'
            >
              {i18n.__('No tags')}
            </span>
          )}
        </div>
        <div>
          {note.isStarred ? (
            <img
              styleName='item-star'
              src='../resources/icon/icon-starred.svg'
            />
          ) : (
            ''
          )}
          {note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
            <i styleName='item-pin' className='fa fa-thumb-tack' />
          ) : (
            ''
          )}
          {note.type === 'MARKDOWN_NOTE' ? (
            <TodoProcess todoStatus={getTodoStatus(note.content)} />
          ) : (
            ''
          )}
        </div>
      </div>
    </div>
  </div>
)

NoteItem.propTypes = {
  isActive: PropTypes.bool.isRequired,
  dateDisplay: PropTypes.string.isRequired,
  coloredTags: PropTypes.object,
  note: PropTypes.shape({
    storage: PropTypes.string.isRequired,
    key: PropTypes.string.isRequired,
    type: PropTypes.string.isRequired,
    title: PropTypes.string.isrequired,
    tags: PropTypes.array,
    isStarred: PropTypes.bool.isRequired,
    isTrashed: PropTypes.bool.isRequired,
    blog: PropTypes.shape({
      blogLink: PropTypes.string,
      blogId: PropTypes.number
    })
  }),
  handleNoteClick: PropTypes.func.isRequired,
  handleNoteContextMenu: PropTypes.func.isRequired,
  handleDragStart: PropTypes.func.isRequired
}

export default CSSModules(NoteItem, styles)


================================================
FILE: browser/components/NoteItem.styl
================================================
$control-height = 30px

.root
  absolute left bottom
  top $topBar-height - 1
  background-color $ui-noteList-backgroundColor

.item
  position relative
  padding 0 20px
  user-select none
  cursor pointer
  background-color $ui-noteList-backgroundColor
  transition 0.2s
  &:hover
    background-color alpha($ui-button--active-backgroundColor, 20%)
    .item-title
    .item-title-icon
    .item-bottom-time
      transition 0.15s
      color $ui-text-color
    .item-bottom-tagList-item
      background-color alpha(white, 0.6)
      color $ui-text-color
    .item-star
      color $ui-favorite-star-button-color
  &:active
    background-color alpha($ui-button--active-backgroundColor, 40%)
    color $ui-text-color
    .item-title
    .item-title-icon
    .item-bottom-time
      transition 0.15s
      color $ui-text-color
    .item-bottom-tagList-item
      background-color alpha(white, 0.6)
      color $ui-text-color

.item-wrapper
  padding 15px 0
  border-bottom $ui-border
  position relative

.item--active
  @extend .item
  background-color alpha($ui-button--active-backgroundColor, 60%)
  color $ui-text-color
  .item-title
  .item-title-empty
  .item-bottom-tagList-empty
  .item-bottom-time
  .item-title-icon
    color $ui-text-color
  .item-bottom-tagList-item
    background-color alpha(white, 0.6)
    color $ui-text-color
  .item-wrapper
    border-color transparent
  .item-star
    color $ui-favorite-star-button-color
  &:hover
    background-color alpha($ui-button--active-backgroundColor, 40%)
    color #e74c3c
    .menu-button-label
      color $ui-text-color
  &:active, &:active:hover
    background-color alpha($ui-button--active-backgroundColor, 40%)
    color #e74c3c
    .menu-button-label
      color $ui-text-color

.item-title-icon
  position relative
  font-size 12px
  color $ui-inactive-text-color
  top 2px

.item-title
  font-size 15px
  font-weight 700
  position relative
  top -12px
  left 20px
  padding 0px 15px 0px 0px
  margin-bottom 4px
  overflow ellipsis
  color $ui-inactive-text-color

.item-title-empty
  font-weight normal
  color $ui-inactive-text-color

.item-middle
  font-size 13px
  padding-left 2px
  padding-bottom 2px

.item-middle-time
  color $ui-inactive-text-color
  display inline-block

.item-middle-app-meta
  float right
  .item-middle-app-meta-label
    opacity 0.4
    color $ui-inactive-text-color
    padding 0 3px
    white-space nowrap
    text-overflow ellipsis
    overflow hidden
    max-width 200px

.item-bottom
  position relative
  bottom 0px
  margin-top 10px
  font-size 12px
  line-height 20px
  overflow ellipsis
  display block

.item-bottom-tagList
  flex 1
  overflow ellipsis
  line-height 25px
  padding-left 2px
  margin-right 40px

.item-bottom-tagList-item
  font-size 11px
  margin-right 8px
  padding 0
  box-sizing border-box
  border-radius 2px
  padding 4px
  vertical-align middle
  background-color white
  color $ui-inactive-text-color

.item-bottom-time
  color $ui-inactive-text-color
  font-size 13px
  padding-left 2px
  padding-bottom 2px

.item-star
  position absolute
  right 2px
  top 5px
  color alpha($ui-favorite-star-button-color, 60%)
  font-size 12px
  padding 0
  border-radius 17px

.item-pin
  position absolute
  right 25px
  top 7px
  color #E54D42
  font-size 14px
  padding 0
  border-radius 17px

body[data-theme="white"]
  .item
    background-color $ui-white-noteList-backgroundColor
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 60%)
    &:active
      background-color $ui-button--active-backgroundColor

  .item--active
    @extend .item
    background-color $ui-button--active-backgroundColor
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 60%)

body[data-theme="dark"]
  .root
    border-color $ui-dark-borderColor
    background-color $ui-dark-noteList-backgroundColor

  .item
    border-color $ui-dark-borderColor
    background-color $ui-dark-noteList-backgroundColor
    &:hover
      transition 0.15s
      background-color alpha($ui-dark-button--active-backgroundColor, 20%)
      color $ui-dark-text-color
      .item-title
      .item-title-icon
      .item-bottom-time
        transition 0.15s
        color $ui-dark-text-color
      .item-bottom-tagList-item
        transition 0.15s
        background-color alpha($ui-dark-tagList-backgroundColor, 20%)
        color $ui-dark-text-color
    &:active
      transition 0.15s
      background-color $ui-dark-button--active-backgroundColor
      color $ui-dark-text-color
      .item-title
      .item-title-icon
      .item-bottom-time
        transition 0.15s
        color $ui-dark-text-color
      .item-bottom-tagList-item
        transition 0.15s
        background-color alpha($ui-dark-tagList-backgroundColor, 10%)
        color $ui-dark-text-color

  .item-wrapper
    border-color alpha($ui-dark-button--active-backgroundColor, 60%)

  .item--active
    border-color $ui-dark-borderColor
    background-color $ui-dark-button--active-backgroundColor
    .item-wrapper
      border-color transparent
    .item-title
    .item-title-icon
    .item-bottom-time
      color $ui-dark-text-color
    .item-bottom-tagList-item
      background-color alpha($ui-dark-tagList-backgroundColor, 10%)
      color $ui-dark-text-color
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 60%)
      color $ui-dark-button--hover-color
      .item-bottom-tagList-item
        background-color alpha($ui-dark-tagList-backgroundColor, 20%)

  .item-title
    color $ui-inactive-text-color

  .item-title-icon
    color $ui-inactive-text-color

  .item-title-empty
    color $ui-inactive-text-color

  .item-bottom-tagList-item
    background-color alpha($ui-dark-button--active-backgroundColor, 40%)
    color $ui-inactive-text-color

  .item-bottom-tagList-empty
    color $ui-inactive-text-color
    vertical-align middle


body[data-theme="solarized-dark"]
  .root
    border-color $ui-solarized-dark-borderColor
    background-color $ui-solarized-dark-noteList-backgroundColor

  .item
    border-color $ui-solarized-dark-borderColor
    background-color $ui-solarized-dark-noteList-backgroundColor
    &:hover
      transition 0.15s
      // background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
      color $ui-solarized-dark-text-color
      .item-title
      .item-title-icon
      .item-bottom-time
        transition 0.15s
        color $ui-solarized-dark-text-color
      .item-bottom-tagList-item
        transition 0.15s
        background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
        color $ui-solarized-dark-text-color
    &:active
      transition 0.15s
      background-color $ui-solarized-dark-noteList-backgroundColor
      color $ui-solarized-dark-text-color
      .item-title
      .item-title-icon
      .item-bottom-time
        transition 0.15s
        color $ui-solarized-dark-text-color
      .item-bottom-tagList-item
        transition 0.15s
        background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%)
        color $ui-solarized-dark-text-color

  .item-wrapper
    border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)

  .item--active
    border-color $ui-solarized-dark-borderColor
    background-color $ui-solarized-dark-button-backgroundColor
    .item-wrapper
      border-color transparent
    .item-title
    .item-title-icon
    .item-bottom-time
      color $ui-solarized-dark-text-color
    .item-bottom-tagList-item
      background-color alpha(white, 10%)
      color $ui-solarized-dark-text-color
    &:hover
      // background-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
      color #c0392b
      .item-bottom-tagList-item
        background-color alpha(#fff, 20%)

  .item-title
    color $ui-inactive-text-color

  .item-title-icon
    color $ui-inactive-text-color

  .item-title-empty
    color $ui-inactive-text-color

  .item-bottom-tagList-item
    background-color alpha($ui-dark-button--active-backgroundColor, 40%)
    color $ui-inactive-text-color

  .item-bottom-tagList-empty
    color $ui-inactive-text-color
    vertical-align middle

apply-theme(theme)
  body[data-theme={theme}]
    .root
      border-color get-theme-var(theme, 'borderColor')
      background-color get-theme-var(theme, 'noteList-backgroundColor')

    .item
      border-color get-theme-var(theme, 'borderColor')
      background-color get-theme-var(theme, 'noteList-backgroundColor')
      &:hover
        transition 0.15s
        // background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
        color get-theme-var(theme, 'text-color')
        .item-title
        .item-title-icon
        .item-bottom-time
          transition 0.15s
          color get-theme-var(theme, 'text-color')
        .item-bottom-tagList-item
          transition 0.15s
          background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
          color get-theme-var(theme, 'text-color')
      &:active
        transition 0.15s
        background-color get-theme-var(theme, 'noteList-backgroundColor')
        color get-theme-var(theme, 'text-color')
        .item-title
        .item-title-icon
        .item-bottom-time
          transition 0.15s
          color get-theme-var(theme, 'text-color')
        .item-bottom-tagList-item
          transition 0.15s
          background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 10%)
          color get-theme-var(theme, 'text-color')

    .item-wrapper
      border-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)

    .item--active
      border-color get-theme-var(theme, 'borderColor')
      background-color get-theme-var(theme, 'button-backgroundColor')
      .item-wrapper
        border-color transparent
      .item-title
      .item-title-icon
      .item-bottom-time
        color get-theme-var(theme, 'active-color')
      .item-bottom-tagList-item
        background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
        color get-theme-var(theme, 'text-color')
      &:hover
        // background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
        color get-theme-var(theme, 'button--hover-color')
        .item-bottom-tagList-item
          background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)

    .item-title
      color $ui-inactive-text-color

    .item-title-icon
      color $ui-inactive-text-color

    .item-title-empty
      color $ui-inactive-text-color

    .item-bottom-tagList-item
      background-color alpha($ui-dark-button--active-backgroundColor, 40%)
      color $ui-inactive-text-color

    .item-bottom-tagList-empty
      color $ui-inactive-text-color
      vertical-align middle

for theme in 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/NoteItemSimple.js
================================================
/**
 * @fileoverview Note item component with simple display mode.
 */
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './NoteItemSimple.styl'
import i18n from 'browser/lib/i18n'

/**
 * @description Note item component when using simple display mode.
 * @param {boolean} isActive
 * @param {Object} note
 * @param {Function} handleNoteClick
 * @param {Function} handleNoteContextMenu
 * @param {Function} handleDragStart
 */
const NoteItemSimple = ({
  isActive,
  isAllNotesView,
  note,
  handleNoteClick,
  handleNoteContextMenu,
  handleDragStart,
  pathname,
  storage
}) => (
  <div
    styleName={isActive ? 'item-simple--active' : 'item-simple'}
    key={note.key}
    onClick={e => handleNoteClick(e, note.key)}
    onContextMenu={e => handleNoteContextMenu(e, note.key)}
    onDragStart={e => handleDragStart(e, note)}
    draggable='true'
  >
    <div styleName='item-simple-title'>
      {note.type === 'SNIPPET_NOTE' ? (
        <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
      ) : (
        <i
          styleName='item-simple-title-icon'
          className='fa fa-fw fa-file-text-o'
        />
      )}
      {note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
        <i styleName='item-pin' className='fa fa-thumb-tack' />
      ) : (
        ''
      )}
      {note.title.trim().length > 0 ? (
        note.title
      ) : (
        <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
      )}
      {isAllNotesView && (
        <div styleName='item-simple-right'>
          <span styleName='item-simple-right-storageName'>{storage.name}</span>
        </div>
      )}
    </div>
  </div>
)

NoteItemSimple.propTypes = {
  isActive: PropTypes.bool.isRequired,
  note: PropTypes.shape({
    storage: PropTypes.string.isRequired,
    key: PropTypes.string.isRequired,
    type: PropTypes.string.isRequired,
    title: PropTypes.string.isrequired
  }),
  handleNoteClick: PropTypes.func.isRequired,
  handleNoteContextMenu: PropTypes.func.isRequired,
  handleDragStart: PropTypes.func.isRequired
}

export default CSSModules(NoteItemSimple, styles)


================================================
FILE: browser/components/NoteItemSimple.styl
================================================
$control-height = 30px

.root
  absolute left bottom
  top $topBar-height - 1
  background-color $ui-noteList-backgroundColor

.item-simple
  position relative
  padding 0 20px
  user-select none
  cursor pointer
  background-color $ui-noteList-backgroundColor
  transition 0.2s
  &:hover
    background-color alpha($ui-button--active-backgroundColor, 20%)
    .item-simple-title
    .item-simple-title-empty
    .item-simple-title-icon
      color $ui-text-color
  &:active
    background-color alpha($ui-button--active-backgroundColor, 40%)
    color $ui-text-color
    .item-simple-title
    .item-simple-title-empty
    .item-simple-title-icon
      color $ui-text-color

.item-simple--active
  @extend .item-simple
  background-color alpha($ui-button--active-backgroundColor, 60%)
  color $ui-text-color
  .item-simple-title
  .item-simple-title-empty
    border-color transparent
    color $ui-text-color
  .item-simple-title-icon
    color $ui-text-color
  &:hover
    background-color alpha($ui-button--active-backgroundColor, 40%)
    color #e74c3c
    .menu-button-label
      color $ui-text-color
  &:active, &:active:hover
    background-color alpha($ui-button--active-backgroundColor, 40%)
    color #e74c3c
    .menu-button-label
      color $ui-text-color

.item-simple-title
  font-size 13px
  height 40px
  padding-right 20px
  box-sizing border-box
  line-height 24px
  padding-top 8px
  overflow ellipsis
  color $ui-inactive-text-color
  border-bottom $ui-border
  position relative

.item-simple-title-icon
  font-size 12px
  color $ui-inactive-text-color
  padding-right 6px

.item-simple-title-empty
  font-weight normal
  color $ui-inactive-text-color

.item-pin
  position absolute
  right 0px
  top 12px
  color #E54D42
  font-size 14px
  padding 0
  border-radius 17px

body[data-theme="white"]
  .item-simple
    background-color $ui-white-noteList-backgroundColor
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 60%)
    &:active
      background-color $ui-button--active-backgroundColor

  .item-simple--active
    @extend .item-simple
    background-color $ui-button--active-backgroundColor
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 60%)

body[data-theme="dark"]
  .root
    border-color $ui-dark-borderColor
    background-color $ui-dark-noteList-backgroundColor

  .item-simple
    border-color $ui-dark-borderColor
    background-color $ui-dark-noteList-backgroundColor
    &:hover
      transition 0.15s
      background-color alpha($ui-dark-button--active-backgroundColor, 20%)
      color $ui-dark-text-color
      .item-simple-title
      .item-simple-title-empty
      .item-simple-title-icon
      .item-simple-bottom-time
        transition 0.15s
        color $ui-dark-text-color
      .item-simple-bottom-tagList-item
        transition 0.15s
        background-color alpha(#fff, 20%)
        color $ui-dark-text-color
    &:active
      transition 0.15s
      background-color $ui-dark-button--active-backgroundColor
      color $ui-dark-text-color
      .item-simple-title
      .item-simple-title-empty
      .item-simple-title-icon
      .item-simple-bottom-time
        transition 0.15s
        color $ui-dark-text-color
      .item-simple-bottom-tagList-item
        transition 0.15s
        background-color alpha(white, 10%)
        color $ui-dark-text-color

  .item-simple--active
    border-color $ui-dark-borderColor
    background-color $ui-dark-button--active-backgroundColor
    .item-simple-wrapper
      border-color transparent
    .item-simple-title
    .item-simple-title-empty
    .item-simple-title-icon
    .item-simple-bottom-time
      color $ui-dark-text-color
    .item-simple-bottom-tagList-item
      background-color alpha(white, 10%)
      color $ui-dark-text-color
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 60%)
      color #c0392b
      .item-simple-bottom-tagList-item
        background-color alpha(#fff, 20%)

  .item-simple-title
    color $ui-inactive-text-color
    border-color alpha($ui-dark-button--active-backgroundColor, 60%)

  .item-simple-title-icon
    color $ui-darkinactive-text-color

  .item-simple-title-empty
    color $ui-dark-inactive-text-color


body[data-theme="solarized-dark"]
  .root
    border-color $ui-solarized-dark-borderColor
    background-color $ui-solarized-dark-noteList-backgroundColor

  .item-simple
    border-color $ui-solarized-dark-borderColor
    background-color $ui-solarized-dark-noteList-backgroundColor
    &:hover
      transition 0.15s
      background-color alpha($ui-dark-button--active-backgroundColor, 60%)
      color $ui-solarized-dark-text-color
      .item-simple-title
      .item-simple-title-empty
      .item-simple-title-icon
      .item-simple-bottom-time
        transition 0.15s
        color $ui-solarized-dark-text-color
      .item-simple-bottom-tagList-item
        transition 0.15s
        background-color alpha(#fff, 20%)
        color $ui-solarized-dark-text-color
    &:active
      transition 0.15s
      // background-color $ui-solarized-dark-button--active-backgroundColor
      color $ui-dark-text-color
      .item-simple-title
      .item-simple-title-empty
      .item-simple-title-icon
      .item-simple-bottom-time
        transition 0.15s
        color $ui-solarized-dark-text-color
      .item-simple-bottom-tagList-item
        transition 0.15s
        background-color alpha(white, 10%)
        color $ui-solarized-dark-text-color

  .item-simple--active
    border-color $ui-solarized-dark-borderColor
    background-color $ui-solarized-dark-tag-backgroundColor
    .item-simple-wrapper
      border-color transparent
    .item-simple-title
    .item-simple-title-empty
    .item-simple-title-icon
       color $ui-dark-text-color
    .item-simple-bottom-time
      color $ui-solarized-dark-text-color
    .item-simple-bottom-tagList-item
      background-color alpha(white, 10%)
      color $ui-solarized-dark-text-color
    &:hover
      // background-color alpha($ui-dark-button--active-backgroundColor, 60%)
      color #c0392b
      .item-simple-bottom-tagList-item
        background-color alpha(#fff, 20%)
  .item-simple-title
    color $ui-dark-text-color
    border-bottom $ui-dark-borderColor
  .item-simple-right
    float right
    .item-simple-right-storageName
      padding-left 4px
      opacity 0.4

apply-theme(theme)
  body[data-theme={theme}]
    .root
      border-color get-theme-var(theme, 'borderColor')
      background-color get-theme-var(theme, 'noteList-backgroundColor')

    .item-simple
      border-color get-theme-var(theme, 'borderColor')
      background-color get-theme-var(theme, 'noteList-backgroundColor')
      &:hover
        transition 0.15s
        background-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
        color get-theme-var(theme, 'text-color')
        .item-simple-title
        .item-simple-title-empty
        .item-simple-title-icon
        .item-simple-bottom-time
          transition 0.15s
          color get-theme-var(theme, 'text-color')
        .item-simple-bottom-tagList-item
          transition 0.15s
          background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
          color get-theme-var(theme, 'text-color')
      &:active
        transition 0.15s
        background-color get-theme-var(theme, 'button--active-backgroundColor')
        color get-theme-var(theme, 'text-color')
        .item-simple-title
        .item-simple-title-empty
        .item-simple-title-icon
        .item-simple-bottom-time
          transition 0.15s
          color get-theme-var(theme, 'text-color')
        .item-simple-bottom-tagList-item
          transition 0.15s
          background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
          color get-theme-var(theme, 'text-color')

    .item-simple--active
      border-color get-theme-var(theme, 'borderColor')
      background-color get-theme-var(theme, 'button--active-backgroundColor')
      .item-simple-wrapper
        border-color transparent
      .item-simple-title
      .item-simple-title-empty
      .item-simple-title-icon
      .item-simple-bottom-time
        color get-theme-var(theme, 'text-color')
      .item-simple-bottom-tagList-item
        background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
        color get-theme-var(theme, 'text-color')
      &:hover
        // background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
        color #c0392b
        .item-simple-bottom-tagList-item
          background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
    .item-simple-title
      color $ui-dark-text-color
      border-bottom $ui-dark-borderColor
    .item-simple-right
      float right
      .item-simple-right-storageName
        padding-left 4px
        opacity 0.4

for theme in 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/RealtimeNotification.js
================================================
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RealtimeNotification.styl'

const electron = require('electron')
const { shell } = electron

class RealtimeNotification extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      notifications: []
    }
  }

  componentDidMount() {
    this.fetchNotifications()
  }

  fetchNotifications() {
    const notificationsUrl =
      'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
    fetch(notificationsUrl)
      .then(response => {
        return response.json()
      })
      .then(json => {
        this.setState({ notifications: json.notifications })
      })
  }

  handleLinkClick(e) {
    shell.openExternal(e.currentTarget.href)
    e.preventDefault()
  }

  render() {
    const { notifications } = this.state
    const link =
      notifications.length > 0 ? (
        <a
          styleName='notification-link'
          href={notifications[0].linkUrl}
          onClick={e => this.handleLinkClick(e)}
        >
          Info: {notifications[0].text}
        </a>
      ) : (
        ''
      )

    return (
      <div styleName='notification-area' style={this.props.style}>
        {link}
      </div>
    )
  }
}

RealtimeNotification.propTypes = {}

export default CSSModules(RealtimeNotification, styles)


================================================
FILE: browser/components/RealtimeNotification.styl
================================================
.notification-area
  z-index 1000
  font-size 12px
  position: relative
  top: 12px
  background-color none

.notification-link
  position absolute
  text-decoration none
  color #282A36
  font-size 14px
  border 1px solid #6FA8E6
  background-color alpha(#6FA8E6, 0.2)
  padding 5px 12px
  border-radius 2px
  transition 0.2s
  &:hover
    color #1378BD

body[data-theme="dark"]
  .notification-area
    background-color none

  .notification-link
    color #fff
    border 1px solid alpha(#5CB85C, 0.6)
    background-color alpha(#5CB85C, 0.2)
    transition 0.2s
    &:hover
      color #5CB85C

apply-theme(theme)
  body[data-theme={theme}]
    .notification-area
      background-color none

    .notification-link
      color get-theme-var(theme, 'text-color')
      border none
      background-color get-theme-var(theme, 'button-backgroundColor')
      &:hover
        color get-theme-var(theme, 'button--hover-color')

for theme in 'solarized-dark' 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/SideNavFilter.js
================================================
/**
 * @fileoverview Filter for all notes.
 */
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SideNavFilter.styl'
import i18n from 'browser/lib/i18n'

/**
 * @param {boolean} isFolded
 * @param {boolean} isHomeActive
 * @param {Function} handleAllNotesButtonClick
 * @param {boolean} isStarredActive
 * @param {Function} handleStarredButtonClick
 * @return {React.Component}
 */
const SideNavFilter = ({
  isFolded,
  isHomeActive,
  handleAllNotesButtonClick,
  isStarredActive,
  handleStarredButtonClick,
  isTrashedActive,
  handleTrashedButtonClick,
  counterDelNote,
  counterTotalNote,
  counterStarredNote,
  handleFilterButtonContextMenu
}) => (
  <div styleName={isFolded ? 'menu--folded' : 'menu'}>
    <button
      styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
      onClick={handleAllNotesButtonClick}
    >
      <div styleName='iconWrap'>
        <img
          src={
            isHomeActive
              ? '../resources/icon/icon-all-active.svg'
              : '../resources/icon/icon-all.svg'
          }
        />
      </div>
      <span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
      <span styleName='counters'>{counterTotalNote}</span>
    </button>

    <button
      styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
      onClick={handleStarredButtonClick}
    >
      <div styleName='iconWrap'>
        <img
          src={
            isStarredActive
              ? '../resources/icon/icon-star-active.svg'
              : '../resources/icon/icon-star-sidenav.svg'
          }
        />
      </div>
      <span styleName='menu-button-label'>{i18n.__('Starred')}</span>
      <span styleName='counters'>{counterStarredNote}</span>
    </button>

    <button
      styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
      onClick={handleTrashedButtonClick}
      onContextMenu={handleFilterButtonContextMenu}
    >
      <div styleName='iconWrap'>
        <img
          src={
            isTrashedActive
              ? '../resources/icon/icon-trash-active.svg'
              : '../resources/icon/icon-trash-sidenav.svg'
          }
        />
      </div>
      <span styleName='menu-button-label'>{i18n.__('Trash')}</span>
      <span styleName='counters'>{counterDelNote}</span>
    </button>
  </div>
)

SideNavFilter.propTypes = {
  isFolded: PropTypes.bool,
  isHomeActive: PropTypes.bool.isRequired,
  handleAllNotesButtonClick: PropTypes.func.isRequired,
  isStarredActive: PropTypes.bool.isRequired,
  isTrashedActive: PropTypes.bool.isRequired,
  handleStarredButtonClick: PropTypes.func.isRequired,
  handleTrashedButtonClick: PropTypes.func.isRequired
}

export default CSSModules(SideNavFilter, styles)


================================================
FILE: browser/components/SideNavFilter.styl
================================================
.menu
  margin-bottom 20px

.menu-button
  navButtonColor()
  height 36px
  padding 0 15px 0 20px
  font-size 14px
  width 100%
  text-align left
  overflow ellipsis
  display flex
  align-items center
  &:hover, &:active, &:active:hover
    color #1EC38B
    background-color alpha($ui-button-default--active-backgroundColor, 20%)

.iconWrap
  width 20px
  text-align center

.counters
  float right
  color $ui-inactive-text-color

.menu-button--active
  @extend .menu-button
  SideNavFilter()
  color #1EC38B
  background-color alpha($ui-button-default--active-backgroundColor, 20%)
  .menu-button-label, .counters
    color #1EC38B
  &:hover
    color #1EC38B

.menu-button-star--active
  @extend .menu-button
  SideNavFilter()
  color #1EC38B
  background-color alpha($ui-button-default--active-backgroundColor, 20%)
  .menu-button-label, .counters
    color #1EC38B

.menu-button-trash--active
  @extend .menu-button
  SideNavFilter()
  color #1EC38B
  background-color alpha($ui-button-default--active-backgroundColor, 20%)
  .menu-button-label, .counters
    color #1EC38B

.menu-button-label
  margin-left 10px
  flex 1

.menu--folded
  @extend .menu
  .menu-button, .menu-button--active, .menu-button-star--active, .menu-button-trash--active
    text-align center
    padding 0 12px
    &:hover .menu-button-label
      transition opacity 0.15s
      opacity 1
      color $ui-tooltip-text-color
      background-color $ui-tooltip-backgroundColor


  .menu-button-label
    position fixed
    display inline-block
    height 36px
    left 44px
    padding 0 10px
    margin-left 0
    overflow ellipsis
    z-index 10
    line-height 32px
    border-top-right-radius 2px
    border-bottom-right-radius 2px
    pointer-events none
    opacity 0
    font-size 13px
  .counters
    display none

body[data-theme="white"]
  .menu-button
    navWhiteButtonColor()

  .counters
    color $ui-inactive-text-color

  .menu-button--active
    color #e74c3c
    background-color $ui-button--active-backgroundColor
    .menu-button-label
      color $ui-text-color
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 50%)
      color #e74c3c
      .menu-button-label
        color $ui-text-color
    &:active, &:active:hover
      background-color alpha($ui-button--active-backgroundColor, 50%)
      color #e74c3c
      .menu-button-label
        color $ui-text-color

  .menu-button-star--active
    color #F9BF3B
    background-color $ui-button--active-backgroundColor
    .menu-button-label
      color $ui-text-color
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 50%)
      color #F9BF3B
      .menu-button-label
        color $ui-text-color
    &:active, &:active:hover
      background-color alpha($ui-button--active-backgroundColor, 50%)
      color #F9BF3B
      .menu-button-label
        color $ui-text-color

  .menu-button-trash--active
    color #5D9E36
    background-color $ui-button--active-backgroundColor
    .menu-button-label
      color $ui-text-color
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 50%)
      color #5D9E36
      .menu-button-label
        color $ui-text-color
    &:active, &:active:hover
      background-color alpha($ui-button--active-backgroundColor, 50%)
      color #5D9E36
      .menu-button-label
        color $ui-text-color

body[data-theme="dark"]
  .menu-button
    &:active
      background-color $ui-dark-button--active-backgroundColor
      color $ui-dark-text-color
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 20%)
      color $ui-dark-text-color

  .menu-button--active
    color #c0392b
    background-color $ui-dark-button--active-backgroundColor
    .menu-button-label
      color $ui-dark-text-color
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 50%)
      color #c0392b
      .menu-button-label
        color $ui-dark-text-color

  .menu-button-star--active
    color $ui-favorite-star-button-color
    background-color $ui-dark-button--active-backgroundColor
    .menu-button-label
      color $ui-dark-text-color
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 50%)
      color $ui-favorite-star-button-color
      .menu-button-label
        color $ui-dark-text-color

  .menu-button-trash--active
    color #5D9E36
    background-color $ui-dark-button--active-backgroundColor
    .menu-button-label
      color $ui-dark-text-color
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 50%)
      color #5D9E36
      .menu-button-label
        color $ui-dark-text-color

apply-theme(theme)
  body[data-theme={theme}]
    .menu-button
      &:active
        background-color get-theme-var(theme, 'noteList-backgroundColor')
        color get-theme-var(theme, 'text-color')
      &:hover
        background-color get-theme-var(theme, 'button-backgroundColor')
        color get-theme-var(theme, 'text-color')

    .menu-button--active
      color get-theme-var(theme, 'text-color')
      background-color get-theme-var(theme, 'button-backgroundColor')
      .menu-button-label
        color get-theme-var(theme, 'text-color')
      &:hover
        background-color get-theme-var(theme, 'button-backgroundColor')
        color get-theme-var(theme, 'text-color')
        .menu-button-label
          color get-theme-var(theme, 'text-color')

    .menu-button-star--active
      color get-theme-var(theme, 'text-color')
      background-color get-theme-var(theme, 'button-backgroundColor')
      .menu-button-label
        color get-theme-var(theme, 'text-color')
      &:hover
        background-color get-theme-var(theme, 'button-backgroundColor')
        color get-theme-var(theme, 'text-color')
        .menu-button-label
          color get-theme-var(theme, 'text-color')

    .menu-button-trash--active
      color get-theme-var(theme, 'text-color')
      background-color get-theme-var(theme, 'button-backgroundColor')
      .menu-button-label
        color get-theme-var(theme, 'text-color')
      &:hover
        background-color get-theme-var(theme, 'button-backgroundColor')
        color get-theme-var(theme, 'text-color')
        .menu-button-label
          color get-theme-var(theme, 'text-color')

for theme in 'solarized-dark' 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/SnippetTab.js
================================================
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SnippetTab.styl'
import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'

class SnippetTab extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      isRenaming: false,
      name: props.snippet.name
    }
  }

  componentWillUpdate(nextProps) {
    if (nextProps.snippet.name !== this.props.snippet.name) {
      this.setState({
        name: nextProps.snippet.name
      })
    }
  }

  handleClick(e) {
    this.props.onClick(e)
  }

  handleContextMenu(e) {
    context.popup([
      {
        label: i18n.__('Rename'),
        click: e => this.handleRenameClick(e)
      }
    ])
  }

  handleRenameClick(e) {
    this.startRenaming()
  }

  handleNameInputBlur(e) {
    this.handleRename()
  }

  handleNameInputChange(e) {
    this.setState({
      name: e.target.value
    })
  }

  handleNameInputKeyDown(e) {
    switch (e.keyCode) {
      case 13:
        this.handleRename()
        break
      case 27:
        this.setState((prevState, props) => ({
          name: props.snippet.name,
          isRenaming: false
        }))
        break
    }
  }

  handleRename() {
    this.setState(
      {
        isRenaming: false
      },
      () => {
        if (this.props.snippet.name !== this.state.name) {
          this.props.onRename(this.state.name)
        }
      }
    )
  }

  handleDeleteButtonClick(e) {
    this.props.onDelete(e)
  }

  startRenaming() {
    this.setState(
      {
        isRenaming: true
      },
      () => {
        this.refs.name.focus()
        this.refs.name.select()
      }
    )
  }

  handleDragStart(e) {
    e.dataTransfer.dropEffect = 'move'
    this.props.onDragStart(e)
  }

  handleDrop(e) {
    this.props.onDrop(e)
  }

  render() {
    const { isActive, snippet, isDeletable } = this.props
    return (
      <div styleName={isActive ? 'root--active' : 'root'}>
        {!this.state.isRenaming ? (
          <button
            styleName='button'
            onClick={e => this.handleClick(e)}
            onDoubleClick={e => this.handleRenameClick(e)}
            onContextMenu={e => this.handleContextMenu(e)}
            onDragStart={e => this.handleDragStart(e)}
            onDrop={e => this.handleDrop(e)}
            draggable='true'
          >
            {snippet.name.trim().length > 0 ? (
              snippet.name
            ) : (
              <span>{i18n.__('Unnamed')}</span>
            )}
          </button>
        ) : (
          <input
            styleName='input'
            ref='name'
            value={this.state.name}
            onChange={e => this.handleNameInputChange(e)}
            onBlur={e => this.handleNameInputBlur(e)}
            onKeyDown={e => this.handleNameInputKeyDown(e)}
          />
        )}
        {isDeletable && (
          <button
            styleName='deleteButton'
            onClick={e => this.handleDeleteButtonClick(e)}
          >
            <i className='fa fa-times' />
          </button>
        )}
      </div>
    )
  }
}

SnippetTab.propTypes = {}

export default CSSModules(SnippetTab, styles)


================================================
FILE: browser/components/SnippetTab.styl
================================================
.root
  position relative
  flex 1
  min-width 70px
  overflow hidden
  border-left 1px solid $ui-borderColor
  border-top 1px solid $ui-borderColor
  &:hover
    background-color alpha($ui-button--active-backgroundColor, 20%)
    .deleteButton
      color: $ui-text-color
      visibility visible
      transition 0.15s
    .button
      color: $ui-text-color
      transition 0.15s

.root--active
  @extend .root
  min-width 100px
  background-color alpha($ui-button--active-backgroundColor, 60%)
  .deleteButton
    visibility visible
    color: $ui-text-color
    transition 0.15s
  .button
    font-weight bold
    color: $ui-text-color
    transition 0.15s

.button
  width 100%
  height 29px
  overflow ellipsis
  text-align left
  padding-right 23px
  border none
  background-color transparent
  transition 0.15s
  border-left 4px solid transparent
  color $ui-inactive-text-color

.deleteButton
  position absolute
  top 5px
  height 20px
  right 5px
  width 20px
  text-align center
  border none
  padding 0
  color $ui-inactive-text-color
  background-color transparent
  border-radius 2px
  visibility hidden

.input
  height 29px
  border $ui-active-color
  padding 0 5px
  width 100%
  outline none

body[data-theme="default"], body[data-theme="white"]
  .root--active
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 60%)

body[data-theme="dark"]
  .root
    border-color $ui-dark-borderColor
    border-top 1px solid $ui-dark-borderColor
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 20%)
      transition 0.15s
      .button
        color $ui-dark-text-color
        transition 0.15s
      .deleteButton
        color $ui-dark-text-color
        transition 0.15s

  .root--active
    background-color $ui-dark-button--active-backgroundColor
    border-left 1px solid $ui-dark-borderColor
    border-top 1px solid $ui-dark-borderColor
    .button
      color $ui-dark-text-color
    .deleteButton
      color $ui-dark-text-color

  .button
    border none
    background-color transparent
    transition color background-color 0.15s
    border-left 4px solid transparent

  .input
    background-color $ui-dark-button--active-backgroundColor
    color $ui-dark-text-color
    transition 0.15s

apply-theme(theme)
  body[data-theme={theme}]
    .root
      border-color get-theme-var(theme, 'borderColor')
      &:hover
        background-color get-theme-var(theme, 'noteDetail-backgroundColor')
        transition 0.15s
        .deleteButton
          color get-theme-var(theme, 'text-color')
          transition 0.15s
        .button
          color get-theme-var(theme, 'text-color')
          transition 0.15s

    .root--active
      color get-theme-var(theme, 'active-color')
      background-color get-theme-var(theme, 'button-backgroundColor')
      border-color get-theme-var(theme, 'borderColor')
      .deleteButton
        color get-theme-var(theme, 'text-color')
      .button
        color get-theme-var(theme, 'active-color')

    .button
      border none
      color $ui-inactive-text-color
      background-color transparent
      transition color background-color 0.15s
      border-left 4px solid transparent

    .input
      background-color get-theme-var(theme, 'noteDetail-backgroundColor')
      color get-theme-var(theme, 'text-color')
      transition 0.15s

for theme in 'solarized-dark' 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/StorageItem.js
================================================
/**
 * @fileoverview Micro component for showing storage.
 */
import PropTypes from 'prop-types'
import React from 'react'
import styles from './StorageItem.styl'
import CSSModules from 'browser/lib/CSSModules'
import _ from 'lodash'
import { SortableHandle } from 'react-sortable-hoc'

const DraggableIcon = SortableHandle(({ className }) => (
  <i className={`fa ${className}`} />
))

const FolderIcon = ({ className, color, isActive }) => {
  const iconStyle = isActive ? 'fa-folder-open-o' : 'fa-folder-o'
  return (
    <i className={`fa ${iconStyle} ${className}`} style={{ color: color }} />
  )
}

/**
 * @param {boolean} isActive
 * @param {object} tooltipRef,
 * @param {Function} handleButtonClick
 * @param {Function} handleMouseEnter
 * @param {Function} handleContextMenu
 * @param {string} folderName
 * @param {string} folderColor
 * @param {boolean} isFolded
 * @param {number} noteCount
 * @param {Function} handleDrop
 * @param {Function} handleDragEnter
 * @param {Function} handleDragOut
 * @return {React.Component}
 */
const StorageItem = ({
  styles,
  isActive,
  tooltipRef,
  handleButtonClick,
  handleMouseEnter,
  handleContextMenu,
  folderName,
  folderColor,
  isFolded,
  noteCount,
  handleDrop,
  handleDragEnter,
  handleDragLeave
}) => {
  return (
    <button
      styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
      onClick={handleButtonClick}
      onMouseEnter={handleMouseEnter}
      onContextMenu={handleContextMenu}
      onDrop={handleDrop}
      onDragEnter={handleDragEnter}
      onDragLeave={handleDragLeave}
    >
      {!isFolded && (
        <DraggableIcon className={styles['folderList-item-reorder']} />
      )}
      <span
        styleName={
          isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
        }
      >
        <FolderIcon
          styleName='folderList-item-icon'
          color={folderColor}
          isActive={isActive}
        />
        {isFolded
          ? _.truncate(folderName, { length: 1, omission: '' })
          : folderName}
      </span>
      {!isFolded && _.isNumber(noteCount) && (
        <span styleName='folderList-item-noteCount'>{noteCount}</span>
      )}
      {isFolded && (
        <span styleName='folderList-item-tooltip' ref={tooltipRef}>
          {folderName}
        </span>
      )}
    </button>
  )
}

StorageItem.propTypes = {
  isActive: PropTypes.bool.isRequired,
  tooltipRef: PropTypes.object,
  handleButtonClick: PropTypes.func,
  handleMouseEnter: PropTypes.func,
  handleContextMenu: PropTypes.func,
  folderName: PropTypes.string.isRequired,
  folderColor: PropTypes.string,
  isFolded: PropTypes.bool.isRequired,
  handleDragEnter: PropTypes.func.isRequired,
  handleDragLeave: PropTypes.func.isRequired,
  noteCount: PropTypes.number
}

export default CSSModules(StorageItem, styles)


================================================
FILE: browser/components/StorageItem.styl
================================================
.root
  width 100%
  user-select none

.folderList-item
  display flex
  width 100%
  height 34px
  background-color transparent
  color $ui-inactive-text-color
  padding 0
  text-align left
  border none
  overflow ellipsis
  font-size 14px
  align-items: center
  &:first-child
    margin-top 0
  &:hover
    color #1EC38B;
    background-color alpha($ui-button-default--active-backgroundColor, 20%)
    transition background-color 0.15s
  &:active
    color $$ui-button-default-color
    background-color alpha($ui-button-default--active-backgroundColor, 20%)

.folderList-item--active
  @extend .folderList-item
  color #1EC38B
  background-color alpha($ui-button-default--active-backgroundColor, 20%)
  &:hover
    color #1EC38B;
    background-color alpha($ui-button-default--active-backgroundColor, 50%)

.folderList-item-name
  display block
  flex 1
  padding-right: 10px
  border-width 0 0 0 2px
  border-style solid
  border-color transparent
  overflow hidden
  text-overflow ellipsis

.folderList-item-noteCount
  float right
  line-height 26px
  padding-right 15px
  font-size 13px

.folderList-item-tooltip
  tooltip()
  position fixed
  padding 0 10px
  left 44px
  z-index 10
  pointer-events none
  opacity 0
  border-top-right-radius 2px
  border-bottom-right-radius 2px
  height 34px
  line-height 32px
  transition-property opacity

.folderList-item:hover, .folderList-item--active:hover
  .folderList-item-tooltip
    opacity 1

.folderList-item-name--folded
  @extend .folderList-item-name
  padding-left 7px
  .folderList-item-icon
    font-size 9px

.folderList-item-icon
  padding-right: 10px

.folderList-item-reorder
  font-size: 9px
  padding: 10px 8px 10px 9px;
  color: rgba(147, 147, 149, 0.3)
  cursor: ns-resize
  &:before
    content: "\f142 \f142"

body[data-theme="white"]
  .folderList-item
    color $ui-inactive-text-color
    &:hover
      color $ui-text-color
      background-color alpha($ui-button--active-backgroundColor, 20%)
      transition background-color 0.15s
    &:active
      color $ui-text-color
      background-color $ui-button--active-backgroundColor

  .folderList-item--active
    @extend .folderList-item
    color $ui-text-color
    background-color $ui-button--active-backgroundColor
    &:hover
      color $ui-text-color
      background-color alpha($ui-button--active-backgroundColor, 50%)

body[data-theme="dark"]
  .folderList-item
    &:hover
      background-color alpha($ui-dark-button--active-backgroundColor, 20%)
      color $ui-dark-text-color
    &:active
      color $ui-dark-text-color
      background-color $ui-dark-button--active-backgroundColor

  .folderList-item--active
    @extend .folderList-item
    color $ui-dark-text-color
    background-color $ui-dark-button--active-backgroundColor
    &:active
      background-color alpha($ui-dark-button--active-backgroundColor, 50%)
    &:hover
      color $ui-dark-text-color
      background-color alpha($ui-dark-button--active-backgroundColor, 50%)

apply-theme(theme)
  body[data-theme={theme}]
    .folderList-item
      &:hover
        background-color get-theme-var(theme, 'button-backgroundColor')
        color get-theme-var(theme, 'text-color')
      &:active
        color get-theme-var(theme, 'text-color')
        background-color get-theme-var(theme, 'button-backgroundColor')

    .folderList-item--active
      @extend .folderList-item
      color get-theme-var(theme, 'text-color')
      background-color get-theme-var(theme, 'button-backgroundColor')
      &:active
        background-color get-theme-var(theme, 'button-backgroundColor')
      &:hover
        color get-theme-var(theme, 'text-color')
        background-color get-theme-var(theme, 'button-backgroundColor')

for theme in 'solarized-dark' 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/StorageList.js
================================================
/**
 * @fileoverview Micro component for showing StorageList
 */
import PropTypes from 'prop-types'
import React from 'react'
import styles from './StorageList.styl'
import CSSModules from 'browser/lib/CSSModules'

/**
 * @param {Array} storageList
 */

const StorageList = ({ storageList, isFolded }) => (
  <div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
    {storageList.length > 0 ? (
      storageList
    ) : (
      <div styleName='storageList-empty'>No storage mount.</div>
    )}
  </div>
)

StorageList.propTypes = {
  storageList: PropTypes.arrayOf(PropTypes.element).isRequired
}
export default CSSModules(StorageList, styles)


================================================
FILE: browser/components/StorageList.styl
================================================
.storageList
  absolute left right
  bottom 37px
  top 180px
  overflow-y auto

.storageList-folded
  @extend .storageList
  width 44px

.storageList-empty
  padding 0 10px
  margin-top 15px
  line-height 24px
  color $ui-inactive-text-color

body[data-theme="dark"]
  .storageList-empty
    color $ui-dark-inactive-text-color

.root-folded
  .storageList-empty
    white-space nowrap
    transform rotate(90deg)


================================================
FILE: browser/components/TagListItem.js
================================================
/**
 * @fileoverview Micro component for showing TagList.
 */
import PropTypes from 'prop-types'
import React from 'react'
import styles from './TagListItem.styl'
import CSSModules from 'browser/lib/CSSModules'

/**
 * @param {string} name
 * @param {Function} handleClickTagListItem
 * @param {Function} handleClickNarrowToTag
 * @param {boolean} isActive
 * @param {boolean} isRelated
 * @param {string} bgColor tab backgroundColor
 */

const TagListItem = ({
  name,
  handleClickTagListItem,
  handleClickNarrowToTag,
  handleContextMenu,
  isActive,
  isRelated,
  count,
  color
}) => (
  <div
    styleName='tagList-itemContainer'
    onContextMenu={e => handleContextMenu(e, name)}
  >
    {isRelated ? (
      <button
        styleName={
          isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
        }
        onClick={() => handleClickNarrowToTag(name)}
      >
        <i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
      </button>
    ) : (
      <div
        styleName={
          isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
        }
      />
    )}
    <button
      styleName={isActive ? 'tagList-item-active' : 'tagList-item'}
      onClick={() => handleClickTagListItem(name)}
    >
      <span
        styleName='tagList-item-color'
        style={{ backgroundColor: color || 'transparent' }}
      />
      <span styleName='tagList-item-name'>
        {`# ${name}`}
        <span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
      </span>
    </button>
  </div>
)

TagListItem.propTypes = {
  name: PropTypes.string.isRequired,
  handleClickTagListItem: PropTypes.func.isRequired,
  color: PropTypes.string
}

export default CSSModules(TagListItem, styles)


================================================
FILE: browser/components/TagListItem.styl
================================================
.tagList-itemContainer
  display flex

.tagList-item
  display flex
  flex 1
  width 100%
  height 26px
  background-color transparent
  color $ui-inactive-text-color
  padding 0
  margin-bottom 5px
  text-align left
  border none
  overflow ellipsis
  font-size 13px
  &:first-child
    margin-top 0
  &:hover
    color $ui-button-default-color
    background-color alpha($ui-button-default--active-backgroundColor, 20%)
    transition background-color 0.15s
  &:active, &:active:hover
    color $ui-button-default-color
    background-color $ui-button-default--active-backgroundColor

.tagList-itemNarrow
  composes tagList-item
  flex none
  width 20px
  padding 0 4px

.tagList-item-active
  background-color $ui-button-default--active-backgroundColor
  display flex
  flex 1
  width 100%
  height 26px
  padding 0
  margin-bottom 5px
  text-align left
  border none
  overflow ellipsis
  font-size 13px
  color $ui-button-default-color
  &:hover
    background-color alpha($ui-button-default--active-backgroundColor, 60%)
    transition 0.2s

.tagList-itemNarrow-active
  composes tagList-item-active
  flex none
  width 20px
  padding 0 4px

.tagList-item-name
  display block
  flex 1
  padding 0 8px 0 4px
  height 26px
  line-height 26px
  border-width 0 0 0 2px
  border-style solid
  border-color transparent
  overflow hidden
  text-overflow ellipsis

.tagList-item-count
  float right
  line-height 26px
  padding-right 15px
  font-size 13px

.tagList-item-color
  height 26px
  width 3px
  display inline-block

body[data-theme="white"]
  .tagList-item
    color $ui-inactive-text-color
    &:hover
      color $ui-text-color
      background-color alpha($ui-button--active-backgroundColor, 20%)
    &:active
      color $ui-text-color
      background-color $ui-button--active-backgroundColor

  .tagList-item-active
    background-color $ui-button--active-backgroundColor
    color $ui-text-color
    &:hover
      background-color alpha($ui-button--active-backgroundColor, 60%)
  .tagList-item-count
    color $ui-text-color

apply-theme(theme)
  body[data-theme={theme}]
    .tagList-item
      color get-theme-var(theme, 'inactive-text-color')
      &:hover
        color get-theme-var(theme, 'text-color')
        background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
      &:active
        color get-theme-var(theme, 'text-color')
        background-color get-theme-var(theme, 'button--active-backgroundColor')

    .tagList-item-active
      background-color get-theme-var(theme, 'button--active-backgroundColor')
      color get-theme-var(theme, 'text-color')
      &:active
        background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
      &:hover
        color get-theme-var(theme, 'text-color')
        background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
    .tagList-item-count
      color get-theme-var(theme, 'button--active-color')

for theme in 'dark'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/TodoListPercentage.js
================================================
/**
 * @fileoverview Percentage of todo achievement.
 */

import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoListPercentage.styl'

/**
 * @param {number} percentageOfTodo
 */

const TodoListPercentage = ({ percentageOfTodo, onClearCheckboxClick }) => (
  <div
    styleName='percentageBar'
    style={{ display: isNaN(percentageOfTodo) ? 'none' : '' }}
  >
    <div styleName='progressBar' style={{ width: `${percentageOfTodo}%` }}>
      <div styleName='progressBarInner'>
        <p styleName='percentageText'>{percentageOfTodo}%</p>
      </div>
    </div>
    <div styleName='todoClear'>
      <p styleName='todoClearText' onClick={e => onClearCheckboxClick(e)}>
        clear
      </p>
    </div>
  </div>
)

TodoListPercentage.propTypes = {
  percentageOfTodo: PropTypes.number.isRequired,
  onClearCheckboxClick: PropTypes.func.isRequired
}

export default CSSModules(TodoListPercentage, styles)


================================================
FILE: browser/components/TodoListPercentage.styl
================================================
.percentageBar
  display: flex
  position absolute
  top 72px
  right 0px
  left 0px
  background-color #DADFE1
  width 100%
  height: 17px
  font-size: 12px
  z-index 100
  border-radius 2px

.progressBar
  background-color: #1EC38B
  height 17px
  border-radius 2px
  transition 0.4s cubic-bezier(0.4, 0.4, 0, 1)

.progressBarInner
  padding 0 10px
  min-width 1px
  height 100%
  display -webkit-box
  display box
  justify-content center
  align-items center


.percentageText
  color #f4f4f4
  font-weight 600

.todoClear
  display flex
  justify-content: flex-end
  position absolute
  z-index 120
  width 100%
  height 100%
  padding 2px 10px

.todoClearText
  color #f4f4f4
  cursor pointer
  font-weight 500

body[data-theme="dark"]
  .percentageBar
    background-color #444444

  .progressBar
    background-color: #1EC38B

  .percentageText
    color $ui-dark-text-color

  .todoClearText
    color $ui-dark-text-color

body[data-theme="solarized-dark"]
  .percentageBar
    background-color #002b36

  .progressBar
    background-color: #2aa198

  .percentageText
    color #fdf6e3

  .todoClearText
    color #fdf6e3

apply-theme(theme)
  body[data-theme={theme}]
    .percentageBar
      background-color: get-theme-var(theme, 'borderColor')

    .progressBar
      background-color get-theme-var(theme, 'active-color')

    .percentageText
      color get-theme-var(theme, 'text-color')

for theme in 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)

================================================
FILE: browser/components/TodoProcess.js
================================================
/**
 * @fileoverview Percentage of todo achievement.
 */

import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoProcess.styl'

const TodoProcess = ({
  todoStatus: { total: totalTodo, completed: completedTodo }
}) => (
  <div
    styleName='todo-process'
    style={{ display: totalTodo > 0 ? '' : 'none' }}
  >
    <div styleName='todo-process-text'>
      <i className='fa fa-fw fa-check-square-o' />
      {completedTodo} of {totalTodo}
    </div>
    <div styleName='todo-process-bar'>
      <div
        styleName='todo-process-bar--inner'
        style={{ width: parseInt((completedTodo / totalTodo) * 100) + '%' }}
      />
    </div>
  </div>
)

TodoProcess.propTypes = {
  todoStatus: PropTypes.exact({
    total: PropTypes.number.isRequired,
    completed: PropTypes.number.isRequired
  })
}

export default CSSModules(TodoProcess, styles)


================================================
FILE: browser/components/TodoProcess.styl
================================================
.todo-process
  font-size 12px
  display flex
  padding-top 15px
  width 85%

.todo-process-text
  display inline-block
  padding-right 10px
  white-space nowrap
  text-overflow ellipsis
  color $ui-inactive-text-color
  i
    color $ui-inactive-text-color
    padding-right 5px

.todo-process-bar
  display inline-block
  margin auto
  height 4px
  border-radius 10px
  background-color #DADFE1
  border-radius 2px
  flex-grow 1
  border 1px solid alpha(#6C7A89, 10%)

.todo-process-bar--inner
  height 100%
  border-radius 5px
  background-color #6C7A89
  transition 0.3s


body[data-theme="dark"]
  .todo-process
    color $ui-dark-text-color

  .todo-process-bar
    background-color #363A3D

  .todo-process-text
    color $ui-inactive-text-color

  .todo-process-bar--inner
    background-color: alpha(#939395, 50%)


================================================
FILE: browser/components/markdown.styl
================================================
global-reset()

borderColor = #D0D0D0 // using
highlightenBorderColor = darken(borderColor, 20%)
invBorderColor = #404849
brandBorderColor = #3FB399

focusBorderColor = #369DCD

buttonBorderColor = #4C4C4C

lightButtonColor = #898989

hoverBackgroundColor= transparentify(#444, 4%) // using

inactiveTextColor = #888 // using
textColor = #4D4D4D // using
backgroundColor= white
fontSize= 14px // using

shadowColor= #C5C5C5

invBackgroundColor = #4C4C4C
invTextColor = white

btnColor = #888
btnHighlightenColor = #000

brandColor = #2BAC8F

popupShadow = 0 0 5px 0 #888


tableHeadBgColor = white
tableOddBgColor = #F9F9F9
tableEvenBgColor = white

facebookColor= #3b5998
githubBtn= #201F1F

// using
successBackgroundColor= #E0F0D9
successTextColor= #3E753F
errorBackgroundColor= #F2DEDE
errorTextColor= #A64444
infoBackgroundColor= #D9EDF7
infoTextColor= #34708E

popupZIndex= 500

body
  font-size 16px
  padding 15px
  font-family helvetica, arial, sans-serif
  line-height 1.6
  overflow-x hidden
  background-color $ui-noteDetail-backgroundColor
  // do not allow display line breaks
  .katex-display > .katex
    white-space nowrap
  // allow inline line breaks
  .katex
    white-space initial
  .katex .katex-html
    display inline-flex
  .katex .mfrac>.vlist>span:nth-child(2)
    top 0 !important
  .katex-error
    background-color errorBackgroundColor
    color errorTextColor
    padding 5px
    margin -5px
    border-radius 5px
  .flowchart-error, .sequence-error .chart-error
    background-color errorBackgroundColor
    color errorTextColor
    padding 5px
    border-radius 5px
    justify-content left
li
  label.taskListItem
    margin-left -1.8em
    &.checked
      text-decoration line-through
      opacity 0.5
  &.taskListItem.checked
    text-decoration line-through
    opacity 0.5
div.math-rendered
  text-align center
.math-failed
  background-color alpha(red, 0.1)
  color darken(red, 15%)
  padding 5px
  margin 5px 0
  border-radius 5px
sup
  position relative
  top -.4em
  font-size 0.8em
  vertical-align top
sub
  position relative
  bottom -.4em
  font-size 0.8em
  vertical-align top
a
  color brandColor
  text-decoration none
  padding 5px
  border-radius 5px
  margin -5px
  transition .1s
  img
    vertical-align sub
  &:hover
    color lighten(brandColor, 5%)
    text-decoration underline
    background-color alpha(#FFC95C, 0.3)
  &:visited
    color brandColor
hr
  border-top none
  border-bottom solid 1px borderColor
  margin 15px 0
h1, h2, h3, h4, h5, h6
  margin 1em 0 1.5em
  line-height 1.4
  font-weight bold
  word-wrap break-word
  padding .2em 0 .2em
h1
  font-size 2.55em
  line-height 1.2
  border-bottom solid 1px borderColor
  &:first-child
    margin-top 0
h2
  font-size 1.75em
  line-height 1.225
  border-bottom solid 1px borderColor
  &:first-child
    margin-top 0
h3
  font-size 1.5em
  line-height 1.43
h4
  font-size 1.25em
  line-height 1.4
h5
  font-size 1em
  line-height 1.1
h6
  font-size 1em
  color #777
p
  line-height 1.6em
  margin 0 0 1em
  white-space pre-line
  word-wrap break-word
img
  cursor zoom-in
  max-width 100%
strong, b
  font-weight bold
em, i
  font-style italic
s, del, strike
  text-decoration line-through
u
  text-decoration underline
blockquote
  border-left solid 4px brandBorderColor
  margin 0 0 1em
  padding 0 25px
ul
  list-style-type disc
  padding-left 2em
  margin-bottom 1em
  li
    display list-item
    &.taskListItem
      list-style none
      &>input
        margin-left -1.6em
      &>p
        margin-left -1.8em
    p
      margin 0
  &>li>ul, &>li>ol
    margin 0
  &>li>ul
    list-style-type circle
    &>li>ul
      list-style-type square
ol
  list-style-type decimal
  padding-left 2em
  margin-bottom 1em
  li
    display list-item
    p
      margin 0
  &>li>ul, &>li>ol
    margin 0
code
  padding 0.2em 0.4em
  background-color #f7f7f7
  border-radius 3px
  font-size 1em
  text-decoration none
  margin-right 2px
pre
  padding 0.5rem !important
  border solid 1px #D1D1D1
  border-radius 5px
  overflow-x auto
  margin 0 0 1rem
  display flex
  line-height 1.4em
  code
    background-color inherit
    margin 0
    padding 0
    border none
    border-radius 0
  &.CodeMirror
    height initial
    flex-wrap wrap
    &>code
      flex 1
      overflow-x auto
  &.mermaid svg
    max-width 100% !important
  &>span.filename
    margin -0.5rem 100% 0.5rem -0.5rem
    padding 0.125rem 0.375rem
    background-color #777;
    color white
    &:empty
      display none
  &>span.lineNumber
    display none
    font-size 1em
    padding 0.5rem 0
    margin -0.5rem 0.5rem -0.5rem -0.5rem
    border-right 1px solid
    text-align right
    border-top-left-radius 4px
    border-bottom-left-radius 4px
    &.CodeMirror-gutters
      position initial
      top initial
      left initial
      min-height 0 !important
    &>span
      display block
      padding 0 .5em 0
table
  display block
  width 100%
  margin 0 0 1em
  overflow-x auto
  thead
    tr
      background-color tableHeadBgColor
    th
      border-style solid
      padding 6px 13px
      line-height 1.6
      border-width 1px 0 2px 1px
      border-color borderColor
      font-weight bold
      &:last-child
        border-right solid 1px borderColor
  tbody
    tr:nth-child(2n + 1)
      background-color tableOddBgColor
    tr:nth-child(2n)
      background-color tableEvenBgColor
    td
      border-style solid
      padding 6px 13px
      line-height 1.6
      border-width 0 0 1px 1px
      border-color borderColor
      &:last-child
        border-right solid 1px borderColor
kbd
  background-color #fafbfc
  border solid 1px borderColor
  border-bottom-color btnColor
  border-radius 3px
  box-shadow inset 0 -1px 0 #959da5
  display inline-block
  font-size .8em
  line-height 1
  padding 3px 5px

$admonition
    box-shadow 0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)
    position relative
    margin 1.5625em 0
    padding 0 1.2rem
    border-left .4rem solid #448aff
    border-radius .2rem
    overflow auto

html .admonition>:last-child
    margin-bottom 1.2rem

.admonition .admonition
    margin 1em 0

.admonition p
    margin-top: 0.5em

$admonition-icon
    position absolute
    left 1.2rem
    font-family: "Material Icons"
    font-weight: normal;
    font-style: normal;
    font-size: 24px
    display: inline-block;
    line-height: 1;
    text-transform: none;
    letter-spacing: normal;
    word-wrap: normal;
    white-space: nowrap;
    direction: ltr;

    /* Support for all WebKit browsers. */
    -webkit-font-smoothing: antialiased;
    /* Support for Safari and Chrome. */
    text-rendering: optimizeLegibility;

    /* Support for Firefox. */
    -moz-osx-font-smoothing: grayscale;

    /* Support for IE. */
    font-feature-settings: 'liga';

$admonition-title
    margin 0 -1.2rem
    padding .8rem 1.2rem .8rem 4rem
    border-bottom .1rem solid rgba(68,138,255,.1)
    background-color rgba(68,138,255,.1)
    font-weight 700

.admonition>.admonition-title:last-child
    margin-bottom 0

admonition_types = {
    note: {color: #0288D1, icon: "note"},
    hint: {color: #009688, icon: "info_outline"},
    danger: {color: #c2185b, icon: "block"},
    caution: {color: #ffa726, icon: "warning"},
    error: {color: #d32f2f, icon: "error_outline"},
    question: {color: #64dd17, icon: "help_outline"},
    quote: {color: #9e9e9e, icon: "format_quote"},
    abstract: {color: #00b0ff, icon: "subject"},
    attention: {color: #455a64, icon: "priority_high"},
}

for name, val in admonition_types
    .admonition.{name}
        @extend $admonition
        border-left-color: val[color]

    .admonition.{name}>.admonition-title
        @extend $admonition-title
        border-bottom-color: .1rem solid rgba(val[color], 0.2)
        background-color: rgba(val[color], 0.2)

    .admonition.{name}>.admonition-title:before
        @extend $admonition-icon
        color: val[color]
        content: val[icon]

dl
  margin 2rem 0
  padding 0
  display flex
  width 100%
  flex-wrap wrap
  align-items flex-start
  border-bottom 1px solid borderColor
  background-color tableHeadBgColor

dt
  border-top 1px solid borderColor
  font-weight bold
  text-align right
  overflow hidden
  flex-basis 20%
  padding 0.4rem 0.9rem
  box-sizing border-box

dd
  border-top 1px solid borderColor
  flex-basis 80%
  padding 0.4rem 0.9rem
  min-height 2.5rem
  background-color $ui-noteDetail-backgroundColor
  box-sizing border-box

dd + dd
  margin-left 20%

pre.fence
  flex-wrap wrap

  .chart, .flowchart, .mermaid, .sequence
    display flex
    justify-content center
    background-color white
    max-width 100%
    flex-grow 1

    canvas, svg
      max-width 100% !important

    svg[ratio]
      width 100%

  .gallery
    width 100%
    height 50vh

    .carousel
      .carousel-main img
        min-width auto
        max-width 100%
        min-height auto
        max-height 100%

      .carousel-footer::-webkit-scrollbar-corner
        background-color transparent

      .carousel-main, .carousel-footer
        background-color $ui-noteDetail-backgroundColor
      .prev, .next
        color $ui-text-color
        background-color $ui-tag-backgroundColor

.markdownIt-TOC-wrapper
  list-style none
  position fixed
  right 0
  top 0
  margin-left 15px
  z-index 1000
  transition transform .2s ease-in-out
  transform translateX(100%)

  .markdownIt-TOC
    display block
    max-height 90vh
    overflow-y auto
    padding 25px
    padding-left 38px

  &,
  &:before
    background-color $ui-dark-backgroundColor
    color: $ui-dark-text-color

  &:hover
    transform translateX(-15px)

  &:before
    content 'TOC'
    position absolute
    width 60px
    height 30px
    top 60px
    left -29px
    display flex
    align-items center
    justify-content center
    transform-origin top left
    transform rotate(-90deg)

themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%)
themeDarkPreview = $ui-dark-noteDetail-backgroundColor
themeDarkTableOdd = themeDarkPreview
themeDarkTableEven = darken(themeDarkPreview, 10%)
themeDarkTableHead = themeDarkTableEven
themeDarkTableBorder = themeDarkBorder
themeDarkModalButtonDefault = themeDarkPreview
themeDarkModalButtonDanger = #BF360C

body[data-theme="dark"]
  color themeDarkText
  border-color themeDarkBorder
  background-color themeDarkPreview
  a:hover
    background-color alpha(lighten(brandColor, 30%), 0.2) !important

  code
    color #EA6730
    border-color darken(themeDarkBorder, 10%)
    background-color lighten(themeDarkPreview, 5%)

  pre
    border-color lighten(#21252B, 20%)
    code
      background-color transparent

  label.taskListItem
    background-color themeDarkPreview
  table
    thead
      tr
        background-color themeDarkTableHead
      th
        border-color themeDarkTableBorder
        &:last-child
          border-right solid 1px themeDarkTableBorder
    tbody
      tr:nth-child(2n + 1)
        background-color themeDarkTableOdd
      tr:nth-child(2n)
        background-color themeDarkTableEven
      td
        border-color themeDarkTableBorder
        &:last-child
          border-right solid 1px themeDarkTableBorder
  kbd
    background-color themeDarkBorder
    color themeDarkText
  dl
    border-color themeDarkBorder
    background-color themeDarkTableHead
  dt
    border-color themeDarkBorder
  dd
    border-color themeDarkBorder
    background-color themeDarkPreview

  pre.fence
    .gallery
      .carousel-main, .carousel-footer
        background-color $ui-dark-noteDetail-backgroundColor
      .prev, .next
        color $ui-dark-text-color
        background-color $ui-dark-tag-backgroundColor

  .markdownIt-TOC-wrapper
    &,
    &:before
      background-color darken(themeDarkBackground, 5%)
      color themeDarkText

apply-theme(theme)
  body[data-theme={theme}]
    color get-theme-var(theme, 'text-color')
    border-color themeDarkBorder
    background-color get-theme-var(theme, 'noteDetail-backgroundColor')
    table
      thead
        tr
          background-color get-theme-var(theme, 'table-head-backgroundColor')
        th
          border-color get-theme-var(theme, 'table-borderColor')
          &:last-child
            border-right solid 1px get-theme-var(theme, 'table-borderColor')
      tbody
        tr:nth-child(2n + 1)
          background-color get-theme-var(theme, 'table-odd-backgroundColor')
        tr:nth-child(2n)
          background-color get-theme-var(theme, 'table-even-backgroundColor')
        td
          border-color get-theme-var(theme, 'table-borderColor')
          &:last-child
            border-right solid 1px get-theme-var(theme, 'table-borderColor')
    kbd
      background-color get-theme-var(theme, 'kbd-backgroundColor')
      color get-theme-var(theme, 'kbd-color')

    dl
      border-color themeDarkBorder
      background-color get-theme-var(theme, 'table-head-backgroundColor')
    dt
      border-color themeDarkBorder
    dd
      border-color themeDarkBorder
      background-color get-theme-var(theme, 'noteDetail-backgroundColor')

    pre.fence
      .gallery
        .carousel-main, .carousel-footer
          background-color get-theme-var(theme, 'noteDetail-backgroundColor')
        .prev, .next
          color get-theme-var(theme, 'button--active-color')
          background-color get-theme-var(theme, 'button-backgroundColor')

    .markdownIt-TOC-wrapper
      &,
      &:before
        background-color darken(get-theme-var(theme, 'noteDetail-backgroundColor'), 15%)
        color themeDarkText

for theme in 'solarized-dark' 'dracula'
  apply-theme(theme)

for theme in $themes
  apply-theme(theme)


================================================
FILE: browser/components/render/MermaidRender.js
================================================
import mermaidAPI from 'mermaid/dist/mermaid.min.js'
import uiThemes from 'browser/lib/ui-themes'

// fixes bad styling in the mermaid dark theme
const darkThemeStyling = `
.loopText tspan {
  fill: white;
}`

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min)) + min
}

function getId() {
  const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
  let id = 'm-'
  for (let i = 0; i < 7; i++) {
    id += pool[getRandomInt(0, 16)]
  }
  return id
}

function render(element, content, theme, enableHTMLLabel) {
  try {
    const height = element.attributes.getNamedItem('data-height')
    const isPredefined = height && height.value !== 'undefined'

    if (isPredefined) {
      element.style.height = height.value + 'vh'
    }

    const isDarkTheme = uiThemes.some(
      item => item.name === theme && item.isDark
    )

    mermaidAPI.initialize({
      theme: isDarkTheme ? 'dark' : 'default',
      themeCSS: isDarkTheme ? darkThemeStyling : '',
      flowchart: {
        htmlLabels: enableHTMLLabel
      },
      gantt: {
        useWidth: element.clientWidth
      }
    })

    mermaidAPI.render(getId(), content, svgGraph => {
      element.innerHTML = svgGraph

      if (!isPredefined) {
        const el = element.firstChild
        const viewBox = el.getAttribute('viewBox').split(' ')

        let ratio = viewBox[2] / viewBox[3]

        if (el.style.maxWidth) {
          const maxWidth = parseFloat(el.style.maxWidth)

          ratio *= el.parentNode.clientWidth / maxWidth
        }

        el.setAttribute('ratio', ratio)
        el.setAttribute('height', el.parentNode.clientWidth / ratio)
      }
    })
  } catch (e) {
    element.className = 'mermaid-error'
    element.innerHTML = 'mermaid diagram parse error: ' + e.message
  }
}

export default render


================================================
FILE: browser/lib/CMLanguageList.js
================================================
export const languageMaps = {
  brainfuck: 'Brainfuck',
  cpp: 'C++',
  cs: 'C#',
  clojure: 'Clojure',
  'clojure-repl': 'ClojureScript',
  cmake: 'CMake',
  coffeescript: 'CoffeeScript',
  crystal: 'Crystal',
  css: 'CSS',
  d: 'D',
  dart: 'Dart',
  delphi: 'Pascal',
  diff: 'Diff',
  django: 'Django',
  dockerfile: 'Dockerfile',
  ebnf: 'EBNF',
  elm: 'Elm',
  erlang: 'Erlang',
  'erlang-repl': 'Erlang',
  fortran: 'Fortran',
  fsharp: 'F#',
  gherkin: 'Gherkin',
  go: 'Go',
  groovy: 'Groovy',
  haml: 'HAML',
  haskell: 'Haskell',
  haxe: 'Haxe',
  http: 'HTTP',
  ini: 'toml',
  java: 'Java',
  javascript: 'JavaScript',
  json: 'JSON',
  julia: 'Julia',
  kotlin: 'Kotlin',
  less: 'LESS',
  livescript: 'LiveScript',
  lua: 'Lua',
  markdown: 'Markdown',
  mathematica: 'Mathematica',
  nginx: 'Nginx',
  nsis: 'NSIS',
  objectivec: 'Objective-C',
  ocaml: 'Ocaml',
  perl: 'Perl',
  php: 'PHP',
  powershell: 'PowerShell',
  properties: 'Properties files',
  protobuf: 'ProtoBuf',
  python: 'Python',
  puppet: 'Puppet',
  q: 'Q',
  r: 'R',
  ruby: 'Ruby',
  rust: 'Rust',
  sas: 'SAS',
  scala: 'Scala',
  scheme: 'Scheme',
  scss: 'SCSS',
  shell: 'Shell',
  smalltalk: 'Smalltalk',
  sml: 'SML',
  sql: 'SQL',
  stylus: 'Stylus',
  swift: 'Swift',
  tcl: 'Tcl',
  tex: 'LaTex',
  typescript: 'TypeScript',
  twig: 'Twig',
  vbnet: 'VB.NET',
  vbscript: 'VBScript',
  verilog: 'Verilog',
  vhdl: 'VHDL',
  xml: 'HTML',
  xquery: 'XQuery',
  yaml: 'YAML',
  elixir: 'Elixir'
}


================================================
FILE: browser/lib/CSSModules.js
================================================
import CSSModules from 'react-css-modules'

export default function(component, styles) {
  return CSSModules(component, styles, { handleNotFoundStyleName: 'log' })
}


================================================
FILE: browser/lib/Languages.js
================================================
const languages = [
  {
    name: 'Albanian',
    locale: 'sq'
  },
  {
    name: 'Chinese (zh-CN)',
    locale: 'zh-CN'
  },
  {
    name: 'Chinese (zh-TW)',
    locale: 'zh-TW'
  },
  {
    name: 'Czech',
    locale: 'cs'
  },
  {
    name: 'Danish',
    locale: 'da'
  },
  {
    name: 'English',
    locale: 'en'
  },
  {
    name: 'French',
    locale: 'fr'
  },
  {
    name: 'German',
    locale: 'de'
  },
  {
    name: 'Hungarian',
    locale: 'hu'
  },
  {
    name: 'Japanese',
    locale: 'ja'
  },
  {
    name: 'Korean',
    locale: 'ko'
  },
  {
    name: 'Norwegian',
    locale: 'no'
  },
  {
    name: 'Polish',
    locale: 'pl'
  },
  {
    name: 'Portuguese (PT-BR)',
    locale: 'pt-BR'
  },
  {
    name: 'Portuguese (PT-PT)',
    locale: 'pt-PT'
  },
  {
    name: 'Russian',
    locale: 'ru'
  },
  {
    name: 'Spanish',
    locale: 'es-ES'
  },
  {
    name: 'Turkish',
    locale: 'tr'
  },
  {
    name: 'Thai',
    locale: 'th'
  }
]

module.exports = {
  getLocales() {
    return languages.reduce(function(localeList, locale) {
      localeList.push(locale.locale)
      return localeList
    }, [])
  },
  getLanguages() {
    return languages
  }
}


================================================
FILE: browser/lib/Mutable.js
================================================
class MutableMap {
  constructor(iterable) {
    this._map = new Map(iterable)
    Object.defineProperty(this, 'size', {
      get: () => this._map.size,
      set: function(value) {
        this['size'] = value
      }
    })
  }

  get(...args) {
    return this._map.get(...args)
  }

  set(...args) {
    return this._map.set(...args)
  }

  delete(...args) {
    return this._map.delete(...args)
  }

  has(...args) {
    return this._map.has(...args)
  }

  clear(...args) {
    return this._map.clear(...args)
  }

  forEach(...args) {
    return this._map.forEach(...args)
  }

  [Symbol.iterator]() {
    return this._map[Symbol.iterator]()
  }

  map(cb) {
    const result = []
    for (const [key, value] of this._map) {
      result.push(cb(value, key))
    }
    return result
  }

  toJS() {
    const result = {}
    for (let [key, value] of this._map) {
      if (value instanceof MutableSet || value instanceof MutableMap) {
        value = value.toJS()
      }
      result[key] = value
    }
    return result
  }
}

class MutableSet {
  constructor(iterable) {
    this._set = new Set(iterable)
    Object.defineProperty(this, 'size', {
      get: () => this._set.size,
      set: function(value) {
        this['size'] = value
      }
    })
  }

  add(...args) {
    return this._set.add(...args)
  }

  delete(..
Download .txt
gitextract_1f_tfmxn/

├── .babelrc
├── .boostnoterc.sample
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .prettierrc
├── .snapcraft/
│   └── travis_snapcraft.cfg
├── .travis.yml
├── FAQ.md
├── ISSUE_TEMPLATE.md
├── LICENSE
├── PULL_REQUEST_TEMPLATE.md
├── __mocks__/
│   └── electron.js
├── appdmg.json
├── browser/
│   ├── components/
│   │   ├── CodeEditor.js
│   │   ├── CodeEditor.styl
│   │   ├── ColorPicker.js
│   │   ├── ColorPicker.styl
│   │   ├── MarkdownEditor.js
│   │   ├── MarkdownEditor.styl
│   │   ├── MarkdownPreview.js
│   │   ├── MarkdownSplitEditor.js
│   │   ├── MarkdownSplitEditor.styl
│   │   ├── ModalEscButton.js
│   │   ├── ModalEscButton.styl
│   │   ├── NavToggleButton.js
│   │   ├── NavToggleButton.styl
│   │   ├── NoteItem.js
│   │   ├── NoteItem.styl
│   │   ├── NoteItemSimple.js
│   │   ├── NoteItemSimple.styl
│   │   ├── RealtimeNotification.js
│   │   ├── RealtimeNotification.styl
│   │   ├── SideNavFilter.js
│   │   ├── SideNavFilter.styl
│   │   ├── SnippetTab.js
│   │   ├── SnippetTab.styl
│   │   ├── StorageItem.js
│   │   ├── StorageItem.styl
│   │   ├── StorageList.js
│   │   ├── StorageList.styl
│   │   ├── TagListItem.js
│   │   ├── TagListItem.styl
│   │   ├── TodoListPercentage.js
│   │   ├── TodoListPercentage.styl
│   │   ├── TodoProcess.js
│   │   ├── TodoProcess.styl
│   │   ├── markdown.styl
│   │   └── render/
│   │       └── MermaidRender.js
│   ├── lib/
│   │   ├── CMLanguageList.js
│   │   ├── CSSModules.js
│   │   ├── Languages.js
│   │   ├── Mutable.js
│   │   ├── RcParser.js
│   │   ├── SnippetManager.js
│   │   ├── TextEditorInterface.js
│   │   ├── confirmDeleteNote.js
│   │   ├── consts.js
│   │   ├── context.js
│   │   ├── contextMenuBuilder.js
│   │   ├── convertModeName.js
│   │   ├── customMeta.js
│   │   ├── date-formatter.js
│   │   ├── findNoteTitle.js
│   │   ├── findStorage.js
│   │   ├── getTodoStatus.js
│   │   ├── htmlTextHelper.js
│   │   ├── i18n.js
│   │   ├── keygen.js
│   │   ├── markdown-it-deflist.js
│   │   ├── markdown-it-fence.js
│   │   ├── markdown-it-frontmatter.js
│   │   ├── markdown-it-sanitize-html.js
│   │   ├── markdown-toc-generator.js
│   │   ├── markdown.js
│   │   ├── markdown2.js
│   │   ├── markdownTextHelper.js
│   │   ├── newNote.js
│   │   ├── normalizeEditorFontFamily.js
│   │   ├── search.js
│   │   ├── slugify.js
│   │   ├── spellcheck.js
│   │   ├── turndown.js
│   │   ├── ui-themes.js
│   │   ├── utils.js
│   │   └── wakatime-plugin.js
│   ├── main/
│   │   ├── Detail/
│   │   │   ├── Detail.styl
│   │   │   ├── DetailVars.styl
│   │   │   ├── FolderSelect.js
│   │   │   ├── FolderSelect.styl
│   │   │   ├── FromUrlButton.js
│   │   │   ├── FromUrlButton.styl
│   │   │   ├── FullscreenButton.js
│   │   │   ├── FullscreenButton.styl
│   │   │   ├── InfoButton.js
│   │   │   ├── InfoButton.styl
│   │   │   ├── InfoPanel.js
│   │   │   ├── InfoPanel.styl
│   │   │   ├── InfoPanelTrashed.js
│   │   │   ├── MarkdownNoteDetail.js
│   │   │   ├── MarkdownNoteDetail.styl
│   │   │   ├── NoteDetailInfo.styl
│   │   │   ├── PermanentDeleteButton.js
│   │   │   ├── RestoreButton.js
│   │   │   ├── RestoreButton.styl
│   │   │   ├── SnippetNoteDetail.js
│   │   │   ├── SnippetNoteDetail.styl
│   │   │   ├── StarButton.js
│   │   │   ├── StarButton.styl
│   │   │   ├── TagSelect.js
│   │   │   ├── TagSelect.styl
│   │   │   ├── ToggleDirectionButton.js
│   │   │   ├── ToggleDirectionButton.styl
│   │   │   ├── ToggleModeButton.js
│   │   │   ├── ToggleModeButton.styl
│   │   │   ├── TrashButton.js
│   │   │   ├── TrashButton.styl
│   │   │   └── index.js
│   │   ├── DevTools/
│   │   │   ├── index.dev.js
│   │   │   ├── index.js
│   │   │   └── index.prod.js
│   │   ├── Main.js
│   │   ├── Main.styl
│   │   ├── NewNoteButton/
│   │   │   ├── NewNoteButton.styl
│   │   │   └── index.js
│   │   ├── NoteList/
│   │   │   ├── NoteList.styl
│   │   │   └── index.js
│   │   ├── SideNav/
│   │   │   ├── ListButton.js
│   │   │   ├── PreferenceButton.js
│   │   │   ├── PreferenceButton.styl
│   │   │   ├── SearchButton.js
│   │   │   ├── SearchButton.styl
│   │   │   ├── SideNav.styl
│   │   │   ├── StorageItem.js
│   │   │   ├── StorageItem.styl
│   │   │   ├── SwitchButton.styl
│   │   │   ├── TagButton.js
│   │   │   └── index.js
│   │   ├── StatusBar/
│   │   │   ├── StatusBar.styl
│   │   │   └── index.js
│   │   ├── TopBar/
│   │   │   ├── TopBar.styl
│   │   │   └── index.js
│   │   ├── global.styl
│   │   ├── index.js
│   │   ├── lib/
│   │   │   ├── AwsMobileAnalyticsConfig.js
│   │   │   ├── Commander.js
│   │   │   ├── ConfigManager.js
│   │   │   ├── ThemeManager.js
│   │   │   ├── ZoomManager.js
│   │   │   ├── dataApi/
│   │   │   │   ├── addStorage.js
│   │   │   │   ├── attachmentManagement.js
│   │   │   │   ├── copyFile.js
│   │   │   │   ├── createFolder.js
│   │   │   │   ├── createNote.js
│   │   │   │   ├── createNoteFromUrl.js
│   │   │   │   ├── createSnippet.js
│   │   │   │   ├── deleteFolder.js
│   │   │   │   ├── deleteNote.js
│   │   │   │   ├── deleteSnippet.js
│   │   │   │   ├── exportFolder.js
│   │   │   │   ├── exportNote.js
│   │   │   │   ├── exportNoteAs.js
│   │   │   │   ├── exportStorage.js
│   │   │   │   ├── exportTag.js
│   │   │   │   ├── fetchSnippet.js
│   │   │   │   ├── formatHTML.js
│   │   │   │   ├── formatMarkdown.js
│   │   │   │   ├── formatPDF.js
│   │   │   │   ├── getContentFormatter.js
│   │   │   │   ├── getFilename.js
│   │   │   │   ├── index.js
│   │   │   │   ├── init.js
│   │   │   │   ├── migrateFromV5Storage.js
│   │   │   │   ├── migrateFromV6Storage.js
│   │   │   │   ├── moveNote.js
│   │   │   │   ├── removeStorage.js
│   │   │   │   ├── renameStorage.js
│   │   │   │   ├── reorderFolder.js
│   │   │   │   ├── resolveStorageData.js
│   │   │   │   ├── resolveStorageNotes.js
│   │   │   │   ├── toggleStorage.js
│   │   │   │   ├── updateFolder.js
│   │   │   │   ├── updateNote.js
│   │   │   │   └── updateSnippet.js
│   │   │   ├── eventEmitter.js
│   │   │   ├── ipcClient.js
│   │   │   ├── modal.js
│   │   │   ├── notify.js
│   │   │   ├── shortcut.js
│   │   │   └── shortcutManager.js
│   │   ├── modals/
│   │   │   ├── CreateFolderModal.js
│   │   │   ├── CreateFolderModal.styl
│   │   │   ├── CreateMarkdownFromURLModal.js
│   │   │   ├── CreateMarkdownFromURLModal.styl
│   │   │   ├── NewNoteModal.js
│   │   │   ├── NewNoteModal.styl
│   │   │   ├── PreferencesModal/
│   │   │   │   ├── Blog.js
│   │   │   │   ├── ConfigTab.styl
│   │   │   │   ├── Crowdfunding.js
│   │   │   │   ├── Crowdfunding.styl
│   │   │   │   ├── ExportTab.js
│   │   │   │   ├── FolderItem.js
│   │   │   │   ├── FolderItem.styl
│   │   │   │   ├── FolderList.js
│   │   │   │   ├── FolderList.styl
│   │   │   │   ├── HotkeyTab.js
│   │   │   │   ├── InfoTab.js
│   │   │   │   ├── InfoTab.styl
│   │   │   │   ├── PluginsTab.js
│   │   │   │   ├── PreferencesModal.styl
│   │   │   │   ├── SnippetEditor.js
│   │   │   │   ├── SnippetList.js
│   │   │   │   ├── SnippetTab.js
│   │   │   │   ├── SnippetTab.styl
│   │   │   │   ├── StorageItem.js
│   │   │   │   ├── StorageItem.styl
│   │   │   │   ├── StoragesTab.js
│   │   │   │   ├── StoragesTab.styl
│   │   │   │   ├── Tab.styl
│   │   │   │   ├── UiTab.js
│   │   │   │   └── index.js
│   │   │   ├── RenameFolderModal.js
│   │   │   ├── RenameModal.styl
│   │   │   └── RenameTagModal.js
│   │   └── store.js
│   └── styles/
│       ├── Detail/
│       │   └── TagSelect.styl
│       ├── index.styl
│       ├── mixins/
│       │   ├── alert.styl
│       │   ├── btn.styl
│       │   ├── circle.styl
│       │   ├── fullsize.styl
│       │   ├── input.styl
│       │   ├── marked.styl
│       │   ├── tooltip.styl
│       │   └── util.styl
│       ├── shared/
│       │   └── btn.styl
│       ├── theme/
│       │   └── dark.styl
│       └── vars.styl
├── contributing.md
├── dev-scripts/
│   └── dev.js
├── dictionaries/
│   ├── de_DE/
│   │   ├── LICENSE
│   │   ├── de_DE.aff
│   │   └── de_DE.dic
│   ├── en_GB/
│   │   ├── LICENSE
│   │   ├── en_GB.aff
│   │   └── en_GB.dic
│   └── fr_FR/
│       ├── fr_FR.aff
│       └── fr_FR.dic
├── docs/
│   ├── build.md
│   ├── code_style.md
│   ├── de/
│   │   ├── build.md
│   │   └── debug.md
│   ├── debug.md
│   ├── fr/
│   │   ├── build.md
│   │   └── debug.md
│   ├── jp/
│   │   ├── build.md
│   │   └── debug.md
│   ├── ko/
│   │   ├── build.md
│   │   └── debug.md
│   ├── pt_BR/
│   │   ├── build.md
│   │   └── debug.md
│   ├── ru/
│   │   ├── build.md
│   │   └── debug.md
│   ├── zh_CN/
│   │   ├── build.md
│   │   └── debug.md
│   └── zh_TW/
│       └── build.md
├── extra_scripts/
│   ├── boost/
│   │   └── boostNewLineIndentContinueMarkdownList.js
│   └── codemirror/
│       ├── addon/
│       │   ├── edit/
│       │   │   └── closebrackets.js
│       │   └── hyperlink/
│       │       └── hyperlink.js
│       ├── mode/
│       │   ├── bfm/
│       │   │   ├── bfm.css
│       │   │   └── bfm.js
│       │   └── gfm/
│       │       └── gfm.js
│       └── theme/
│           └── nord.css
├── gruntfile.js
├── index.js
├── lib/
│   ├── ipcServer.js
│   ├── main-app.js
│   ├── main-menu.js
│   ├── main-window.js
│   ├── main.development.html
│   ├── main.production.html
│   └── touchbar-menu.js
├── locales/
│   ├── cs.json
│   ├── da.json
│   ├── de.json
│   ├── en.json
│   ├── es-ES.json
│   ├── fa.json
│   ├── fr.json
│   ├── hu.json
│   ├── it.json
│   ├── ja.json
│   ├── ko.json
│   ├── no.json
│   ├── pl.json
│   ├── pt-BR.json
│   ├── pt-PT.json
│   ├── ru.json
│   ├── sq.json
│   ├── th.json
│   ├── tr.json
│   ├── zh-CN.json
│   └── zh-TW.json
├── package.json
├── prettier.config
├── readme.md
├── resources/
│   ├── app.icns
│   └── dmg.icns
├── snap/
│   ├── gui/
│   │   └── boostnote.desktop
│   └── snapcraft.yaml
├── tests/
│   ├── .gitignore
│   ├── components/
│   │   ├── TagListItem.snapshot.test.js
│   │   └── __snapshots__/
│   │       └── TagListItem.snapshot.test.js.snap
│   ├── dataApi/
│   │   ├── addStorage.js
│   │   ├── attachmentManagement.test.js
│   │   ├── copyFile.test.js
│   │   ├── createFolder.test.js
│   │   ├── createNote.test.js
│   │   ├── createNoteFromUrl.test.js
│   │   ├── createSnippet.test.js
│   │   ├── deleteFolder.test.js
│   │   ├── deleteNote-test.js
│   │   ├── deleteSnippet-test.js
│   │   ├── exportFolder-test.js
│   │   ├── exportStorage-test.js
│   │   ├── init.js
│   │   ├── migrateFromV6Storage-test.js
│   │   ├── moveNote-test.js
│   │   ├── removeStorage-test.js
│   │   ├── renameStorage-test.js
│   │   ├── reorderFolder-test.js
│   │   ├── toggleStorage-test.js
│   │   ├── updateFolder-test.js
│   │   ├── updateNote-test.js
│   │   └── updateSnippet-test.js
│   ├── date-formatter-test.js
│   ├── fixtures/
│   │   ├── TestDummy.js
│   │   └── markdowns.js
│   ├── helpers/
│   │   ├── setup-browser-env.js
│   │   └── setup-electron-mock.js
│   ├── jest.js
│   └── lib/
│       ├── __snapshots__/
│       │   └── markdown.test.js.snap
│       ├── boostnoterc/
│       │   ├── .boostnoterc.all
│       │   ├── .boostnoterc.invalid
│       │   └── .boostnoterc.valid
│       ├── contextMenuBuilder.test.js
│       ├── escapeHtmlCharacters.test.js
│       ├── find-storage.test.js
│       ├── find-title.test.js
│       ├── get-todo-status.test.js
│       ├── html-text-helper.test.js
│       ├── markdown-text-helper.test.js
│       ├── markdown-toc-generator.test.js
│       ├── markdown.test.js
│       ├── normalize-editor-font-family.test.js
│       ├── rc-parser.test.js
│       ├── search.test.js
│       ├── slugify.test.js
│       ├── snapshots/
│       │   └── markdown-test.js.md
│       ├── spellcheck.test.js
│       ├── themeManager.test.js
│       └── utils.test.js
├── webpack-production.config.js
├── webpack-skeleton.js
└── webpack.config.js
Download .txt
SYMBOL INDEX (982 symbols across 143 files)

FILE: browser/components/CodeEditor.js
  function translateHotkey (line 44) | function translateHotkey(hotkey) {
  class CodeEditor (line 51) | class CodeEditor extends React.Component {
    method constructor (line 52) | constructor(props) {
    method handleSearch (line 130) | handleSearch(msg) {
    method handleFormatTable (line 164) | handleFormatTable() {
    method handleEditorActivity (line 172) | handleEditorActivity() {
    method updateDefaultKeyMap (line 182) | updateDefaultKeyMap() {
    method updateTableEditorState (line 300) | updateTableEditorState() {
    method componentDidMount (line 316) | componentDidMount() {
    method getWordBeforeCursor (line 535) | getWordBeforeCursor(line, lineNumber, cursorPosition) {
    method quitEditor (line 572) | quitEditor() {
    method componentWillUnmount (line 576) | componentWillUnmount() {
    method componentDidUpdate (line 597) | componentDidUpdate(prevProps, prevState) {
    method getCodeEditorLintConfig (line 752) | getCodeEditorLintConfig() {
    method validatorOfMarkdown (line 764) | validatorOfMarkdown(text, updateLinting) {
    method setMode (line 804) | setMode(mode) {
    method handleChange (line 812) | handleChange(editor, changeObject) {
    method linePossibleContainsHeadline (line 871) | linePossibleContainsHeadline(currentLine) {
    method incrementLines (line 877) | incrementLines(start, linesAdded, linesRemoved, editor) {
    method handleHighlight (line 912) | handleHighlight(editor, changeObject) {
    method updateHighlight (line 935) | updateHighlight(editor, changeObject) {
    method moveCursorTo (line 966) | moveCursorTo(row, col) {}
    method scrollToLine (line 968) | scrollToLine(event, num) {
    method focus (line 979) | focus() {
    method blur (line 983) | blur() {
    method reload (line 987) | reload() {
    method setValue (line 1008) | setValue(value) {
    method setLineContent (line 1019) | setLineContent(lineNumber, content) {
    method handleDropImage (line 1029) | handleDropImage(dropEvent) {
    method insertAttachmentMd (line 1040) | insertAttachmentMd(imageMd) {
    method autoDetectLanguage (line 1044) | autoDetectLanguage(content) {
    method handlePaste (line 1049) | handlePaste(editor, forceSmartPaste) {
    method handleScroll (line 1161) | handleScroll(e) {
    method handlePasteUrl (line 1167) | handlePasteUrl(editor, pastedTxt) {
    method handlePasteHtml (line 1217) | handlePasteHtml(editor, pastedHtml) {
    method handlePasteText (line 1222) | handlePasteText(editor, pastedTxt) {
    method mapNormalResponse (line 1226) | mapNormalResponse(response, pastedTxt) {
    method initialHighlighting (line 1248) | initialHighlighting() {
    method restartHighlighting (line 1270) | restartHighlighting() {
    method mapImageResponse (line 1275) | mapImageResponse(response, pastedTxt) {
    method decodeResponse (line 1288) | decodeResponse(response) {
    method extractContentTypeCharset (line 1308) | extractContentTypeCharset(contentType) {
    method render (line 1322) | render() {
    method createSpellCheckPanel (line 1342) | createSpellCheckPanel() {

FILE: browser/components/ColorPicker.js
  class ColorPicker (line 9) | class ColorPicker extends React.Component {
    method constructor (line 10) | constructor(props) {
    method componentWillReceiveProps (line 21) | componentWillReceiveProps(nextProps) {
    method onColorChange (line 25) | onColorChange(color) {
    method handleConfirm (line 31) | handleConfirm() {
    method render (line 35) | render() {

FILE: browser/components/MarkdownEditor.js
  class MarkdownEditor (line 13) | class MarkdownEditor extends React.Component {
    method constructor (line 14) | constructor(props) {
    method componentDidMount (line 39) | componentDidMount() {
    method componentDidUpdate (line 45) | componentDidUpdate() {
    method UNSAFE_componentWillReceiveProps (line 49) | UNSAFE_componentWillReceiveProps(props) {
    method componentWillUnmount (line 55) | componentWillUnmount() {
    method focusEditor (line 61) | focusEditor() {
    method queueRendering (line 75) | queueRendering(value) {
    method cancelQueue (line 82) | cancelQueue() {
    method renderPreview (line 86) | renderPreview(value) {
    method setValue (line 92) | setValue(value) {
    method handleChange (line 96) | handleChange(e) {
    method handleContextMenu (line 101) | handleContextMenu(e) {
    method handleBlur (line 126) | handleBlur(e) {
    method handleDoubleClick (line 149) | handleDoubleClick(e) {
    method handlePreviewMouseDown (line 166) | handlePreviewMouseDown(e) {
    method handlePreviewMouseUp (line 170) | handlePreviewMouseUp(e) {
    method handleCheckboxClick (line 188) | handleCheckboxClick(e) {
    method focus (line 214) | focus() {
    method reload (line 230) | reload() {
    method handleKeyDown (line 236) | handleKeyDown(e) {
    method addMdAroundWord (line 263) | addMdAroundWord(mdElement) {
    method addMdAroundSelection (line 277) | addMdAroundSelection(mdElement) {
    method handleDropImage (line 283) | handleDropImage(dropEvent) {
    method handleKeyUp (line 308) | handleKeyUp(e) {
    method handleLockEditor (line 314) | handleLockEditor() {
    method render (line 318) | render() {

FILE: browser/components/MarkdownPreview.js
  function getSourceLineNumberByElement (line 78) | function getSourceLineNumberByElement(element) {
  class MarkdownPreview (line 88) | class MarkdownPreview extends React.Component {
    method constructor (line 89) | constructor(props) {
    method initMarkdown (line 113) | initMarkdown() {
    method handleCheckboxClick (line 122) | handleCheckboxClick(e) {
    method handleScroll (line 126) | handleScroll(e) {
    method handleContextMenu (line 132) | handleContextMenu(event) {
    method handleDoubleClick (line 142) | handleDoubleClick(e) {
    method handleMouseDown (line 146) | handleMouseDown(e) {
    method handleMouseUp (line 176) | handleMouseUp(e) {
    method handleSaveAsText (line 184) | handleSaveAsText() {
    method handleSaveAsMd (line 188) | handleSaveAsMd() {
    method handleSaveAsHtml (line 192) | handleSaveAsHtml() {
    method handleSaveAsPdf (line 196) | handleSaveAsPdf() {
    method handlePrint (line 200) | handlePrint() {
    method exportAsDocument (line 204) | exportAsDocument(fileType, contentFormatter) {
    method fixDecodedURI (line 238) | fixDecodedURI(node) {
    method getScrollBarStyle (line 250) | getScrollBarStyle() {
    method componentDidMount (line 258) | componentDidMount() {
    method componentWillUnmount (line 316) | componentWillUnmount() {
    method componentDidUpdate (line 358) | componentDidUpdate(prevProps) {
    method applyStyle (line 400) | applyStyle() {
    method rewriteIframe (line 431) | rewriteIframe() {
    method setImgOnClickEventHelper (line 660) | setImgOnClickEventHelper(img, rect) {
    method handleResize (line 731) | handleResize() {
    method focus (line 740) | focus() {
    method getWindow (line 744) | getWindow() {
    method scrollToLine (line 752) | scrollToLine(targetLine) {
    method scrollTo (line 774) | scrollTo(x, y) {
    method preventImageDroppedHandler (line 778) | preventImageDroppedHandler(e) {
    method notify (line 783) | notify(title, options) {
    method handleLinkClick (line 794) | handleLinkClick(e) {
    method openExternal (line 867) | openExternal(href) {
    method render (line 878) | render() {

FILE: browser/components/MarkdownSplitEditor.js
  class MarkdownSplitEditor (line 10) | class MarkdownSplitEditor extends React.Component {
    method constructor (line 11) | constructor(props) {
    method componentDidUpdate (line 24) | componentDidUpdate(prevProps) {
    method handleCursorActivity (line 33) | handleCursorActivity(editor) {
    method setValue (line 90) | setValue(value) {
    method handleOnChange (line 94) | handleOnChange(e) {
    method handleEditorScroll (line 99) | handleEditorScroll(e) {
    method handlePreviewScroll (line 164) | handlePreviewScroll(e) {
    method handleCheckboxClick (line 221) | handleCheckboxClick(e) {
    method handleMouseMove (line 247) | handleMouseMove(e) {
    method handleMouseUp (line 289) | handleMouseUp(e) {
    method handleMouseDown (line 296) | handleMouseDown(e) {
    method scrollTo (line 303) | scrollTo(from, to, scroller) {
    method render (line 332) | render() {

FILE: browser/components/RealtimeNotification.js
  class RealtimeNotification (line 8) | class RealtimeNotification extends React.Component {
    method constructor (line 9) | constructor(props) {
    method componentDidMount (line 17) | componentDidMount() {
    method fetchNotifications (line 21) | fetchNotifications() {
    method handleLinkClick (line 33) | handleLinkClick(e) {
    method render (line 38) | render() {

FILE: browser/components/SnippetTab.js
  class SnippetTab (line 7) | class SnippetTab extends React.Component {
    method constructor (line 8) | constructor(props) {
    method componentWillUpdate (line 17) | componentWillUpdate(nextProps) {
    method handleClick (line 25) | handleClick(e) {
    method handleContextMenu (line 29) | handleContextMenu(e) {
    method handleRenameClick (line 38) | handleRenameClick(e) {
    method handleNameInputBlur (line 42) | handleNameInputBlur(e) {
    method handleNameInputChange (line 46) | handleNameInputChange(e) {
    method handleNameInputKeyDown (line 52) | handleNameInputKeyDown(e) {
    method handleRename (line 66) | handleRename() {
    method handleDeleteButtonClick (line 79) | handleDeleteButtonClick(e) {
    method startRenaming (line 83) | startRenaming() {
    method handleDragStart (line 95) | handleDragStart(e) {
    method handleDrop (line 100) | handleDrop(e) {
    method render (line 104) | render() {

FILE: browser/components/render/MermaidRender.js
  function getRandomInt (line 10) | function getRandomInt(min, max) {
  function getId (line 14) | function getId() {
  function render (line 23) | function render(element, content, theme, enableHTMLLabel) {

FILE: browser/lib/Languages.js
  method getLocales (line 81) | getLocales() {
  method getLanguages (line 87) | getLanguages() {

FILE: browser/lib/Mutable.js
  class MutableMap (line 1) | class MutableMap {
    method constructor (line 2) | constructor(iterable) {
    method get (line 12) | get(...args) {
    method set (line 16) | set(...args) {
    method delete (line 20) | delete(...args) {
    method has (line 24) | has(...args) {
    method clear (line 28) | clear(...args) {
    method forEach (line 32) | forEach(...args) {
    method map (line 40) | map(cb) {
    method toJS (line 48) | toJS() {
  method [Symbol.iterator] (line 36) | [Symbol.iterator]() {
  class MutableSet (line 60) | class MutableSet {
    method constructor (line 61) | constructor(iterable) {
    method add (line 71) | add(...args) {
    method delete (line 75) | delete(...args) {
    method forEach (line 79) | forEach(...args) {
    method map (line 87) | map(cb) {
    method toJS (line 96) | toJS() {
  method [Symbol.iterator] (line 83) | [Symbol.iterator]() {

FILE: browser/lib/RcParser.js
  constant BOOSTNOTERC (line 4) | const BOOSTNOTERC = '.boostnoterc'
  function parse (line 8) | function parse(boostnotercPath = _boostnotercPath) {

FILE: browser/lib/SnippetManager.js
  class SnippetManager (line 5) | class SnippetManager {
    method constructor (line 6) | constructor() {
    method init (line 22) | init() {
    method assignSnippets (line 41) | assignSnippets(snippets) {
    method expandSnippet (line 45) | expandSnippet(wordBeforeCursor, cursor, cm) {

FILE: browser/lib/TextEditorInterface.js
  class TextEditorInterface (line 3) | class TextEditorInterface {
    method constructor (line 4) | constructor(editor) {
    method getCursorPosition (line 10) | getCursorPosition() {
    method setCursorPosition (line 15) | setCursorPosition(pos) {
    method setSelectionRange (line 22) | setSelectionRange(range) {
    method getLastRow (line 29) | getLastRow() {
    method acceptsTableEdit (line 33) | acceptsTableEdit() {
    method getLine (line 37) | getLine(row) {
    method insertLine (line 41) | insertLine(row, line) {
    method deleteLine (line 59) | deleteLine(row) {
    method replaceLines (line 83) | replaceLines(startRow, endRow, lines) {
    method transact (line 101) | transact(func) {

FILE: browser/lib/confirmDeleteNote.js
  function confirmDeleteNote (line 6) | function confirmDeleteNote(confirmDeletion, permanent) {

FILE: browser/lib/consts.js
  constant CODEMIRROR_THEME_PATH (line 6) | const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
  constant CODEMIRROR_EXTRA_THEME_PATH (line 7) | const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'

FILE: browser/lib/context.js
  function popup (line 4) | function popup(templates) {

FILE: browser/lib/convertModeName.js
  function convertModeName (line 1) | function convertModeName(name) {

FILE: browser/lib/date-formatter.js
  function formatDate (line 11) | function formatDate(date) {

FILE: browser/lib/findNoteTitle.js
  function findNoteTitle (line 1) | function findNoteTitle(

FILE: browser/lib/findStorage.js
  function findStorage (line 3) | function findStorage(storageKey) {

FILE: browser/lib/getTodoStatus.js
  function getTodoStatus (line 1) | function getTodoStatus(content) {
  function getTodoPercentageOfCompleted (line 22) | function getTodoPercentageOfCompleted(content) {

FILE: browser/lib/htmlTextHelper.js
  function decodeEntities (line 10) | function decodeEntities(text) {
  function encodeEntities (line 27) | function encodeEntities(text) {

FILE: browser/lib/markdown-it-deflist.js
  function skipMarker (line 8) | function skipMarker(state, line) {
  function markTightParagraphs (line 32) | function markTightParagraphs(state, idx) {
  function deflist (line 49) | function deflist(state, startLine, endLine, silent) {

FILE: browser/lib/markdown-it-fence.js
  function fence (line 6) | function fence(state, startLine, endLine, silent) {

FILE: browser/lib/markdown-it-frontmatter.js
  function frontmatter (line 4) | function frontmatter(state, startLine, endLine, silent) {

FILE: browser/lib/markdown-it-sanitize-html.js
  function sanitizeInline (line 43) | function sanitizeInline(html, options) {
  function naughtyHRef (line 110) | function naughtyHRef(href, options) {
  function naughtyIFrame (line 133) | function naughtyIFrame(src, options) {

FILE: browser/lib/markdown-toc-generator.js
  function uniqueSlug (line 15) | function uniqueSlug(slug, slugs, opts) {
  function linkify (line 23) | function linkify(token) {
  constant TOC_MARKER_START (line 28) | const TOC_MARKER_START = '<!-- toc -->'
  constant TOC_MARKER_END (line 29) | const TOC_MARKER_END = '<!-- tocstop -->'
  function generateInEditor (line 39) | function generateInEditor(editor) {
  function tocExistsInEditor (line 62) | function tocExistsInEditor(editor) {
  function generate (line 71) | function generate(markdownText) {
  function wrapTocWithEol (line 91) | function wrapTocWithEol(toc, editor) {

FILE: browser/lib/markdown.js
  function createGutter (line 13) | function createGutter(str, firstLineNumber) {
  class Markdown (line 25) | class Markdown {
    method constructor (line 26) | constructor(options = {}) {
    method render (line 520) | render(content) {

FILE: browser/lib/markdownTextHelper.js
  function strip (line 9) | function strip(input) {

FILE: browser/lib/newNote.js
  function createMarkdownNote (line 7) | function createMarkdownNote(
  function createSnippetNote (line 53) | function createSnippetNote(

FILE: browser/lib/normalizeEditorFontFamily.js
  function normalizeEditorFontFamily (line 4) | function normalizeEditorFontFamily(fontFamily) {

FILE: browser/lib/search.js
  function searchFromNotes (line 3) | function searchFromNotes(notes, search) {
  function findByWordOrTag (line 16) | function findByWordOrTag(notes, block) {

FILE: browser/lib/spellcheck.js
  constant CSS_ERROR_CLASS (line 7) | const CSS_ERROR_CLASS = 'codeEditor-typo'
  constant SPELLCHECK_DISABLED (line 8) | const SPELLCHECK_DISABLED = 'NONE'
  constant DICTIONARY_PATH (line 9) | const DICTIONARY_PATH = '../dictionaries'
  constant MILLISECONDS_TILL_LIVECHECK (line 10) | const MILLISECONDS_TILL_LIVECHECK = 500
  function getAvailableDictionaries (line 15) | function getAvailableDictionaries() {
  function setDictionaryForTestsOnly (line 27) | function setDictionaryForTestsOnly(newDictionary) {
  function setLanguage (line 37) | function setLanguage(editor, lang) {
  function checkWholeDocument (line 62) | function checkWholeDocument(editor) {
  function checkMultiLineRange (line 77) | function checkMultiLineRange(editor, from, to) {
  function checkWord (line 115) | function checkWord(editor, wordRange) {
  function checkChangeRange (line 133) | function checkChangeRange(editor, fromChangeObject, toChangeObject) {
  function saveLiveSpellCheckFrom (line 187) | function saveLiveSpellCheckFrom(changeObject) {
  function handleChange (line 213) | function handleChange(editor, changeObject) {
  function getSpellingSuggestion (line 227) | function getSpellingSuggestion(word) {
  function getCSSClassName (line 237) | function getCSSClassName() {

FILE: browser/lib/utils.js
  function lastFindInArray (line 1) | function lastFindInArray(array, callback) {
  function escapeHtmlCharacters (line 9) | function escapeHtmlCharacters(
  function isObjectEqual (line 118) | function isObjectEqual(a, b) {
  function isMarkdownTitleURL (line 135) | function isMarkdownTitleURL(str) {
  function humanFileSize (line 141) | function humanFileSize(bytes) {

FILE: browser/lib/wakatime-plugin.js
  function sendWakatimeHeartBeat (line 6) | function sendWakatimeHeartBeat(

FILE: browser/main/Detail/FolderSelect.js
  class FolderSelect (line 8) | class FolderSelect extends React.Component {
    method constructor (line 9) | constructor(props) {
    method componentDidMount (line 19) | componentDidMount() {
    method componentDidUpdate (line 23) | componentDidUpdate() {
    method handleClick (line 27) | handleClick(e) {
    method handleFocus (line 39) | handleFocus(e) {
    method handleBlur (line 47) | handleBlur(e) {
    method handleKeyDown (line 55) | handleKeyDown(e) {
    method handleSearchInputBlur (line 97) | handleSearchInputBlur(e) {
    method handleSearchInputChange (line 105) | handleSearchInputChange(e) {
    method handleSearchInputKeyDown (line 123) | handleSearchInputKeyDown(e) {
    method nextOption (line 150) | nextOption() {
    method previousOption (line 162) | previousOption() {
    method selectOption (line 174) | selectOption() {
    method handleOptionClick (line 192) | handleOptionClick(storageKey, folderKey) {
    method setValue (line 207) | setValue(value) {
    method render (line 212) | render() {

FILE: browser/main/Detail/FromUrlButton.js
  class FromUrlButton (line 8) | class FromUrlButton extends React.Component {
    method constructor (line 9) | constructor(props) {
    method handleMouseDown (line 17) | handleMouseDown(e) {
    method handleMouseUp (line 23) | handleMouseUp(e) {
    method handleMouseLeave (line 29) | handleMouseLeave(e) {
    method render (line 35) | render() {

FILE: browser/main/Detail/FullscreenButton.js
  constant OSX (line 7) | const OSX = global.process.platform === 'darwin'

FILE: browser/main/Detail/InfoPanel.js
  class InfoPanel (line 8) | class InfoPanel extends React.Component {
    method copyNoteLink (line 9) | copyNoteLink() {
    method render (line 15) | render() {

FILE: browser/main/Detail/MarkdownNoteDetail.js
  class MarkdownNoteDetail (line 37) | class MarkdownNoteDetail extends React.Component {
    method constructor (line 38) | constructor(props) {
    method focus (line 67) | focus() {
    method componentDidMount (line 71) | componentDidMount() {
    method UNSAFE_componentWillReceiveProps (line 84) | UNSAFE_componentWillReceiveProps(nextProps) {
    method componentWillUnmount (line 116) | componentWillUnmount() {
    method handleUpdateTag (line 123) | handleUpdateTag() {
    method handleUpdateContent (line 129) | handleUpdateContent() {
    method updateNote (line 145) | updateNote(note) {
    method save (line 152) | save() {
    method saveNow (line 159) | saveNow() {
    method handleFolderChange (line 173) | handleFolderChange(e) {
    method handleStarButtonClick (line 211) | handleStarButtonClick(e) {
    method exportAsFile (line 228) | exportAsFile() {}
    method exportAsMd (line 230) | exportAsMd() {
    method exportAsTxt (line 234) | exportAsTxt() {
    method exportAsHtml (line 238) | exportAsHtml() {
    method exportAsPdf (line 242) | exportAsPdf() {
    method handleKeyDown (line 246) | handleKeyDown(e) {
    method handleTrashButtonClick (line 279) | handleTrashButtonClick(e) {
    method handleUndoButtonClick (line 319) | handleUndoButtonClick(e) {
    method handleFullScreenButton (line 336) | handleFullScreenButton(e) {
    method handleLockButtonMouseDown (line 340) | handleLockButtonMouseDown(e) {
    method getToggleLockButton (line 347) | getToggleLockButton() {
    method handleDeleteKeyDown (line 353) | handleDeleteKeyDown(e) {
    method handleToggleLockButton (line 357) | handleToggleLockButton(event, noteStatus) {
    method handleGenerateToc (line 366) | handleGenerateToc() {
    method handleFocus (line 371) | handleFocus(e) {
    method handleInfoButtonClick (line 375) | handleInfoButtonClick(e) {
    method print (line 382) | print(e) {
    method handleSwitchMode (line 386) | handleSwitchMode(type) {
    method handleSwitchStackDirection (line 399) | handleSwitchStackDirection() {
    method handleSwitchDirection (line 411) | handleSwitchDirection() {
    method handleDeleteNote (line 420) | handleDeleteNote() {
    method handleClearTodo (line 424) | handleClearTodo() {
    method getNote (line 445) | getNote() {
    method renderEditor (line 449) | renderEditor() {
    method render (line 488) | render() {

FILE: browser/main/Detail/SnippetNoteDetail.js
  class SnippetNoteDetail (line 39) | class SnippetNoteDetail extends React.Component {
    method constructor (line 40) | constructor(props) {
    method componentDidMount (line 66) | componentDidMount() {
    method componentWillReceiveProps (line 81) | componentWillReceiveProps(nextProps) {
    method componentWillUnmount (line 116) | componentWillUnmount() {
    method handleGenerateToc (line 121) | handleGenerateToc() {
    method handleChange (line 130) | handleChange(e) {
    method save (line 148) | save() {
    method saveNow (line 155) | saveNow() {
    method handleFolderChange (line 169) | handleFolderChange(e) {
    method handleStarButtonClick (line 207) | handleStarButtonClick(e) {
    method exportAsFile (line 224) | exportAsFile() {}
    method handleTrashButtonClick (line 226) | handleTrashButtonClick(e) {
    method handleUndoButtonClick (line 266) | handleUndoButtonClick(e) {
    method handleFullScreenButton (line 282) | handleFullScreenButton(e) {
    method handleTabMoveLeftButtonClick (line 286) | handleTabMoveLeftButtonClick(e) {
    method handleTabMoveRightButtonClick (line 313) | handleTabMoveRightButtonClick(e) {
    method handleTabPlusButtonClick (line 337) | handleTabPlusButtonClick(e) {
    method handleTabButtonClick (line 341) | handleTabButtonClick(e, index) {
    method handleTabDragStart (line 347) | handleTabDragStart(e, index) {
    method handleTabDrop (line 351) | handleTabDrop(e, index) {
    method handleTabDeleteButtonClick (line 368) | handleTabDeleteButtonClick(e, index) {
    method deleteSnippetByIndex (line 386) | deleteSnippetByIndex(index) {
    method renameSnippetByIndex (line 416) | renameSnippetByIndex(index, name) {
    method handleModeOptionClick (line 441) | handleModeOptionClick(index, name) {
    method handleCodeChange (line 464) | handleCodeChange(index) {
    method handleKeyDown (line 484) | handleKeyDown(e) {
    method handleModeButtonClick (line 539) | handleModeButtonClick(e, index) {
    method handleIndentTypeButtonClick (line 554) | handleIndentTypeButtonClick(e) {
    method handleIndentSizeButtonClick (line 567) | handleIndentSizeButtonClick(e) {
    method handleWrapLineButtonClick (line 584) | handleWrapLineButtonClick(e) {
    method handleIndentSizeItemClick (line 597) | handleIndentSizeItemClick(e, indentSize) {
    method handleIndentTypeItemClick (line 613) | handleIndentTypeItemClick(e, indentType) {
    method handleWrapLineItemClick (line 629) | handleWrapLineItemClick(e, lineWrapping) {
    method focus (line 645) | focus() {
    method moveToTab (line 649) | moveToTab(tab) {
    method getArrowsState (line 684) | getArrowsState() {
    method addSnippet (line 696) | addSnippet() {
    method jumpNextTab (line 737) | jumpNextTab() {
    method jumpPrevTab (line 748) | jumpPrevTab() {
    method focusEditor (line 761) | focusEditor() {
    method handleInfoButtonClick (line 765) | handleInfoButtonClick(e) {
    method showWarning (line 772) | showWarning(e, msg) {
    method render (line 792) | render() {

FILE: browser/main/Detail/StarButton.js
  class StarButton (line 8) | class StarButton extends React.Component {
    method constructor (line 9) | constructor(props) {
    method handleMouseDown (line 17) | handleMouseDown(e) {
    method handleMouseUp (line 23) | handleMouseUp(e) {
    method handleMouseLeave (line 29) | handleMouseLeave(e) {
    method render (line 35) | render() {

FILE: browser/main/Detail/TagSelect.js
  class TagSelect (line 13) | class TagSelect extends React.Component {
    method constructor (line 14) | constructor(props) {
    method addNewTag (line 36) | addNewTag(newTag) {
    method buildSuggestions (line 73) | buildSuggestions() {
    method componentDidMount (line 86) | componentDidMount() {
    method componentDidUpdate (line 95) | componentDidUpdate() {
    method componentWillUnmount (line 99) | componentWillUnmount() {
    method handleAddTag (line 104) | handleAddTag() {
    method handleRenameTag (line 108) | handleRenameTag(event, tagChange) {
    method handleTagLabelClick (line 118) | handleTagLabelClick(tag) {
    method handleTagRemoveButtonClick (line 126) | handleTagRemoveButtonClick(tag) {
    method onInputBlur (line 132) | onInputBlur(e) {
    method onInputChange (line 136) | onInputChange(e, { newValue, method }) {
    method onInputKeyDown (line 142) | onInputKeyDown(e) {
    method onSuggestionsClearRequested (line 158) | onSuggestionsClearRequested() {
    method onSuggestionsFetchRequested (line 164) | onSuggestionsFetchRequested({ value }) {
    method onSuggestionSelected (line 177) | onSuggestionSelected(event, { suggestion, suggestionValue }) {
    method removeLastTag (line 181) | removeLastTag() {
    method removeTagByCallback (line 187) | removeTagByCallback(callback, tag = null) {
    method reset (line 198) | reset() {
    method submitNewTag (line 206) | submitNewTag() {
    method render (line 210) | render() {

FILE: browser/main/Detail/index.js
  constant OSX (line 15) | const OSX = global.process.platform === 'darwin'
  class Detail (line 17) | class Detail extends React.Component {
    method constructor (line 18) | constructor(props) {
    method componentDidMount (line 29) | componentDidMount() {
    method componentWillUnmount (line 34) | componentWillUnmount() {
    method render (line 39) | render() {

FILE: browser/main/Main.js
  class Main (line 27) | class Main extends React.Component {
    method constructor (line 28) | constructor(props) {
    method getChildContext (line 50) | getChildContext() {
    method init (line 59) | init() {
    method componentDidMount (line 149) | componentDidMount() {
    method componentWillUnmount (line 191) | componentWillUnmount() {
    method changeRoutePush (line 201) | changeRoutePush(event, destination) {
    method toggleMenuBarVisible (line 206) | toggleMenuBarVisible() {
    method handleLeftSlideMouseDown (line 215) | handleLeftSlideMouseDown(e) {
    method handleRightSlideMouseDown (line 222) | handleRightSlideMouseDown(e) {
    method handleMouseUp (line 229) | handleMouseUp(e) {
    method handleMouseMove (line 269) | handleMouseMove(e) {
    method handleFullScreenButton (line 295) | handleFullScreenButton(e) {
    method hideLeftLists (line 309) | hideLeftLists(noteDetail, noteList, mainBody) {
    method showLeftLists (line 317) | showLeftLists(noteDetail, noteList, mainBody) {
    method render (line 323) | render() {

FILE: browser/main/NewNoteButton/index.js
  constant OSX (line 15) | const OSX = window.process.platform === 'darwin'
  class NewNoteButton (line 17) | class NewNoteButton extends React.Component {
    method constructor (line 18) | constructor(props) {
    method componentDidMount (line 26) | componentDidMount() {
    method componentWillUnmount (line 30) | componentWillUnmount() {
    method handleNewNoteButtonClick (line 34) | handleNewNoteButtonClick(e) {
    method resolveTargetFolder (line 72) | resolveTargetFolder() {
    method showMessageBox (line 99) | showMessageBox(message) {
    method render (line 107) | render() {

FILE: browser/main/NoteList/index.js
  constant WP_POST_PATH (line 29) | const WP_POST_PATH = '/wp/v2/posts'
  function sortByCreatedAt (line 33) | function sortByCreatedAt(a, b) {
  function sortByAlphabetical (line 37) | function sortByAlphabetical(a, b) {
  function sortByUpdatedAt (line 57) | function sortByUpdatedAt(a, b) {
  function findNoteByKey (line 61) | function findNoteByKey(notes, noteKey) {
  function findNotesByKeys (line 65) | function findNotesByKeys(notes, noteKeys) {
  function getNoteKey (line 69) | function getNoteKey(note) {
  class NoteList (line 73) | class NoteList extends React.Component {
    method constructor (line 74) | constructor(props) {
    method componentDidMount (line 116) | componentDidMount() {
    method componentWillReceiveProps (line 128) | componentWillReceiveProps(nextProps) {
    method resetScroll (line 134) | resetScroll() {
    method componentWillUnmount (line 138) | componentWillUnmount() {
    method componentDidUpdate (line 150) | componentDidUpdate(prevProps) {
    method focusNote (line 213) | focusNote(selectedNoteKeys, noteKey, pathname) {
    method getNoteKeyFromTargetIndex (line 230) | getNoteKeyFromTargetIndex(targetIndex) {
    method selectPriorNote (line 236) | selectPriorNote() {
    method selectNextNote (line 266) | selectNextNote() {
    method jumpNoteByHashHandler (line 303) | jumpNoteByHashHandler(event, noteHash) {
    method handleNoteListKeyDown (line 327) | handleNoteListKeyDown(e) {
    method handleNoteListKeyUp (line 367) | handleNoteListKeyUp(e) {
    method handleNoteListBlur (line 377) | handleNoteListBlur() {
    method getNotes (line 384) | getNotes() {
    method getContextNotes (line 438) | getContextNotes() {
    method sortByPin (line 459) | sortByPin(unorderedNotes) {
    method getNoteIndexByKey (line 474) | getNoteIndexByKey(noteKey) {
    method handleNoteClick (line 482) | handleNoteClick(e, uniqueKey) {
    method handleSortByChange (line 552) | handleSortByChange(e) {
    method handleListStyleButtonClick (line 571) | handleListStyleButtonClick(e, style) {
    method handleListDirectionButtonClick (line 585) | handleListDirectionButtonClick(e, direction) {
    method alertIfSnippet (line 599) | alertIfSnippet(msg) {
    method handleDragStart (line 622) | handleDragStart(e, note) {
    method handleExportClick (line 638) | handleExportClick(e, note, fileType) {
    method handleNoteContextMenu (line 670) | handleNoteContextMenu(e, uniqueKey) {
    method updateSelectedNotes (line 784) | updateSelectedNotes(updateFunc, cleanSelection = true) {
    method pinToTop (line 816) | pinToTop() {
    method restoreNote (line 823) | restoreNote() {
    method deleteNote (line 830) | deleteNote() {
    method cloneNote (line 889) | cloneNote() {
    method copyNoteLink (line 936) | copyNoteLink(note) {
    method navigate (line 941) | navigate(sender, pathname) {
    method save (line 953) | save(note) {
    method publishMarkdown (line 963) | publishMarkdown() {
    method publishMarkdownNow (line 972) | publishMarkdownNow() {
    method confirmPublishError (line 1032) | confirmPublishError() {
    method confirmPublish (line 1044) | confirmPublish(note) {
    method openBlog (line 1057) | openBlog(note) {
    method importFromFile (line 1062) | importFromFile() {
    method handleDrop (line 1073) | handleDrop(e) {
    method addNotesFromFiles (line 1083) | addNotesFromFiles(filepaths) {
    method getTargetIndex (line 1129) | getTargetIndex() {
    method resolveTargetFolder (line 1138) | resolveTargetFolder() {
    method showMessageBox (line 1164) | showMessageBox(message) {
    method getNoteStorage (line 1172) | getNoteStorage(note) {
    method getNoteFolder (line 1177) | getNoteFolder(note) {
    method getViewType (line 1185) | getViewType() {
    method render (line 1195) | render() {

FILE: browser/main/SideNav/StorageItem.js
  class StorageItem (line 21) | class StorageItem extends React.Component {
    method constructor (line 22) | constructor(props) {
    method handleHeaderContextMenu (line 33) | handleHeaderContextMenu(e) {
    method handleUnlinkStorageClick (line 73) | handleUnlinkStorageClick(e) {
    method handleExportStorageClick (line 99) | handleExportStorageClick(e, fileType) {
    method handleToggleButtonClick (line 134) | handleToggleButtonClick(e) {
    method handleAddFolderButtonClick (line 149) | handleAddFolderButtonClick(e) {
    method handleHeaderInfoClick (line 157) | handleHeaderInfoClick(e) {
    method handleFolderButtonClick (line 162) | handleFolderButtonClick(folderKey) {
    method handleFolderMouseEnter (line 169) | handleFolderMouseEnter(e, tooltipRef, isFolded) {
    method handleFolderButtonContextMenu (line 178) | handleFolderButtonContextMenu(e, folder) {
    method handleRenameFolderClick (line 218) | handleRenameFolderClick(e, folder) {
    method handleExportFolderClick (line 226) | handleExportFolderClick(e, folder, fileType) {
    method handleFolderDeleteClick (line 262) | handleFolderDeleteClick(e, folder) {
    method handleDragEnter (line 284) | handleDragEnter(e, key) {
    method handleDragLeave (line 294) | handleDragLeave(e) {
    method dropNote (line 304) | dropNote(storage, folder, dispatch, location, noteData) {
    method handleDrop (line 329) | handleDrop(e, storage, folder, dispatch, location) {
    method render (line 340) | render() {

FILE: browser/main/SideNav/index.js
  function matchActiveTags (line 31) | function matchActiveTags(tags, activeTags) {
  class SideNav (line 35) | class SideNav extends React.Component {
    method constructor (line 37) | constructor(props) {
    method componentDidMount (line 59) | componentDidMount() {
    method componentWillUnmount (line 63) | componentWillUnmount() {
    method deleteTag (line 67) | deleteTag(tag) {
    method handleMenuButtonClick (line 122) | handleMenuButtonClick(e) {
    method handleSearchButtonClick (line 126) | handleSearchButtonClick(e) {
    method handleSearchInputClear (line 134) | handleSearchInputClear(e) {
    method handleSearchInputChange (line 140) | handleSearchInputChange(e) {
    method handleHomeButtonClick (line 146) | handleHomeButtonClick(e) {
    method handleStarredButtonClick (line 151) | handleStarredButtonClick(e) {
    method handleTagContextMenu (line 156) | handleTagContextMenu(e, tag) {
    method handleExportTagClick (line 204) | handleExportTagClick(e, tag, fileType) {
    method dismissColorPicker (line 233) | dismissColorPicker() {
    method displayColorPicker (line 241) | displayColorPicker(tagName, rect) {
    method handleRenameTagClick (line 253) | handleRenameTagClick(tagName) {
    method handleColorPickerConfirm (line 263) | handleColorPickerConfirm(color) {
    method handleColorPickerReset (line 284) | handleColorPickerReset() {
    method handleToggleButtonClick (line 305) | handleToggleButtonClick(e) {
    method handleTrashedButtonClick (line 322) | handleTrashedButtonClick(e) {
    method handleSwitchFoldersButtonClick (line 327) | handleSwitchFoldersButtonClick() {
    method handleSwitchTagsButtonClick (line 332) | handleSwitchTagsButtonClick() {
    method onSortEnd (line 337) | onSortEnd(storage) {
    method SideNavComponent (line 346) | SideNavComponent(isFolded) {
    method tagListComponent (line 452) | tagListComponent() {
    method getRelatedTags (line 507) | getRelatedTags(activeTags, noteMap) {
    method getTagActive (line 519) | getTagActive(path, tag) {
    method getActiveTags (line 523) | getActiveTags(path) {
    method handleClickTagListItem (line 529) | handleClickTagListItem(name) {
    method handleSortTagsByChange (line 534) | handleSortTagsByChange(e) {
    method handleClickNarrowToTag (line 548) | handleClickNarrowToTag(tag) {
    method emptyTrash (line 560) | emptyTrash(entries) {
    method handleFilterButtonContextMenu (line 578) | handleFilterButtonContextMenu(event) {
    method render (line 591) | render() {

FILE: browser/main/StatusBar/index.js
  class StatusBar (line 30) | class StatusBar extends React.Component {
    method constructor (line 31) | constructor(props) {
    method componentDidMount (line 38) | componentDidMount() {
    method componentWillUnmount (line 44) | componentWillUnmount() {
    method updateApp (line 50) | updateApp() {
    method handleZoomButtonClick (line 63) | handleZoomButtonClick(e) {
    method handleZoomMenuItemClick (line 76) | handleZoomMenuItemClick(zoomFactor) {
    method handleZoomInMenuItem (line 85) | handleZoomInMenuItem() {
    method handleZoomOutMenuItem (line 90) | handleZoomOutMenuItem() {
    method handleZoomResetMenuItem (line 95) | handleZoomResetMenuItem() {
    method render (line 99) | render() {

FILE: browser/main/TopBar/index.js
  class TopBar (line 13) | class TopBar extends React.Component {
    method constructor (line 14) | constructor(props) {
    method componentDidMount (line 51) | componentDidMount() {
    method componentWillUnmount (line 66) | componentWillUnmount() {
    method handleSearchClearButton (line 71) | handleSearchClearButton(e) {
    method handleKeyDown (line 83) | handleKeyDown(e) {
    method handleSearchChange (line 107) | handleSearchChange(e) {
    method handleSearchFocus (line 112) | handleSearchFocus(e) {
    method handleSearchBlur (line 118) | handleSearchBlur(e) {
    method handleOnSearchFocus (line 137) | handleOnSearchFocus() {
    method handleCodeInit (line 146) | handleCodeInit() {
    method render (line 150) | render() {

FILE: browser/main/index.js
  function notify (line 94) | function notify(...args) {
  function updateApp (line 98) | function updateApp() {
  function downloadUpdate (line 111) | function downloadUpdate() {

FILE: browser/main/lib/AwsMobileAnalyticsConfig.js
  constant AWS (line 1) | const AWS = require('aws-sdk')
  constant AMA (line 2) | const AMA = require('aws-sdk-mobile-analytics')
  function convertPlatformName (line 25) | function convertPlatformName(platformName) {
  function getSendEventCond (line 37) | function getSendEventCond() {
  function initAwsMobileAnalytics (line 44) | function initAwsMobileAnalytics() {
  function recordDynamicCustomEvent (line 54) | function recordDynamicCustomEvent(type, options = {}) {
  function recordStaticCustomEvent (line 65) | function recordStaticCustomEvent() {

FILE: browser/main/lib/Commander.js
  function bind (line 3) | function bind(name, el) {
  function release (line 10) | function release(el) {
  function fire (line 14) | function fire(command) {

FILE: browser/main/lib/ConfigManager.js
  constant OSX (line 6) | const OSX = global.process.platform === 'darwin'
  constant DEFAULT_MARKDOWN_LINT_CONFIG (line 15) | const DEFAULT_MARKDOWN_LINT_CONFIG = `{
  constant DEFAULT_CSS_CONFIG (line 19) | const DEFAULT_CSS_CONFIG = `
  constant DEFAULT_CONFIG (line 35) | const DEFAULT_CONFIG = {
  function validate (line 158) | function validate(config) {
  function _save (line 167) | function _save(config) {
  function get (line 171) | function get() {
  function set (line 220) | function set(updates) {
  function assignConfigValues (line 263) | function assignConfigValues(originalConfig, rcConfig) {
  function rewriteHotkey (line 307) | function rewriteHotkey(config) {

FILE: browser/main/lib/ZoomManager.js
  function _init (line 8) | function _init() {
  function _saveZoom (line 12) | function _saveZoom(zoomFactor) {
  function setZoom (line 16) | function setZoom(zoomFactor, noSave = false) {
  function getZoom (line 21) | function getZoom() {

FILE: browser/main/lib/dataApi/addStorage.js
  constant CSON (line 7) | const CSON = require('@rokt33r/season')
  function addStorage (line 19) | function addStorage(input) {

FILE: browser/main/lib/dataApi/attachmentManagement.js
  constant STORAGE_FOLDER_PLACEHOLDER (line 13) | const STORAGE_FOLDER_PLACEHOLDER = ':storage'
  constant DESTINATION_FOLDER (line 14) | const DESTINATION_FOLDER = 'attachments'
  constant PATH_SEPARATORS (line 15) | const PATH_SEPARATORS =
  function getImage (line 23) | function getImage(file) {
  function getOrientation (line 59) | function getOrientation(file) {
  function fixRotate (line 112) | function fixRotate(file) {
  function copyAttachment (line 166) | function copyAttachment(
  function createAttachmentDestinationFolder (line 238) | function createAttachmentDestinationFolder(destinationStoragePath, noteK...
  function migrateAttachments (line 259) | function migrateAttachments(markdownContent, storagePath, noteKey) {
  function fixLocalURLS (line 295) | function fixLocalURLS(renderedHTML, storagePath) {
  function generateAttachmentMarkdown (line 329) | function generateAttachmentMarkdown(fileName, path, showPreview) {
  function handleAttachmentDrop (line 341) | function handleAttachmentDrop(codeEditor, storageKey, noteKey, dropEvent) {
  function handlePasteImageEvent (line 459) | function handlePasteImageEvent(
  function handlePasteNativeImage (line 520) | function handlePasteNativeImage(codeEditor, storageKey, noteKey, image) {
  function getAttachmentsInMarkdownContent (line 568) | function getAttachmentsInMarkdownContent(markdownContent) {
  function getAbsolutePathsOfAttachmentsInContent (line 595) | function getAbsolutePathsOfAttachmentsInContent(markdownContent, storage...
  function importAttachments (line 616) | function importAttachments(markDownContent, filepath, storageKey, noteKe...
  function moveAttachments (line 675) | function moveAttachments(oldPath, newPath, noteKey, newNoteKey, noteCont...
  function replaceNoteKeyWithNewNoteKey (line 691) | function replaceNoteKeyWithNewNoteKey(noteContent, oldNoteKey, newNoteKe...
  function replaceStorageReferences (line 715) | function replaceStorageReferences(input, noteKey, destinationFolder) {
  function deleteAttachmentFolder (line 750) | function deleteAttachmentFolder(storageKey, noteKey) {
  function deleteAttachmentsNotPresentInNote (line 766) | function deleteAttachmentsNotPresentInNote(
  function getAttachmentsPathAndStatus (line 841) | function getAttachmentsPathAndStatus(markdownContent, storageKey, noteKe...
  function removeAttachmentsByPaths (line 914) | function removeAttachmentsByPaths(attachments) {
  function cloneAttachments (line 939) | function cloneAttachments(oldNote, newNote) {
  function generateFileNotFoundMarkdown (line 979) | function generateFileNotFoundMarkdown() {
  function isAttachmentLink (line 994) | function isAttachmentLink(text) {
  function handleAttachmentLinkPaste (line 1021) | function handleAttachmentLinkPaste(storageKey, noteKey, linkText) {

FILE: browser/main/lib/dataApi/copyFile.js
  function copyFile (line 11) | function copyFile(srcPath, dstPath) {

FILE: browser/main/lib/dataApi/createFolder.js
  constant CSON (line 5) | const CSON = require('@rokt33r/season')
  function createFolder (line 25) | function createFolder(storageKey, input) {

FILE: browser/main/lib/dataApi/createNote.js
  constant CSON (line 6) | const CSON = require('@rokt33r/season')
  function validateInput (line 9) | function validateInput(input) {
  function createNote (line 43) | function createNote(storageKey, input) {

FILE: browser/main/lib/dataApi/createNoteFromUrl.js
  function validateUrl (line 9) | function validateUrl(str) {
  constant ERROR_MESSAGES (line 21) | const ERROR_MESSAGES = {
  function createNoteFromUrl (line 29) | function createNoteFromUrl(

FILE: browser/main/lib/dataApi/createSnippet.js
  function createSnippet (line 6) | function createSnippet(snippetFile) {

FILE: browser/main/lib/dataApi/deleteFolder.js
  constant CSON (line 5) | const CSON = require('@rokt33r/season')
  function deleteFolder (line 21) | function deleteFolder(storageKey, folderKey) {

FILE: browser/main/lib/dataApi/deleteNote.js
  function deleteNote (line 7) | function deleteNote(storageKey, noteKey) {

FILE: browser/main/lib/dataApi/deleteSnippet.js
  function deleteSnippet (line 5) | function deleteSnippet(snippet, snippetFile) {

FILE: browser/main/lib/dataApi/exportFolder.js
  function exportFolder (line 26) | function exportFolder(storageKey, folderKey, fileType, exportDir, config) {

FILE: browser/main/lib/dataApi/exportNote.js
  function exportNote (line 19) | function exportNote(storageKey, note, targetPath, outputFormatter) {
  function prepareTasks (line 49) | function prepareTasks(tasks, storagePath, targetPath) {
  function saveToFile (line 63) | function saveToFile(data, filename) {
  function rollbackExport (line 77) | function rollbackExport(tasks) {

FILE: browser/main/lib/dataApi/exportNoteAs.js
  function exportNoteAs (line 12) | function exportNoteAs(note, filename, fileType, config) {

FILE: browser/main/lib/dataApi/exportStorage.js
  function exportStorage (line 27) | function exportStorage(storageKey, fileType, exportDir, config) {

FILE: browser/main/lib/dataApi/exportTag.js
  function exportTag (line 12) | function exportTag(data, tag, fileType, exportDir, config) {

FILE: browser/main/lib/dataApi/fetchSnippet.js
  function fetchSnippet (line 4) | function fetchSnippet(id, snippetFile) {

FILE: browser/main/lib/dataApi/formatHTML.js
  constant CSS_FILES (line 21) | const CSS_FILES = [
  function unprefix (line 44) | function unprefix(file) {
  function formatHTML (line 73) | function formatHTML(props) {
  function getStyleParams (line 528) | function getStyleParams(props) {
  function getCodeThemeLink (line 572) | function getCodeThemeLink(name) {
  function buildStyle (line 580) | function buildStyle(
  function escapeHtmlCharactersInCodeTag (line 777) | function escapeHtmlCharactersInCodeTag(splitWithCodeTag) {

FILE: browser/main/lib/dataApi/formatMarkdown.js
  function formatMarkdown (line 15) | function formatMarkdown(props) {
  function getFrontMatter (line 79) | function getFrontMatter(markdown) {
  function replaceFrontMatter (line 92) | function replaceFrontMatter(markdown, metadata) {

FILE: browser/main/lib/dataApi/formatPDF.js
  function formatPDF (line 4) | function formatPDF(props) {

FILE: browser/main/lib/dataApi/getContentFormatter.js
  function getContentFormatter (line 11) | function getContentFormatter(storage, fileType, config) {

FILE: browser/main/lib/dataApi/getFilename.js
  function getFilename (line 14) | function getFilename(note, fileType, directory, deduplicator) {

FILE: browser/main/lib/dataApi/init.js
  constant CSON (line 8) | const CSON = require('@rokt33r/season')
  function init (line 24) | function init() {

FILE: browser/main/lib/dataApi/migrateFromV5Storage.js
  constant CSON (line 5) | const CSON = require('@rokt33r/season')
  function migrateFromV5Storage (line 9) | function migrateFromV5Storage(storageKey, data) {
  function importAll (line 26) | function importAll(storage, data) {

FILE: browser/main/lib/dataApi/migrateFromV6Storage.js
  constant CSON (line 5) | const CSON = require('@rokt33r/season')
  function migrateFromV5Storage (line 7) | function migrateFromV5Storage(storagePath) {

FILE: browser/main/lib/dataApi/moveNote.js
  constant CSON (line 4) | const CSON = require('@rokt33r/season')
  function moveNote (line 10) | function moveNote(storageKey, noteKey, newStorageKey, newFolderKey) {

FILE: browser/main/lib/dataApi/removeStorage.js
  function removeStorage (line 7) | function removeStorage(key) {

FILE: browser/main/lib/dataApi/renameStorage.js
  function renameStorage (line 9) | function renameStorage(key, name) {

FILE: browser/main/lib/dataApi/reorderFolder.js
  constant CSON (line 5) | const CSON = require('@rokt33r/season')
  function reorderFolder (line 20) | function reorderFolder(storageKey, oldIndex, newIndex) {

FILE: browser/main/lib/dataApi/resolveStorageData.js
  constant CSON (line 3) | const CSON = require('@rokt33r/season')
  function resolveStorageData (line 6) | function resolveStorageData(storageCache) {

FILE: browser/main/lib/dataApi/resolveStorageNotes.js
  constant CSON (line 3) | const CSON = require('@rokt33r/season')
  function resolveStorageNotes (line 5) | function resolveStorageNotes(storage) {

FILE: browser/main/lib/dataApi/toggleStorage.js
  function toggleStorage (line 9) | function toggleStorage(key, isOpen) {

FILE: browser/main/lib/dataApi/updateFolder.js
  constant CSON (line 4) | const CSON = require('@rokt33r/season')
  function updateFolder (line 25) | function updateFolder(storageKey, folderKey, input) {

FILE: browser/main/lib/dataApi/updateNote.js
  constant CSON (line 4) | const CSON = require('@rokt33r/season')
  function validateInput (line 7) | function validateInput(input) {
  function updateNote (line 82) | function updateNote(storageKey, noteKey, input) {

FILE: browser/main/lib/dataApi/updateSnippet.js
  function updateSnippet (line 4) | function updateSnippet(snippet, snippetFile) {

FILE: browser/main/lib/eventEmitter.js
  function on (line 4) | function on(name, listener) {
  function off (line 8) | function off(name, listener) {
  function once (line 12) | function once(name, listener) {
  function emit (line 16) | function emit(name, ...args) {

FILE: browser/main/lib/modal.js
  class ModalBase (line 6) | class ModalBase extends React.Component {
    method constructor (line 7) | constructor(props) {
    method close (line 16) | close() {
    method render (line 30) | render() {
  function openModal (line 51) | function openModal(component, props) {
  function closeModal (line 68) | function closeModal() {
  function isModalOpen (line 75) | function isModalOpen() {

FILE: browser/main/lib/notify.js
  function notify (line 3) | function notify(title, options) {

FILE: browser/main/lib/shortcutManager.js
  function updateShortcut (line 18) | function updateShortcut(newHotkey) {
  function formatShortcut (line 24) | function formatShortcut(shortcut) {
  function applyShortcuts (line 28) | function applyShortcuts(shortcuts) {

FILE: browser/main/modals/CreateFolderModal.js
  class CreateFolderModal (line 12) | class CreateFolderModal extends React.Component {
    method constructor (line 13) | constructor(props) {
    method componentDidMount (line 21) | componentDidMount() {
    method handleCloseButtonClick (line 26) | handleCloseButtonClick(e) {
    method handleChange (line 30) | handleChange(e) {
    method handleKeyDown (line 36) | handleKeyDown(e) {
    method handleInputKeyDown (line 42) | handleInputKeyDown(e) {
    method handleConfirmButtonClick (line 49) | handleConfirmButtonClick(e) {
    method confirm (line 53) | confirm() {
    method render (line 77) | render() {

FILE: browser/main/modals/CreateMarkdownFromURLModal.js
  class CreateMarkdownFromURLModal (line 9) | class CreateMarkdownFromURLModal extends React.Component {
    method constructor (line 10) | constructor(props) {
    method componentDidMount (line 20) | componentDidMount() {
    method handleCloseButtonClick (line 25) | handleCloseButtonClick(e) {
    method handleChange (line 29) | handleChange(e) {
    method handleKeyDown (line 35) | handleKeyDown(e) {
    method handleInputKeyDown (line 41) | handleInputKeyDown(e) {
    method handleConfirmButtonClick (line 48) | handleConfirmButtonClick(e) {
    method showError (line 52) | showError(message) {
    method hideError (line 59) | hideError() {
    method confirm (line 66) | confirm() {
    method render (line 80) | render() {

FILE: browser/main/modals/NewNoteModal.js
  class NewNoteModal (line 11) | class NewNoteModal extends React.Component {
    method constructor (line 12) | constructor(props) {
    method componentDidMount (line 18) | componentDidMount() {
    method handleCloseButtonClick (line 22) | handleCloseButtonClick(e) {
    method handleCreateMarkdownFromUrlClick (line 26) | handleCreateMarkdownFromUrlClick(e) {
    method handleMarkdownNoteButtonClick (line 38) | handleMarkdownNoteButtonClick(e) {
    method handleMarkdownNoteButtonKeyDown (line 56) | handleMarkdownNoteButtonKeyDown(e) {
    method handleSnippetNoteButtonClick (line 63) | handleSnippetNoteButtonClick(e) {
    method handleSnippetNoteButtonKeyDown (line 81) | handleSnippetNoteButtonKeyDown(e) {
    method handleKeyDown (line 88) | handleKeyDown(e) {
    method render (line 94) | render() {

FILE: browser/main/modals/PreferencesModal/Blog.js
  class Blog (line 13) | class Blog extends React.Component {
    method constructor (line 14) | constructor(props) {
    method handleLinkClick (line 23) | handleLinkClick(e) {
    method clearMessage (line 28) | clearMessage() {
    method componentDidMount (line 36) | componentDidMount() {
    method handleBlogChange (line 59) | handleBlogChange(e) {
    method handleSaveButtonClick (line 89) | handleSaveButtonClick(e) {
    method render (line 104) | render() {

FILE: browser/main/modals/PreferencesModal/Crowdfunding.js
  class Crowdfunding (line 9) | class Crowdfunding extends React.Component {
    method constructor (line 10) | constructor(props) {
    method handleLinkClick (line 16) | handleLinkClick(e) {
    method render (line 21) | render() {

FILE: browser/main/modals/PreferencesModal/ExportTab.js
  class ExportTab (line 13) | class ExportTab extends React.Component {
    method constructor (line 14) | constructor(props) {
    method clearMessage (line 22) | clearMessage() {
    method componentDidMount (line 30) | componentDidMount() {
    method componentWillUnmount (line 55) | componentWillUnmount() {
    method handleSaveButtonClick (line 60) | handleSaveButtonClick(e) {
    method handleExportChange (line 76) | handleExportChange(e) {
    method render (line 102) | render() {

FILE: browser/main/modals/PreferencesModal/FolderItem.js
  class FolderItem (line 12) | class FolderItem extends React.Component {
    method constructor (line 13) | constructor(props) {
    method handleEditChange (line 27) | handleEditChange(e) {
    method handleConfirmButtonClick (line 36) | handleConfirmButtonClick(e) {
    method confirm (line 40) | confirm() {
    method handleColorButtonClick (line 58) | handleColorButtonClick(e) {
    method handleColorChange (line 80) | handleColorChange(color) {
    method handleColorPickerClose (line 85) | handleColorPickerClose(event) {
    method handleCancelButtonClick (line 92) | handleCancelButtonClick(e) {
    method handleFolderItemBlur (line 98) | handleFolderItemBlur(e) {
    method renderEdit (line 109) | renderEdit(e) {
    method handleDeleteConfirmButtonClick (line 184) | handleDeleteConfirmButtonClick(e) {
    method renderDelete (line 195) | renderDelete() {
    method handleEditButtonClick (line 221) | handleEditButtonClick(e) {
    method handleDeleteButtonClick (line 236) | handleDeleteButtonClick(e) {
    method renderIdle (line 242) | renderIdle() {
    method render (line 271) | render() {
  class Handle (line 303) | class Handle extends React.Component {
    method render (line 304) | render() {
  class SortableFolderItemComponent (line 313) | class SortableFolderItemComponent extends React.Component {
    method render (line 314) | render() {

FILE: browser/main/modals/PreferencesModal/FolderList.js
  class FolderList (line 11) | class FolderList extends React.Component {
    method render (line 12) | render() {
  class SortableFolderListComponent (line 58) | class SortableFolderListComponent extends React.Component {
    method constructor (line 59) | constructor(props) {
    method render (line 73) | render() {

FILE: browser/main/modals/PreferencesModal/HotkeyTab.js
  class HotkeyTab (line 13) | class HotkeyTab extends React.Component {
    method constructor (line 14) | constructor(props) {
    method componentDidMount (line 23) | componentDidMount() {
    method componentWillUnmount (line 59) | componentWillUnmount() {
    method handleSaveButtonClick (line 64) | handleSaveButtonClick(e) {
    method handleHintToggleButtonClick (line 79) | handleHintToggleButtonClick(e) {
    method handleHotkeyChange (line 85) | handleHotkeyChange(e) {
    method clearMessage (line 112) | clearMessage() {
    method render (line 120) | render() {

FILE: browser/main/modals/PreferencesModal/InfoTab.js
  class InfoTab (line 14) | class InfoTab extends React.Component {
    method constructor (line 15) | constructor(props) {
    method componentDidMount (line 26) | componentDidMount() {
    method handleLinkClick (line 32) | handleLinkClick(e) {
    method handleConfigChange (line 37) | handleConfigChange(e) {
    method handleSubscriptionFormSubmit (line 47) | handleSubscriptionFormSubmit(e) {
    method handleSubscriptionFormEmailChange (line 83) | handleSubscriptionFormEmailChange(e) {
    method handleSaveButtonClick (line 89) | handleSaveButtonClick(e) {
    method infoMessage (line 117) | infoMessage() {
    method handleAutoUpdateChange (line 122) | handleAutoUpdateChange() {
    method render (line 128) | render() {

FILE: browser/main/modals/PreferencesModal/PluginsTab.js
  class PluginsTab (line 14) | class PluginsTab extends React.Component {
    method constructor (line 15) | constructor(props) {
    method componentDidMount (line 23) | componentDidMount() {
    method componentWillUnmount (line 46) | componentWillUnmount() {
    method checkWakatimePluginRequirement (line 51) | checkWakatimePluginRequirement() {
    method handleSaveButtonClick (line 77) | handleSaveButtonClick(e) {
    method handleIsWakatimePluginActiveChange (line 96) | handleIsWakatimePluginActiveChange(e) {
    method handleWakatimeKeyChange (line 113) | handleWakatimeKeyChange(e) {
    method clearMessage (line 133) | clearMessage() {
    method render (line 141) | render() {

FILE: browser/main/modals/PreferencesModal/SnippetEditor.js
  class SnippetEditor (line 20) | class SnippetEditor extends React.Component {
    method componentDidMount (line 21) | componentDidMount() {
    method componentWillUnmount (line 66) | componentWillUnmount() {
    method onSnippetChanged (line 70) | onSnippetChanged(newSnippet) {
    method onSnippetNameOrPrefixChanged (line 75) | onSnippetNameOrPrefixChanged(newSnippet) {
    method saveSnippet (line 84) | saveSnippet() {
    method render (line 93) | render() {

FILE: browser/main/modals/PreferencesModal/SnippetList.js
  class SnippetList (line 9) | class SnippetList extends React.Component {
    method constructor (line 10) | constructor(props) {
    method componentDidMount (line 17) | componentDidMount() {
    method reloadSnippetList (line 22) | reloadSnippetList() {
    method handleSnippetContextMenu (line 29) | handleSnippetContextMenu(snippet) {
    method deleteSnippet (line 38) | deleteSnippet(snippet) {
    method handleSnippetClick (line 50) | handleSnippetClick(snippet) {
    method createSnippet (line 54) | createSnippet() {
    method defineSnippetStyleName (line 68) | defineSnippetStyleName(snippet) {
    method render (line 82) | render() {

FILE: browser/main/modals/PreferencesModal/SnippetTab.js
  class SnippetTab (line 13) | class SnippetTab extends React.Component {
    method constructor (line 14) | constructor(props) {
    method notify (line 22) | notify(title, options) {
    method handleSnippetNameOrPrefixChange (line 33) | handleSnippetNameOrPrefixChange() {
    method handleSnippetSelect (line 42) | handleSnippetSelect(snippet) {
    method onSnippetNameOrPrefixChanged (line 55) | onSnippetNameOrPrefixChanged(e, type) {
    method handleDeleteSnippet (line 66) | handleDeleteSnippet(snippet) {
    method handleCopySnippet (line 73) | handleCopySnippet(e) {
    method render (line 84) | render() {

FILE: browser/main/modals/PreferencesModal/StorageItem.js
  class StorageItem (line 14) | class StorageItem extends React.Component {
    method constructor (line 15) | constructor(props) {
    method handleNewFolderButtonClick (line 23) | handleNewFolderButtonClick(e) {
    method handleExternalButtonClick (line 43) | handleExternalButtonClick() {
    method handleUnlinkButtonClick (line 48) | handleUnlinkButtonClick(e) {
    method handleLabelClick (line 74) | handleLabelClick(e) {
    method handleLabelChange (line 86) | handleLabelChange(e) {
    method handleLabelBlur (line 92) | handleLabelBlur(e) {
    method render (line 105) | render() {

FILE: browser/main/modals/PreferencesModal/StoragesTab.js
  function browseFolder (line 15) | function browseFolder() {
  class StoragesTab (line 34) | class StoragesTab extends React.Component {
    method constructor (line 35) | constructor(props) {
    method loadAttachmentStorage (line 50) | loadAttachmentStorage() {
    method handleAddStorageButton (line 69) | handleAddStorageButton(e) {
    method handleLinkClick (line 85) | handleLinkClick(e) {
    method handleRemoveUnusedAttachments (line 90) | handleRemoveUnusedAttachments(attachments) {
    method renderList (line 97) | renderList() {
    method handleAddStorageBrowseButtonClick (line 183) | handleAddStorageBrowseButtonClick(e) {
    method handleAddStorageChange (line 200) | handleAddStorageChange(e) {
    method handleAddStorageCreateButton (line 209) | handleAddStorageCreateButton(e) {
    method handleAddStorageCancelButton (line 228) | handleAddStorageCancelButton(e) {
    method renderAddStorage (line 234) | renderAddStorage() {
    method renderContent (line 318) | renderContent() {
    method render (line 329) | render() {

FILE: browser/main/modals/PreferencesModal/UiTab.js
  constant OSX (line 18) | const OSX = global.process.platform === 'darwin'
  class UiTab (line 23) | class UiTab extends React.Component {
    method constructor (line 24) | constructor(props) {
    method componentDidMount (line 32) | componentDidMount() {
    method componentWillUnmount (line 69) | componentWillUnmount() {
    method handleUIChange (line 74) | handleUIChange(e) {
    method handleSaveUIClick (line 199) | handleSaveUIClick(e) {
    method clearMessage (line 219) | clearMessage() {
    method formatTime (line 227) | formatTime(time) {
    method render (line 242) | render() {

FILE: browser/main/modals/PreferencesModal/index.js
  class Preferences (line 20) | class Preferences extends React.Component {
    method constructor (line 21) | constructor(props) {
    method componentDidMount (line 33) | componentDidMount() {
    method switchTeam (line 39) | switchTeam(teamId) {
    method handleNavButtonClick (line 43) | handleNavButtonClick(tab) {
    method handleEscButtonClick (line 49) | handleEscButtonClick() {
    method renderContent (line 53) | renderContent() {
    method handleKeyDown (line 117) | handleKeyDown(e) {
    method getContentBoundingBox (line 123) | getContentBoundingBox() {
    method haveToSaveNotif (line 127) | haveToSaveNotif(type, message) {
    method render (line 131) | render() {

FILE: browser/main/modals/RenameFolderModal.js
  class RenameFolderModal (line 10) | class RenameFolderModal extends React.Component {
    method constructor (line 11) | constructor(props) {
    method componentDidMount (line 19) | componentDidMount() {
    method handleCloseButtonClick (line 24) | handleCloseButtonClick(e) {
    method handleChange (line 28) | handleChange(e) {
    method handleKeyDown (line 34) | handleKeyDown(e) {
    method handleInputKeyDown (line 40) | handleInputKeyDown(e) {
    method handleConfirmButtonClick (line 47) | handleConfirmButtonClick(e) {
    method confirm (line 51) | confirm() {
    method render (line 69) | render() {

FILE: browser/main/modals/RenameTagModal.js
  class RenameTagModal (line 16) | class RenameTagModal extends React.Component {
    method constructor (line 17) | constructor(props) {
    method componentDidMount (line 34) | componentDidMount() {
    method handleChange (line 39) | handleChange(e) {
    method handleKeyDown (line 47) | handleKeyDown(e) {
    method handleInputKeyDown (line 53) | handleInputKeyDown(e) {
    method handleConfirm (line 60) | handleConfirm() {
    method showError (line 67) | showError(message) {
    method renameTag (line 74) | renameTag(tag, updatedTag) {
    method render (line 147) | render() {

FILE: browser/main/store.js
  function defaultDataMap (line 9) | function defaultDataMap() {
  function data (line 21) | function data(state = defaultDataMap(), action) {
  function config (line 367) | function config(state = defaultConfig, action) {
  function status (line 393) | function status(state = defaultStatus, action) {
  function updateStarredChange (line 403) | function updateStarredChange(oldNote, note, state, uniqueKey) {
  function updateFolderChange (line 414) | function updateFolderChange(oldNote, note, state, folderKey, uniqueKey) {
  function updateTagChanges (line 432) | function updateTagChanges(oldNote, note, state, uniqueKey) {
  function assignToTags (line 441) | function assignToTags(tags, state, uniqueKey) {
  function removeFromTags (line 449) | function removeFromTags(tags, state, uniqueKey) {
  function getOrInitItem (line 461) | function getOrInitItem(target, key) {

FILE: dev-scripts/dev.js
  function startServer (line 18) | function startServer() {
  function startElectron (line 51) | function startElectron() {

FILE: extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js
  function incrementRemainingMarkdownListNumbers (line 55) | function incrementRemainingMarkdownListNumbers(cm, pos) {

FILE: extra_scripts/codemirror/addon/edit/closebrackets.js
  function getOption (line 33) | function getOption(conf, name) {
  function ensureBound (line 40) | function ensureBound(chars) {
  function handler (line 48) | function handler(ch) {
  function getConfig (line 52) | function getConfig(cm) {
  function handleBackspace (line 64) | function handleBackspace(cm) {
  function handleEnter (line 81) | function handleEnter(cm) {
  function contractSelection (line 105) | function contractSelection(sel) {
  function handleChar (line 111) | function handleChar(cm, ch) {
  function charsAround (line 185) | function charsAround(cm, pos) {
  function stringStartsAfter (line 191) | function stringStartsAfter(cm, pos) {

FILE: extra_scripts/codemirror/addon/hyperlink/hyperlink.js
  class HyperLink (line 27) | class HyperLink {
    method constructor (line 28) | constructor(cm) {
    method getUrl (line 64) | getUrl(el) {
    method specialLinkHandler (line 86) | specialLinkHandler(e, rawHref, linkHash) {
    method onMouseDown (line 140) | onMouseDown(e) {
    method onMouseEnter (line 176) | onMouseEnter(e) {
    method onMouseLeave (line 193) | onMouseLeave(e) {
    method onMouseMove (line 203) | onMouseMove(e) {
    method showInfo (line 212) | showInfo(relatedTo) {

FILE: extra_scripts/codemirror/mode/bfm/bfm.js
  function getMode (line 23) | function getMode(name, params, config, cm) {

FILE: extra_scripts/codemirror/mode/gfm/gfm.js
  function blankLine (line 30) | function blankLine(state) {

FILE: gruntfile.js
  constant WIN (line 6) | const WIN = process.platform === 'win32'
  function getTarget (line 244) | function getTarget() {
  function generateRule (line 303) | function generateRule(selector, bgColor, fgColor) {

FILE: index.js
  function execMainApp (line 7) | function execMainApp() {

FILE: lib/ipcServer.js
  function toggleMainWindow (line 10) | function toggleMainWindow() {

FILE: lib/main-app.js
  function checkUpdate (line 40) | function checkUpdate(manualTriggered = false) {

FILE: lib/main-menu.js
  constant LINUX (line 10) | const LINUX = process.platform === 'linux'
  method click (line 26) | click() {
  method click (line 63) | click() {
  method click (line 83) | click() {
  method click (line 90) | click() {
  method click (line 97) | click() {
  method click (line 104) | click() {
  method click (line 116) | click() {
  method click (line 127) | click() {
  method click (line 134) | click() {
  method click (line 141) | click() {
  method click (line 148) | click() {
  method click (line 161) | click() {
  method click (line 167) | click() {
  method click (line 177) | click() {
  method click (line 187) | click() {
  method click (line 205) | click() {
  method click (line 261) | click() {
  method click (line 274) | click() {
  method click (line 281) | click() {
  method click (line 291) | click() {
  method click (line 298) | click() {
  method click (line 308) | click() {
  method click (line 318) | click() {
  method click (line 325) | click() {
  method click (line 331) | click() {
  method click (line 341) | click() {
  method click (line 348) | click() {
  method click (line 355) | click() {
  method click (line 408) | click() {
  method click (line 414) | click() {
  method click (line 420) | click() {
  method click (line 426) | click() {
  method click (line 435) | click() {
  method click (line 443) | click() {
  method click (line 449) | click() {
  method click (line 455) | click() {
  method click (line 468) | click() {

FILE: lib/main-window.js
  function storeWindowSize (line 102) | function storeWindowSize() {

FILE: tests/dataApi/addStorage.js
  constant CSON (line 18) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/createFolder.test.js
  constant CSON (line 17) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/createNote.test.js
  constant CSON (line 16) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/createNoteFromUrl.test.js
  constant CSON (line 16) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/deleteFolder.test.js
  constant CSON (line 21) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/deleteNote-test.js
  constant CSON (line 18) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/migrateFromV6Storage-test.js
  constant CSON (line 16) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/moveNote-test.js
  constant CSON (line 17) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/reorderFolder-test.js
  constant CSON (line 18) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/updateFolder-test.js
  constant CSON (line 18) | const CSON = require('@rokt33r/season')

FILE: tests/dataApi/updateNote-test.js
  constant CSON (line 18) | const CSON = require('@rokt33r/season')

FILE: tests/fixtures/TestDummy.js
  constant CSON (line 5) | const CSON = require('@rokt33r/season')
  function dummyFolder (line 8) | function dummyFolder(override = {}) {
  function dummyBoostnoteJSONData (line 20) | function dummyBoostnoteJSONData(override = {}, isLegacy = false) {
  function dummyNote (line 46) | function dummyNote(override = {}) {
  function dummyStorage (line 100) | function dummyStorage(storagePath, override = {}) {
  function dummyLegacyStorage (line 144) | function dummyLegacyStorage(storagePath, override = {}) {

FILE: tests/helpers/setup-browser-env.js
  method getItem (line 24) | getItem() {

FILE: tests/lib/markdown-toc-generator.test.js
  constant EOL (line 8) | const EOL = require('os').EOL

FILE: tests/lib/rc-parser.test.js
  function filePath (line 62) | function filePath(filename) {

FILE: tests/lib/themeManager.test.js
  method constructor (line 17) | constructor() {
Condensed preview — 367 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,851K chars).
[
  {
    "path": ".babelrc",
    "chars": 291,
    "preview": "{\n  \"presets\": [\"react\", \"es2015\"],\n  \"env\": {\n    \"development\": {\n      \"presets\": [\"react-hmre\"]\n    },\n    \"test\": {"
  },
  {
    "path": ".boostnoterc.sample",
    "chars": 791,
    "preview": "{\n    \"amaEnabled\": true,\n    \"editor\": {\n        \"fontFamily\": \"Monaco, Consolas\",\n        \"fontSize\": \"14\",\n        \"i"
  },
  {
    "path": ".editorconfig",
    "chars": 385,
    "preview": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Space indentation\n[*]\ni"
  },
  {
    "path": ".eslintignore",
    "chars": 44,
    "preview": "node_modules/\ncompiled/\ndist/\nextra_scripts/"
  },
  {
    "path": ".eslintrc",
    "chars": 700,
    "preview": "{\n  \"extends\": [\"standard\", \"standard-jsx\", \"plugin:react/recommended\", \"prettier\"],\n  \"plugins\": [\"react\", \"prettier\"],"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 29,
    "preview": "issuehunt: BoostIo/Boostnote\n"
  },
  {
    "path": ".gitignore",
    "chars": 146,
    "preview": ".DS_Store\n.env\nDesktop.ini\nThumbs.db\nnode_modules/*\n!node_modules/boost\n/dist\n/compiled\n/secret\n*.log\n.idea\n.vscode\npack"
  },
  {
    "path": ".prettierrc",
    "chars": 68,
    "preview": "{\n  \"singleQuote\": true,\n  \"semi\": false,\n  \"jsxSingleQuote\": true\n}"
  },
  {
    "path": ".travis.yml",
    "chars": 692,
    "preview": "language: node_js\nnode_js:\n  - 8\nscript:\n  - npm run lint && npm run test\n  - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAV"
  },
  {
    "path": "FAQ.md",
    "chars": 813,
    "preview": "# Frequently Asked Questions\n\n\n<details><summary>Allowing dangerous HTML tags</summary>\n\nSometimes it is useful to allow"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "chars": 727,
    "preview": "# Current behavior\n\n<!--\nLet us know what is currently happening.\n\nPlease include some **screenshots** with the **develo"
  },
  {
    "path": "LICENSE",
    "chars": 736,
    "preview": "GPL-3.0\n\nBoostnote - an open source note-taking app made for programmers just like you.\n\nCopyright (C) 2017 - 2019 Boost"
  },
  {
    "path": "PULL_REQUEST_TEMPLATE.md",
    "chars": 1433,
    "preview": "<!--\nBefore submitting this PR, please make sure that:\n- You have read and understand the contributing.md\n- You have che"
  },
  {
    "path": "__mocks__/electron.js",
    "chars": 187,
    "preview": "module.exports = {\n  require: jest.genMockFunction(),\n  match: jest.genMockFunction(),\n  app: jest.genMockFunction(),\n  "
  },
  {
    "path": "appdmg.json",
    "chars": 314,
    "preview": "{\n  \"title\": \"Boostnote\",\n  \"icon\": \"resources/dmg.icns\",\n  \"background\": \"resources/boostnote-install.png\",\n  \"icon-siz"
  },
  {
    "path": "browser/components/CodeEditor.js",
    "chars": 42138,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport _ from 'lodash'\nimport CodeMirror from 'codemirror'\n"
  },
  {
    "path": "browser/components/CodeEditor.styl",
    "chars": 89,
    "preview": ".codeEditor-typo\n  text-decoration underline wavy red\n\n.spellcheck-select\n  border: none\n"
  },
  {
    "path": "browser/components/ColorPicker.js",
    "chars": 1939,
    "preview": "import React from 'react'\nimport PropTypes from 'prop-types'\nimport { SketchPicker } from 'react-color'\nimport CSSModule"
  },
  {
    "path": "browser/components/ColorPicker.styl",
    "chars": 620,
    "preview": ".colorPicker\n  position fixed\n  z-index 2\n  display flex\n  flex-direction column\n\n.cover\n  position fixed\n  top 0\n  righ"
  },
  {
    "path": "browser/components/MarkdownEditor.js",
    "chars": 13122,
    "preview": "/* eslint-disable camelcase */\nimport PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'brow"
  },
  {
    "path": "browser/components/MarkdownEditor.styl",
    "chars": 345,
    "preview": ".root\n  position relative\n\n.codeEditor\n  absolute top bottom left right\n\n.hide\n  z-index 0\n  opacity 0\n  pointer-events "
  },
  {
    "path": "browser/components/MarkdownPreview.js",
    "chars": 27589,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { connect } from 'react-redux'\nimport Markdown from "
  },
  {
    "path": "browser/components/MarkdownSplitEditor.js",
    "chars": 14501,
    "preview": "import React from 'react'\nimport CodeEditor from 'browser/components/CodeEditor'\nimport MarkdownPreview from 'browser/co"
  },
  {
    "path": "browser/components/MarkdownSplitEditor.styl",
    "chars": 705,
    "preview": ".root\n  width 100%\n  height 100%\n  font-size 30px\n  display flex\n  flex-wrap wrap\n  .slider\n    absolute top bottom\n    "
  },
  {
    "path": "browser/components/ModalEscButton.js",
    "chars": 476,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/components/ModalEscButton.styl",
    "chars": 279,
    "preview": ".escButton\n  height 50px\n  position absolute\n  background-color transparent\n  color $ui-inactive-text-color\n  border non"
  },
  {
    "path": "browser/components/NavToggleButton.js",
    "chars": 776,
    "preview": "/**\n * @fileoverview Micro component for toggle SideNav\n */\nimport PropTypes from 'prop-types'\nimport React from 'react'"
  },
  {
    "path": "browser/components/NavToggleButton.styl",
    "chars": 745,
    "preview": ".navToggle\n  navButtonColor()\n  display block\n  position absolute\n  left 5px\n  bottom 5px\n  border-radius 16.5px\n  heigh"
  },
  {
    "path": "browser/components/NoteItem.js",
    "chars": 5052,
    "preview": "/**\n * @fileoverview Note item component.\n */\nimport PropTypes from 'prop-types'\nimport React from 'react'\nimport { isAr"
  },
  {
    "path": "browser/components/NoteItem.styl",
    "chars": 10897,
    "preview": "$control-height = 30px\n\n.root\n  absolute left bottom\n  top $topBar-height - 1\n  background-color $ui-noteList-background"
  },
  {
    "path": "browser/components/NoteItemSimple.js",
    "chars": 2201,
    "preview": "/**\n * @fileoverview Note item component with simple display mode.\n */\nimport PropTypes from 'prop-types'\nimport React f"
  },
  {
    "path": "browser/components/NoteItemSimple.styl",
    "chars": 9001,
    "preview": "$control-height = 30px\n\n.root\n  absolute left bottom\n  top $topBar-height - 1\n  background-color $ui-noteList-background"
  },
  {
    "path": "browser/components/RealtimeNotification.js",
    "chars": 1382,
    "preview": "import React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styles from './RealtimeNotification.sty"
  },
  {
    "path": "browser/components/RealtimeNotification.styl",
    "chars": 1031,
    "preview": ".notification-area\n  z-index 1000\n  font-size 12px\n  position: relative\n  top: 12px\n  background-color none\n\n.notificati"
  },
  {
    "path": "browser/components/SideNavFilter.js",
    "chars": 2817,
    "preview": "/**\n * @fileoverview Filter for all notes.\n */\nimport PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSMo"
  },
  {
    "path": "browser/components/SideNavFilter.styl",
    "chars": 6396,
    "preview": ".menu\n  margin-bottom 20px\n\n.menu-button\n  navButtonColor()\n  height 36px\n  padding 0 15px 0 20px\n  font-size 14px\n  wid"
  },
  {
    "path": "browser/components/SnippetTab.js",
    "chars": 3180,
    "preview": "import React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styles from './SnippetTab.styl'\nimport "
  },
  {
    "path": "browser/components/SnippetTab.styl",
    "chars": 3471,
    "preview": ".root\n  position relative\n  flex 1\n  min-width 70px\n  overflow hidden\n  border-left 1px solid $ui-borderColor\n  border-t"
  },
  {
    "path": "browser/components/StorageItem.js",
    "chars": 2853,
    "preview": "/**\n * @fileoverview Micro component for showing storage.\n */\nimport PropTypes from 'prop-types'\nimport React from 'reac"
  },
  {
    "path": "browser/components/StorageItem.styl",
    "chars": 3826,
    "preview": ".root\n  width 100%\n  user-select none\n\n.folderList-item\n  display flex\n  width 100%\n  height 34px\n  background-color tra"
  },
  {
    "path": "browser/components/StorageList.js",
    "chars": 657,
    "preview": "/**\n * @fileoverview Micro component for showing StorageList\n */\nimport PropTypes from 'prop-types'\nimport React from 'r"
  },
  {
    "path": "browser/components/StorageList.styl",
    "chars": 413,
    "preview": ".storageList\n  absolute left right\n  bottom 37px\n  top 180px\n  overflow-y auto\n\n.storageList-folded\n  @extend .storageLi"
  },
  {
    "path": "browser/components/TagListItem.js",
    "chars": 1758,
    "preview": "/**\n * @fileoverview Micro component for showing TagList.\n */\nimport PropTypes from 'prop-types'\nimport React from 'reac"
  },
  {
    "path": "browser/components/TagListItem.styl",
    "chars": 3040,
    "preview": ".tagList-itemContainer\n  display flex\n\n.tagList-item\n  display flex\n  flex 1\n  width 100%\n  height 26px\n  background-col"
  },
  {
    "path": "browser/components/TodoListPercentage.js",
    "chars": 988,
    "preview": "/**\n * @fileoverview Percentage of todo achievement.\n */\n\nimport PropTypes from 'prop-types'\nimport React from 'react'\ni"
  },
  {
    "path": "browser/components/TodoListPercentage.styl",
    "chars": 1490,
    "preview": ".percentageBar\n  display: flex\n  position absolute\n  top 72px\n  right 0px\n  left 0px\n  background-color #DADFE1\n  width "
  },
  {
    "path": "browser/components/TodoProcess.js",
    "chars": 931,
    "preview": "/**\n * @fileoverview Percentage of todo achievement.\n */\n\nimport PropTypes from 'prop-types'\nimport React from 'react'\ni"
  },
  {
    "path": "browser/components/TodoProcess.styl",
    "chars": 822,
    "preview": ".todo-process\n  font-size 12px\n  display flex\n  padding-top 15px\n  width 85%\n\n.todo-process-text\n  display inline-block\n"
  },
  {
    "path": "browser/components/markdown.styl",
    "chars": 13758,
    "preview": "global-reset()\n\nborderColor = #D0D0D0 // using\nhighlightenBorderColor = darken(borderColor, 20%)\ninvBorderColor = #40484"
  },
  {
    "path": "browser/components/render/MermaidRender.js",
    "chars": 1837,
    "preview": "import mermaidAPI from 'mermaid/dist/mermaid.min.js'\nimport uiThemes from 'browser/lib/ui-themes'\n\n// fixes bad styling "
  },
  {
    "path": "browser/lib/CMLanguageList.js",
    "chars": 1493,
    "preview": "export const languageMaps = {\n  brainfuck: 'Brainfuck',\n  cpp: 'C++',\n  cs: 'C#',\n  clojure: 'Clojure',\n  'clojure-repl'"
  },
  {
    "path": "browser/lib/CSSModules.js",
    "chars": 166,
    "preview": "import CSSModules from 'react-css-modules'\n\nexport default function(component, styles) {\n  return CSSModules(component, "
  },
  {
    "path": "browser/lib/Languages.js",
    "chars": 1182,
    "preview": "const languages = [\n  {\n    name: 'Albanian',\n    locale: 'sq'\n  },\n  {\n    name: 'Chinese (zh-CN)',\n    locale: 'zh-CN'"
  },
  {
    "path": "browser/lib/Mutable.js",
    "chars": 1798,
    "preview": "class MutableMap {\n  constructor(iterable) {\n    this._map = new Map(iterable)\n    Object.defineProperty(this, 'size', {"
  },
  {
    "path": "browser/lib/RcParser.js",
    "chars": 564,
    "preview": "import path from 'path'\nimport sander from 'sander'\n\nconst BOOSTNOTERC = '.boostnoterc'\nconst homePath = global.process."
  },
  {
    "path": "browser/lib/SnippetManager.js",
    "chars": 2831,
    "preview": "import crypto from 'crypto'\nimport fs from 'fs'\nimport consts from './consts'\n\nclass SnippetManager {\n  constructor() {\n"
  },
  {
    "path": "browser/lib/TextEditorInterface.js",
    "chars": 2506,
    "preview": "import { Point } from '@susisu/mte-kernel'\n\nexport default class TextEditorInterface {\n  constructor(editor) {\n    this."
  },
  {
    "path": "browser/lib/confirmDeleteNote.js",
    "chars": 618,
    "preview": "import electron from 'electron'\nimport i18n from 'browser/lib/i18n'\nconst { remote } = electron\nconst { dialog } = remot"
  },
  {
    "path": "browser/lib/consts.js",
    "chars": 2141,
    "preview": "const path = require('path')\nconst fs = require('sander')\nconst { remote } = require('electron')\nconst { app } = remote\n"
  },
  {
    "path": "browser/lib/context.js",
    "chars": 318,
    "preview": "const { remote } = require('electron')\nconst { Menu, MenuItem } = remote\n\nfunction popup(templates) {\n  const menu = new"
  },
  {
    "path": "browser/lib/contextMenuBuilder.js",
    "chars": 4075,
    "preview": "import i18n from 'browser/lib/i18n'\nimport fs from 'fs'\n\nconst { remote } = require('electron')\nconst { Menu } = remote."
  },
  {
    "path": "browser/lib/convertModeName.js",
    "chars": 298,
    "preview": "export default function convertModeName(name) {\n  switch (name) {\n    case 'ejs':\n      return 'Embedded Javascript'\n   "
  },
  {
    "path": "browser/lib/customMeta.js",
    "chars": 467,
    "preview": "import CodeMirror from 'codemirror'\nimport 'codemirror-mode-elixir'\n\nconst stylusCodeInfo = CodeMirror.modeInfo.find(inf"
  },
  {
    "path": "browser/lib/date-formatter.js",
    "chars": 358,
    "preview": "/**\n * @fileoverview Formatting date string.\n */\nimport moment from 'moment'\n\n/**\n * @description Return date string. Fo"
  },
  {
    "path": "browser/lib/findNoteTitle.js",
    "chars": 1353,
    "preview": "export function findNoteTitle(\n  value,\n  enableFrontMatterTitle,\n  frontMatterTitleField = 'title'\n) {\n  const splitted"
  },
  {
    "path": "browser/lib/findStorage.js",
    "chars": 431,
    "preview": "const _ = require('lodash')\n\nexport function findStorage(storageKey) {\n  const cachedStorageList = JSON.parse(localStora"
  },
  {
    "path": "browser/lib/getTodoStatus.js",
    "chars": 631,
    "preview": "export function getTodoStatus(content) {\n  const splitted = content.split('\\n')\n  let numberOfTodo = 0\n  let numberOfCom"
  },
  {
    "path": "browser/lib/htmlTextHelper.js",
    "chars": 768,
    "preview": "/**\n * @fileoverview Text trimmer for html.\n */\n\n/**\n * @param {string} text\n * @return {string}\n */\n\nexport function de"
  },
  {
    "path": "browser/lib/i18n.js",
    "chars": 508,
    "preview": "const path = require('path')\nconst { remote } = require('electron')\nconst { app } = remote\nconst { getLocales } = requir"
  },
  {
    "path": "browser/lib/keygen.js",
    "chars": 245,
    "preview": "const crypto = require('crypto')\nconst uuidv4 = require('uuid/v4')\n\nmodule.exports = function(uuid) {\n  if (typeof uuid "
  },
  {
    "path": "browser/lib/markdown-it-deflist.js",
    "chars": 6521,
    "preview": "'use strict'\n\nmodule.exports = function definitionListPlugin(md) {\n  var isSpace = md.utils.isSpace\n\n  // Search `[:~][\\"
  },
  {
    "path": "browser/lib/markdown-it-fence.js",
    "chars": 3375,
    "preview": "'use strict'\n\nmodule.exports = function(md, renderers, defaultRenderer) {\n  const paramsRE = /^[ \\t]*([\\w+#-]+)?(?:\\(((?"
  },
  {
    "path": "browser/lib/markdown-it-frontmatter.js",
    "chars": 622,
    "preview": "'use strict'\n\nmodule.exports = function frontMatterPlugin(md) {\n  function frontmatter(state, startLine, endLine, silent"
  },
  {
    "path": "browser/lib/markdown-it-sanitize-html.js",
    "chars": 3554,
    "preview": "'use strict'\n\nimport sanitizeHtml from 'sanitize-html'\nimport { escapeHtmlCharacters } from './utils'\nimport url from 'u"
  },
  {
    "path": "browser/lib/markdown-toc-generator.js",
    "chars": 2536,
    "preview": "/**\n * @fileoverview Markdown table of contents generator\n */\n\nimport { EOL } from 'os'\nimport toc from 'markdown-toc'\ni"
  },
  {
    "path": "browser/lib/markdown.js",
    "chars": 14739,
    "preview": "import markdownit from 'markdown-it'\nimport sanitize from './markdown-it-sanitize-html'\nimport emoji from 'markdown-it-e"
  },
  {
    "path": "browser/lib/markdown2.js",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "browser/lib/markdownTextHelper.js",
    "chars": 959,
    "preview": "/**\n * @fileoverview Text trimmer for markdown note.\n */\n\n/**\n * @param {string} input\n * @return {string}\n */\nexport fu"
  },
  {
    "path": "browser/lib/newNote.js",
    "chars": 2376,
    "preview": "import dataApi from 'browser/main/lib/dataApi'\nimport ee from 'browser/main/lib/eventEmitter'\nimport AwsMobileAnalyticsC"
  },
  {
    "path": "browser/lib/normalizeEditorFontFamily.js",
    "chars": 372,
    "preview": "import consts from 'browser/lib/consts'\nimport isString from 'lodash/isString'\n\nexport default function normalizeEditorF"
  },
  {
    "path": "browser/lib/search.js",
    "chars": 1122,
    "preview": "import _ from 'lodash'\n\nexport default function searchFromNotes(notes, search) {\n  if (search.trim().length === 0) retur"
  },
  {
    "path": "browser/lib/slugify.js",
    "chars": 304,
    "preview": "module.exports = function slugify(title) {\n  const slug = encodeURI(\n    title\n      .trim()\n      .replace(/^\\s+/, '')\n"
  },
  {
    "path": "browser/lib/spellcheck.js",
    "chars": 7361,
    "preview": "import styles from '../components/CodeEditor.styl'\nimport i18n from 'browser/lib/i18n'\n\nconst Typo = require('typo-js')\n"
  },
  {
    "path": "browser/lib/turndown.js",
    "chars": 251,
    "preview": "const TurndownService = require('turndown')\nconst { gfm } = require('turndown-plugin-gfm')\n\nexport const createTurndownS"
  },
  {
    "path": "browser/lib/ui-themes.js",
    "chars": 677,
    "preview": "import i18n from 'browser/lib/i18n'\n\nexport default [\n  {\n    name: 'dark',\n    label: i18n.__('Dark'),\n    isDark: true"
  },
  {
    "path": "browser/lib/utils.js",
    "chars": 5648,
    "preview": "export function lastFindInArray(array, callback) {\n  for (let i = array.length - 1; i >= 0; --i) {\n    if (callback(arra"
  },
  {
    "path": "browser/lib/wakatime-plugin.js",
    "chars": 1247,
    "preview": "import config from 'browser/main/lib/ConfigManager'\nconst exec = require('child_process').exec\nconst path = require('pat"
  },
  {
    "path": "browser/main/Detail/Detail.styl",
    "chars": 836,
    "preview": ".root\n  absolute top bottom right\n  display flex\n  align-items center\n  justify-content center\n\n.empty\n  height 320px\n  "
  },
  {
    "path": "browser/main/Detail/DetailVars.styl",
    "chars": 322,
    "preview": "/**\n * Varibales for note detail space.\n */\n\n// Margin on the left side and the right side for NoteDetail component.\n$no"
  },
  {
    "path": "browser/main/Detail/FolderSelect.js",
    "chars": 7630,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/FolderSelect.styl",
    "chars": 4139,
    "preview": ".root\n  position relative\n  border solid 1px transparent\n  vertical-align middle\n  border-radius 2px\n  height 30px\n  tra"
  },
  {
    "path": "browser/main/Detail/FromUrlButton.js",
    "chars": 1587,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/FromUrlButton.styl",
    "chars": 723,
    "preview": ".root\n  top 45px\n  topBarButtonRight()\n  &:hover\n    transition 0.2s\n    color alpha($ui-favorite-star-button-color, 0.6"
  },
  {
    "path": "browser/main/Detail/FullscreenButton.js",
    "chars": 835,
    "preview": "import PropTypes from 'prop-types'\r\nimport React from 'react'\r\nimport CSSModules from 'browser/lib/CSSModules'\r\nimport s"
  },
  {
    "path": "browser/main/Detail/FullscreenButton.styl",
    "chars": 405,
    "preview": ".control-fullScreenButton\n  top 80px\n  topBarButtonRight()\n  &:hover .tooltip\n    opacity 1\n\n.tooltip\n  tooltip()\n  posi"
  },
  {
    "path": "browser/main/Detail/InfoButton.js",
    "chars": 545,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/InfoButton.styl",
    "chars": 370,
    "preview": ".control-infoButton\n  top 10px\n  topBarButtonRight()\n  &:hover .tooltip\n    opacity 1\n\n.tooltip\n  tooltip()\n  position a"
  },
  {
    "path": "browser/main/Detail/InfoPanel.js",
    "chars": 4320,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/InfoPanel.styl",
    "chars": 3853,
    "preview": ".control-infoPanel\n  position fixed\n  pointer-events none\n  top 50px\n  z-index 200\n  line-height normal\n  border-radius "
  },
  {
    "path": "browser/main/Detail/InfoPanelTrashed.js",
    "chars": 2334,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/MarkdownNoteDetail.js",
    "chars": 18386,
    "preview": "/* eslint-disable camelcase */\nimport PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'brow"
  },
  {
    "path": "browser/main/Detail/MarkdownNoteDetail.styl",
    "chars": 1526,
    "preview": "@import('NoteDetailInfo')\n@import('DetailVars')\n\n.root\n  absolute top right bottom\n  border-left 1px solid alpha(#DEDEDE"
  },
  {
    "path": "browser/main/Detail/NoteDetailInfo.styl",
    "chars": 2322,
    "preview": "@import('DetailVars')\n\n$info-height = 60px\n$info-margin-under-border = 30px\n\n.info\n  absolute top left right\n  left 0\n  "
  },
  {
    "path": "browser/main/Detail/PermanentDeleteButton.js",
    "chars": 580,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/RestoreButton.js",
    "chars": 545,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/RestoreButton.styl",
    "chars": 349,
    "preview": ".control-restoreButton\n  top 115px\n  topBarButtonRight()\n  &:hover .tooltip\n    opacity 1\n\n.tooltip\n  tooltip()\n  positi"
  },
  {
    "path": "browser/main/Detail/SnippetNoteDetail.js",
    "chars": 31286,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/SnippetNoteDetail.styl",
    "chars": 4205,
    "preview": "@import('NoteDetailInfo')\n@import('DetailVars')\n\n.root\n  absolute top bottom right\n  border-left 1px solid alpha(#DEDEDE"
  },
  {
    "path": "browser/main/Detail/StarButton.js",
    "chars": 1584,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/StarButton.styl",
    "chars": 774,
    "preview": ".root\n  top 45px\n  topBarButtonRight()\n  &:hover\n    transition 0.2s\n    color alpha($ui-favorite-star-button-color, 0.6"
  },
  {
    "path": "browser/main/Detail/TagSelect.js",
    "chars": 7242,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport invertColor from 'invert-color'\nimport CSSModules fr"
  },
  {
    "path": "browser/main/Detail/TagSelect.styl",
    "chars": 1397,
    "preview": ".root\n  display flex\n  align-items center\n  user-select none\n  vertical-align middle\n  width 96%\n  overflow-x auto\n  whi"
  },
  {
    "path": "browser/main/Detail/ToggleDirectionButton.js",
    "chars": 907,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/ToggleDirectionButton.styl",
    "chars": 1605,
    "preview": ".control-toggleModeButton\n  height 25px\n  border-radius 50px\n  background-color #F4F4F4\n  width 52px\n  display flex\n  al"
  },
  {
    "path": "browser/main/Detail/ToggleModeButton.js",
    "chars": 1177,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/ToggleModeButton.styl",
    "chars": 1587,
    "preview": ".control-toggleModeButton\n  height 25px\n  border-radius 50px\n  background-color #F4F4F4\n  width 52px\n  display flex\n  al"
  },
  {
    "path": "browser/main/Detail/TrashButton.js",
    "chars": 560,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Detail/TrashButton.styl",
    "chars": 490,
    "preview": ".control-trashButton\n  top 115px\n  topBarButtonRight()\n  &:hover .tooltip\n    opacity 1\n\n.tooltip\n  tooltip()\n  position"
  },
  {
    "path": "browser/main/Detail/index.js",
    "chars": 3855,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/DevTools/index.dev.js",
    "chars": 406,
    "preview": "import React from 'react'\nimport { createDevTools } from 'redux-devtools'\nimport LogMonitor from 'redux-devtools-log-mon"
  },
  {
    "path": "browser/main/DevTools/index.js",
    "chars": 278,
    "preview": "/* eslint-disable no-undef */\nif (process.env.NODE_ENV === 'development') {\n  // eslint-disable-next-line global-require"
  },
  {
    "path": "browser/main/DevTools/index.prod.js",
    "chars": 114,
    "preview": "import React from 'react'\n\nconst DevTools = () => <div />\nDevTools.instrument = () => {}\n\nexport default DevTools\n"
  },
  {
    "path": "browser/main/Main.js",
    "chars": 12991,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/Main.styl",
    "chars": 598,
    "preview": ".root\n  absolute top left bottom right\n\n.body\n  absolute right top bottom\n  left $sideNav-width\n\n.body--expanded\n  @exte"
  },
  {
    "path": "browser/main/NewNoteButton/NewNoteButton.styl",
    "chars": 1792,
    "preview": ".root\n  position relative\n  background-color $ui-noteList-backgroundColor\n  height $topBar-height - 1\n  margin-left: aut"
  },
  {
    "path": "browser/main/NewNoteButton/index.js",
    "chars": 3348,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/NoteList/NoteList.styl",
    "chars": 2051,
    "preview": "$control-height = 30px\n\n.root\n  absolute left bottom\n  top $topBar-height - 1\n  background-color $ui-noteList-background"
  },
  {
    "path": "browser/main/NoteList/index.js",
    "chars": 38841,
    "preview": "/* global electron */\nimport PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/C"
  },
  {
    "path": "browser/main/SideNav/ListButton.js",
    "chars": 749,
    "preview": "import PropTypes from 'prop-types'\r\nimport React from 'react'\r\nimport CSSModules from 'browser/lib/CSSModules'\r\nimport s"
  },
  {
    "path": "browser/main/SideNav/PreferenceButton.js",
    "chars": 575,
    "preview": "import PropTypes from 'prop-types'\r\nimport React from 'react'\r\nimport CSSModules from 'browser/lib/CSSModules'\r\nimport s"
  },
  {
    "path": "browser/main/SideNav/PreferenceButton.styl",
    "chars": 1106,
    "preview": ".top-menu-preference\n  navButtonColor()\n  width 2em\n  background-color transparent\n  &:hover\n    color $ui-button-defaul"
  },
  {
    "path": "browser/main/SideNav/SearchButton.js",
    "chars": 703,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/SideNav/SearchButton.styl",
    "chars": 1172,
    "preview": ".top-menu-search\n  navButtonColor()\n  position relative\n  margin-right 6px\n  top 3px\n  width 2em\n  background-color tran"
  },
  {
    "path": "browser/main/SideNav/SideNav.styl",
    "chars": 3811,
    "preview": ".root\n  absolute top left bottom\n  width $sideNav-width\n  background-color #2E3235\n  user-select none\n  color $ui-text-c"
  },
  {
    "path": "browser/main/SideNav/StorageItem.js",
    "chars": 13092,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/SideNav/StorageItem.styl",
    "chars": 4755,
    "preview": ".root\n  width 100%\n  user-select none\n  padding-top 20px\n\n.header\n  position relative\n  height 36px\n  width 100%\n  margi"
  },
  {
    "path": "browser/main/SideNav/SwitchButton.styl",
    "chars": 1074,
    "preview": ".non-active-button\n  color $ui-inactive-text-color\n  font-size 16px\n  border 0\n  background-color transparent\n  transiti"
  },
  {
    "path": "browser/main/SideNav/TagButton.js",
    "chars": 743,
    "preview": "import PropTypes from 'prop-types'\r\nimport React from 'react'\r\nimport CSSModules from 'browser/lib/CSSModules'\r\nimport s"
  },
  {
    "path": "browser/main/SideNav/index.js",
    "chars": 19369,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { push } from 'connected-react-router'\nimport CSSMod"
  },
  {
    "path": "browser/main/StatusBar/StatusBar.styl",
    "chars": 1757,
    "preview": "@import('../Detail/DetailVars')\n\n.root\n  position absolute\n  bottom 10px\n  right 10px\n  z-index 100\n  display flex\n\n.bla"
  },
  {
    "path": "browser/main/StatusBar/index.js",
    "chars": 3403,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/TopBar/TopBar.styl",
    "chars": 5584,
    "preview": ".root\n  position relative\n  background-color $ui-noteList-backgroundColor\n  height $topBar-height - 1\n\n.root--expanded\n "
  },
  {
    "path": "browser/main/TopBar/index.js",
    "chars": 5374,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/global.styl",
    "chars": 3145,
    "preview": "global-reset()\n@import '../styles/vars.styl'\n\nDEFAULT_FONTS = 'OpenSans', helvetica, arial, sans-serif\n\nhtml, body\n  wid"
  },
  {
    "path": "browser/main/index.js",
    "chars": 5166,
    "preview": "import { Provider } from 'react-redux'\nimport Main from './Main'\nimport { store, history } from './store'\nimport React, "
  },
  {
    "path": "browser/main/lib/AwsMobileAnalyticsConfig.js",
    "chars": 2160,
    "preview": "const AWS = require('aws-sdk')\nconst AMA = require('aws-sdk-mobile-analytics')\nconst ConfigManager = require('browser/ma"
  },
  {
    "path": "browser/main/lib/Commander.js",
    "chars": 555,
    "preview": "let callees = []\n\nfunction bind(name, el) {\n  callees.push({\n    name: name,\n    element: el\n  })\n}\n\nfunction release(el"
  },
  {
    "path": "browser/main/lib/ConfigManager.js",
    "chars": 8536,
    "preview": "import _ from 'lodash'\nimport RcParser from 'browser/lib/RcParser'\nimport i18n from 'browser/lib/i18n'\nimport ee from 'b"
  },
  {
    "path": "browser/main/lib/ThemeManager.js",
    "chars": 1491,
    "preview": "import ConfigManager from 'browser/main/lib/ConfigManager'\nimport uiThemes from 'browser/lib/ui-themes'\n\nconst saveChang"
  },
  {
    "path": "browser/main/lib/ZoomManager.js",
    "chars": 516,
    "preview": "import ConfigManager from './ConfigManager'\n\nconst electron = require('electron')\nconst { remote } = electron\n\n_init()\n\n"
  },
  {
    "path": "browser/main/lib/dataApi/addStorage.js",
    "chars": 2484,
    "preview": "const _ = require('lodash')\nconst keygen = require('browser/lib/keygen')\nconst resolveStorageData = require('./resolveSt"
  },
  {
    "path": "browser/main/lib/dataApi/attachmentManagement.js",
    "chars": 36425,
    "preview": "const uniqueSlug = require('unique-slug')\nconst fs = require('fs')\nconst path = require('path')\nconst findStorage = requ"
  },
  {
    "path": "browser/main/lib/dataApi/copyFile.js",
    "chars": 761,
    "preview": "import fs from 'fs'\nimport fx from 'fs-extra'\nimport path from 'path'\n\n/**\n * @description Copy a file from source to de"
  },
  {
    "path": "browser/main/lib/dataApi/createFolder.js",
    "chars": 1351,
    "preview": "const _ = require('lodash')\nconst keygen = require('browser/lib/keygen')\nconst path = require('path')\nconst resolveStora"
  },
  {
    "path": "browser/main/lib/dataApi/createNote.js",
    "chars": 2623,
    "preview": "const sander = require('sander')\nconst resolveStorageData = require('./resolveStorageData')\nconst _ = require('lodash')\n"
  },
  {
    "path": "browser/main/lib/dataApi/createNoteFromUrl.js",
    "chars": 2888,
    "preview": "const http = require('http')\nconst https = require('https')\nconst { createTurndownService } = require('../../../lib/turn"
  },
  {
    "path": "browser/main/lib/dataApi/createSnippet.js",
    "chars": 826,
    "preview": "import fs from 'fs'\nimport crypto from 'crypto'\nimport consts from 'browser/lib/consts'\nimport fetchSnippet from 'browse"
  },
  {
    "path": "browser/main/lib/dataApi/deleteFolder.js",
    "chars": 1704,
    "preview": "const _ = require('lodash')\nconst path = require('path')\nconst resolveStorageData = require('./resolveStorageData')\ncons"
  },
  {
    "path": "browser/main/lib/dataApi/deleteNote.js",
    "chars": 1007,
    "preview": "const resolveStorageData = require('./resolveStorageData')\nconst path = require('path')\nconst sander = require('sander')"
  },
  {
    "path": "browser/main/lib/dataApi/deleteSnippet.js",
    "chars": 619,
    "preview": "import fs from 'fs'\nimport consts from 'browser/lib/consts'\nimport fetchSnippet from 'browser/main/lib/dataApi/fetchSnip"
  },
  {
    "path": "browser/main/lib/dataApi/exportFolder.js",
    "chars": 1688,
    "preview": "import { findStorage } from 'browser/lib/findStorage'\nimport resolveStorageData from './resolveStorageData'\nimport resol"
  },
  {
    "path": "browser/main/lib/dataApi/exportNote.js",
    "chars": 2462,
    "preview": "import copyFile from 'browser/main/lib/dataApi/copyFile'\nimport { findStorage } from 'browser/lib/findStorage'\n\nconst fs"
  },
  {
    "path": "browser/main/lib/dataApi/exportNoteAs.js",
    "chars": 541,
    "preview": "import { findStorage } from 'browser/lib/findStorage'\nimport exportNote from './exportNote'\nimport getContentFormatter f"
  },
  {
    "path": "browser/main/lib/dataApi/exportStorage.js",
    "chars": 2101,
    "preview": "import { findStorage } from 'browser/lib/findStorage'\nimport resolveStorageData from './resolveStorageData'\nimport resol"
  },
  {
    "path": "browser/main/lib/dataApi/exportTag.js",
    "chars": 638,
    "preview": "import exportNoteAs from './exportNoteAs'\nimport getFilename from './getFilename'\n\n/**\n * @param {Object} data\n * @param"
  },
  {
    "path": "browser/main/lib/dataApi/fetchSnippet.js",
    "chars": 523,
    "preview": "import fs from 'fs'\nimport consts from 'browser/lib/consts'\n\nfunction fetchSnippet(id, snippetFile) {\n  return new Promi"
  },
  {
    "path": "browser/main/lib/dataApi/formatHTML.js",
    "chars": 18247,
    "preview": "import path from 'path'\nimport fileUrl from 'file-url'\nimport fs from 'fs'\nimport { remote } from 'electron'\nimport cons"
  },
  {
    "path": "browser/main/lib/dataApi/formatMarkdown.js",
    "chars": 2558,
    "preview": "import attachmentManagement from './attachmentManagement'\nimport yaml from 'js-yaml'\nimport path from 'path'\n\nconst deli"
  },
  {
    "path": "browser/main/lib/dataApi/formatPDF.js",
    "chars": 718,
    "preview": "import formatHTML from './formatHTML'\nimport { remote } from 'electron'\n\nexport default function formatPDF(props) {\n  re"
  },
  {
    "path": "browser/main/lib/dataApi/getContentFormatter.js",
    "chars": 1972,
    "preview": "import formatMarkdown from './formatMarkdown'\nimport formatHTML from './formatHTML'\nimport formatPDF from './formatPDF'\n"
  },
  {
    "path": "browser/main/lib/dataApi/getFilename.js",
    "chars": 794,
    "preview": "import filenamify from 'filenamify'\nimport i18n from 'browser/lib/i18n'\nimport path from 'path'\n\n/**\n * @param {Object} "
  },
  {
    "path": "browser/main/lib/dataApi/index.js",
    "chars": 1246,
    "preview": "const dataApi = {\n  init: require('./init'),\n  toggleStorage: require('./toggleStorage'),\n  addStorage: require('./addSt"
  },
  {
    "path": "browser/main/lib/dataApi/init.js",
    "chars": 2809,
    "preview": "'use strict'\nconst _ = require('lodash')\nconst resolveStorageData = require('./resolveStorageData')\nconst resolveStorage"
  },
  {
    "path": "browser/main/lib/dataApi/migrateFromV5Storage.js",
    "chars": 3387,
    "preview": "const _ = require('lodash')\nconst keygen = require('browser/lib/keygen')\nconst resolveStorageData = require('./resolveSt"
  },
  {
    "path": "browser/main/lib/dataApi/migrateFromV6Storage.js",
    "chars": 3097,
    "preview": "const path = require('path')\nconst sander = require('sander')\nconst keygen = require('browser/lib/keygen')\nconst _ = req"
  },
  {
    "path": "browser/main/lib/dataApi/moveNote.js",
    "chars": 3306,
    "preview": "const resolveStorageData = require('./resolveStorageData')\nconst _ = require('lodash')\nconst path = require('path')\ncons"
  },
  {
    "path": "browser/main/lib/dataApi/removeStorage.js",
    "chars": 598,
    "preview": "const _ = require('lodash')\n\n/**\n * @param {String} key\n * @return {key}\n */\nfunction removeStorage(key) {\n  let rawStor"
  },
  {
    "path": "browser/main/lib/dataApi/renameStorage.js",
    "chars": 883,
    "preview": "const _ = require('lodash')\nconst resolveStorageData = require('./resolveStorageData')\n\n/**\n * @param {String} key\n * @p"
  },
  {
    "path": "browser/main/lib/dataApi/reorderFolder.js",
    "chars": 1106,
    "preview": "const _ = require('lodash')\n_.move = require('lodash-move').default\nconst path = require('path')\nconst resolveStorageDat"
  },
  {
    "path": "browser/main/lib/dataApi/resolveStorageData.js",
    "chars": 1201,
    "preview": "const _ = require('lodash')\nconst path = require('path')\nconst CSON = require('@rokt33r/season')\nconst migrateFromV6Stor"
  },
  {
    "path": "browser/main/lib/dataApi/resolveStorageNotes.js",
    "chars": 1175,
    "preview": "const sander = require('sander')\nconst path = require('path')\nconst CSON = require('@rokt33r/season')\n\nfunction resolveS"
  },
  {
    "path": "browser/main/lib/dataApi/toggleStorage.js",
    "chars": 781,
    "preview": "const _ = require('lodash')\nconst resolveStorageData = require('./resolveStorageData')\n\n/**\n * @param {String} key\n * @p"
  },
  {
    "path": "browser/main/lib/dataApi/updateFolder.js",
    "chars": 1323,
    "preview": "const _ = require('lodash')\nconst path = require('path')\nconst resolveStorageData = require('./resolveStorageData')\ncons"
  },
  {
    "path": "browser/main/lib/dataApi/updateNote.js",
    "chars": 4167,
    "preview": "const resolveStorageData = require('./resolveStorageData')\nconst _ = require('lodash')\nconst path = require('path')\ncons"
  },
  {
    "path": "browser/main/lib/dataApi/updateSnippet.js",
    "chars": 1270,
    "preview": "import fs from 'fs'\nimport consts from 'browser/lib/consts'\n\nfunction updateSnippet(snippet, snippetFile) {\n  return new"
  },
  {
    "path": "browser/main/lib/eventEmitter.js",
    "chars": 435,
    "preview": "const electron = require('electron')\nconst { ipcRenderer, remote } = electron\n\nfunction on(name, listener) {\n  ipcRender"
  },
  {
    "path": "browser/main/lib/ipcClient.js",
    "chars": 682,
    "preview": "import ConfigManager from './ConfigManager'\n\nconst nodeIpc = require('node-ipc')\nconst { remote, ipcRenderer } = require"
  },
  {
    "path": "browser/main/lib/modal.js",
    "chars": 1876,
    "preview": "import React from 'react'\nimport { Provider } from 'react-redux'\nimport ReactDOM from 'react-dom'\nimport { store } from "
  },
  {
    "path": "browser/main/lib/notify.js",
    "chars": 316,
    "preview": "const path = require('path')\n\nfunction notify(title, options) {\n  if (process.platform === 'win32') {\n    options.icon ="
  },
  {
    "path": "browser/main/lib/shortcut.js",
    "chars": 337,
    "preview": "import ee from 'browser/main/lib/eventEmitter'\n\nmodule.exports = {\n  toggleMode: () => {\n    ee.emit('topbar:togglemodeb"
  },
  {
    "path": "browser/main/lib/shortcutManager.js",
    "chars": 1029,
    "preview": "import Mousetrap from 'mousetrap'\nimport CM from 'browser/main/lib/ConfigManager'\nimport ee from 'browser/main/lib/event"
  },
  {
    "path": "browser/main/modals/CreateFolderModal.js",
    "chars": 2844,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/modals/CreateFolderModal.styl",
    "chars": 1800,
    "preview": ".root\n  modal()\n  width 500px\n  height 270px\n  overflow hidden\n  position relative\n\n.header\n  height 80px\n  margin-botto"
  },
  {
    "path": "browser/main/modals/CreateMarkdownFromURLModal.js",
    "chars": 2920,
    "preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styl"
  },
  {
    "path": "browser/main/modals/CreateMarkdownFromURLModal.styl",
    "chars": 1698,
    "preview": ".root\n  modal()\n  width 500px\n  height 270px\n  overflow hidden\n  position relative\n\n.header\n  height 80px\n  margin-botto"
  },
  {
    "path": "browser/main/modals/NewNoteModal.js",
    "chars": 4490,
    "preview": "import React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styles from './NewNoteModal.styl'\nimpor"
  },
  {
    "path": "browser/main/modals/NewNoteModal.styl",
    "chars": 1391,
    "preview": ".root\n  modal()\n  max-width 540px\n  overflow hidden\n  position relative\n\n.header\n  height 50px\n  font-size 18px\n  line-h"
  },
  {
    "path": "browser/main/modals/PreferencesModal/Blog.js",
    "chars": 6583,
    "preview": "import React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styles from './ConfigTab.styl'\nimport C"
  },
  {
    "path": "browser/main/modals/PreferencesModal/ConfigTab.styl",
    "chars": 6434,
    "preview": "@import('./Tab')\n\n.container\n  display flex\n  flex-direction column\n  align-items center\n  justify-content center\n  posi"
  },
  {
    "path": "browser/main/modals/PreferencesModal/Crowdfunding.js",
    "chars": 3170,
    "preview": "import React from 'react'\nimport CSSModules from 'browser/lib/CSSModules'\nimport styles from './Crowdfunding.styl'\nimpor"
  }
]

// ... and 167 more files (download for full content)

About this extraction

This page contains the full source code of the BoostIO/BoostNote-Legacy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 367 files (4.3 MB), approximately 1.2M tokens, and a symbol index with 982 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!