提交 aa54bd98 编写于 作者: F Fabio Spampinato

Initial commit

上级
{
"ignore": [
"./src/renderer/template/dist"
]
}
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
# Numerous always-ignore extensions
*.diff
*.err
*.log
*.orig
*.rej
*.swo
*.swp
*.vi
*.zip
*~
*.sass-cache
*.ruby-version
*.rbenv-version
# OS or Editor folders
._*
.cache
.DS_Store
.idea
.project
.settings
.tmproj
*.esproj
*.sublime-project
*.sublime-workspace
nbproject
Thumbs.db
.fseventsd
.DocumentRevisions*
.TemporaryItems
.Trashes
# Other paths to ignore
bower_components
node_modules
package-lock.json
dist
releases
Usability:
☐ add platform-aware shortcuts-codes to buttons' titles @next
☐ maybe add global shortcut for quickly adding a new note @next
Drag & Drop:
☐ note(s) into tag @next
☐ markdown file(s) into middlebar/tag @next
☐ attachment(s) into note @next
Others:
☐ ensure rendering notes doesn't block the main thread @next
☐ run import logic into another process @next
☐ search (non fuzzyly) the content of notes too @next
☐ dark theme @next
☐ note versioning (via git?) @next
☐ encrypted notes/attachments (easy?) @next
### Version 1.0.0
- Initial release.
The MIT License (MIT)
Copyright (c) 2018-present Fabio Spampinato
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
# Notable ([DOWNLOAD](https://github.com/fabiospampinato/noty/releases))
<p align="center">
<img src="resources/demo/main.png" alt="Notable" width="500">
</p>
The markdown-based note-taking app that doesn't suck.
I couldn't find a note-taking app that ticked all the boxes I'm interested in: notes are written and rendered in GitHub-flavored Markdown, no WYSIWYG, no proprietary formats, I can run a search & replace across all notes, notes support attachments, the app isn't bloated, the app has a pretty interface, tags are indefinitely nestable and can import Evernote notes (because that's what I was using before).
So I built my own.
## Features
```
/path/to/your/data_directory
├─┬ attachments
│ ├── foo.ext
│ ├── bar.ext
│ └── …
└─┬ notes
├── foo.md
├── bar.md
└── …
```
- **No proprietary formats**: Notable is just a pretty front-end for a folder structured as shown above. Notes are plain Markdown files, their metadata is stored as Markdown front matter. Attachments are also plain files, if you attach a `picture.jpg` to a note everything about it will be preserved, and it will remain accessible like any other file.
- **Proper editor**: Notable doesn't use any WYSIWYG editor, you just write some Markdown and it gets rendered as GitHub-flavored Markdown. The built-in editor is [CodeMirror](https://codemirror.net), this means you get things like multi-cursor by default. If you need more advanced editing features with a single shortcut you can open the current note in your default Markdown editor.
- **Indefinitely nestable tags**: Pretty much all the other note-taking apps differenciate between notebooks, tags and templates. IMHO this unnecessarily complicates things. In Notable you can have root tags (`foo`), indefinitely nestable tags (`foo/bar`, `foo/.../qux`) and it still supports notebooks and templates, they are just special tags with a different icon (`Notebooks/foo`, `Templates/foo/bar`).
Upon first instantiation some tutorial notes will be added to the app, check out those for more in-depth details.
## Comparison
```
+=============================+=================================+==============================================+======================================+=======================================+==================================================+========================================+=========================================+
| | Notable | Evernote | Notion.so | Boostnote | Quiver | Bear | Simplenote |
+=============================+=================================+==============================================+======================================+=======================================+==================================================+========================================+=========================================+
| Free | ✔ | △, with paid plans | △, with paid plans | ✔ | ✘, only during trial | △, with paid plans | ✔ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Open source | ✔ | ✘ | ✘ | ✔ | ✘ | ✘ | ✔ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Cross platform | ✔ | ✔ | ✔ | ✔ | ✘ | ✘ | ✔ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| No account required | ✔ | ✘ | ✘ | ✔ | ✔ | ✔ | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| No proprietary formats | ✔ | △, can only export to HTML | ✘ | ✔, but notes are stored in .cson | △, no inside Text cells | △, can export to Markdown | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| No WYSIWYG | ✔ | ✘ | ✘ | ✔ | △, no inside Text cells | ✘, but it's a surprisingly decent one | △, only if you set "Markdown formatted" |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| No bloat | ✔ | ✘, work chat, webclipper, annotations etc... | ✘, spreadsheets, kanban board etc... | △, publish to blog, snippets | △, presentation mode | ✔ | △, publish to website |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Pretty UI | ✔ | ✘ | ✘, too much bloat | ✘ | ✘ | ✔ | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| GitHub-flavored Markdown | ✔ | ✘ | ✘ | ✔ | ✔, only within Markdown cells | ✘ | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Code syntax highlighting | ✔ | ✘, only generic code blocks | ✔ | ✔ | ✔ | ✔ | ✘, only generic code blocks |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Attachments | ✔ | ✔, but base64 encoded in HTML when exported | ✔, but 5MB limit on free plan | ✘ | ✔ | ✔ | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Fuzzy search | ✔ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Indefinitely nestable tags | ✔ | ✘ | ✘ | ✘ | ✘ | ✔ | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Multi-note simple editing | ✔ | ✔ | ✘ | △, very limited | ✔ | ✔ | △, very limited |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Multi-note search & replace | ✔ | ✘ | ✘ | ✔ | △, with some effort, notes are stored as .qvnote | ✘ | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Keyboard friendly | ✔ | ✔ | ✔ | △, can't toggle edit and preview mode | ✘, I couldn't edit a note without the mouse | ✔, but some shortcuts are undocumented | ✘ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Mobile app | ✘, but notes are Markdown files | ✔ | ✔ | ✔ | ✘ | △, only iOS | ✔ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Synchronization | △, via Dropbox/etc. | ✔ | ✔ | △, via Dropbox/etc. | △, via Dropbox/etc. | ✔ | ✔ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
| Version control | △, via Git | ✘ | ✔, but paid | △, via Git | △, via Git, but cumbersome .qvnote format | ✘ | ✔ |
+-----------------------------+---------------------------------+----------------------------------------------+--------------------------------------+---------------------------------------+--------------------------------------------------+----------------------------------------+-----------------------------------------+
```
Part of this comparison is personal opinion: you may disagree on the UI front, things I consider bloat can be consider features by somebody else etc. but hopefully this comparison did a good job at illustrating the main differences.
## Demo
### Editor
<img src="resources/demo/editor.png" alt="Editor" width="750">
### Multi-Note Editor
<img src="resources/demo/multi_editor.png" alt="Editor" width="750">
## Contributing
If you have an idea, or found an problem, please open an [issue](https://github.com/fabiospampinato/notable/issues) about it.
If you want to make a pull request, or fork the app, you should:
```bash
git clone https://github.com/fabiospampinato/notable.git
npm install
npm run svelto:dev
npm run iconfont
npm run tutorial
npm run dev
```
## Related
- **[enex-dump](https://github.com/fabiospampinato/enex-dump)**: Dump the content of Evernote's `.enex` files, preserving attachements, some metadata and optionally converting notes to Markdown.
- **[Noty](https://github.com/fabiospampinato/noty)**: Autosaving sticky note with support for multiple notes without needing multiple windows.
- **[Markdown Todo](https://marketplace.visualstudio.com/items?itemName=fabiospampinato.vscode-markdown-todo)**: Manage todo lists inside markdown files with ease. Have the same todo-related shortcuts that Notable provides, but in Visual Studio Code.
- **[Todo+](https://marketplace.visualstudio.com/items?itemName=fabiospampinato.vscode-todo-plus)**: Manage todo lists with ease. Powerful, easy to use and customizable.
## License
MIT © Fabio Spampinato
{
"sources": [
"./resources/font_icons/[icon].svg",
"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/icons/svg/[icon].svg"
],
"icons": [
"chevron-down",
"close",
"delete-forever",
"delete-restore",
"delete",
"folder-search",
"magnify",
"note",
"notebook-multiple",
"notebook",
"open-in-new",
"paperclip",
"pencil",
"pin-outline",
"pin",
"plus",
"star-outline",
"star",
"tag-crossed",
"tag-minus",
"tag-multiple",
"tag-outline-multiple",
"tag-outline",
"tag-plus",
"tag"
],
"output": {
"icons": "./resources/font_icons",
"fonts": "./src/renderer/template/dist/fonts",
"fontName": "IconFont",
"formats": [
"woff2"
]
}
}
{
"name": "notable",
"productName": "Notable",
"description": "The markdown-based note-taking app that doesn't suck.",
"version": "1.0.0",
"scripts": {
"clean:deps": "del 'node_modules/**/{README,LICENSE,license,.travis.yml,tsconfig.json,*.{md,MD,map,png,svg,ts}}' '!node_modules/**/*.d.ts'",
"clean:dist": "del dist",
"clean:releases": "del releases",
"clean": "npm-run-all -p clean:dist clean:releases",
"compile": "npm run svelto:prod && electron-webpack app --env.minify=false",
"build:mac": "npm run compile && electron-builder --mac",
"build:win": "npm run compile && electron-builder --win",
"build:linux": "npm run compile && electron-builder --linux",
"build:all": "npm run compile && electron-builder -mwl",
"svelto:dev": "svelto build --env development",
"svelto:prod": "svelto build",
"iconfont": "icon-font-buildr",
"tutorial": "tar cf ./src/renderer/template//dist/tutorial.tar -C ./resources/tutorial .",
"dev": "electron-webpack dev",
"prod": "npm run clean:deps && npm run prepare && npm run compile && CSC_IDENTITY_AUTO_DISCOVERY=false electron-builder --mac dir && open releases/mac/*.app",
"publish": "electron-builder --publish onTag"
},
"electronWebpack": {
"staticSourceDirectory": "src/renderer/template/dist",
"main": {
"webpackConfig": "webpack.js"
},
"renderer": {
"webpackConfig": "webpack.js"
}
},
"build": {
"appId": "com.fabiospampinato.notable",
"copyright": "Copyright © 2018-present Fabio Spampinato",
"directories": {
"output": "releases"
},
"mac": {
"target": [
"dmg",
"pkg",
"zip"
],
"category": "public.app-category.utilities",
"icon": "resources/icon/icon.png",
"type": "distribution"
},
"dmg": {
"background": "resources/dmg_background/background.png",
"iconSize": 160,
"iconTextSize": 12,
"window": {
"width": 660,
"height": 400
},
"contents": [
{
"x": 180,
"y": 170,
"type": "file"
},
{
"x": 480,
"y": 170,
"type": "link",
"path": "/Applications"
}
]
},
"pkg": {
"license": "LICENSE"
},
"win": {
"target": [
"nsis",
"portable",
"zip"
],
"icon": "resources/icon/icon.ico"
},
"nsis": {
"installerIcon": "resources/icon/icon.ico",
"license": "LICENSE",
"warningsAsErrors": false
},
"linux": {
"target": [
"AppImage",
"deb",
"rpm",
"snap"
],
"icon": "resources/icon/icon.png",
"category": "Utility"
},
"snap": {
"grade": "stable",
"summary": "The markdown-based note-taking app that doesn't suck."
},
"publish": {
"provider": "github",
"owner": "fabiospampinato",
"releaseType": "release",
"publishAutoUpdate": true
}
},
"license": "MIT",
"author": {
"name": "Fabio Spampinato",
"email": "spampinabio@gmail.com"
},
"homepage": "https://github.com/fabiospampinato/notable",
"repository": {
"type": "git",
"url": "https://github.com/fabiospampinato/notable.git"
},
"bugs": {
"url": "https://github.com/fabiospampinato/notable/issues"
},
"tutorial": {
"url": "https://github.com/fabiospampinato/notable/tree/master/resources/tutorial/notes"
},
"keywords": [
"electron",
"react",
"webpack"
],
"dependencies": {
"calls-batch": "^1.0.0",
"chokidar": "^2.0.4",
"codemirror": "git://github.com/fabiospampinato/CodeMirror.git",
"codemirror-github-light": "^0.4.2",
"crc-32": "^1.2.0",
"decompress": "^4.2.0",
"electron-context-menu": "^0.10.1",
"electron-dialog": "^1.0.0",
"electron-is": "^3.0.0",
"electron-localshortcut": "^3.1.0",
"electron-store": "^2.0.0",
"electron-updater": "^4.0.5",
"electron-window-state": "^4.1.1",
"enex-dump": "^1.3.0",
"filenamify": "^2.1.0",
"globby": "^8.0.1",
"gray-matter": "^4.0.1",
"highlight.js": "^9.13.1",
"js-yaml": "^3.12.0",
"lodash": "^4.17.11",
"mkdirp": "^0.5.1",
"overstated": "^1.1.2",
"pify": "^4.0.1",
"primer-markdown": "^3.7.11",
"react": "^16.6.3",
"react-codemirror2": "^5.1.0",
"react-component-identity": "^1.0.1",
"react-component-renderless": "^1.0.2",
"react-dom": "^16.6.3",
"react-router-static": "^1.0.0",
"react-window": "^1.3.1",
"recompose": "^0.30.0",
"remark": "^10.0.1",
"sha1": "^1.1.1",
"shallowequal": "^1.1.0",
"showdown": "^1.9.0",
"showdown-highlight": "^2.1.3",
"showdown-target-blank": "^1.0.2",
"strip-markdown": "^3.0.3"
},
"devDependencies": {
"@babel/preset-react": "^7.0.0",
"@types/codemirror": "0.0.70",
"@types/lodash": "^4.14.118",
"@types/node": "^10.12.12",
"@types/react": "^16.7.13",
"@types/react-dom": "^16.0.11",
"del-cli": "^1.1.0",
"electron": "3.0.10",
"electron-builder": "^20.38.2",
"electron-builder-squirrel-windows": "^20.38.2",
"electron-devtools-installer": "^2.2.4",
"electron-webpack": "git://github.com/fabiospampinato/electron-webpack.git#package-electron-webpack",
"electron-webpack-ts": "^3.1.0",
"icon-font-buildr": "^1.2.4",
"node-sass": "^4.10.0",
"react-hot-loader": "^4.3.12",
"react-log-updates": "^1.0.7",
"sass-loader": "^7.1.0",
"svelto": "^1.1.0",
"terser-webpack-plugin": "^1.1.0",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"typescript": "^3.2.2",
"webpack": "^4.27.1"
}
}
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 7.41348,8.58407L 11.9995,13.1701L 16.5855,8.58407L 17.9995,9.99807L 11.9995,15.9981L 5.99948,9.99807L 7.41348,8.58407 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-linejoin="round" d="M 19,6.41L 17.59,5L 12,10.59L 6.41,5L 5,6.41L 10.59,12L 5,17.59L 6.41,19L 12,13.41L 17.59,19L 19,17.59L 13.41,12L 19,6.41 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="1.33333" stroke-linejoin="miter" d="M 6,19C 6,20.1 6.9,21 8,21L 16,21C 17.1,21 18,20.1 18,19L 18,7L 6,7L 6,19 Z M 8.46,11.88L 9.87,10.47L 12,12.59L 14.12,10.47L 15.53,11.88L 13.41,14L 15.53,16.12L 14.12,17.53L 12,15.41L 9.88,17.53L 8.47,16.12L 10.59,14L 8.46,11.88 Z M 15.5,4L 14.5,3L 9.5,3L 8.5,4L 5,4L 5,6L 19,6L 19,4L 15.5,4 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="1.33333" stroke-linejoin="miter" d="M 14.0156,14.0156L 15.9844,14.0156L 12,9.9844L 8.01563,14.0156L 9.98438,14.0156L 9.98438,18L 14.0156,18L 14.0156,14.0156 Z M 6,6.9844L 18,6.9844L 18,18.9844C 18,19.5156 17.7969,19.9844 17.3906,20.3906C 16.9844,20.7969 16.5156,21 15.9844,21L 8.01563,21C 7.48438,21 7.01563,20.7969 6.60938,20.3906C 6.20312,19.9844 6,19.5156 6,18.9844L 6,6.9844 Z M 18.9844,3.9844L 18.9844,6L 5.01563,6L 5.01563,3.9844L 8.48438,3.9844L 9.51563,3L 14.4844,3L 15.5156,3.9844L 18.9844,3.9844 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-linejoin="round" d="M 19,4L 15.5,4L 14.5,3L 9.5,3L 8.5,4L 5,4L 5,6L 19,6M 6,19C 6,20.1 6.9,21 8,21L 16,21C 17.1,21 18,20.1 18,19L 18,7L 6,7L 6,19 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 16.5,12C 18.9853,12 21,14.0147 21,16.5C 21,17.3826 20.7459,18.2059 20.3069,18.9006L 23.3906,21.9844L 21.9844,23.3906L 18.8763,20.3221C 18.1867,20.7518 17.3723,21 16.5,21C 14.0147,21 12,18.9853 12,16.5C 12,14.0147 14.0147,12 16.5,12 Z M 16.5,14C 15.1193,14 14,15.1193 14,16.5C 14,17.8807 15.1193,19 16.5,19C 17.8807,19 19,17.8807 19,16.5C 19,15.1193 17.8807,14 16.5,14 Z M 8.99936,3.99807L 10.9994,5.99807L 18.9994,5.99807C 20.1029,5.99807 20.9994,6.89406 20.9994,7.99807L 20.9994,11.809C 19.8315,10.6886 18.2462,10 16.5,10C 12.9101,10 10,12.9101 10,16.5C 10,17.7878 10.3745,18.9882 11.0206,19.9981L 2.99936,19.9981C 1.89436,19.9981 0.999362,19.1021 0.999362,17.9981L 1.00936,5.99807C 1.00936,4.89406 1.89436,3.99807 2.99936,3.99807L 8.99936,3.99807 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 9.5,3C 13.0899,3 16,5.91015 16,9.5C 16,11.1149 15.411,12.5923 14.4362,13.7291L 14.7071,14L 15.5,14L 20.5,19L 19,20.5L 14,15.5L 14,14.7071L 13.7291,14.4362C 12.5923,15.411 11.1149,16 9.5,16C 5.91015,16 3,13.0899 3,9.5C 3,5.91015 5.91015,3 9.5,3 Z M 9.5,5.00001C 7.01472,5.00001 5,7.01473 5,9.50001C 5,11.9853 7.01472,14 9.5,14C 11.9853,14 14,11.9853 14,9.50001C 14,7.01473 11.9853,5.00001 9.5,5.00001 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 13.9991,9.99831L 19.4991,9.99831L 13.9991,4.49831L 13.9991,9.99831 Z M 4.99914,2.99832L 14.9991,2.99832L 20.9991,8.99831L 20.9991,18.9983C 20.9991,20.1023 20.1031,20.9983 18.9991,20.9983L 4.98913,20.9983C 3.88513,20.9983 2.99914,20.1023 2.99914,18.9983L 3.00915,4.99832C 3.00915,3.89431 3.89412,2.99832 4.99914,2.99832 Z M 5,12L 5,14L 19,14L 19,12L 5,12 Z M 5,16L 5,18L 14,18L 14,16L 5,16 Z "/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g transform="matrix(-0.833333,0,0,0.833333,21.1169,3.61139)">
<path d="M2,2.4L0,2.4L0,20C0,21.097 0.903,22 2,22L16.4,22L16.4,20L2,20L2,2.4Z" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(0.827805,0,0,0.827805,0.399665,0.399669)">
<path d="M19,22L7,22C5.95,22 5,21.05 5,20L5,19L3,19L3,17L5,17L5,13L3,13L3,11L5,11L5,7L3,7L3,5L5,5L5,4C5,2.89 5.9,2 7,2L19,2C20.05,2 21,2.95 21,4L21,20C21,21.05 20.05,22 19,22ZM7,17L5,17L5,19L7,19L7,17ZM7,11L5,11L5,13L7,13L7,11ZM18.751,7.017L9.224,7.017L9.224,8.985L18.751,8.985L18.751,7.017ZM7,5L5,5L5,7L7,7L7,5Z" style="fill-rule:nonzero;"/>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<path d="M19,22L7,22C5.95,22 5,21.05 5,20L5,19L3,19L3,17L5,17L5,13L3,13L3,11L5,11L5,7L3,7L3,5L5,5L5,4C5,2.89 5.9,2 7,2L19,2C20.05,2 21,2.95 21,4L21,20C21,21.05 20.05,22 19,22ZM7,17L5,17L5,19L7,19L7,17ZM7,11L5,11L5,13L7,13L7,11ZM18.751,7.017L9.224,7.017L9.224,8.985L18.751,8.985L18.751,7.017ZM7,5L5,5L5,7L7,7L7,5Z" style="fill-rule:nonzero;"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-linejoin="round" d="M 14,3L 14,5L 17.59,5L 7.76,14.83L 9.17,16.24L 19,6.41L 19,10L 21,10L 21,3M 19,19L 5,19L 5,5L 12,5L 12,3L 5,3C 3.89,3 3,3.9 3,5L 3,19C 3,20.1 3.89,21 5,21L 19,21C 20.1,21 21,20.1 21,19L 21,12L 19,12L 19,19 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-linejoin="round" d="M 16.5,6L 16.5,17.5C 16.5,19.71 14.71,21.5 12.5,21.5C 10.29,21.5 8.5,19.71 8.5,17.5L 8.5,5C 8.5,3.62 9.62,2.5 11,2.5C 12.38,2.5 13.5,3.62 13.5,5L 13.5,15.5C 13.5,16.05 13.05,16.5 12.5,16.5C 11.95,16.5 11.5,16.05 11.5,15.5L 11.5,6L 10,6L 10,15.5C 10,16.88 11.12,18 12.5,18C 13.88,18 15,16.88 15,15.5L 15,5C 15,2.79 13.21,1 11,1C 8.79,1 7,2.79 7,5L 7,17.5C 7,20.54 9.46,23 12.5,23C 15.54,23 18,20.54 18,17.5L 18,6L 16.5,6 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 20.7062,7.04108C 21.0972,6.65002 21.0972,6.01703 20.7062,5.62708L 18.3702,3.29108C 17.9802,2.90002 17.3472,2.90002 16.9562,3.29108L 15.1242,5.12305L 18.8742,8.87305M 2.99916,17.248L 2.99916,20.998L 6.74916,20.998L 17.8142,9.93304L 14.0642,6.18304L 2.99916,17.248 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="1.33333" stroke-linejoin="miter" d="M 16,12L 16,4L 17,4L 17,2L 7,2L 7,4L 8,4L 8,12L 6,14L 6,16L 11.2,16L 11.2,22L 12.8,22L 12.8,16L 18,16L 18,14L 16,12 Z M 8.8,14L 10,12.8L 10,4L 14,4L 14,12.8L 15.2,14L 8.8,14 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 15.9994,11.9981L 15.9994,3.99807L 16.9994,3.99807L 16.9994,1.99807L 6.99939,1.99807L 6.99939,3.99807L 7.99939,3.99807L 7.99939,11.9981L 5.99939,13.9981L 5.99939,15.9981L 11.1994,15.9981L 11.1994,21.9981L 12.7994,21.9981L 12.7994,15.9981L 17.9994,15.9981L 17.9994,13.9981L 15.9994,11.9981 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 18.9994,12.998L 12.9994,12.998L 12.9994,18.998L 10.9994,18.998L 10.9994,12.998L 4.99936,12.998L 4.99936,10.998L 10.9994,10.998L 10.9994,4.99805L 12.9994,4.99805L 12.9994,10.998L 18.9994,10.998L 18.9994,12.998 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 11.9994,15.3943L 8.2364,17.6643L 9.2314,13.3833L 5.9094,10.5053L 10.2894,10.1293L 11.9994,6.09327L 13.7094,10.1293L 18.0894,10.5053L 14.7674,13.3833L 15.7624,17.6643M 21.9994,9.24227L 14.8084,8.62526L 11.9994,1.99827L 9.1904,8.62526L 1.9994,9.24227L 7.4544,13.9693L 5.8194,20.9983L 11.9994,17.2703L 18.1794,20.9983L 16.5444,13.9693L 21.9994,9.24227 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 11.9994,17.2708L 18.1794,20.9978L 16.5444,13.9688L 21.9994,9.24277L 14.8084,8.62477L 11.9994,1.99777L 9.1904,8.62477L 1.9994,9.24277L 7.4544,13.9688L 5.8194,20.9978L 11.9994,17.2708 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<polyline points="21.4,3.2 20.1,1.9 2.9,19.2 4.2,20.4 7.5,17.1 "/>
<g>
<g>
<g>
<path d="M21.4,11.6L16,6.1L6.1,16l5.5,5.5C12,21.8,12.5,22,13,22s1-0.2,1.4-0.6l7-7C21.8,14,22,13.5,22,13S21.8,11.9,21.4,11.6z"
/>
</g>
</g>
<g>
<g>
<path d="M12.4,2.6C12,2.2,11.5,2,11,2H4C2.9,2,2,2.9,2,4v7c0,0.5,0.2,1,0.6,1.4l2.3,2.3l9.8-9.8L12.4,2.6z M5.5,7
C4.7,7,4,6.3,4,5.5S4.7,4,5.5,4S7,4.7,7,5.5S6.3,7,5.5,7z"/>
</g>
</g>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="1.33333" stroke-linejoin="miter" d="M 21.41,11.58L 12.41,2.58C 12.04,2.21 11.53,2 11,2L 4,2C 2.89543,2 2,2.8954 2,4L 2,11C 2,11.53 2.21,12.04 2.59,12.41L 3,12.81C 3.9,12.27 4.94,12 6,12C 9.31371,12 12,14.6863 12,18C 12,19.06 11.72,20.09 11.18,21L 11.58,21.4C 11.95,21.78 12.47,22 13,22C 13.53,22 14.04,21.79 14.41,21.41L 21.41,14.41C 21.79,14.04 22,13.53 22,13C 22,12.47 21.79,11.96 21.41,11.58M 5.5,7C 4.67157,7 4,6.3284 4,5.5C 4,4.6716 4.67157,4 5.5,4C 6.32843,4 7,4.6716 7,5.5C 7,6.3284 6.32843,7 5.5,7M 10,19L 2,19L 2,17L 10,17L 10,19 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 5.49875,8.99951C 6.32776,8.99951 6.99875,8.32747 6.99875,7.49951C 6.99875,6.67057 6.32776,5.99951 5.49875,5.99951C 4.67076,5.99951 3.99875,6.67057 3.99875,7.49951C 3.99875,8.32747 4.67076,8.99951 5.49875,8.99951 Z M 17.4098,11.5801C 17.7738,11.9421 17.9988,12.4441 17.9988,12.9991C 17.9988,13.5511 17.7758,14.0501 17.4138,14.4131L 12.4138,19.4131C 12.0518,19.7741 11.5518,19.9991 10.9988,19.9991C 10.4478,19.9991 9.94781,19.7751 9.58481,19.4131L 2.58681,12.4151C 2.22382,12.0531 1.99881,11.5521 1.99881,10.9991L 1.99875,5.99959C 1.99875,4.89461 2.89475,3.99959 3.99875,3.99959L 8.99881,3.99911C 9.55181,3.99911 10.0518,4.22209 10.4138,4.58415L 17.4098,11.5801 Z M 13.5363,5.70634L 14.5363,4.70637L 21.411,11.581C 21.775,11.943 22,12.445 22,13C 22,13.552 21.777,14.051 21.415,14.414L 16.0358,19.7932L 15.0358,18.7932L 20.75,13L 13.5363,5.70634 Z "/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<path d="M17.41,11.58C17.774,11.942 17.999,12.444 17.999,12.999C17.999,13.551 17.776,14.05 17.414,14.413L12.414,19.413C12.052,19.774 11.552,19.999 10.999,19.999C10.448,19.999 9.948,19.775 9.585,19.413L2.587,12.415C2.224,12.053 1.999,11.552 1.999,10.999L1.999,6C1.999,4.895 2.895,4 3.999,4L8.999,3.999C9.552,3.999 10.052,4.222 10.414,4.584L17.41,11.58ZM13.536,5.706L14.536,4.706L21.411,11.581C21.775,11.943 22,12.445 22,13C22,13.552 21.777,14.051 21.415,14.414L16.036,19.793L15.036,18.793L20.75,13L13.536,5.706ZM11.016,18.685L16.598,13.104L10.556,7.062L5.003,12.672L11.016,18.685ZM5.499,9C6.328,9 6.999,8.327 6.999,7.5C6.999,6.671 6.328,6 5.499,6C4.671,6 3.999,6.671 3.999,7.5C3.999,8.327 4.671,9 5.499,9Z" style="fill-rule:nonzero;"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 5.49888,6.99854C 6.32788,6.99854 6.99888,6.3265 6.99888,5.49854C 6.99888,4.6696 6.32788,3.99854 5.49888,3.99854C 4.67088,3.99854 3.99888,4.6696 3.99888,5.49854C 3.99888,6.3265 4.67088,6.99854 5.49888,6.99854 Z M 21.4099,11.5796C 21.7739,11.9416 21.9989,12.4436 21.9989,12.9986C 21.9989,13.5506 21.7759,14.0496 21.4139,14.4126L 14.4139,21.4126C 14.0519,21.7736 13.5519,21.9986 12.9989,21.9986C 12.4479,21.9986 11.9479,21.7746 11.5849,21.4126L 2.58687,12.4146C 2.22388,12.0526 1.99887,11.5516 1.99887,10.9986L 1.99887,3.99862C 1.99887,2.89364 2.89487,1.99862 3.99887,1.99862L 10.9989,1.99862C 11.5519,1.99862 12.0519,2.2216 12.4139,2.58366L 21.4099,11.5796 Z M 13,20L 20,13L 11.5,4.50001L 4.5,11.5L 13,20 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="1.33333" stroke-linejoin="miter" d="M 21.41,11.58L 12.41,2.58C 12.035,2.2081 11.5281,1.9996 11,2L 4,2C 2.89543,2 2,2.8954 2,4L 2,11C 2.00223,11.5296 2.21441,12.0366 2.59,12.41L 2.99,12.81C 3.90239,12.2746 4.94213,11.9948 6,12C 9.31371,12 12,14.6863 12,18C 12.0049,19.056 11.7213,20.0933 11.18,21L 11.58,21.4C 11.9541,21.7817 12.4655,21.9978 13,22C 13.5296,21.9978 14.0366,21.7856 14.41,21.41L 21.41,14.41C 21.7856,14.0366 21.9978,13.5296 22,13C 22.0004,12.467 21.788,11.9558 21.41,11.58 Z M 5.5,7C 4.67157,7 4,6.3284 4,5.5C 4,4.6716 4.67157,4 5.5,4C 6.32843,4 7,4.6716 7,5.5C 7,6.3284 6.32843,7 5.5,7 Z M 10,19L 7,19L 7,22L 5,22L 5,19L 2,19L 2,17L 5,17L 5,14L 7,14L 7,17L 10,17L 10,19 Z "/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="24" height="24" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 5.49888,6.99854C 4.67088,6.99854 3.99888,6.32654 3.99888,5.49854C 3.99888,4.66956 4.67088,3.99854 5.49888,3.99854C 6.32788,3.99854 6.99888,4.66956 6.99888,5.49854C 6.99888,6.32654 6.32788,6.99854 5.49888,6.99854 Z M 21.4099,11.5796L 12.4139,2.58362C 12.0519,2.22162 11.5519,1.9986 10.9989,1.9986L 3.99887,1.9986C 2.89487,1.9986 1.99887,2.89362 1.99887,3.9986L 1.99887,10.9986C 1.99887,11.5516 2.22387,12.0526 2.58687,12.4146L 11.5849,21.4126C 11.9479,21.7746 12.4479,21.9986 12.9989,21.9986C 13.5519,21.9986 14.0519,21.7736 14.4139,21.4126L 21.4139,14.4126C 21.7759,14.0496 21.9989,13.5506 21.9989,12.9986C 21.9989,12.4436 21.7739,11.9416 21.4099,11.5796 Z "/>
</svg>
---
title: 01 - The Data Directory
pinned: false
tags: [Basics, Notebooks/Tutorial]
---
# 01 - The Data Directory
The data directory is where all your notes and attachments will be stored, it has the following structure:
```
/path/to/your/data_directory
├─┬ attachments
│ ├── foo.ext
│ ├── bar.ext
│ └── …
└─┬ notes
├── foo.md
├── bar.md
└── …
```
## Features
- The data directory gives you freedom since your notes are never locked into some sort of proprietary database, all your files use sane formats and are easily accessible and portable.
- You can open your data directory via `Notable -> Open Data Directory`.
- You can also change data directory at any time via `Notable -> Change Data Directory...`, the current content won't be copied over to the new one.
- You can edit your notes/attachments without even using Notable, all changes you make to them will be reflected here instantly. In fact you could also import a Markdown note simply by copying it into the `notes` directory.
## Advanced Features
The data directory allows you to leverage third-party tools to have powerful features like synchronization, versioning and encryption, we'll talk about those in the [advanced](@tag/Advanced) sections.
---
title: 02 - The Sidebar
pinned: false
tags: [Basics, Notebooks/Tutorial]
---
# 02 - The Sidebar
The sidebar is where where all your notes are categorized.
## Categories
- **All Notes**: This section contains all notes.
- **Favorites**: This section contains all notes you've favorited.
- **Notebooks**: This section contains all notes tagged with the special `Notebooks/*` tag.
- **Tags**: This section contains all notes tagged with any tag except the special ones: `Notebooks/*` and `Templates/*`.
- **Templates**: This section contains all notes tagged with the special `Templates/*` tag. These notes won't be displayed in any other category.
- **Untagged**: This section contains all notes that have no tags.
- **Trash**: This section contains all notes that have been deleted. These notes won't be displayed in any other category.
You can create sub-categories in the following sections: Notebooks, Tags and Templates by using nested tags.
---
title: 03 - The Middlebar
pinned: false
tags: [Basics, Notebooks/Tutorial]
---
# 03 - The Middlebar
The middlebar shows you all notes contained in the currently active category, properly ordered and filtered by the search query.
## Search
To search just type something in the search bar.
We use _fuzzy_ search, which basically means that you can omit some characters from the query: if for instance there's a note titled "Notable" you can also find it by typing "Noab" or "Notae", as long as the characters are in the right order the note will be found.
## New Note Button
Next to the search bar there's also a button for creating a new note.
## Sorting Order
Right below the search bar you can change the order in which notes are being displayed.
By default this is by `Title - Ascending`, so that the tutorial notes get displayed in order, but you might want to change this later to `Date Modified - Descending`, so that the most recently edited notes are at the top.
## Notes
Lastly there's the notes list.
Notes will have some badges if they are pinned, favorited or have attachments.
Pinned notes are displayed before the others.
If you right-click them you can access some commands, all of them are also available from the app menu, most of them are also available from the mainbar's toolbar.
---
title: 04 - The Mainbar
tags: [Basics, Notebooks/Tutorial]
---
# 04 - The Mainbar
The mainbar is where is where you can preview and edit the currently active note.
## Toolbar
The toolbar contains buttons for triggering actions to the current note, all of them are also accessible via shortcuts.
## Preview/Editor/Multi-Editor
Right below the toolbar there's the preview/editor/multi-editor area.
#### Preview
Rendered notes are displayed here.
#### Editor
When editing you'll use [CodeMirror](https://codemirror.net), a nice embeddable editor which comes with features like multi-cursor built-in. Read about the shortcuts this editor supports in the [Shortcuts](@note/07 - Shortcuts.md) tutorial note.
In case the built-in editor doesn't cut it there's also a button in the toolbar for opening the current note in the default Markdown editor. We just can't include into Notable all features and nice plugins you might have in your default editor.
#### Multi-Editor
When 2 or more notes are selected a multi-note editor will be displayed in the mainbar, read more about this [here](@note/09 - Multi-Note Editing.md).
---
title: 05 - Notes
tags: [Basics, Notebooks/Tutorial]
---
# 05 - Notes
## Syntax
Notes are written in [GitHub-flavored Markdown](https://guides.github.com/features/mastering-markdown), so you can write emojis (`:joy:` -> :joy:), ~~striketrhough~~ text etc. in a familiar fashion.
This also means that your notes aren't locked into any proprietary format.
Notes can have some metadata: if they are favorited or not, which tags they have, which attachments they have, etc. These metadata are written as Markdown front matter. This is taken care of for you.
## Attachments
Notes can have attachments, because sooner or later you'll want to save a file in a note, be it a boarding pass for your next trip or something else.
Attachments can be added by clicking the attachment into in the mainbar's toolbar. Attachments are simply copied into your data directory, under the `attachments` sub-directory.
You can open/remove them at any time.
---
title: 06 - Tags
pinned: false
tags: [Basics, Notebooks/Tutorial]
---
# 06 - Tags
Notes can have multiple tags, which are useful for better categorization.
## Syntax
- **Root**: Root tags don't contain any forward slash (`/`), and they will be rendered right below the special `Tags` section in the sidebar.
- **Nested**: Tags can also be nested, _indefinitely_, just write them like a path, separating the levels with a forward slash: `foo/bar/baz`.
## Special Tags
- **Notebooks**: You've probably noticed that Notable supports notebooks too. To create one just add a tag starting with `Notebooks/` to a note.
- **Templates**: Notable also supports Templates, to create one just add the `Templates` tag to a note. Of course nesting is supported here too, i.e. `Templates/Work`.
Feel free to use these features, if you don't need them their icons won't be displayed sidebar.
## Collapse/Expand
Tags with children can be collapsed/expanded, just right-click on them and select the option.
## Editing
There are multiple ways to add/remove tags:
- **Single-note editing**: There's a button in the toolbar for editing a note's tags.
- **Multi-note editing**: Tags can be added/removed from multiple notes at once via the [multi-note editing](@note/09 - Multi-Note Editing.md) features provided.
- **Advanced search & replace**: Alternatively you could just open your data directory with your editor and perform a search & replace there, this way you can also use advanced features like regexes.
---
title: 07 - Shortcuts
pinned: false
tags: [Intermediate, Notebooks/Tutorial]
---
# 07 - Shortcuts
The following are macOS shortcuts, if you're using a different OS replace <kbd>Cmd</kbd> with <kbd>Ctrl</kbd>, or <kbd>Alt</kbd> if <kbd>Ctrl</kbd> is already used.
## Note
- <kbd>Cmd+N</kbd> - New.
- <kbd>Cmd+Shift+N</kbd> - Duplicate.
- <kbd>Cmd+O</kbd> - Open in default app.
- <kbd>Cmd+Alt+R</kbd> - Reveal in Finder/Folder.
- <kbd>Cmd+Shift+E</kbd> - Toggle editing.
- <kbd>Cmd+Shift+P</kbd> - Toggle editing.
- <kbd>Cmd+Shift+T</kbd> - Toggle tags editing.
- <kbd>Cmd+Shift+A</kbd> - Toggle attachments editing.
- <kbd>Cmd+D</kbd> - Toggle favorite.
- <kbd>Cmd+P</kbd> - Toggle pin.
- <kbd>Cmd+Backspace</kbd> - Move to trash, when previewing.
- <kbd>Cmd+Alt+Backspace</kbd> - Move to trash, when editing.
- <kbd>Cmd+Shift+Backspace</kbd> - Restore from trash.
## Editor
- <kbd>Tab</kbd> - Indent current line.
- <kbd>Shift+Tab</kbd> - Outdent current line.
- <kbd>Cmd+Ctrl+Up</kbd> - Move current line up.
- <kbd>Cmd+Ctrl+Down</kbd> - Move current line down.
- <kbd>Alt+Click</kbd> - Add a new cursor.
- <kbd>Alt-Z</kbd> - Toggle line wrapping.
- <kbd>Cmd+Enter</kbd> - Toggle a todo's box.
- <kbd>Alt+D</kbd> - Toggle a todo's check mark.
## Multi-Editor
- <kbd>Cmd+Alt+A</kbd> - Select all notes.
- <kbd>Cmd+Alt+I</kbd> - Invert notes selection.
- <kbd>Cmd+Alt+C</kbd> - Clear notes selection.
## Navigation
- <kbd>Ctrl+Alt+Shift+Tab</kbd> - Previous tag.
- <kbd>Ctrl+Alt+Tab</kbd> - Next tag.
- <kbd>Up</kbd> - Previous note, when previewing.
- <kbd>Left</kbd> - Previous note, when previewing.
- <kbd>Ctrl+Shift+Tab</kbd> - Previous note.
- <kbd>Down</kbd> - Next note, when previewing.
- <kbd>Right</kbd> - Next note, when previewing.
- <kbd>Ctrl+Tab</kbd> - Next note.
## Others
- <kbd>Cmd+S</kbd> - Switch to preview mode, when editing.
- <kbd>Esc</kbd> - Close the multi-editor or switch to preview mode, when editing.
- <kbd>Cmd+F</kbd> - Focus to search bar.
- <kbd>Cmd+Alt+F</kbd> - Toggle focus mode.
---
title: 08 - Importing
tags: [Intermediate, Notebooks/Tutorial]
---
# 08 - Importing
You can import the following formats via `Notable -> Import`:
- Markdown files with extension: `md`, `mkd`, `mdwn`, `mdown`, `markdown`, `markdn`, `mdtxt` or `mdtext`
- Evernotes' exports with extension: `enex`
Alternatively you could also just put your Markdown notes into the `notes` sub-directory into your data directory.
The more notes you are importing the longer it will take, in some cases the interface may freeze until the operation is completed.
Newly imported tags will be tagged with a special `Imported-XXXX` tag, so that you can easily edit them later using [multi-note editing](@note/09 - Multi-Note Editing.md).
---
title: 09 - Multi-Note Editing
tags: [Intermediate, Notebooks/Tutorial]
---
# 09 - Multi-Note Editing
## Built-in
Some multi-note editing features are built-in.
There are multiple ways to select notes:
- **Click**: you can toggle a note's selection just by clicking it in the middlebar with <kbd>Cmd+Click</kbd> on macOS, or with <kbd>Ctrl+Click</kbd> elsewhere.
- **Shortcuts**: some shortcuts are provided under the `Edit` menu entry for selecting all notes, inverting the selection and unselecting all of them.
When 2 or more notes are selected a multi-note editor will be displayed in the mainbar, you'll be asked for confirmation for all changes that will mutate the notes.
These are the actions you can take on selected note:
- Favorite/unfavorite them.
- Pin/unpin them.
- Move to trash/restore/permanently delete them.
- Open them in the default app.
- Add one or multiple tags to them. Useful when importing exported Evernote notebooks since the notebook tag is not preserved.
- Remove one or multiple tags from them. Useful when editing imported notes, which get automatically tagged with a special `Imported-XXXX` tag, so that you can easily select them all for multi-editing.
## Advanced
If you need more advanced multi-note editing, like global search & replace, remember that your notes are just plain Markdown files.
You could open your data directory into your favorite editor of choice and perform the search & replace there, this way you can also use advanced features like regexes.
All the edits performed with a third-party application will be reflected into Notable immediately.
---
title: 10 - Linking Attachments/Notes/Tags
tags: [Intermediate, Notebooks/Tutorial]
attachments: [icon_small.png]
---
# 10 - Linking Attachments/Notes/Tags
Sometimes, like when writing a tutorial for a note-taking app :wink:, you may need to link to other notes or embed a few attachments. Notable makes this easy for you.
These special links can also be right-clicked so that you can perform some actions on them.
> **Note**: You don't actually need to escape these special urls, it's done for you, check the actual source of this note.
## Attachments
Attachments can be rendered inline, linked to, and linked to via a button. The `@attachmet` token is used for this.
##### Syntax
```markdown
![Icon](@attachment/icon_small.png)
[Icon](@attachment/icon_small.png)
[](@attachment/icon_small.png)
```
##### Result
![Icon](@attachment/icon_small.png)
[Icon](@attachment/icon_small.png)
[](@attachment/icon_small.png)
## Notes
Notes can be linked to, and linked to via a button. The `@note` token is used for this.
##### Syntax
```markdown
[Shortcuts](@note/07 - Shortcuts.md)
[](@note/07 - Shortcuts.md)
```
##### Result
[Shortcuts](@note/07 - Shortcuts.md)
[](@note/07 - Shortcuts.md)
## Tags
Tags can be linked to, and linked to via a button. The `@tag` token is used for this.
##### Syntax
```markdown
[Basics](@tag/Basics)
[](@tag/Basics)
```
##### Result
[Basics](@tag/Basics)
[](@tag/Basics)
---
title: 11 - Synchronization
pinned: false
tags: [Advanced, Notebooks/Tutorial]
---
# 11 - Synchronization
Notable doesn't have synchronization built-in, but you can have your data synchronized across computers just by putting the data directory into a shared folder, like Dropbox/Google Drive/etc.
This way the third-party service will take care of the synchronization.
---
title: 12 - Mobile Editing
pinned: false
tags: [Advanced, Notebooks/Tutorial]
---
# 12 - Mobile Editing
Notable doesn't have a mobile app yet, but there are many apps for editing Markdown files already on mobile.
If you put your data directory into a shared folder, like Dropbox/Google Drive/etc. you could use any of those apps for editing notes or making new ones.
It wouldn't be perfect, especially if you need to change some metadata or add an attachment, but it would be ok most of the times.
---
title: 13 - Collaborative Editing
pinned: false
tags: [Advanced, Notebooks/Tutorial]
---
# 13 - Collaborative Editing
Notable doesn't have collaborative editing built-in, but if you put your data directory in a shared folder, like Dropbox/Google Drive/etc. then multiple people can access it and make edits at the same time.
Just make sure no 2 people are working on the same note at the same time, or some work might get lost.
This is by no means a perfect solution though.
---
title: 14 - Version Control
pinned: false
tags: [Advanced, Notebooks/Tutorial]
---
# 14 - Version Control
Notable doesn't have version control built-in, but since the data directory is just a regular directory you could make it a git repository and every once in a while take a snapshot of your notes, or pehaps a small script could make commits automatically for you everytime something changes.
If there's a big demand for this perhaps support for version controlled notes can be added to Notable itself, [let us know](https://github.com/fabiospampinato/notable/issues) if you need this.
---
title: 15 - Encrypted Notes
pinned: false
tags: [Advanced, Notebooks/Tutorial]
---
# 15 - Encrypted Notes
Notable doesn't support encrypted notes yet, but if you really need this you could make an encrypted image on your computer and put a data directory in there.
This way the third-party program will take care of the encryption.
If there's a big demand for this perhaps support for encrypted notes can be added to Notable itself, [let us know](https://github.com/fabiospampinato/notable/issues) if you need this.
---
title: 'Welcome to Notable :raising_hand_woman:'
pinned: true
attachments: [icon.png]
tags: [Notebooks/Tutorial]
---
# Welcome to Notable :raising_hand_woman:
<p align="center">
<img src="@attachment/icon.png" width="192">
</p>
## Tutorial
Some tutorial notes have been added to your data directory.
They will guide you towards the main features Notable provides.
Once you're done exploring feel free to permanently delete them, if at any point you'd like to read them again you can re-add them to your data directory or just read the online version via the `Help -> Tutorial` menu item.
---
title: 'Wrapping up :tada:'
pinned: false
tags: [Notebooks/Tutorial]
---
# Wrapping up :tada:
Awesome, you've reached the end of the tutorial!
The next step is deleting all these tutorial notes, you can do this one-by-one, using multi-note editing, or you could just trash the whole `notes` sub-directory from your data directory.
## Feedback
If you've reached this far chances are you're considering using Notable as your main note-taking app, that's great!
Feel free to [contact us](https://github.com/fabiospampinato/notable/issues) about any issues you may encounter, any features suggestions and generally sharing your opinion about Notable and how we can improve it.
Have a wonderful day! :wave:
/* IMPORT */
import * as path from 'path';
import Settings from './settings';
/* CONFIG */
const Config = {
get cwd () {
return Settings.get ( 'cwd' );
},
attachments: {
get path () {
const cwd = Config.cwd;
return cwd ? path.join ( cwd, 'attachments' ) : undefined;
},
globs: ['**/*', '!**/.*'],
re: /attachments(?:\\|\/)(?!\.).*$/, // Excluding dot files
token: '@attachment' // Usable in urls
},
notes: {
get path () {
const cwd = Config.cwd;
return cwd ? path.join ( cwd, 'notes' ) : undefined;
},
globs: ['**/*.{md,mkd,mdwn,mdown,markdown,markdn,mdtxt,mdtext}'],
re: /\.(?:md|mkd|mdwn|mdown|markdown|markdn|mdtxt|mdtext)$/,
token: '@note' // Usable in urls
},
tags: {
token: '@tag' // Usable in urls
},
sorting: {
by: Settings.get ( 'sorting.by' ),
type: Settings.get ( 'sorting.type' )
},
flags: {
TUTORIAL: true, // Write the tutorial notes upon first instantiation
OPTIMISTIC_RENDERING: true // Assume writes are successful in order to render changes faster
}
};
/* EXPORT */
export default Config;
/* ENVIRONMENT */
const Environment = {
environment: process.env.NODE_ENV,
isDevelopment: ( process.env.NODE_ENV !== 'production' ),
wds: { // Webpack Development Server
protocol: 'http',
hostname: 'localhost',
port: process.env.ELECTRON_WEBPACK_WDS_PORT
}
};
/* EXPORT */
export default Environment;
/* IMPORT */
import * as os from 'os';
import * as Store from 'electron-store';
/* SETTINGS */
const Settings = new Store ({
name: '.notable',
cwd: os.homedir (),
defaults: {
cwd: undefined,
codemirror: {
options: {
lineWrapping: true
}
},
sorting: {
by: 'title',
type: 'ascending'
},
tutorial: false // Did we import the tutorial yet?
}
});
/* EXPORT */
export default Settings;
/* GLOBALS */
declare const __static: string;
declare const Svelto: any;
declare const $: any;
/* BASE OBJECTS */
type AttachmentObj = {
fileName: string,
filePath: string
};
type AttachmentsObj = {
[fileName: string]: AttachmentObj
};
type NoteObj = {
content: string,
filePath: string,
hash: number,
plainContent: string,
metadata: {
attachments: string[],
created?: number,
dateCreated: Date,
dateModified: Date,
deleted: boolean,
favorited: boolean,
pinned: boolean,
stat: import ( 'fs' ).Stats,
tags: string[],
title: string
}
};
type NotesObj = {
[filePath: string]: NoteObj
};
type TagObj = {
collapsed: boolean,
name: string,
notes: NoteObj[],
path: string,
tags: {
[name: string]: TagObj
}
};
type TagsObj = {
[filePath: string]: TagObj
};
/* MAIN CONTAINERS STATES */
type AttachmentState = {};
type AttachmentsState = {
attachments: AttachmentsObj
editing: boolean
};
type EditorState = {
editing: boolean
};
type EditorEditingState = undefined | {
filePath: string,
scrollTop: number,
selections: any[]
};
type EditorPreviewingState = undefined | {
filePath: string,
scrollTop: number
};
type ImportState = {};
type LoadingState = {
loading: boolean
};
type MultiEditorState = {
notes: NoteObj[]
};
type NoteState = {
note: NoteObj | undefined
};
type NotesState = {
notes: NotesObj
};
type SearchState = {
query: string,
notes: NoteObj[]
};
type SortingState = {
by: import ( '@renderer/utils/sorting' ).SortingBys,
type: import ( '@renderer/utils/sorting' ).SortingTypes
};
type TagState = {
tag: string
};
type TagsState = {
tags: TagsObj,
editing: boolean
};
type TrashState = {};
type TutorialState = {};
type WindowState = {
focus: boolean,
fullscreen: boolean
};
/* MAIN */
type MainState = {
attachment: AttachmentState,
attachments: AttachmentsState,
editor: EditorState,
import: ImportState,
loading: LoadingState,
multiEditor: MultiEditorState,
note: NoteState,
notes: NotesState,
search: SearchState,
sorting: SortingState,
tag: TagState,
tags: TagsState,
trash: TrashState,
tutorial: TutorialState,
window: WindowState
};
type MainCTX = {
_prevFlags?: StateFlags,
state: MainState,
suspend (),
unsuspend (),
suspendMiddlewares (),
unsuspendMiddlewares (),
refresh (),
listen (),
attachment: import ( '@renderer/containers/main/attachment' ).default,
attachments: import ( '@renderer/containers/main/attachments' ).default,
editor: import ( '@renderer/containers/main/editor' ).default,
import: import ( '@renderer/containers/main/import' ).default,
loading: import ( '@renderer/containers/main/loading' ).default,
multiEditor: import ( '@renderer/containers/main/multi_editor' ).default,
note: import ( '@renderer/containers/main/note' ).default,
notes: import ( '@renderer/containers/main/notes' ).default,
search: import ( '@renderer/containers/main/search' ).default,
sorting: import ( '@renderer/containers/main/sorting' ).default,
tag: import ( '@renderer/containers/main/tag' ).default,
tags: import ( '@renderer/containers/main/tags' ).default,
trash: import ( '@renderer/containers/main/trash' ).default,
tutorial: import ( '@renderer/containers/main/tutorial' ).default,
window: import ( '@renderer/containers/main/window' ).default
};
type IMain = MainCTX & { ctx: MainCTX };
/* CWD */
type CWDState = {};
type CWDCTX = {
tutorial: import ( '@renderer/containers/main/tutorial' ).default
};
type ICWD = CWDCTX & { ctx: CWDCTX };
/* OTHERS */
type StateFlags = {
hasNote: boolean,
isAttachmentsEditing: boolean,
isEditorEditing: boolean,
isMultiEditorEditing: boolean,
isNoteDeleted: boolean,
isNoteFavorited: boolean,
isNotePinned: boolean,
isTagsEditing: boolean
};
/* IMPORT */
import {app, ipcMain as ipc} from 'electron';
import {autoUpdater} from 'electron-updater';
import * as is from 'electron-is';
import * as fs from 'fs';
import pkg from '@root/package.json';
import Config from '@common/config';
import Environment from '@common/environment';
import CWD from './windows/cwd';
import Main from './windows/main';
import Window from './windows/window';
/* APP */
class App {
/* VARIABLES */
win: Window;
/* CONSTRUCTOR */
constructor () {
this.init ();
this.events ();
}
/* SPECIAL */
init () {
this.initAbout ();
this.initContextMenu ();
}
initAbout () {
if ( !is.macOS () ) return;
const {productName, version, license, author} = pkg;
app.setAboutPanelOptions ({
applicationName: productName,
applicationVersion: version,
copyright: `${license} © ${author.name}`,
version: ''
});
}
initContextMenu () {}
async initDebug () {
if ( !Environment.isDevelopment ) return;
const {default: installExtension, REACT_DEVELOPER_TOOLS} = await import ( 'electron-devtools-installer' );
installExtension ( REACT_DEVELOPER_TOOLS );
}
events () {
this.___windowAllClosed ();
this.___activate ();
this.___ready ();
this.___cwdChanged ();
}
/* WINDOW ALL CLOSED */
___windowAllClosed () {
app.on ( 'window-all-closed', this.__windowAllClosed.bind ( this ) );
}
__windowAllClosed () {
if ( is.macOS () ) return;
app.quit ();
}
/* ACTIVATE */
___activate () {
app.on ( 'activate', this.__activate.bind ( this ) );
}
__activate () {
if ( this.win && this.win.win ) return;
this.load ();
}
/* READY */
___ready () {
app.on ( 'ready', this.__ready.bind ( this ) );
}
__ready () {
this.initDebug ();
autoUpdater.checkForUpdatesAndNotify ();
this.load ();
}
/* CWD CHANGED */
___cwdChanged () {
ipc.on ( 'cwd-changed', this.__cwdChanged.bind ( this ) );
}
__cwdChanged () {
if ( this.win ) this.win.win.close ();
this.load ();
}
/* API */
load () {
const cwd = Config.cwd;
if ( cwd && fs.existsSync ( cwd ) ) {
this.win = new Main ();
} else {
this.win = new CWD ();
}
}
}
/* EXPORT */
export default App;
/* IMPORT */
import App from './app';
/* MAIN */
new App ();
/* IMPORT */
import * as _ from 'lodash';
/* MENU */
const Menu = {
filterTemplate ( template ) { // Removes items with `visible == false`
return _.cloneDeepWith ( template, val => {
if ( !_.isArray ( val ) ) return;
return val.filter ( ele => !_.isObject ( ele ) || !ele.hasOwnProperty ( 'visible' ) || ele.visible ).map ( Menu.filterTemplate );
});
}
};
/* EXPORT */
export default Menu;
/* IMPORT */
import Route from './route';
/* CWD */
class CWD extends Route {
/* CONSTRUCTOR */
constructor ( name = 'cwd', options = { resizable: false, minWidth: 560, minHeight: 470 }, stateOptions = { defaultWidth: 560, defaultHeight: 470 } ) {
super ( name, options, stateOptions );
}
}
/* EXPORT */
export default CWD;
/* IMPORT */
import * as _ from 'lodash';
import {app, ipcMain as ipc, Menu, MenuItemConstructorOptions, shell} from 'electron';
import * as is from 'electron-is';
import pkg from '@root/package.json';
import Environment from '@common/environment';
import UMenu from '@main/utils/menu';
import Route from './route';
/* MAIN */
class Main extends Route {
/* CONSTRUCTOR */
constructor ( name = 'main', options = { minWidth: 685, minHeight: 425 }, stateOptions = { defaultWidth: 850, defaultHeight: 525 } ) {
super ( name, options, stateOptions );
}
/* SPECIAL */
initLocalShortcuts () {}
initMenu ( flags: StateFlags | false = false ) {
const template: MenuItemConstructorOptions[] = UMenu.filterTemplate ([
{
label: app.getName (),
submenu: [
{
role: 'about',
visible: is.macOS ()
},
{
type: 'separator'
},
{
label: 'Import...',
click: () => this.win.webContents.send ( 'import' )
},
{
type: 'separator'
},
{
label: 'Open Data Directory',
click: () => this.win.webContents.send ( 'cwd-open-in-app' )
},
{
label: 'Change Data Directory...',
click: () => this.win.webContents.send ( 'cwd-change' )
},
{
type: 'separator',
visible: is.macOS ()
},
{
role: 'services',
submenu: [] ,
visible: is.macOS ()
},
{
type: 'separator',
visible: is.macOS ()
},
{
role: 'hide',
visible: is.macOS ()
},
{
role: 'hideothers',
visible: is.macOS ()
},
{
role: 'unhide',
visible: is.macOS ()
},
{
type: 'separator',
visible: is.macOS ()
},
{ role: 'quit' }
]
},
{
label: 'Note',
submenu: [
{
label: 'New',
accelerator: 'CommandOrControl+N',
enabled: flags && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-new' )
},
{
label: 'Duplicate',
accelerator: 'CommandOrControl+Shift+N',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-duplicate' )
},
{
type: 'separator'
},
{
label: 'Open in Default App',
accelerator: 'CommandOrControl+O',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-open-in-app' )
},
{
label: `Reveal in ${is.macOS () ? 'Finder' : 'Folder'}`,
accelerator: 'CommandOrControl+Alt+R',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-reveal' )
},
{
type: 'separator'
},
{
label: flags && flags.hasNote && flags.isEditorEditing ? 'Stop Editing' : 'Edit',
accelerator: 'CommandOrControl+Shift+P',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-edit-toggle' )
},
{
label: flags && flags.hasNote && flags.isTagsEditing ? 'Stop Editing Tags' : 'Edit Tags',
accelerator: 'CommandOrControl+Shift+T',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-edit-tags-toggle' )
},
{
label: flags && flags.hasNote && flags.isAttachmentsEditing ? 'Stop Editing Attachments' : 'Edit Attachments',
accelerator: 'CommandOrControl+Shift+A',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-edit-attachments-toggle' )
},
{
type: 'separator'
},
{
label: flags && flags.hasNote && flags.isNoteFavorited ? 'Unfavorite' : 'Favorite',
accelerator: 'CommandOrControl+D',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-favorite-toggle' )
},
{
label: flags && flags.hasNote && flags.isNotePinned ? 'Unpin' : 'Pin',
accelerator: 'CommandOrControl+P',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
click: () => this.win.webContents.send ( 'note-pin-toggle' )
},
{
type: 'separator'
},
{
label: 'Move to Trash',
accelerator: 'CommandOrControl+Backspace',
enabled: flags && flags.hasNote && !flags.isNoteDeleted && !flags.isMultiEditorEditing,
visible: flags && flags.hasNote && !flags.isNoteDeleted && !flags.isEditorEditing,
click: () => this.win.webContents.send ( 'note-move-to-trash' )
},
{
label: 'Move to Trash',
accelerator: 'CommandOrControl+Alt+Backspace',
enabled: flags && flags.hasNote && !flags.isNoteDeleted && !flags.isMultiEditorEditing,
visible: flags && flags.hasNote && !flags.isNoteDeleted && flags.isEditorEditing,
click: () => this.win.webContents.send ( 'note-move-to-trash' )
},
{
label: 'Restore',
accelerator: 'CommandOrControl+Shift+Backspace',
enabled: flags && flags.hasNote && flags.isNoteDeleted && !flags.isMultiEditorEditing,
visible: flags && flags.hasNote && flags.isNoteDeleted,
click: () => this.win.webContents.send ( 'note-restore' )
},
{
label: 'Permanently Delete',
enabled: flags && flags.hasNote && !flags.isMultiEditorEditing,
visible: flags && flags.hasNote,
click: () => this.win.webContents.send ( 'note-permanently-delete' )
}
]
},
{
label: 'Edit',
submenu: [
// { role: 'undo' },
// { role: 'redo' },
// { type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'pasteandmatchstyle' },
{ role: 'delete' },
{ role: 'selectall' },
{
type: 'separator'
},
{
label: 'Select Notes - All',
accelerator: 'CommandOrControl+Alt+A',
click: () => this.win.webContents.send ( 'multi-editor-select-all' )
},
{
label: 'Select Notes - Invert',
accelerator: 'CommandOrControl+Alt+I',
click: () => this.win.webContents.send ( 'multi-editor-select-invert' )
},
{
label: 'Select Notes - Clear',
accelerator: 'CommandOrControl+Alt+C',
click: () => this.win.webContents.send ( 'multi-editor-select-clear' )
},
{
type: 'separator'
},
{
label: 'Empty Trash',
click: () => this.win.webContents.send ( 'trash-empty' )
},
{
type: 'separator',
visible: is.macOS ()
},
{
label: 'Speech',
submenu: [
{ role: 'startspeaking' },
{ role: 'stopspeaking' }
],
visible: is.macOS ()
}
]
},
{
label: 'View',
submenu: [
{
role: 'reload',
visible: Environment.isDevelopment
},
{
role: 'forcereload',
visible: Environment.isDevelopment
},
{
role: 'toggledevtools',
visible: Environment.isDevelopment
},
{
type: 'separator',
visible: Environment.isDevelopment
},
{ role: 'resetzoom' },
{ role: 'zoomin' },
{ role: 'zoomout' },
{ type: 'separator' },
{
label: 'Toggle Focus Mode',
accelerator: 'CommandOrControl+Alt+F',
click: () => this.win.webContents.send ( 'window-focus-toggle' )
},
{ role: 'togglefullscreen' }
]
},
{
role: 'window',
submenu: [
{ role: 'close' },
{ role: 'minimize' },
{
role: 'zoom',
visible: is.macOS ()
},
{
type: 'separator'
},
{
label: 'Search',
accelerator: 'CommandOrControl+F',
click: () => this.win.webContents.send ( 'search-focus' )
},
{
type: 'separator'
},
{
label: 'Previous Tag',
accelerator: 'Control+Alt+Shift+Tab',
click: () => this.win.webContents.send ( 'tag-previous' )
},
{
label: 'Next Tag',
accelerator: 'Control+Alt+Tab',
click: () => this.win.webContents.send ( 'tag-next' )
},
{
type: 'separator'
},
{
label: 'Previous Note',
accelerator: 'Control+Shift+Tab',
click: () => this.win.webContents.send ( 'search-previous' )
},
{
label: 'Next Note',
accelerator: 'Control+Tab',
click: () => this.win.webContents.send ( 'search-next' )
},
{
type: 'separator',
visible: is.macOS ()
},
{
role: 'front',
visible: is.macOS ()
}
]
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click: () => shell.openExternal ( pkg.homepage )
},
{
label: 'Tutorial',
click: () => this.win.webContents.send ( 'tutorial-dialog' )
},
{
label: 'Support',
click: () => shell.openExternal ( pkg.bugs.url )
},
{ type: 'separator' },
{
label: 'View License',
click: () => shell.openExternal ( `${pkg.homepage}/blob/master/LICENSE` )
}
]
}
]);
const menu = Menu.buildFromTemplate ( template );
Menu.setApplicationMenu ( menu );
}
events () {
super.events ();
this.___fullscreenEnter ();
this.___fullscreenLeave ();
this.___flagsUpdate ();
this.___navigateUrl ();
}
/* FULLSCREEN ENTER */
___fullscreenEnter () {
this.win.on ( 'enter-full-screen', this.__fullscreenEnter.bind ( this ) );
}
__fullscreenEnter () {
this.win.webContents.send ( 'window-fullscreen-set', true );
}
/* FULLSCREEN LEAVE */
___fullscreenLeave () {
this.win.on ( 'leave-full-screen', this.__fullscreenLeave.bind ( this ) );
}
__fullscreenLeave () {
this.win.webContents.send ( 'window-fullscreen-set', false );
}
/* FLAGS UPDATE */
___flagsUpdate () {
ipc.on ( 'flags-update', this.__flagsUpdate.bind ( this ) );
}
__flagsUpdate ( event, flags ) {
this.initMenu ( flags );
}
/* NAVIGATE URL */
___navigateUrl () {
this.win.webContents.on ( 'new-window', this.__navigateUrl.bind ( this ) );
}
__navigateUrl ( event, url ) {
if ( url === this.win.webContents.getURL () ) return;
event.preventDefault ();
shell.openExternal ( url );
}
}
/* EXPORT */
export default Main;
/* IMPORT */
import * as path from 'path';
import {format as formatURL} from 'url';
import Environment from '@common/environment';
import Window from './window';
/* ROUTE */
class Route extends Window {
/* API */
load () {
const route = this.name;
if ( Environment.isDevelopment ) {
const {protocol, hostname, port} = Environment.wds;
this.win.loadURL ( `${protocol}://${hostname}:${port}?route=${route}` );
} else {
this.win.loadURL ( formatURL ({
pathname: path.join ( __dirname, 'index.html' ),
protocol: 'file',
slashes: true,
query: {
route
}
}));
}
}
}
/* EXPORT */
export default Route;
/* IMPORT */
import * as _ from 'lodash';
import * as path from 'path';
import {app, BrowserWindow} from 'electron';
import * as is from 'electron-is';
import * as windowStateKeeper from 'electron-window-state';
import Environment from '@common/environment';
/* WINDOW */
class Window {
/* VARIABLES */
name: string;
win: BrowserWindow;
options: object;
stateOptions: object;
/* CONSTRUCTOR */
constructor ( name, options = {}, stateOptions = {} ) {
this.name = name;
this.options = options;
this.stateOptions = stateOptions;
this.init ();
this.events ();
}
/* SPECIAL */
init () {
this.initWindow ();
this.initDebug ();
this.initLocalShortcuts ();
this.initMenu ();
this.load ();
}
initWindow () {
this.win = this.make ();
}
initDebug () {
if ( !Environment.isDevelopment ) return;
this.win.webContents.openDevTools ();
this.win.webContents.on ( 'devtools-opened', () => {
this.win.focus ();
setImmediate ( () => this.win.focus () );
});
}
initMenu () {}
initLocalShortcuts () {}
events () {
this.___readyToShow ();
this.___closed ();
}
/* READY TO SHOW */
___readyToShow () {
this.win.on ( 'ready-to-show', this.__readyToShow.bind ( this ) );
}
__readyToShow () {
this.win.show ();
this.win.focus ();
}
/* CLOSED */
___closed () {
this.win.on ( 'closed', this.__closed.bind ( this ) );
}
__closed () {
delete this.win;
}
/* API */
make ( id = this.name, options = this.options, stateOptions = this.stateOptions ) {
stateOptions = _.merge ({
file: `${id}.json`,
defaultWidth: 600,
defaultHeight: 600
}, stateOptions );
const state = windowStateKeeper ( stateOptions ),
dimensions = _.pick ( state, ['x', 'y', 'width', 'height'] );
options = _.merge ( dimensions, {
frame: !is.macOS (),
autoHideMenuBar: !is.macOS (),
backgroundColor: '#fdfdfd',
icon: path.join ( __static, 'images', `icon.${is.windows () ? 'ico' : 'png'}` ),
show: false,
title: app.getName (),
titleBarStyle: 'hiddenInset',
webPreferences: {
webSecurity: false
}
}, options );
const win = new BrowserWindow ( options );
state.manage ( win );
return win;
}
load () {}
}
/* EXPORT */
export default Window;
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import CWD from '@renderer/containers/cwd';
/* CONTENT */
const Content = ({ select }) => (
<>
<div className="layout-content container sharp centerer">
<div className="button centered compact circular giant secondary z-depth-3" title="Select..." onClick={select}>
<i className="icon">folder_search</i>
</div>
</div>
<div className="layout-content container sharp details">
<p>The data directory is where all notes and their attachments are stored.</p>
<p>If you want synchronization across computers, or you want access to your data from mobile, consider putting the data directory inside Dropbox/Google Drive/etc.</p>
<p>You can change this later.</p>
</div>
</>
);
/* EXPORT */
export default connect ({
container: CWD,
selector: ({ container }) => ({
select: container.select
})
})( Content );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import CWD from '@renderer/containers/cwd';
/* FOOTER */
const Footer = ({ select, selectDefault }) => (
<div className="layout-footer container sharp">
<div className="multiple center-y">
<div className="button" onClick={selectDefault}>
<span>Use Default</span>
<span className="xsmall disabled">~/.notable</span>
</div>
<div className="spacer"></div>
<div className="button secondary" onClick={select}>Select</div>
</div>
</div>
);
/* EXPORT */
export default connect ({
container: CWD,
selector: ({ container }) => ({
select: container.select,
selectDefault: container.selectDefault
})
})( Footer );
/* IMPORT */
import * as React from 'react';
import * as is from 'electron-is';
/* HEADER */
const Header = () => {
if ( !is.macOS () ) return null;
return (
<div className="layout-header centerer">
<div className="title small">Select Data Directory</div>
</div>
);
};
/* EXPORT */
export default Header;
/* IMPORT */
import * as React from 'react';
import Header from './header';
import Content from './content';
import Footer from './footer';
/* CWD */
const CWD = () => (
<div id="cwd" className="app-wrapper layout">
<Header />
<Content />
<Footer />
</div>
);
/* EXPORT */
export default CWD;
/* IMPORT */
import {shell} from 'electron';
import * as is from 'electron-is';
import * as React from 'react';
import pkg from '@root/package.json';
/* ERROR BOUNDARY */
class ErrorBoundary extends React.Component<any, { error?: Error }> {
/* STATE */
state = {
error: undefined as Error | undefined
};
/* SPECIAL */
componentDidCatch ( error: Error ) {
this.setState ({ error });
}
/* API */
report = () => {
shell.openExternal ( pkg.bugs.url );
}
/* RENDER */
render () {
const {error} = this.state;
if ( !error ) return this.props.children;
const isMacOS = is.macOS ();
return (
<div id="error-boundary" className="app-wrapper layout">
{!isMacOS ? null : (
<div className="layout-header centerer">
<div className="title small">An Error Occurred!</div>
</div>
)}
<div className="layout-content container">
{isMacOS ? null : (
<h1 className="text-center">An Error Occurred!</h1>
)}
<pre className="error-stack">{error.stack}</pre>
</div>
<div className="layout-footer container centerer">
<div className="button red" onClick={this.report}>Report It</div>
</div>
</div>
);
}
}
/* EXPORT */
export default ErrorBoundary;
/* IMPORT */
import * as _ from 'lodash';
import * as contextMenu from 'electron-context-menu';
import Dialog from 'electron-dialog';
import * as is from 'electron-is';
import {connect} from 'overstated';
import {Component} from 'react-component-renderless';
import Main from '@renderer/containers/main';
/* CONTEXT MENU */
class ContextMenu extends Component<{ container: IMain }, undefined> {
/* VARIABLES */
ele; attachment; note; tag; // Globals pointing to the current element/attachment/note/tag object
/* SPECIAL */
componentDidMount () {
this.initAttachmentMenu ();
this.initNoteMenu ();
this.initNoteTagMenu ();
this.initTagMenu ();
this.initTrashMenu ();
this.initFallbackMenu ();
}
/* UTILITIES */
_getItem ( x, y, selector ) {
const eles = document.elementsFromPoint ( x, y );
return $(eles).filter ( selector )[0];
}
_makeMenu ( selector: string | Function = '*', items: any[] = [], itemsUpdater = _.noop ) {
contextMenu ({
prepend: () => items,
shouldShowMenu: ( event, { x, y } ) => {
const ele = _.isString ( selector ) ? this._getItem ( x, y, selector ) : selector ( x, y );
if ( !ele ) return false;
this.ele = ele;
itemsUpdater ( items );
return true;
}
});
}
/* INIT */
initAttachmentMenu () {
this._makeMenu ( '.attachment', [
{
label: 'Open',
click: () => this.props.container.attachment.openInApp ( this.attachment )
},
{
label: `Reveal in ${is.macOS () ? 'Finder' : 'Folder'}`,
click: () => this.props.container.attachment.reveal ( this.attachment )
},
{
type: 'separator'
},
{
label: 'Rename',
click: () => Dialog.alert ( 'Simply rename the actual attachment file while Notable is open' )
},
{
label: 'Delete',
click: () => this.props.container.note.removeAttachment ( undefined, this.attachment )
}
], this.updateAttachmentMenu.bind ( this ) );
}
initNoteMenu () {
this._makeMenu ( '.note-button, .editor .note', [
{
label: 'Duplicate',
click: () => this.props.container.note.duplicate ( this.note )
},
{
type: 'separator'
},
{
label: 'Open in Default App',
click: () => this.props.container.note.openInApp ( this.note )
},
{
label: `Reveal in ${is.macOS () ? 'Finder' : 'Folder'}`,
click: () => this.props.container.note.reveal ( this.note )
},
{
type: 'separator'
},
{
label: 'Favorite',
click: () => this.props.container.note.toggleFavorite ( this.note, true )
},
{
label: 'Unfavorite',
click: () => this.props.container.note.toggleFavorite ( this.note, false )
},
{
type: 'separator'
},
{
label: 'Move to Trash',
click: () => this.props.container.note.toggleDeleted ( this.note, true )
},
{
label: 'Restore',
click: () => this.props.container.note.toggleDeleted ( this.note, false )
},
{
label: 'Permanently Delete',
click: () => this.props.container.note.delete ( this.note )
}
], this.updateNoteMenu.bind ( this ) );
}
initNoteTagMenu () {
this._makeMenu ( '.tag:not([data-has-children]):not(a)', [
{
label: 'Remove',
click: () => this.props.container.note.removeTag ( undefined, $(this.ele).text () )
}
]);
}
initTagMenu () {
this._makeMenu ( '.tag[data-has-children="true"]', [
{
label: 'Collapse',
click: () => this.props.container.tag.toggleCollapse ( this.tag, true )
},
{
label: 'Expand',
click: () => this.props.container.tag.toggleCollapse ( this.tag, false )
}
], this.updateTagMenu.bind ( this ) );
}
initTrashMenu () {
this._makeMenu ( '.tag[title="Trash"]', [
{
label: 'Empty Trash',
click: this.props.container.trash.empty
}
], this.updateTrashMenu.bind ( this ) );
}
initFallbackMenu () {
this._makeMenu ( ( x, y ) => !this._getItem ( x, y, '.attachment, .note-button, .editor .note, .tag:not([data-has-children]), .tag[data-has-children="true"], .tag[title="Trash"]' ) );
}
/* UPDATE */
updateAttachmentMenu ( items ) {
const fileName = $(this.ele).data ( 'filename' );
this.attachment = this.props.container.attachment.get ( fileName );
}
updateNoteMenu ( items ) {
const filePath = $(this.ele).data ( 'filepath' );
this.note = this.props.container.note.get ( filePath );
const isFavorited = this.props.container.note.isFavorited ( this.note ),
isDeleted = this.props.container.note.isDeleted ( this.note )
items[5].visible = !isFavorited;
items[6].visible = !!isFavorited;
items[8].visible = !isDeleted;
items[9].visible = !!isDeleted;
}
updateTagMenu ( items ) {
this.tag = $(this.ele).data ( 'tag' );
const isCollapsed = this.props.container.tag.isCollapsed ( this.tag );
items[0].visible = !isCollapsed;
items[1].visible = isCollapsed;
}
updateTrashMenu ( items ) {
items[0].enabled = !this.props.container.trash.isEmpty ();
}
}
/* EXPORT */
export default connect ({
container: Main,
shouldComponentUpdate: false
})( ContextMenu );
/* IMPORT */
import {ipcRenderer as ipc} from 'electron';
import {connect} from 'overstated';
import {Component} from 'react-component-renderless';
import CWD from '@renderer/containers/cwd';
import Main from '@renderer/containers/main';
/* IPC */
class IPC extends Component<{ containers: [IMain, ICWD]}, undefined> {
/* VARIABLES */
main; cwd;
/* CONSTRUCTOR */
constructor ( props ) {
super ( props );
this.main = props.containers[0] as IMain;
this.cwd = props.containers[1] as ICWD;
}
/* SPECIAL */
componentDidMount () {
ipc.on ( 'cwd-change', this.__cwdChange );
ipc.on ( 'cwd-open-in-app', this.__cwdOpenInApp );
ipc.on ( 'import', this.__import );
ipc.on ( 'window-focus-toggle', this.__windowFocusToggle );
ipc.on ( 'window-fullscreen-set', this.__windowFullscreenSet );
ipc.on ( 'multi-editor-select-all', this.__multiEditorSelectAll );
ipc.on ( 'multi-editor-select-invert', this.__multiEditorSelectInvert );
ipc.on ( 'multi-editor-select-clear', this.__multiEditorSelectClear );
ipc.on ( 'note-edit-attachments-toggle', this.__noteEditAttachmentsToggle );
ipc.on ( 'note-edit-tags-toggle', this.__noteEditTagsToggle );
ipc.on ( 'note-edit-toggle', this.__noteEditToggle );
ipc.on ( 'note-favorite-toggle', this.__noteFavoriteToggle );
ipc.on ( 'note-move-to-trash', this.__noteMoveToTrash );
ipc.on ( 'note-new', this.__noteNew );
ipc.on ( 'note-duplicate', this.__noteDuplicate );
ipc.on ( 'note-open-in-app', this.__noteOpenInApp );
ipc.on ( 'note-permanently-delete', this.__notePermanentlyDelete );
ipc.on ( 'note-pin-toggle', this.__notePinToggle );
ipc.on ( 'note-restore', this.__noteRestore );
ipc.on ( 'note-reveal', this.__noteReveal );
ipc.on ( 'search-focus', this.__searchFocus );
ipc.on ( 'search-next', this.__searchNext );
ipc.on ( 'search-previous', this.__searchPrevious );
ipc.on ( 'tag-next', this.__tagNext );
ipc.on ( 'tag-previous', this.__tagPrevious );
ipc.on ( 'trash-empty', this.__trashEmpty );
ipc.on ( 'tutorial-dialog', this.__tutorialDialog );
}
componentWillUnmount () {
ipc.removeListener ( 'cwd-change', this.__cwdChange );
ipc.removeListener ( 'cwd-open-in-app', this.__cwdOpenInApp );
ipc.removeListener ( 'import', this.__import );
ipc.removeListener ( 'window-focus-toggle', this.__windowFocusToggle );
ipc.removeListener ( 'window-fullscreen-set', this.__windowFullscreenSet );
ipc.removeListener ( 'multi-editor-select-all', this.__multiEditorSelectAll );
ipc.removeListener ( 'multi-editor-select-invert', this.__multiEditorSelectInvert );
ipc.removeListener ( 'multi-editor-select-clear', this.__multiEditorSelectClear );
ipc.removeListener ( 'note-edit-attachments-toggle', this.__noteEditAttachmentsToggle );
ipc.removeListener ( 'note-edit-tags-toggle', this.__noteEditTagsToggle );
ipc.removeListener ( 'note-edit-toggle', this.__noteEditToggle );
ipc.removeListener ( 'note-favorite-toggle', this.__noteFavoriteToggle );
ipc.removeListener ( 'note-move-to-trash', this.__noteMoveToTrash );
ipc.removeListener ( 'note-new', this.__noteNew );
ipc.removeListener ( 'note-duplicate', this.__noteDuplicate );
ipc.removeListener ( 'note-open-in-app', this.__noteOpenInApp );
ipc.removeListener ( 'note-permanently-delete', this.__notePermanentlyDelete );
ipc.removeListener ( 'note-pin-toggle', this.__notePinToggle );
ipc.removeListener ( 'note-restore', this.__noteRestore );
ipc.removeListener ( 'note-reveal', this.__noteReveal );
ipc.removeListener ( 'search-focus', this.__searchFocus );
ipc.removeListener ( 'search-next', this.__searchNext );
ipc.removeListener ( 'search-previous', this.__searchPrevious );
ipc.removeListener ( 'tag-next', this.__tagNext );
ipc.removeListener ( 'tag-previous', this.__tagPrevious );
ipc.removeListener ( 'trash-empty', this.__trashEmpty );
ipc.removeListener ( 'tutorial-dialog', this.__tutorialDialog );
}
/* HANDLERS */
__cwdChange = () => {
this.cwd.select ();
}
__cwdOpenInApp = () => {
this.cwd.openInApp ();
}
__import = () => {
this.main.import.select ();
}
__windowFocusToggle = () => {
this.main.window.toggleFocus ();
}
__windowFullscreenSet = ( event, isFullscreen? ) => {
this.main.window.toggleFullscreen ( isFullscreen );
}
__multiEditorSelectAll = () => {
this.main.multiEditor.selectAll ();
}
__multiEditorSelectInvert = () => {
this.main.multiEditor.selectInvert ();
}
__multiEditorSelectClear = () => {
this.main.multiEditor.selectClear ();
}
__noteEditAttachmentsToggle = () => {
this.main.attachments.toggleEditing ();
}
__noteEditTagsToggle = () => {
this.main.tags.toggleEditing ();
}
__noteEditToggle = () => {
this.main.editor.toggleEditing ();
}
__noteFavoriteToggle = () => {
this.main.note.toggleFavorite ();
}
__noteMoveToTrash = () => {
this.main.note.toggleDeleted ( undefined, true );
}
__noteNew = () => {
this.main.note.new ();
}
__noteDuplicate = () => {
this.main.note.duplicate ();
}
__noteOpenInApp = () => {
this.main.note.openInApp ();
}
__notePermanentlyDelete = () => {
this.main.note.delete ();
}
__notePinToggle = () => {
this.main.note.togglePin ();
}
__noteRestore = () => {
this.main.note.toggleDeleted ( undefined, false );
}
__noteReveal = () => {
this.main.note.reveal ();
}
__searchFocus = () => {
this.main.search.focus ();
}
__searchNext = () => {
this.main.search.next ();
}
__searchPrevious = () => {
this.main.search.previous ();
}
__tagNext = () => {
this.main.tag.next ();
}
__tagPrevious = () => {
this.main.tag.previous ();
}
__trashEmpty = () => {
this.main.trash.empty ();
}
__tutorialDialog = () => {
this.main.tutorial.dialog ();
}
}
/* EXPORT */
export default connect ({
containers: [Main, CWD],
shouldComponentUpdate: false
})( IPC );
/* IMPORT */
import {connect} from 'overstated';
import {Component} from 'react-component-renderless';
import Main from '@renderer/containers/main';
/* PREVIEW PLUGINS */
class PreviewPlugins extends Component<{ container: IMain }, undefined> {
/* SPECIAL */
componentDidMount () {
$.$document.on ( 'click', '.editor.preview a.note', this.__noteClick );
$.$document.on ( 'click', '.editor.preview a.tag', this.__tagClick );
}
componentWillUnmount () {
$.$document.off ( 'click', this.__noteClick );
$.$document.off ( 'click', this.__tagClick );
}
/* HANDLERS */
__noteClick = ( event ) => {
const filePath = $(event.currentTarget).data ( 'filepath' ),
note = this.props.container.note.get ( filePath );
this.props.container.note.set ( note, true );
return false;
}
__tagClick = ( event ) => {
const tag = $(event.currentTarget).data ( 'tag' );
this.props.container.tag.set ( tag );
return false;
}
}
/* EXPORT */
export default connect ({
container: Main,
shouldComponentUpdate: false
})( PreviewPlugins );
/* IMPORT */
import {connect} from 'overstated';
import {Component} from 'react-component-renderless';
import Main from '@renderer/containers/main';
/* SHORTCUTS */
class Shortcuts extends Component<{ container: IMain }, undefined> {
/* VARIABLES */
shortcuts = {
'ctmd+shift+e': [this.__editorToggle, true],
'ctmd+s': [this.__editorSave, true],
'esc': [this.__editorsEscape, true],
'up, left': [this.__searchPrevious, false],
'down, right': [this.__searchNext, false],
};
/* SPECIAL */
componentDidMount () {
$.$document.on ( 'keydown', this.__keydown );
}
componentWillUnmount () {
$.$document.off ( 'keydown', this.__keydown );
}
/* KEYDOWN */
__keydown = event => {
const isEditable = $.isEditable ( document.activeElement );
for ( let shortcuts in this.shortcuts ) {
const [handler, hasPriority] = this.shortcuts[shortcuts];
if ( !hasPriority && isEditable ) continue;
const shortcutArr = shortcuts.split ( ',' );
for ( let i = 0, l = shortcutArr.length; i < l; i++ ) {
const shortcut = shortcutArr[i];
if ( !Svelto.Keyboard.keystroke.match ( event, shortcut ) ) continue;
if ( handler.call ( this ) !== null ) {
event.preventDefault ();
event.stopImmediatePropagation ();
}
return;
}
}
}
/* HANDLERS */
__editorToggle () {
this.props.container.editor.toggleEditing ();
}
__editorSave () {
if ( !this.props.container.editor.isEditing () ) return null;
this.props.container.editor.toggleEditing ();
return; //TSC
}
__editorsEscape () {
if ( this.props.container.attachments.isEditing () || this.props.container.tags.isEditing () ) return null;
if ( this.props.container.multiEditor.isEditing () ) return this.props.container.multiEditor.selectClear ();
if ( this.props.container.editor.isEditing () ) return this.props.container.editor.toggleEditing ( false );
return null;
}
__searchPrevious () {
this.props.container.search.previous ();
}
__searchNext () {
this.props.container.search.next ();
}
}
/* EXPORT */
export default connect ({
container: Main,
shouldComponentUpdate: false
})( Shortcuts );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import MainContainer from '@renderer/containers/main';
import Mainbar from './mainbar';
import Middlebar from './middlebar';
import Sidebar from './sidebar';
import ContextMenu from './extra/context_menu';
import IPC from './extra/ipc';
import PreviewPlugins from './extra/preview_plugins';
import Shortcuts from './extra/shortcuts';
import Wrapper from './wrapper';
/* MAIN */
class Main extends React.Component<any, undefined> {
/* SPECIAL */
async componentDidMount () {
if ( this.props.loading ) {
await this.props.refresh ();
}
await this.props.listen ();
}
/* RENDER */
render () {
if ( this.props.loading ) return null;
return (
<>
<ContextMenu />
<IPC />
<PreviewPlugins />
<Shortcuts />
<Wrapper>
<Sidebar />
<Middlebar />
<Mainbar />
</Wrapper>
</>
);
}
}
/* EXPORT */
export default connect ({
container: MainContainer,
selector: ({ container }) => ({
listen: container.listen,
refresh: container.refresh,
loading: container.loading.get ()
})
})( Main );
/* IMPORT */
import 'codemirror/lib/codemirror.css';
import 'codemirror-github-light/lib/codemirror-github-light-theme.css';
import 'primer-markdown/build/build.css';
import 'codemirror/keymap/sublime.js';
import 'codemirror/mode/apl/apl.js';
import 'codemirror/mode/asciiarmor/asciiarmor.js';
import 'codemirror/mode/asn.1/asn.1.js';
import 'codemirror/mode/asterisk/asterisk.js';
import 'codemirror/mode/brainfuck/brainfuck.js';
import 'codemirror/mode/clike/clike.js';
import 'codemirror/mode/clojure/clojure.js';
import 'codemirror/mode/cmake/cmake.js';
import 'codemirror/mode/cobol/cobol.js';
import 'codemirror/mode/coffeescript/coffeescript.js';
import 'codemirror/mode/commonlisp/commonlisp.js';
import 'codemirror/mode/crystal/crystal.js';
import 'codemirror/mode/css/css.js';
import 'codemirror/mode/cypher/cypher.js';
import 'codemirror/mode/d/d.js';
import 'codemirror/mode/dart/dart.js';
import 'codemirror/mode/diff/diff.js';
import 'codemirror/mode/django/django.js';
import 'codemirror/mode/dockerfile/dockerfile.js';
import 'codemirror/mode/dtd/dtd.js';
import 'codemirror/mode/dylan/dylan.js';
import 'codemirror/mode/ebnf/ebnf.js';
import 'codemirror/mode/ecl/ecl.js';
import 'codemirror/mode/eiffel/eiffel.js';
import 'codemirror/mode/elm/elm.js';
import 'codemirror/mode/erlang/erlang.js';
import 'codemirror/mode/factor/factor.js';
import 'codemirror/mode/fcl/fcl.js';
import 'codemirror/mode/forth/forth.js';
import 'codemirror/mode/fortran/fortran.js';
import 'codemirror/mode/gas/gas.js';
import 'codemirror/mode/gfm/gfm.js';
import 'codemirror/mode/gherkin/gherkin.js';
import 'codemirror/mode/go/go.js';
import 'codemirror/mode/groovy/groovy.js';
import 'codemirror/mode/haml/haml.js';
import 'codemirror/mode/handlebars/handlebars.js';
import 'codemirror/mode/haskell/haskell.js';
import 'codemirror/mode/haskell-literate/haskell-literate.js';
import 'codemirror/mode/haxe/haxe.js';
import 'codemirror/mode/htmlembedded/htmlembedded.js';
import 'codemirror/mode/htmlmixed/htmlmixed.js';
import 'codemirror/mode/http/http.js';
import 'codemirror/mode/idl/idl.js';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/jinja2/jinja2.js';
import 'codemirror/mode/jsx/jsx.js';
import 'codemirror/mode/julia/julia.js';
import 'codemirror/mode/livescript/livescript.js';
import 'codemirror/mode/lua/lua.js';
import 'codemirror/mode/markdown/markdown.js';
import 'codemirror/mode/mathematica/mathematica.js';
import 'codemirror/mode/mbox/mbox.js';
import 'codemirror/mode/mirc/mirc.js';
import 'codemirror/mode/mllike/mllike.js';
import 'codemirror/mode/modelica/modelica.js';
import 'codemirror/mode/mscgen/mscgen.js';
import 'codemirror/mode/mumps/mumps.js';
import 'codemirror/mode/nginx/nginx.js';
import 'codemirror/mode/nsis/nsis.js';
import 'codemirror/mode/ntriples/ntriples.js';
import 'codemirror/mode/octave/octave.js';
import 'codemirror/mode/oz/oz.js';
import 'codemirror/mode/pascal/pascal.js';
import 'codemirror/mode/pegjs/pegjs.js';
import 'codemirror/mode/perl/perl.js';
import 'codemirror/mode/php/php.js';
import 'codemirror/mode/pig/pig.js';
import 'codemirror/mode/powershell/powershell.js';
import 'codemirror/mode/properties/properties.js';
import 'codemirror/mode/protobuf/protobuf.js';
import 'codemirror/mode/pug/pug.js';
import 'codemirror/mode/puppet/puppet.js';
import 'codemirror/mode/python/python.js';
import 'codemirror/mode/q/q.js';
import 'codemirror/mode/r/r.js';
import 'codemirror/mode/rpm/rpm.js';
import 'codemirror/mode/rst/rst.js';
import 'codemirror/mode/ruby/ruby.js';
import 'codemirror/mode/rust/rust.js';
import 'codemirror/mode/sas/sas.js';
import 'codemirror/mode/sass/sass.js';
import 'codemirror/mode/scheme/scheme.js';
import 'codemirror/mode/shell/shell.js';
import 'codemirror/mode/sieve/sieve.js';
import 'codemirror/mode/slim/slim.js';
import 'codemirror/mode/smalltalk/smalltalk.js';
import 'codemirror/mode/smarty/smarty.js';
import 'codemirror/mode/solr/solr.js';
import 'codemirror/mode/soy/soy.js';
import 'codemirror/mode/sparql/sparql.js';
import 'codemirror/mode/spreadsheet/spreadsheet.js';
import 'codemirror/mode/sql/sql.js';
import 'codemirror/mode/stex/stex.js';
import 'codemirror/mode/stylus/stylus.js';
import 'codemirror/mode/swift/swift.js';
import 'codemirror/mode/tcl/tcl.js';
import 'codemirror/mode/textile/textile.js';
import 'codemirror/mode/tiddlywiki/tiddlywiki.js';
import 'codemirror/mode/tiki/tiki.js';
import 'codemirror/mode/toml/toml.js';
import 'codemirror/mode/tornado/tornado.js';
import 'codemirror/mode/troff/troff.js';
import 'codemirror/mode/ttcn/ttcn.js';
import 'codemirror/mode/ttcn-cfg/ttcn-cfg.js';
import 'codemirror/mode/turtle/turtle.js';
import 'codemirror/mode/twig/twig.js';
import 'codemirror/mode/vb/vb.js';
import 'codemirror/mode/vbscript/vbscript.js';
import 'codemirror/mode/velocity/velocity.js';
import 'codemirror/mode/verilog/verilog.js';
import 'codemirror/mode/vhdl/vhdl.js';
import 'codemirror/mode/vue/vue.js';
import 'codemirror/mode/webidl/webidl.js';
import 'codemirror/mode/xml/xml.js';
import 'codemirror/mode/xquery/xquery.js';
import 'codemirror/mode/yacas/yacas.js';
import 'codemirror/mode/yaml/yaml.js';
import 'codemirror/mode/yaml-frontmatter/yaml-frontmatter.js';
import 'codemirror/mode/z80/z80.js';
import * as _ from 'lodash';
import * as CodeMirrorLib from 'codemirror/lib/codemirror';
import * as CodeMirror from 'codemirror';
/* WEIRD FIX */ //UGLY: Why the hell is this required? Why are `codemirror` and `codemirror/lib/codemirror` separate beasts? do they get cached on they own or something?
_.extend ( CodeMirror['keyMap'], CodeMirrorLib.keyMap );
_.extend ( CodeMirror['commands'], CodeMirrorLib.commands );
_.extend ( CodeMirror, CodeMirrorLib );
( CodeMirror as any ).prototype = CodeMirrorLib.prototype; //TSC
/* EXPORT */
export default CodeMirrorLib;
/* IMPORT */
import './codemirror';
import * as is from 'electron-is';
import * as React from 'react';
import {UnControlled as CodeMirror} from 'react-codemirror2';
import Todo from './items/todo';
import Utils from './utils';
/* OPTIONS */
const CTMD = is.macOS () ? 'Cmd' : 'Ctrl', // `Cmd` on macOS, `Ctrl` otherwise
ALMD = is.macOS () ? 'Cmd' : 'Alt'; // `Cmd` on macOS, `Alt` otherwise
const options: any = { //TSC
autofocus: true,
electricChars: false,
indentUnit: 2,
indentWithTabs: false,
lineNumbers: false,
lineSeparator: '\n',
lineWrapping: true,
mode: 'gfm',
gitHubSpice: false,
highlightFormatting: true,
scrollbarStyle: 'native',
smartIndent: false,
tabSize: 2,
undoDepth: 1000,
keyMap: 'sublime',
theme: 'github-light',
viewportMargin: Infinity,
extraKeys: {
'Backspace': 'delCharBefore',
[`${CTMD}-Z`]: 'undo',
[`${CTMD}-Shift-Z`]: 'redo',
'Tab': 'indentMore',
'Shift-Tab': 'indentLess',
[`${ALMD}-Ctrl-Up`]: 'swapLineUp',
[`${ALMD}-Ctrl-Down`]: 'swapLineDown',
'Alt-LeftClick': Utils.addSelection,
'Alt-Z': Utils.toggleWrapping,
[`${CTMD}-Enter`]: Todo.toggleBox,
'Alt-D': Todo.toggleDone,
[`${CTMD}-M`]: false,
[`${CTMD}-H`]: false,
[`${CTMD}-LeftClick`]: false
}
};
/* CODE */
const Code = ({ className, value }) => {
Utils.initOptions ( options );
return <CodeMirror className={className} value={value} options={options} />
};
/* EXPORT */
export default Code;
/* IMPORT */
import * as _ from 'lodash';
import Utils from '../utils';
/* TODO */
const Todo = {
bulletSymbol: '-',
doneSymbol: 'x',
lineRe: /^(\s*)([*+-]?\s*)(.*)$/,
todoRe: /^(\s*)([*+-]\s+\[[ xX]\]\s*)(.*)$/,
todoBoxRe: /^(\s*)([*+-]\s+\[ \]\s*)(.*)$/,
todoDoneRe: /^(\s*)([*+-]\s+\[[xX]\]\s*)(.*)$/,
toggleRules ( cm, rules ) {
Utils.walkSelections ( cm, ( line, lineNr ) => {
rules.find ( ([ regex, replacement ]) => {
if ( !regex.test ( line ) ) return false;
const lineNext = line.replace ( regex, replacement );
Utils.replace ( cm, lineNr, lineNext, 0, line.length );
return true;
});
});
},
toggleBox ( cm ) {
const {bulletSymbol, lineRe, todoBoxRe, todoDoneRe} = Todo;
Todo.toggleRules ( cm, [
[todoBoxRe, '$1$3'],
[todoDoneRe, `$1${bulletSymbol} [ ] $3`],
[lineRe, `$1${bulletSymbol} [ ] $3`]
]);
},
toggleDone ( cm ) {
const {bulletSymbol, doneSymbol, lineRe, todoBoxRe, todoDoneRe} = Todo;
Todo.toggleRules ( cm, [
[todoDoneRe, `$1${bulletSymbol} [ ] $3`],
[todoBoxRe, `$1${bulletSymbol} [${doneSymbol}] $3`],
[lineRe, `$1${bulletSymbol} [${doneSymbol}] $3`]
]);
}
};
/* EXPORT */
export default Todo;
/* IMPORT */
import * as _ from 'lodash';
import Settings from '@common/settings';
/* UTILS */
const Utils = {
initOptions ( options ) {
options.lineWrapping = Settings.get ( 'codemirror.options.lineWrapping' );
},
toggleWrapping ( cm ) {
const lineWrapping = !cm.getOption ( 'lineWrapping' );
cm.setOption ( 'lineWrapping', lineWrapping );
Settings.set ( 'codemirror.options.lineWrapping', lineWrapping );
},
addSelection ( cm, pos ) {
cm.getDoc ().addSelection ( pos );
},
focus ( cm ) {
cm.focus ();
cm.setCursor ({ line: 0, ch: 0 });
},
walkSelections ( cm, callback ) {
cm.listSelections ().forEach ( selection => {
const lineNr = Math.min ( selection.anchor.line, selection.head.line ),
line = cm.getLine ( lineNr );
callback ( line, lineNr );
});
},
replace ( cm, lineNr, replacement, fromCh, toCh? ) {
const from = { line: lineNr, ch: fromCh };
if ( _.isUndefined ( toCh ) ) {
cm.replaceRange ( replacement, from );
} else {
const to = { line: lineNr, ch: toCh };
cm.replaceRange ( replacement, from, to );
}
}
};
/* EXPORT */
export default Utils;
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import EditorEditing from './editor_editing';
import EditorEmpty from './editor_empty';
import EditorPreview from './editor_preview';
/* EDITOR */
const Editor = ({ hasNote, isEditing }) => {
if ( !hasNote ) return <EditorEmpty />;
if ( isEditing ) return <EditorEditing />;
return <EditorPreview />;
};
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
hasNote: !!container.note.get (),
isEditing: container.editor.isEditing ()
})
})( Editor );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import Code from './code';
import CodeUtils from './code/utils';
/* EDITOR EDITING */
class EditorEditing extends React.Component<any, undefined> {
componentDidMount () {
this.focus ();
}
componentDidUpdate () {
this.focus ();
}
focus () {
const cm = this.props.getCodeMirror ();
if ( !cm ) return;
CodeUtils.focus ( cm );
}
render () {
return <Code className="layout-content editor editing" value={this.props.content} />;
}
}
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
id: container.note.getHash (),
content: container.note.getPlainContent (),
getCodeMirror: container.editor.getCodeMirror
})
})( EditorEditing );
/* EDITOR EMPTY */
const EditorEmpty = () => null;
/* EXPORT */
export default EditorEmpty;
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Markdown from '@renderer/utils/markdown';
import Main from '@renderer/containers/main';
/* EDITOR PREVIEW */
const EditorPreview = ({ content }) => {
const html = Markdown.render ( content );
return <div className="layout-content editor preview markdown-body" dangerouslySetInnerHTML={{ __html: html }}></div>;
};
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
content: container.note.getPlainContent ()
})
})( EditorPreview );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import PopoverNoteAttachments from '../popovers/popover_note_attachments';
import PopoverTagsAttachments from '../popovers/popover_note_tags';
import Editor from './editor';
import MultiEditor from './multi_editor';
import Toolbar from './toolbar';
/* MAINBAR */
const Mainbar = ({ isMultiEditing }) => (
<div id="mainbar" className="layout">
{ isMultiEditing ? (
<MultiEditor />
) : (
<>
<PopoverNoteAttachments />
<PopoverTagsAttachments />
<Toolbar />
<Editor />
</>
)}
</div>
);
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
isMultiEditing: container.multiEditor.isEditing ()
})
})( Mainbar );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import Button from './multi_editor_button';
import Tagbox from './multi_editor_tagbox';
/* MULTI EDITOR */
const MultiEditor = ({ notesNr, favorite, unfavorite, pin, unpin, trash, untrash, del, tagsAdd, tagsRemove, openInApp }) => (
<div className="multi-editor">
<h1>{notesNr} notes selected</h1>
<div className="container bordered actions">
<div className="multiple fluid vertical">
<div className="multiple fluid">
<div className="multiple fluid joined actions-favorite">
<Button icon="star_outline" title="Unfavorite" onClick={unfavorite} />
<Button icon="star" title="Favorite" onClick={favorite} />
</div>
<div className="multiple fluid joined actions-pin">
<Button icon="pin_outline" title="Unpin" onClick={unpin} />
<Button icon="pin" title="Pin" onClick={pin} />
</div>
</div>
<div className="multiple fluid">
<div className="multiple fluid joined actions-delete">
<Button icon="delete" title="Move to Trash" onClick={trash} />
<Button icon="delete_restore" title="Restore" onClick={untrash} />
<Button icon="delete_forever" color="red inverted" title="Permanently Delete" onClick={del} />
</div>
<Button icon="open_in_new" title="Open in Default App" onClick={openInApp} />
</div>
<Tagbox icon="tag_plus" title="Add Tags" placeholder="Add Tag..." onClick={tagsAdd} />
<Tagbox icon="tag_minus" title="Remove Tags" placeholder="Remove Tag..." onClick={tagsRemove} />
</div>
</div>
</div>
);
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
notesNr: container.multiEditor.getNotes ().length,
favorite: container.multiEditor.favorite,
unfavorite: container.multiEditor.unfavorite,
pin: container.multiEditor.pin,
unpin: container.multiEditor.unpin,
trash: container.multiEditor.trash,
untrash: container.multiEditor.untrash,
del: container.multiEditor.delete,
tagsAdd: container.multiEditor.tagsAdd,
tagsRemove: container.multiEditor.tagsRemove,
openInApp: container.multiEditor.openInApp
})
})( MultiEditor );
/* IMPORT */
import * as React from 'react';
/* MULTI EDITOR BUTTON */
const Button = ({ icon, title, onClick, color = '' }) => (
<div className={`button bordered ${color}`} title={title} onClick={onClick}>
<i className="icon">{icon}</i>
</div>
);
/* EXPORT */
export default Button;
/* IMPORT */
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Button from './multi_editor_button';
/* MULTI EDITOR TAGBOX */
class Tagbox extends React.PureComponent<any, any> {
$wrapper; $tagbox;
componentDidMount () {
this.$wrapper = $(ReactDOM.findDOMNode ( this ));
this.$tagbox = this.$wrapper.find ( '.tagbox' );
this.$tagbox.widgetize ();
}
onClick = () => {
const tags = this.$tagbox.tagbox ( 'get' );
this.props.onClick ( tags );
}
render () {
const {icon, title, placeholder} = this.props;
return (
<div className="multiple joined fluid">
<div className="tagbox bordered fluid">
<input name="name" defaultValue="" className="hidden" />
<div className="tagbox-tags">
<input name="partial_name" placeholder={placeholder} className="tagbox-partial autogrow compact small" />
</div>
</div>
<Button icon={icon} title={title} onClick={this.onClick} />
</div>
);
}
}
/* EXPORT */
export default Tagbox;
/* IMPORT */
import * as is from 'electron-is';
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import AttachmentsButton from './toolbar_button_attachments';
import EditorButton from './toolbar_button_editor';
import FavoriteButton from './toolbar_button_favorite';
import OpenButton from './toolbar_button_open';
import PinButton from './toolbar_button_pin';
import TagsButton from './toolbar_button_tags';
import TrashButton from './toolbar_button_trash';
import TrashPermanentlyButton from './toolbar_button_trash_permanently';
/* TOOLBAR */
const Toolbar = ({ hasNote, isFocus, isFullscreen }) => (
<div id="mainbar-toolbar" className="layout-header centerer">
<div className={`${!hasNote ? 'disabled' : ''} multiple grow`}>
{!isFocus || isFullscreen || !is.macOS () ? null : (
<div className="toolbar-semaphore-spacer"></div>
)}
<div className="multiple joined">
<EditorButton />
<TagsButton />
<AttachmentsButton />
</div>
<div className="multiple joined">
<FavoriteButton />
<PinButton />
</div>
<div className="multiple joined">
<TrashButton />
<TrashPermanentlyButton />
</div>
<div className="spacer"></div>
<OpenButton />
</div>
</div>
);
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
hasNote: !!container.note.get (),
isFocus: container.window.isFocus (),
isFullscreen: container.window.isFullscreen ()
})
})( Toolbar );
/* IMPORT */
import * as React from 'react';
/* TOOLBAR BUTTON */
const ToolbarButton = ({ id = '' , icon, title, onClick, isActive = false, color = '', badge = undefined as any }) => ( //TSC
<div id={id ? id : undefined} className={`${isActive ? 'active text-secondary' : ''} button bordered xsmall ${color}`} title={title} onClick={onClick}>
<i className="icon">{icon}</i>
{!badge ? null : (
<div className="badge" title={badge.title}>{badge.text}</div>
)}
</div>
);
/* EXPORT */
export default ToolbarButton;
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import ToolbarButton from './toolbar_button';
/* TOOLBAR BUTTON ATTACHMENTS */
const AttachmentsButton = ({ isEditing, toggleEditing }) => {
if ( !isEditing ) return <ToolbarButton id="popover-note-attachments-trigger" icon="paperclip" title="Edit Attachments" onClick={() => toggleEditing ()} />;
return <ToolbarButton id="popover-note-attachments-trigger" icon="paperclip" title="Stop Editing Attachments" isActive={true} onClick={() => toggleEditing ()} />;
};
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
isEditing: container.attachments.isEditing (),
toggleEditing: container.attachments.toggleEditing
})
})( AttachmentsButton );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import ToolbarButton from './toolbar_button';
/* TOOLBAR BUTTON EDITOR */
const EditorButton = ({ isEditing, toggleEditing }) => {
if ( !isEditing ) return <ToolbarButton icon="pencil" title="Edit" onClick={() => toggleEditing ()} />;
return <ToolbarButton icon="pencil" title="Stop Editing" isActive={true} onClick={() => toggleEditing ()} />;
};
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
isEditing: container.editor.isEditing (),
toggleEditing: container.editor.toggleEditing
})
})( EditorButton );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import ToolbarButton from './toolbar_button';
/* TOOLBAR BUTTON FAVORITE */
const FavoriteButton = ({ isFavorited, toggleFavorite }) => {
if ( !isFavorited ) return <ToolbarButton icon="star_outline" title="Favorite" onClick={() => toggleFavorite ()} />
return <ToolbarButton icon="star" title="Unfavorite" isActive={true} onClick={() => toggleFavorite ()} />;
};
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
isFavorited: container.note.isFavorited (),
toggleFavorite: container.note.toggleFavorite
})
})( FavoriteButton );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import ToolbarButton from './toolbar_button';
/* TOOLBAR BUTTON OPEN */
const OpenButton = ({ openInApp }) => (
<ToolbarButton icon="open_in_new" title="Open in Default App" onClick={() => openInApp ()} />
);
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
openInApp: container.note.openInApp
})
})( OpenButton );
/* IMPORT */
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import ToolbarButton from './toolbar_button';
/* TOOLBAR BUTTON PIN */
const PinButton = ({ isPinned, togglePin }) => {
if ( !isPinned ) return <ToolbarButton icon="pin_outline" title="Pin" onClick={() => togglePin ()} />
return <ToolbarButton icon="pin" title="Unpin" isActive={true} onClick={() => togglePin ()} />;
};
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
isPinned: container.note.isPinned (),
togglePin: container.note.togglePin
})
})( PinButton );
/* IMPORT */
import * as _ from 'lodash';
import * as React from 'react';
import {connect} from 'overstated';
import Main from '@renderer/containers/main';
import ToolbarButton from './toolbar_button';
/* TOOLBAR BUTTON TAGS */
const TagsButton = ({ isEditing, toggleEditing }) => {
if ( !isEditing ) return <ToolbarButton id="popover-note-tags-trigger" icon="tag" title="Edit Tags" onClick={() => toggleEditing ()} />;
return <ToolbarButton id="popover-note-tags-trigger" icon="tag" title="Stop Editing Tags" isActive={true} onClick={() => toggleEditing ()} />;
}
/* EXPORT */
export default connect ({
container: Main,
selector: ({ container }) => ({
isEditing: container.tags.isEditing (),
toggleEditing: container.tags.toggleEditing
})
})( TagsButton );
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册