1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 10:16:26 +00:00

Compare commits

..

524 Commits

Author SHA1 Message Date
Kohei TAKATA
e1a75a13e9 Merge pull request #891 from BoostIO/v0.8.15
v0.8.15
2017-09-23 17:02:56 +09:00
Kazu Yokomizo
ee6f4de183 v0.8.15 2017-09-23 14:41:13 +09:00
Kazu Yokomizo
475885b3ef Merge pull request #893 from BoostIO/downgrade_striptags_version
Downgrade striptags version
2017-09-23 14:35:54 +09:00
Kohei TAKATA
2d2b2d4c6c Downgrade striptags version 2017-09-23 14:20:53 +09:00
SuenagaRyota
4d00454539 Merge pull request #892 from asmsuechan/fix-typo
Fix typo addNotes to addNotesFromFiles
2017-09-23 12:34:15 +09:00
asmsuechan
bf590b5614 Fix typo addNotes to addNotesFromFiles 2017-09-23 12:29:08 +09:00
Kazu Yokomizo
3ef33c065c Merge pull request #793 from asmsuechan/add-drop-on-notelist
Add file drop on NoteList
2017-09-23 12:09:55 +09:00
Kazu Yokomizo
8e89fb8b92 Merge pull request #890 from asmsuechan/iss-888
iss #888 Fix letter count
2017-09-23 11:43:09 +09:00
asmsuechan
8e81cfcf89 iss #888 Fix letter count 2017-09-23 11:40:26 +09:00
Kazu Yokomizo
515736262d Merge pull request #889 from asmsuechan/cloud-sync-comment
Add link for cloud integration
2017-09-23 11:38:01 +09:00
Kazu Yokomizo
bd266dc602 Merge pull request #885 from asmsuechan/exit-by-comma-q-in-vim-mode
iss #832 Make :q work in vim mode
2017-09-23 11:34:01 +09:00
asmsuechan
9b3306157c Add link for cloud integration 2017-09-23 11:15:16 +09:00
asmsuechan
f8e6a939ca Fix comment 2017-09-23 10:20:48 +09:00
Kazu Yokomizo
8c48ee6fc1 Merge pull request #887 from BoostIO/fix-layout
Fix layout
2017-09-23 01:38:28 +09:00
Kazu Yokomizo
5e476054d7 Fix layout at NewNoteModal 2017-09-23 01:34:51 +09:00
Kazu Yokomizo
2fc8547384 Fix layout at CreateFolderModal 2017-09-23 01:20:39 +09:00
asmsuechan
2af2d71540 Rename addNotes to addNotesFromFiles 2017-09-23 00:28:55 +09:00
asmsuechan
6aaf9d9eb2 Change disable drop on trash 2017-09-23 00:28:55 +09:00
asmsuechan
42a9caf5a3 Add file drop on NoteList 2017-09-23 00:28:52 +09:00
asmsuechan
e6c1d7a383 iss #832 Make :qw work in vim mode 2017-09-23 00:21:20 +09:00
asmsuechan
02100bbc0a iss #832 Make :q! and :qw work in vim mode 2017-09-23 00:16:51 +09:00
Kazu Yokomizo
ce7c5f5d40 Merge pull request #884 from BoostIO/fix-layout-at-bottom
Fix layout at bottom
2017-09-23 00:07:14 +09:00
Kazu Yokomizo
69f1ad6eb3 Sorry CI 2017-09-23 00:00:27 +09:00
Kazu Yokomizo
8320fb5024 Fix layout at bottom 2017-09-22 23:57:43 +09:00
asmsuechan
4b79bca6bf iss #832 Make :q work in vim mode 2017-09-22 23:57:14 +09:00
Kazu Yokomizo
4a5fd41249 Fix layout at description in SnippetNoteDetail 2017-09-22 23:48:41 +09:00
Kazu Yokomizo
4e90a93b30 Fix layout at NoteDetail 2017-09-22 23:46:41 +09:00
Kazu Yokomizo
9861fbf7c8 Fix layout at RealtimeNotification 2017-09-22 22:56:32 +09:00
Kohei TAKATA
c762b9ae00 Merge pull request #877 from asmsuechan/add-realtime-info
Add RealtimeNotification
2017-09-22 20:26:20 +09:00
Kazu Yokomizo
cc667a6edf Merge pull request #882 from BoostIO/fix-layout
Fix layout
2017-09-22 16:20:05 +09:00
Kazu Yokomizo
419c57ed3f Fix CI error 2017-09-22 16:14:06 +09:00
Kazu Yokomizo
601f0b0de8 Fix layout at narrow the width of description in SnippetNoteDetail 2017-09-22 16:06:40 +09:00
Kazu Yokomizo
7b1c6c10b7 Change expand icon at NoteDetail 2017-09-22 15:56:49 +09:00
Kazu Yokomizo
5a85c257cf Fix layout at SideNab in PreferenceModal 2017-09-22 15:46:08 +09:00
Kazu Yokomizo
beceb851c2 Fix layout at selector size in PreferenceModal 2017-09-22 15:41:40 +09:00
Kazu Yokomizo
31485d3387 Fix layout at head in PreferenceModal 2017-09-22 15:27:22 +09:00
Kazu Yokomizo
fc552e030a Change save button position to top 2017-09-22 15:21:04 +09:00
Kazu Yokomizo
bf4c9f920a Fix layout at preference modal 2017-09-22 15:13:34 +09:00
Kazu Yokomizo
4ebd503664 Merge pull request #880 from xxdavid/feature-strip-html-from-title
Strip HTML tags from the note title
2017-09-22 13:05:01 +09:00
David Pavlík
0907bc80ef Strip HTML from the note title 2017-09-21 16:48:13 +02:00
Kazu Yokomizo
2cf46a3332 Merge pull request #878 from BoostIO/update-readme
Update readme
2017-09-21 00:38:34 +09:00
Kazu Yokomizo
41868f28e6 Update readme
- Renew slack invitation url
- Change name "Boostnote Team" to "10hz"
2017-09-21 00:34:13 +09:00
asmsuechan
964b7b62de Change url development to production 2017-09-20 10:31:50 +09:00
SuenagaRyota
0d34a03fe0 Merge pull request #857 from BoostIO/feature-ama-event-main-focused
add MAIN_FOCUSED event
2017-09-16 10:57:08 +09:00
Sosuke Suzuki
a62faa471c change the event name for app start from MAIN_FOCUSED to APP_STARTED 2017-09-15 00:23:31 +09:00
Sosuke Suzuki
66f3ce2cb2 add MAIN_FOCUSED event 2017-09-12 19:43:59 +09:00
asmsuechan
e313b5e59d Add RealtimeNotification 2017-09-09 13:47:30 +09:00
SuenagaRyota
bb26d9a0a8 Merge pull request #834 from KuangLei/master
Translate contributing.md to Simplified Chinese
2017-09-09 09:18:35 +09:00
Kazu Yokomizo
0ca41fbdb4 Merge pull request #844 from BoostIO/fix-layout
Fix layout
2017-09-08 01:39:02 +09:00
Kazu Yokomizo
2cfb883bad Merge pull request #847 from BoostIO/update-readme
Update Readme
2017-09-05 16:01:37 +09:00
Kazu Yokomizo
ec8c8bb669 Update Readme 2017-09-05 15:54:47 +09:00
Kazu Yokomizo
0b54f01107 Fix layout at code block in markdown 2017-09-04 11:48:16 +09:00
Kazu Yokomizo
67be198bee Merge pull request #815 from asmsuechan/add-word-count-to-InfoPanel
Add wordcount and lettercount to InfoPanel
2017-09-01 12:22:38 +09:00
Kazu Yokomizo
32a4a1aae1 Merge pull request #837 from BoostIO/update-readme
Update Readme
2017-08-30 16:22:47 +09:00
Kazu Yokomizo
dac7372839 Update Readme 2017-08-30 16:09:38 +09:00
Kazu Yokomizo
521c261a37 Merge pull request #836 from BoostIO/move-to-image
Create repository folder and move top image to here.
2017-08-30 16:06:43 +09:00
Kazu Yokomizo
27367488c2 Create repository folder and move top image to here. 2017-08-30 14:39:37 +09:00
Kazu Yokomizo
0d5c3b1be6 Merge pull request #835 from BoostIO/delete-top-image
Delete top image
2017-08-30 14:35:25 +09:00
Kazu Yokomizo
a67d5ffacb Upload new top image 2017-08-30 14:23:52 +09:00
Kazu Yokomizo
687440a7c7 Delete old top.png 2017-08-30 14:21:40 +09:00
KLsz
bafdc24a6d Add a link from the english doc pages to doc pages in other languages 2017-08-28 19:54:13 +08:00
KLsz
047f9c93c5 Translate debug.md to Simplified Chinese 2017-08-28 19:46:36 +08:00
KLsz
116fafc117 Translate build.md to Simplified Chinese 2017-08-28 18:00:12 +08:00
KLsz
060c92091c Translate contributing.md to Simplified Chinese 2017-08-28 16:26:00 +08:00
Kazu Yokomizo
5802525b73 Merge pull request #829 from BoostIO/add-backers
Add Backers
2017-08-25 09:57:32 +09:00
Kazu Yokomizo
c3580caabc Merge pull request #828 from BoostIO/update-slack-link
Update slack invitation link
2017-08-25 09:57:16 +09:00
Kazu Yokomizo
08c027acc5 Add Backers 2017-08-25 09:52:54 +09:00
Kazu Yokomizo
4468792346 Update slack invitation link 2017-08-25 09:51:25 +09:00
SuenagaRyota
1b16c68cf9 Merge pull request #818 from asmsuechan/work-paste-image
Make pasting an image in CodeEditor work
2017-08-23 13:05:28 +09:00
asmsuechan
b99c1e3b32 Remove unnecessary bind 2017-08-22 13:48:50 +09:00
SuenagaRyota
eb2994e3c2 Merge pull request #820 from asmsuechan/drag-drop-image-caret-ubuntu
Fix image insesration problem
2017-08-20 09:19:23 +09:00
asmsuechan
d88dd26186 Fix image insesration problem, Use a function of CodeMirror instead of dom function 2017-08-20 08:11:00 +09:00
asmsuechan
59fcc58e9c Make pasting an image in CodeEditor work 2017-08-19 18:37:51 +09:00
SuenagaRyota
acba61f36a Merge pull request #816 from asmsuechan/iss-809
iss #809 normalize text works only in img tag
2017-08-19 09:44:56 +09:00
asmsuechan
a3a55a8bb4 iss #809 normalize text only in img tag 2017-08-19 09:25:45 +09:00
SuenagaRyota
22929d84fc Merge pull request #812 from asmsuechan/add-anchor
Add markdown-it-named-headers and adjust to use Japanese or Chinese
2017-08-19 08:42:00 +09:00
asmsuechan
9ea9d30947 Add wordcount and lettercount to InfoPanel 2017-08-19 08:26:55 +09:00
SuenagaRyota
7f08428fe2 Merge pull request #813 from asmsuechan/fix-build.md
Add to specify npm version
2017-08-18 23:51:10 +09:00
asmsuechan
0d80a7d961 Add to specify npm version 2017-08-18 18:52:18 +09:00
SuenagaRyota
2899264b54 Merge pull request #806 from sferra/fix-app-name-and-icon-on-linux
Fix app name and icon on linux
2017-08-18 18:44:37 +09:00
asmsuechan
923de0aa0d Add markdown-it-named-headers and adjust to use Japanese or Chinese 2017-08-18 16:07:15 +09:00
SuenagaRyota
2b729dad15 Merge pull request #811 from RedBug312/multimd-table
Support for extended table markdown syntax
2017-08-18 14:16:20 +09:00
RedBug312
b9b5bae78a Add markdown-it plugin for multimarkdown table 2017-08-18 12:48:55 +08:00
Cristian Beskid
a9acde07d1 Add missing 'productName' attribute to package.json
Fixes incorrect application name displayed in linux/gnome-shell. See
BoostIO/Boostnote#251
2017-08-15 13:52:01 +02:00
Cristian Beskid
a46b8d3079 Configure the icon of the main window
Fixes the incorrect application icon in linux/gnome-shell. See
BoostIO/Boostnote#251
2017-08-15 13:49:32 +02:00
SuenagaRyota
8b92e2cbb7 Merge pull request #805 from asmsuechan/fix-typo-of-fullscreen
Fix typo regarding fullscreen
2017-08-15 20:29:22 +09:00
asmsuechan
881f5a5110 Fix typo regarding fullscreen 2017-08-15 20:22:26 +09:00
SuenagaRyota
4e986a6384 Merge pull request #802 from asmsuechan/fix-by-eslint-react
Fix by eslint react
2017-08-15 10:16:33 +09:00
SuenagaRyota
ce5e1babb7 Merge pull request #803 from asmsuechan/fix-note-creation-in-all-notes
Fix note creation in All Notes
2017-08-15 10:16:07 +09:00
asmsuechan
b85790d2fa Fix note creation in All Notes 2017-08-15 10:09:17 +09:00
asmsuechan
6bc3e7fcf1 [eslint] react/no-direct-mutation-state 2017-08-15 10:01:04 +09:00
asmsuechan
1ca968201d [eslint] react/no-unescaped-entities 2017-08-14 11:56:55 +09:00
SuenagaRyota
f2a03e4cc7 Merge pull request #800 from asmsuechan/componentnize-NewNoteButton
Componentnize NewNoteButton
2017-08-14 11:45:37 +09:00
asmsuechan
a752730718 Change to use sdialog.showMEssageBox() instead of window.alert 2017-08-14 11:39:06 +09:00
asmsuechan
9d20fd91ec Change ee to eventEmitter 2017-08-14 11:20:54 +09:00
asmsuechan
70a6a3acb8 Remove unused variable 2017-08-14 11:20:54 +09:00
asmsuechan
7f52eed4d5 Move condition of trash or not 2017-08-14 11:20:54 +09:00
asmsuechan
105119e1a4 Change let to const 2017-08-14 11:20:54 +09:00
asmsuechan
169e30e029 Fix by lint 2017-08-14 11:20:54 +09:00
asmsuechan
a5fa3e9e7a 🗑️ Remove unused file 2017-08-14 11:20:54 +09:00
asmsuechan
8985062d34 ♻️ Refactor 2017-08-14 11:20:54 +09:00
asmsuechan
56eb9c76ae Componentnize NewNoteButton 2017-08-14 11:20:54 +09:00
SuenagaRyota
e5b6762bf3 Merge pull request #801 from asmsuechan/add-eslint-plugin-react
Add eslint plugin react
2017-08-14 11:17:06 +09:00
asmsuechan
e8bccaef88 Add a rule 2017-08-14 11:13:25 +09:00
asmsuechan
afdb038244 Add rules 2017-08-14 11:10:15 +09:00
asmsuechan
56942d55eb Add eslint-plugin-react to .eslintrc 2017-08-14 11:02:56 +09:00
asmsuechan
9d742c8435 Add eslint-plugin-react 2017-08-14 10:46:40 +09:00
Kazu Yokomizo
6ee4e48de2 Merge pull request #795 from BoostIO/feature-v0-8-14
v0.8.14
2017-08-12 14:50:16 +09:00
Kazu Yokomizo
184f3dc04b v0.8.14 2017-08-12 12:20:30 +09:00
Kazu Yokomizo
2027f60014 Merge pull request #794 from BoostIO/fix-progress-bar-ui
Fix progress bar ui at Noteitem
2017-08-12 12:17:34 +09:00
Kazu Yokomizo
076edd375f Fix progress bar UI at NoteItem 2017-08-12 12:13:07 +09:00
SuenagaRyota
ab1aa56059 Merge pull request #792 from asmsuechan/add-tests
Add tests
2017-08-12 10:31:42 +09:00
asmsuechan
46f7dfdfeb Change " to ' 2017-08-12 10:17:36 +09:00
asmsuechan
fcaa5e21cf Add tests for RcParser 2017-08-12 09:36:39 +09:00
asmsuechan
1e202db50f Change the directory of RcParser 2017-08-12 09:36:30 +09:00
asmsuechan
9405b95825 Change RcParser testable 2017-08-12 09:11:22 +09:00
asmsuechan
f05e256afc Fix .boostnoterc.sample 2017-08-12 09:08:42 +09:00
asmsuechan
731ffd4a22 Add spec for getTodoStatus 2017-08-12 09:07:58 +09:00
SuenagaRyota
8f4566b7e1 Merge pull request #789 from asmsuechan/try-catch-in-rc-parser
Make RcParser ignore errors at JSON.parse()
2017-08-11 08:38:00 +09:00
asmsuechan
95aec54f60 Make RcParser ignore errors at JSON.parse() 2017-08-11 08:24:08 +09:00
SuenagaRyota
f14ce0d68e Merge pull request #787 from asmsuechan/fix-configmanager
Fix configmanager
2017-08-11 00:41:16 +09:00
asmsuechan
cc1c7f3820 Fix stupid JSON 2017-08-11 00:30:57 +09:00
SuenagaRyota
2df901288a Merge pull request #776 from XGHeaven/master
add todo percentage in item list
2017-08-10 23:39:12 +09:00
asmsuechan
821a7c780e Change variable names 2017-08-10 23:30:42 +09:00
asmsuechan
6e2e48fa64 Fix ConfigManger to load the settings from localStorage properly 2017-08-10 23:19:57 +09:00
Kazu Yokomizo
2864ac88f5 Merge pull request #784 from BoostIO/feature-v0-8-13
v0.8.13
2017-08-10 22:05:33 +09:00
XGHeaven
4a292d6518 add transition for TodoProcess inner bar 2017-08-10 20:00:57 +08:00
XGHeaven
e934182e86 change getTodoState to getTodoStatus 2017-08-10 19:58:58 +08:00
Kazu Yokomizo
d8fa73287b v0.8.13 2017-08-10 19:44:20 +09:00
SuenagaRyota
35938c09e8 Merge pull request #783 from asmsuechan/add-boostnoterc-for-config
Add boostnoterc revival
2017-08-10 16:58:23 +09:00
SuenagaRyota
9eaa90c691 Merge pull request #768 from BoostIO/fix-infoPanel-layout-in-trash
Fixed InfoPanel layout in Trash
2017-08-10 16:52:07 +09:00
Kazu Yokomizo
049835d426 Fix typo “in Trash” to “Trash” 2017-08-10 16:43:25 +09:00
asmsuechan
af91c40406 Remove unused variable 2017-08-10 16:42:14 +09:00
asmsuechan
4940ad6825 Add .boostnoterc.sample 2017-08-10 16:38:20 +09:00
asmsuechan
d02b740300 Fix assignConfigValues because it didn't return proper hash object 2017-08-10 16:37:31 +09:00
asmsuechan
9cb443dc2f Remove unused variable 2017-08-10 15:18:55 +09:00
XGHeaven
1c7cba2951 add todo percentage in item list 2017-08-10 12:31:07 +08:00
asmsuechan
473b80710d Remove RcParser.exec 2017-08-10 11:14:42 +09:00
asmsuechan
2247c0835d Ignore any errors in ~/.boostnoterc 2017-08-10 09:54:56 +09:00
asmsuechan
b7b715ba3d Fix a return value of RcParser.parse 2017-08-10 09:44:00 +09:00
asmsuechan
6c43fb2325 Enable to set configs in ~/.boostnoterc 2017-08-10 09:43:50 +09:00
asmsuechan
a6fe3c27d4 Fix a cyclic object value error 2017-08-10 09:43:19 +09:00
asmsuechan
d47ff96b13 Enable to set configs in ~/.boostnoterc 2017-08-10 09:43:19 +09:00
SuenagaRyota
a0def654bd Merge pull request #782 from MrBMT/update-app-wording
Update application wording
2017-08-10 07:15:18 +09:00
Ben
4873b40e49 Update application text
Updates application text in various places to correct the wording of or better reflect the functionality provided.
2017-08-09 16:48:39 +01:00
SuenagaRyota
0a758f20a7 Merge pull request #780 from asmsuechan/fix-storageKey-undefined
Add storageKey to MarkdownEditor
2017-08-09 23:16:53 +09:00
asmsuechan
5e58d457a3 Add storageKey to MarkdownEditor 2017-08-09 23:12:07 +09:00
SuenagaRyota
0f745361ad Merge pull request #779 from MrBMT/fix-default-fonts
Add Lato font-face definition to main.html
2017-08-09 22:51:43 +09:00
Ben
bf6cae9a0e Add Lato font definition to main.html
Lato is the default "Preview font family" font. However, in the case that Lato is not installed on the user's system, it falls back to using a different font instead of using the font included with the application.
2017-08-09 14:42:07 +01:00
SuenagaRyota
ab640a7676 Merge pull request #777 from MrBMT/finder-fix
Finder UI Fix (My Storage positioning)
2017-08-09 21:04:26 +09:00
Ben
820171e19e Fixes storage positioning
Fixes positioning of the My Storage section on Finder, so that it is not shown over the top of the Trash.
2017-08-09 12:34:14 +01:00
Kazu Yokomizo
f1e9d0ab81 Merge pull request #767 from mrseanbaines/fix-typo
Fixes typo in welcome screen
2017-08-09 11:55:58 +09:00
Sean Baines
0646484c83 Fixes wording on folder delete
"This work will deletes all notes in the folder and can not be undone." to "This will delete all notes in the folder and can not be undone."
2017-08-06 11:34:39 +01:00
Kazu Yokomizo
a27b79c213 Add newline at InfoPanel 2017-08-06 13:59:51 +09:00
Kazu Yokomizo
773a9b4b7f Fixed InfoPanel layout in Trash 2017-08-06 12:56:52 +09:00
Sean Baines
07b838ef7b Fixes typo in welcome screen 2017-08-05 17:17:33 +01:00
SuenagaRyota
85217a7171 Merge pull request #766 from asmsuechan/improve-search
Context search
2017-08-05 22:56:31 +09:00
asmsuechan
886d7b7227 Fix test 2017-08-05 22:48:04 +09:00
asmsuechan
6987b762dd Make context-search work 2017-08-05 22:23:33 +09:00
SuenagaRyota
f32ac81f84 Merge pull request #764 from asmsuechan/fix-noteCount-by-trash
Fix noteCount on a note trased
2017-08-05 22:03:44 +09:00
asmsuechan
87ea66bb92 Change iterate variable 2017-08-05 22:00:12 +09:00
SuenagaRyota
ff6fd62932 Merge pull request #765 from asmsuechan/iss-758
iss #758 Add InfoButton and InfoPanel in Trash
2017-08-05 21:56:22 +09:00
asmsuechan
76728448ff iss #758 Add InfoButton and InfoPanel in SnippetNoteDetail 2017-08-05 17:34:26 +09:00
asmsuechan
3b7225e0fa iss #758 Add InfoButton and InfoPanel in Trash 2017-08-05 17:30:59 +09:00
asmsuechan
d6280f4397 Fix noteCount on a note trased 2017-08-05 17:16:57 +09:00
SuenagaRyota
8df867046f Merge pull request #753 from asmsuechan/work-shortcuts-in-right-click
Change to work ctrl-e and ctrl-w in RIGHTCLICK
2017-08-05 12:05:22 +09:00
SuenagaRyota
331c822816 Merge pull request #754 from asmsuechan/add-print
Add Print
2017-08-05 11:59:16 +09:00
SuenagaRyota
6219173945 Merge pull request #763 from asmsuechan/fix-issue_template
Stress words in ISSUE_TEMPLATE
2017-08-04 08:36:27 +09:00
asmsuechan
6207e02e7f Stress words in ISSUE_TEMPLATE 2017-08-04 08:33:09 +09:00
asmsuechan
537ba537dc Add Print 2017-08-01 19:30:48 +09:00
asmsuechan
3e919241e6 Change to work ctrl-e and ctrl-w in RIGHTCLICK 2017-08-01 09:20:22 +09:00
SuenagaRyota
2324327e7e Merge pull request #749 from asmsuechan/change-order-for-ama-event
Change order for AMA event
2017-07-31 23:08:41 +09:00
asmsuechan
b8374494ea Change order for AMA event 2017-07-31 23:02:49 +09:00
SuenagaRyota
a480ca7b55 Merge pull request #731 from asmsuechan/add-find-storage-path-module
Add find storage path module
2017-07-30 16:15:33 +09:00
Kohei TAKATA
bfd67fb7f1 Merge pull request #735 from BoostIO/feature-v0-8-12
v0.8.12
2017-07-29 15:04:53 +09:00
Kohei TAKATA
e6bd6a5077 v0.8.12 2017-07-29 13:58:11 +09:00
Kohei TAKATA
350af844ca Merge pull request #736 from BoostIO/fix-layout
Fix layout
2017-07-29 13:56:21 +09:00
Kazu Yokomizo
a49d53179a Change “Trashed” to “Trash” 2017-07-29 13:49:45 +09:00
Kazu Yokomizo
e68069fd2f Fix undo button layout at notedetail 2017-07-29 13:40:04 +09:00
SuenagaRyota
a193ba3e6c Merge pull request #732 from asmsuechan/fix-image-path-on-dropped
Make it work on windows
2017-07-26 15:55:30 +09:00
asmsuechan
9034b3ab54 Make it work on windows 2017-07-26 15:47:38 +09:00
asmsuechan
f39b7594ab Add tests for findStorage() 2017-07-26 15:16:21 +09:00
asmsuechan
f79734391e Change to use the module 2017-07-26 15:16:18 +09:00
asmsuechan
e54f516418 Add findStorage() 2017-07-26 14:40:50 +09:00
SuenagaRyota
e86464535d Merge pull request #730 from asmsuechan/make-image-path-relative
Make image path relative
2017-07-26 14:33:41 +09:00
asmsuechan
e8553caa65 Make finder work fine 2017-07-26 14:29:55 +09:00
asmsuechan
044e6b7180 Make image path relative 2017-07-26 13:16:32 +09:00
SuenagaRyota
0266770657 Merge pull request #726 from asmsuechan/fix-default-font-on-windows
iss #155 Change defaut font on windows_
2017-07-25 16:04:12 +09:00
SuenagaRyota
2d6f7c08e8 Merge pull request #728 from asmsuechan/iss-727
iss #727 fix incorrect encoding in link
2017-07-25 15:59:28 +09:00
asmsuechan
0a27819a7f iss #727 fix incorrect encoding in link 2017-07-25 15:35:44 +09:00
SuenagaRyota
3185c25ee1 Merge pull request #724 from BoostIO/fix-button-at-markdown-eidt
Fix hidden button
2017-07-25 14:32:37 +09:00
asmsuechan
820802cdc2 iss #155 Change defaut font on windows_ 2017-07-25 14:04:39 +09:00
Kazu Yokomizo
2c169e6f15 Merge pull request #723 from asmsuechan/fix-typo-for-info-data-collection
Fix typo in InfoTab
2017-07-25 13:45:23 +09:00
Kazu Yokomizo
49be1320f9 Fix hidden button 2017-07-25 13:44:17 +09:00
asmsuechan
6bc4ecce48 Fix typo in InfoTab 2017-07-25 11:57:04 +09:00
SuenagaRyota
0290d23832 Merge pull request #722 from BoostIO/fix-layout
Fix layout
2017-07-25 10:18:12 +09:00
Kazu Yokomizo
e001c97e01 Make padding between Storages at SideNav. 2017-07-24 19:18:48 +09:00
Kazu Yokomizo
976d1bb4f3 Delete unnecessary comma. 2017-07-24 19:09:13 +09:00
Kazu Yokomizo
dee6495b08 Fix typo 2017-07-24 18:12:33 +09:00
Kazu Yokomizo
4a77f250f1 Fix button layout 2017-07-24 17:55:47 +09:00
Kazu Yokomizo
c697f19642 Fix layout at export note in InfoPanel 2017-07-24 17:00:08 +09:00
Kazu Yokomizo
f7d1f9e949 Fix InfoTab layout 2017-07-24 14:47:10 +09:00
Kazu Yokomizo
27b9952f8e Change policy content 2017-07-24 14:39:43 +09:00
Kazu Yokomizo
961dab4a97 Merge pull request #721 from BoostIO/udpate-slack-link
Update readme
2017-07-24 12:11:56 +09:00
Kazu Yokomizo
930b6bc927 Update readme
Update the slack link.
2017-07-24 11:53:39 +09:00
SuenagaRyota
ed7d8258cf Merge pull request #720 from asmsuechan/goodby-ctrl-g
Goodbye Ctrl-G
2017-07-22 16:44:48 +09:00
asmsuechan
762425125f Remove Ctrl-G because it's unneccessary and conflicts with CodeMirror 2017-07-22 16:41:08 +09:00
SuenagaRyota
03bdf0c653 Merge pull request #719 from asmsuechan/fix-inseration-image
Fix inseration position
2017-07-22 16:13:25 +09:00
asmsuechan
7002026190 Fix inseration position 2017-07-22 16:05:14 +09:00
SuenagaRyota
b35395e6bd Merge pull request #709 from asmsuechan/add-exports-to-infoPanel
Add exports to infoPanel
2017-07-22 15:47:01 +09:00
SuenagaRyota
b74fb76d03 Merge pull request #718 from asmsuechan/fix-typos
Fix typos Dynamit to Dynamic
2017-07-22 15:42:49 +09:00
asmsuechan
72d2df465b Fix typos Dynamit to Dynamic 2017-07-22 15:33:56 +09:00
SuenagaRyota
2592c943f7 Merge pull request #717 from asmsuechan/fix-a-bug-on-click
Fix a bug on click
2017-07-22 15:28:38 +09:00
asmsuechan
7b3a3ba200 Fix by eslint 2017-07-22 15:24:51 +09:00
asmsuechan
8369832585 Fix a bug on click 2017-07-22 15:11:59 +09:00
SuenagaRyota
d94a674343 Merge pull request #716 from asmsuechan/change-note-link-content
Change ["title"](note-hash) to [the exact title](note-hash)
2017-07-22 15:01:04 +09:00
SuenagaRyota
260611ffd6 Merge pull request #715 from asmsuechan/add-checkbox-for-ama
Add checkbox for ama
2017-07-22 14:57:31 +09:00
asmsuechan
690549b57f Change a word 2017-07-22 14:34:28 +09:00
asmsuechan
5d9aeb4c04 Fix typo 2017-07-22 14:32:18 +09:00
asmsuechan
eddfdea2ca Fix a closing tag and a condition 2017-07-22 14:20:42 +09:00
asmsuechan
a40385f87f Add policy 2017-07-22 13:58:02 +09:00
asmsuechan
0123526c98 Change ["title"](note-hash) to [the exact title](note-hash) 2017-07-22 11:29:39 +09:00
asmsuechan
275e3317a3 Add checkbox for ama 2017-07-22 08:59:27 +09:00
SuenagaRyota
d0b835a825 Merge pull request #710 from asmsuechan/fix-image-dropped
Change to create a new directory when storage/images does not exist
2017-07-16 15:52:03 +09:00
asmsuechan
f450260ff8 Fix a prop name noteKey to noteLink 2017-07-16 15:16:59 +09:00
asmsuechan
5f7b119e5c Remove an useless 'cancel' button 2017-07-16 15:06:22 +09:00
asmsuechan
bb9eb494e9 Change to create a new directory when storage/images does not exist 2017-07-16 15:02:18 +09:00
asmsuechan
6ea2b5e1d9 Add exports to infoPanel 2017-07-15 19:33:58 +09:00
SuenagaRyota
b21baf1ce5 Merge pull request #708 from asmsuechan/change-note-hash
Change noteHash to noteLink in the input in InfoPanel
2017-07-15 16:57:55 +09:00
SuenagaRyota
4c3fd461e4 Merge pull request #707 from asmsuechan/remove-dialogbox-from-trash
Remove a dialogbox from onTrashButtonClick
2017-07-15 14:47:38 +09:00
asmsuechan
8365a60d5d Change noteHash to noteLink in the input in InfoPanel 2017-07-15 13:41:22 +09:00
asmsuechan
804ac1aa96 Remove a dialogbox from onTrashButtonClick 2017-07-15 12:52:55 +09:00
SuenagaRyota
49fd2cfc4d Merge pull request #706 from asmsuechan/change-shotrcut-ctrl-u-to-ctrl-k
Change the keymap of Ctrl-U to Ctrl-K
2017-07-15 12:51:40 +09:00
asmsuechan
2bb2a52f27 Change the keymap of Ctrl-U to Ctrl-K 2017-07-14 18:18:38 +09:00
Kazu Yokomizo
8d71b28afa Merge pull request #705 from BoostIO/udpate-readme
Fix slack link
2017-07-13 16:23:34 +09:00
Kazu Yokomizo
86eac7054d Update Readmd 2017-07-13 15:41:56 +09:00
SuenagaRyota
dccb92d72b Merge pull request #297 from asmsuechan/add-trash-can
Add trash can
2017-07-12 15:53:49 +09:00
asmsuechan
2e628de9c6 Restore missing components 2017-07-12 15:48:11 +09:00
asmsuechan
2d243abc12 Add condition 2017-07-12 15:48:11 +09:00
asmsuechan
ae08bf4d7a Escape from addition a note when the note is in the trashbox 2017-07-12 15:48:11 +09:00
asmsuechan
cbff5fb585 Fix indents pointed by lint 2017-07-12 15:48:11 +09:00
asmsuechan
2650cc2f1c Add trash can 2017-07-12 15:48:08 +09:00
Kazu Yokomizo
ec560ceab1 Merge pull request #702 from BoostIO/update-readme
Update Readme
2017-07-11 01:01:19 +09:00
Kazu Yokomizo
ad17cb8837 Update Readme 2017-07-11 00:55:21 +09:00
Kazu Yokomizo
4f98d9641a Merge pull request #698 from BoostIO/update-layout
Update layout
2017-07-11 00:20:59 +09:00
Kazu Yokomizo
17535ccd4c Add Info Panel at SnippetDetail 2017-07-10 17:43:20 +09:00
Kazu Yokomizo
110c1ea337 Delete update date at under of NoteDetail 2017-07-10 17:13:18 +09:00
Kazu Yokomizo
7e087bfbab Fix info panel layout 2017-07-10 17:07:44 +09:00
SuenagaRyota
85e0d4b922 Merge pull request #697 from asmsuechan/remove-sharp-from-hash
Remove sharp from note hash on list:jump
2017-07-10 16:43:02 +09:00
asmsuechan
265262ccbf Remove sharp from note hash on list:jump 2017-07-10 16:37:07 +09:00
Kazu Yokomizo
345008e9b6 Fix info area layout at NoteDetail 2017-07-10 16:28:50 +09:00
SuenagaRyota
849aa05d76 Merge pull request #696 from asmsuechan/replace-cm-searchbox-to-official-search-addon
Replace cm searchbox to official search addon
2017-07-10 14:21:47 +09:00
asmsuechan
5ff5ec6a71 Remove cm-searchbox 2017-07-09 22:47:15 +09:00
asmsuechan
684c3f64aa Add search addon 2017-07-09 22:46:23 +09:00
SuenagaRyota
4ff73ede59 Merge pull request #695 from asmsuechan/bugfix-on-click-event
Fix an error on click event
2017-07-09 18:20:51 +09:00
asmsuechan
7302d83f60 Fix a bug on click event 2017-07-09 18:08:33 +09:00
SuenagaRyota
f9836fd66e Merge pull request #692 from asmsuechan/add-info-button
Add info button
2017-07-09 17:16:06 +09:00
SuenagaRyota
c0f05695fe Merge pull request #686 from asmsuechan/add-search-box
Add search-box
2017-07-09 17:15:57 +09:00
SuenagaRyota
e50a1172d6 Merge pull request #693 from asmsuechan/feature-links-among-notes
Feature links among notes
2017-07-09 17:15:50 +09:00
Kazu Yokomizo
7a4234e73c Merge pull request #690 from BoostIO/update-layout
Fix layout
2017-07-09 13:29:00 +09:00
Kazu Yokomizo
4b9640e5a3 Fix star position at NoteList 2017-07-09 12:56:02 +09:00
Kazu Yokomizo
4b58054100 Fix tag area layout at NoteItem 2017-07-09 12:46:46 +09:00
Kazu Yokomizo
8951dbf4ed Fix star position at NoteList 2017-07-08 16:05:45 +09:00
Kazu Yokomizo
5047bc94eb Fix search area layout 2017-07-08 15:58:40 +09:00
asmsuechan
75fb29c594 Change == to === 2017-07-08 15:44:20 +09:00
asmsuechan
bd75f7fd2c Remove a display of a note hash 2017-07-08 14:08:28 +09:00
asmsuechan
8b8cb3c9b4 Change styleName in each styles 2017-07-08 14:08:28 +09:00
asmsuechan
b479b21c37 Remove a blank line 2017-07-08 14:08:28 +09:00
asmsuechan
525c490704 Remove console.log 2017-07-08 14:08:28 +09:00
asmsuechan
2e3599d005 Add a feature which enables to link between notes 2017-07-08 14:08:28 +09:00
asmsuechan
f005bb1e46 hoge 2017-07-08 14:02:03 +09:00
asmsuechan
21f96febdb Fix some pointed by lint 2017-07-08 13:37:24 +09:00
asmsuechan
805829be78 Make InfoButton component stateless 2017-07-08 13:32:37 +09:00
asmsuechan
a7bd3f253f Rename getLastUpdated() to formatDate() 2017-07-08 13:16:19 +09:00
asmsuechan
81e8a290f0 Add infomation panel 2017-07-08 13:16:02 +09:00
Kazu Yokomizo
f46a967b6e Fix an error 2017-07-08 12:31:21 +09:00
Kazu Yokomizo
b78d9534aa Fix layout 2017-07-08 02:18:33 +09:00
SuenagaRyota
bdefaf7427 Merge pull request #681 from BoostIO/edit-initial-files
Edit initial files
2017-07-07 20:59:50 +09:00
SuenagaRyota
43550c7dec Merge pull request #688 from asmsuechan/remove-handle-note-context-menu
Remove the context menu for deletion
2017-07-07 20:12:11 +09:00
asmsuechan
1b07d393f2 Rename handleDeleteButtonClick to handleTrashButtonClick 2017-07-07 20:04:47 +09:00
asmsuechan
44fc356775 Remove the context menu for deletion 2017-07-07 19:45:02 +09:00
SuenagaRyota
5623c68170 Merge pull request #684 from BoostIO/change-menu-to-preferences
Change “Menu” to “Preferences”, and icon.
2017-07-07 18:11:01 +09:00
asmsuechan
d5f82943cf Edit style 2017-07-07 18:06:08 +09:00
asmsuechan
96a020341d Add search-box 2017-07-07 17:59:01 +09:00
Kazu Yokomizo
99b5f75763 Change “Menu” to “Preferences”, and icon. 2017-07-06 14:27:18 +09:00
SuenagaRyota
a128ff7cd8 Merge pull request #683 from asmsuechan/add-iss_template.md
Add issue template
2017-07-06 13:53:26 +09:00
Kazu Yokomizo
59329cc5da Fix typo 2017-07-06 13:50:39 +09:00
asmsuechan
ecd5daaf55 Add issue template 2017-07-06 13:44:19 +09:00
Kazu Yokomizo
514232d720 Merge pull request #682 from BoostIO/update-slack-link
Update slack link
2017-07-06 13:29:39 +09:00
Kazu Yokomizo
93abfe3202 Update slack link 2017-07-06 13:23:22 +09:00
Kazu Yokomizo
4d9e0e3bd7 Change default theme at editor and code-block 2017-07-06 02:32:14 +09:00
Kazu Yokomizo
75d9bf8e38 Edit initial files 2017-07-06 02:31:41 +09:00
SuenagaRyota
ccb9752254 Merge pull request #678 from asmsuechan/remove-e2e-document
Remove e2e documents
2017-07-05 13:38:42 +09:00
asmsuechan
0f2ca19c6c Remove the script of e2e test 2017-07-05 13:33:07 +09:00
asmsuechan
d5a77aade4 Remove testing.md 2017-07-05 13:32:06 +09:00
SuenagaRyota
3c89e6a128 Merge pull request #672 from asmsuechan/componentnize-top-bar-of-note-detail
Componentnize TrashButton
2017-07-02 22:29:14 +09:00
SuenagaRyota
a36be79977 Merge pull request #667 from asmsuechan/randomize-image-name
Randomize the image name when it's dropped
2017-07-02 22:22:23 +09:00
asmsuechan
617f0e0f9c Remove duplicated styles 2017-07-02 22:13:35 +09:00
asmsuechan
0db1a94105 Replace a button to TrashButton component 2017-07-02 22:13:35 +09:00
asmsuechan
f29f97a00f Add TrashButton component 2017-07-02 22:13:32 +09:00
asmsuechan
032b5a59f2 Remove encodeURI 2017-07-01 18:07:16 +09:00
SuenagaRyota
1e5bc395ac Merge pull request #671 from asmsuechan/goodbye-spectron
Goodbye spectron
2017-07-01 12:43:06 +09:00
asmsuechan
fef4afe660 Change .travis.yml 2017-07-01 12:02:52 +09:00
asmsuechan
679ed1eacf Remove e2e tests 2017-07-01 12:02:52 +09:00
asmsuechan
78a9320017 Remove spectron 2017-07-01 12:01:16 +09:00
asmsuechan
927981bf30 Randomize the image name when it's dropped 2017-06-28 13:05:40 +09:00
Kazu Yokomizo
70ea2f6c14 Merge pull request #665 from BoostIO/update-slack-link
Update slack link
2017-06-27 13:46:57 +09:00
Kazu Yokomizo
a6779414ff Delete readme.ja 2017-06-27 13:34:25 +09:00
Kazu Yokomizo
b62fcf523a Update slack link 2017-06-27 13:33:48 +09:00
Kazu Yokomizo
d0bbea5a97 Merge pull request #664 from asmsuechan/auth-aws-only-production
Exclude auth of aws only production
2017-06-26 15:38:33 +09:00
asmsuechan
212d7027fc Exclude auth of aws only production 2017-06-26 15:33:58 +09:00
Kohei TAKATA
be1a5b09da Merge pull request #663 from BoostIO/feature-v0-8-11
v0.8.11
2017-06-24 13:28:49 +09:00
Kohei TAKATA
3de2db4459 v0.8.11 2017-06-24 10:38:13 +09:00
Kazu Yokomizo
f2405a5b34 Merge pull request #661 from BoostIO/translate-to-russian
Add Russian files
2017-06-23 00:51:26 +09:00
Kazu Yokomizo
94abb8f959 Add credit 2017-06-22 23:38:23 +09:00
Kazu Yokomizo
2e30db05bc Add credit 2017-06-22 23:38:05 +09:00
Kazu Yokomizo
c93f3cc5dd Add credit 2017-06-22 23:37:14 +09:00
Kazu Yokomizo
068f8f2ba9 Update contributing.md 2017-06-22 23:35:27 +09:00
Kazu Yokomizo
0980c3b012 Add Russian files 2017-06-22 23:29:45 +09:00
SuenagaRyota
5288d6768f Merge pull request #658 from BoostIO/fix-scroll-bar-z-index
Fix not to show scroll bar when full screen mode in Windows
2017-06-22 14:42:46 +09:00
Kohei TAKATA
34491f4ea4 Remove unnecessary style 2017-06-21 22:53:22 +09:00
Kohei TAKATA
8c73ca8854 Fix not to show scroll bar when full screen mode in Windows 2017-06-21 22:51:21 +09:00
Kazu Yokomizo
0786d8eab6 Merge pull request #656 from BoostIO/add-backers-file
Add backers.md
2017-06-21 20:05:59 +09:00
Kazu Yokomizo
209518c815 Add backers.md 2017-06-21 19:42:51 +09:00
SuenagaRyota
b2753b6457 Merge pull request #644 from BoostIO/windows-fullscreen-zIndex
Fixed z-index at NoteDetail when fullscreen mode in windows.
2017-06-21 09:31:33 +09:00
Kazu Yokomizo
4775a920c0 Merge pull request #653 from BoostIO/update-slack-link
Update slack link
2017-06-20 21:51:01 +09:00
Kazu Yokomizo
0e94dc8740 Update slack link 2017-06-20 21:40:41 +09:00
Kazu Yokomizo
cf68d202d5 Update slack linl 2017-06-20 21:40:11 +09:00
SuenagaRyota
d29ba2bf16 Merge pull request #647 from clone1612/productionSizeFix
Fix dependency type of Spectron module
2017-06-18 14:39:31 +09:00
Kazu Yokomizo
720686cef5 Merge pull request #649 from asmsuechan/add-copy-on-ctrl-c-on-vim
Make Ctrl-C copy
2017-06-18 14:31:35 +09:00
asmsuechan
0625c65cf0 Make Ctrl-C copy 2017-06-18 08:14:33 +09:00
Jannick Hemelhof
acaefe22d1 Fix dependency type of spectron module 2017-06-17 18:01:01 +02:00
Kazu Yokomizo
df1f083ebf Merge pull request #645 from AlbertHilb/FixTypo
Rename `TodolistPercentage.js` to `TodoListPercentage.js`.
2017-06-17 22:25:28 +09:00
Raffaele De Feo
5a1dfc2ca9 Rename TodolistPercentage.js to TodoListPercentage.js. 2017-06-17 14:42:40 +02:00
Kazu Yokomizo
d84894f1bf Merge pull request #643 from BoostIO/update-fullscreen-btn-position
Change fullscreen button position at NoteDetail
2017-06-16 22:35:56 +09:00
SuenagaRyota
0fdc444c4c Merge pull request #642 from BoostIO/fix-analytics
Fix initialize logic of analytics
2017-06-16 22:35:33 +09:00
Kazu Yokomizo
4d216c6f13 Fixed z-index at NoteDetail when fullscreen mode in windows. 2017-06-16 22:25:48 +09:00
Kazu Yokomizo
1c0af8eede Change fullscreen button position at NoteDetail 2017-06-16 21:58:02 +09:00
Kohei TAKATA
f443f9264a Fix initialize logic of analytics 2017-06-16 21:35:21 +09:00
SuenagaRyota
d9b2981327 Merge pull request #325 from asmsuechan/feature-import-from-md-file
Add importer which imports files from .md/.txt
2017-06-16 21:28:04 +09:00
asmsuechan
68e36d2a6d Fix some pointed by lint 2017-06-16 21:17:39 +09:00
SuenagaRyota
68b91bf98c Merge pull request #611 from asmsuechan/error-handling-for-loading-notes
Fix errorhandling on cases of invalid notes
2017-06-16 21:09:48 +09:00
Kazu Yokomizo
b91ddfad05 Merge pull request #640 from BoostIO/update-slack-link
Update slack link
2017-06-13 11:44:21 +09:00
Kazu Yokomizo
a110cbfb5d Update slack link 2017-06-13 11:38:57 +09:00
Kazu Yokomizo
d69f45b0c9 Update slack link 2017-06-13 11:38:22 +09:00
asmsuechan
07df26d5c4 Change to use findNoteTitle() 2017-06-13 11:13:07 +09:00
asmsuechan
d24bcb7f86 Change to template literal 2017-06-13 11:06:09 +09:00
asmsuechan
9f383ba491 Add an option to enable selectiong multiple files 2017-06-13 11:06:09 +09:00
asmsuechan
c38a76d587 Add importer which imports files from .md/.txt 2017-06-13 11:06:00 +09:00
SuenagaRyota
994ca5dd02 Merge pull request #639 from hassec/FixAWSInclude
Fix typo in includes
2017-06-13 10:24:56 +09:00
Christoph Hasse
9281f8f6cb fix spelling to avoid include error 2017-06-13 00:59:37 +02:00
Kohei TAKATA
324f579474 Merge pull request #636 from BoostIO/feature-v0-8-10
v0.8.10
2017-06-13 02:52:07 +09:00
Kohei TAKATA
10bd2d4547 v0.8.10 2017-06-12 22:44:28 +09:00
SuenagaRyota
d1942868e4 Merge pull request #635 from asmsuechan/add-notification-on-copying-a-codeblock
Add notification on a copy button clicked
2017-06-12 21:54:56 +09:00
asmsuechan
a409b3e48d Add notification on a copy button clicked 2017-06-12 21:44:29 +09:00
SuenagaRyota
21004aab6a Merge pull request #634 from asmsuechan/enable-analytics-on-develop
Disable awsAnalytics on develop
2017-06-12 19:07:32 +09:00
asmsuechan
eaf88c6491 Disable awsAnalytics on develop 2017-06-12 18:57:40 +09:00
SuenagaRyota
17dcd1b6f1 Merge pull request #633 from asmsuechan/iss-619
Fix a rendering bug
2017-06-12 18:44:40 +09:00
asmsuechan
244a06eea6 Fix some pointed by lint 2017-06-12 18:12:11 +09:00
asmsuechan
afb13af7a1 iss #619 Add a test for teh helper methods 2017-06-12 18:05:32 +09:00
asmsuechan
ff9b935e98 iss #619 Add an entity to fix the rendering bug 2017-06-12 18:01:04 +09:00
SuenagaRyota
4250d6fe52 Merge pull request #632 from asmsuechan/handling-pull-602
Handling pull 602
2017-06-12 17:43:42 +09:00
SuenagaRyota
5bc2094f10 Merge pull request #628 from BoostIO/feature-improve-search-textbox-ux
improve searchTextBox UX
2017-06-12 17:41:27 +09:00
Kevin Nadro
552653c0ed for got run eslint 2017-06-12 17:34:14 +09:00
Kevin Nadro
c9bbc61c95 cleaning up code and fixing slider bug on fullscreen mode 2017-06-12 17:34:08 +09:00
Kevin Nadro
41a04aa3f1 reworking architecture of code 2017-06-12 17:34:02 +09:00
Kevin Nadro
cd421c4662 fixed the state change and cleaned up code for eslint 2017-06-12 17:33:56 +09:00
Kevin Nadro
063e2e02bd fixing some code from the comments 2017-06-12 17:33:50 +09:00
Kevin Nadro
0c3019b52e added fullscreen button for notes and snippets
allows user to hide the sidebars for fullscreen editing
2017-06-12 17:33:44 +09:00
Kevin Nadro
2ee2494dc4 for got run eslint 2017-06-12 17:33:37 +09:00
Kevin Nadro
df6ff1fffe cleaning up code and fixing slider bug on fullscreen mode 2017-06-12 17:33:31 +09:00
Kevin Nadro
0d2e6a6a12 forgot this in last commit 2017-06-12 17:33:24 +09:00
Kevin Nadro
79ed55a76f reworking architecture of code 2017-06-12 17:32:59 +09:00
Kevin Nadro
cbe58b9437 fixed the state change and cleaned up code for eslint 2017-06-12 17:30:38 +09:00
Kevin Nadro
afc729b1c3 fixing some code from the comments 2017-06-12 17:30:29 +09:00
Kevin Nadro
5d0cb0302e added fullscreen button for notes and snippets
allows user to hide the sidebars for fullscreen editing
2017-06-12 17:30:19 +09:00
Sosuke Suzuki
29e0d121cd improve searchTextBox ux 2017-06-11 15:42:07 +09:00
Kohei TAKATA
48ca13f82c Merge pull request #620 from BoostIO/feature-AmazonMobileAnalytics
Feature amazon mobile analytics
2017-06-10 13:45:38 +09:00
Sosuke Suzuki
278061e4f1 fix spacing 2017-06-10 12:58:24 +09:00
Sosuke Suzuki
186a815821 config to use dynamic customEvents 2017-06-10 12:51:01 +09:00
Sosuke Suzuki
2fea9eb874 setting AMA custom events 2017-06-10 12:50:19 +09:00
Sosuke Suzuki
f8b6453be9 config AmazonMobileAnalytics 2017-06-10 12:48:28 +09:00
SuenagaRyota
be625f8884 Merge pull request #626 from asmsuechan/iss-625
Fix a bug which is not showing dialog when user tries to export a snippet
2017-06-07 15:32:02 +09:00
asmsuechan
2271def5d3 Fix a bug which is not showing dialog when user tries to export a snippet 2017-06-07 15:23:07 +09:00
SuenagaRyota
275a8ea7cc Merge pull request #624 from asmsuechan/remove-unused-file
Remove an unused file
2017-06-07 14:19:22 +09:00
asmsuechan
dc236f33b1 Remove an unused file 2017-06-07 14:14:55 +09:00
SuenagaRyota
678d739e75 Merge pull request #623 from asmsuechan/add-emacs-keymap
Add emacs keymap
2017-06-07 12:48:39 +09:00
SuenagaRyota
8fe05a4c24 Merge pull request #548 from asmsuechan/add-a-module-findTitle
Add a module to find the title
2017-06-07 10:30:46 +09:00
asmsuechan
77adfdb9f0 Add emacs keymap 2017-06-07 09:37:21 +09:00
Kazu Yokomizo
30c94028fb Merge pull request #622 from BoostIO/fix-copy-button-layout
Fix copy button layout
2017-06-06 23:30:08 +09:00
Kazu Yokomizo
b5bf0780fa Fix copy button layout 2017-06-06 23:14:31 +09:00
SuenagaRyota
ad838d82ee Merge pull request #603 from nadr0/copyclipboard
Copy clipboard button for markdown code blocks
2017-06-06 21:38:00 +09:00
Kazu Yokomizo
d5ec0c0cdd Merge pull request #617 from CodeRi13/master
Translate contributing.md to korean
2017-06-05 16:16:50 +09:00
SuenagaRyota
ff29f1e0c6 Merge pull request #618 from BoostIO/revert-610-add-e2e-tests
Revert "Add e2e tests"
2017-06-05 13:09:10 +09:00
SuenagaRyota
1f58698a04 Revert "Add e2e tests" 2017-06-05 13:06:10 +09:00
CodeRi13
a275f331d0 Translate contributing.md to korean 2017-06-05 10:38:44 +09:00
Sota Sugiura
0862c6e059 Merge pull request #293 from asmsuechan/copy-image-on-dropped-into-CodeEditor
Add dataApi.copyImage for copying image to boostnote storage on an image dropped into CodeEditor
2017-06-05 01:29:11 +09:00
SuenagaRyota
46fddfd26c Merge pull request #614 from asmsuechan/add-documents
Add documents
2017-06-04 15:29:33 +09:00
asmsuechan
c547ccb4cb Update a document 2017-06-04 15:24:40 +09:00
asmsuechan
86e82fb149 Add required environments for building Boostnote on each local 2017-06-04 12:10:15 +09:00
SuenagaRyota
00a284bfbd Merge pull request #612 from asmsuechan/move-a-package-dev-to-prod
Move markdown-it-imsize dev-dependency to production
2017-06-03 20:52:40 +09:00
asmsuechan
f6fbec0a2e Move markdown-it-imsize dev-dependency to production 2017-06-03 20:47:43 +09:00
SuenagaRyota
b24c06bee6 Merge pull request #610 from asmsuechan/add-e2e-tests
Add e2e tests
2017-06-03 18:52:45 +09:00
asmsuechan
95e7f4f645 Fix errorhandling on cases of invalid notes 2017-06-03 18:47:56 +09:00
asmsuechan
4b75d501f5 Edit a e2e test for editting note 2017-06-03 18:28:40 +09:00
asmsuechan
b28d5c8b25 Fix the class name of modalClose button 2017-06-03 18:28:40 +09:00
SuenagaRyota
86e61661e6 Merge pull request #609 from asmsuechan/add-tests-for-search
Add tests for searchFromeNotes()
2017-06-03 17:24:33 +09:00
asmsuechan
cdd5717e63 Fix some pointed by eslint 2017-06-03 17:10:58 +09:00
asmsuechan
42bd7fb4fb Add tests for searchFromeNotes() 2017-06-03 17:04:22 +09:00
SuenagaRyota
66e478a001 Merge pull request #600 from BoostIO/feature-change-search-ux
change search ux
2017-06-03 16:41:36 +09:00
Kazu Yokomizo
c8259abcac Merge pull request #549 from LetItRock/#535
Fix for #535:
2017-06-03 14:35:52 +09:00
SuenagaRyota
d6d16a63a4 Merge pull request #608 from asmsuechan/fix-travis-node-version
Fix travis node version
2017-06-03 14:32:11 +09:00
asmsuechan
df8d1f0714 Add a comment 2017-06-03 14:24:28 +09:00
asmsuechan
fbbc6411c3 Fix the node_version to 4.x 2017-06-03 14:07:56 +09:00
Kazu Yokomizo
bc1e94fcab Merge pull request #607 from BoostIO/update-slack-link
Update slack link
2017-06-02 15:44:32 +09:00
Kazu Yokomizo
418439e3d5 Update slack group link 2017-06-02 15:27:36 +09:00
Kazu Yokomizo
46cbd04ba7 Update slack group link 2017-06-02 15:27:06 +09:00
asmsuechan
0ade6d9ece Add e2e tests for modal click and input texts 2017-06-01 17:26:28 +09:00
asmsuechan
823599192f Fix e2e tests 2017-06-01 15:59:12 +09:00
Sosuke Suzuki
9496ab88f7 fix spacings 2017-05-31 20:52:36 +09:00
Sosuke Suzuki
290e6ab170 change arg findByTag 2017-05-31 20:49:34 +09:00
asmsuechan
a9e3572f4f Add a e2e-test for Modal 2017-05-31 15:38:55 +09:00
Kazu Yokomizo
92b2b7dde3 Merge pull request #605 from BoostIO/improve-dark-theme
I improved the dark theme.
2017-05-31 15:26:55 +09:00
Kazu Yokomizo
dad1e40234 Fix white theme layout 2017-05-31 01:43:07 +09:00
Kazu Yokomizo
b75c8b0373 Fix code highlight 2017-05-31 01:26:40 +09:00
Sosuke Suzuki
fc4c471a87 fix spacing for eslint 2017-05-31 00:30:55 +09:00
Kazu Yokomizo
24de71f240 I improved the dark theme. 2017-05-30 23:33:59 +09:00
Sosuke Suzuki
805c39e60c refactor search function 2017-05-30 22:16:27 +09:00
Sosuke Suzuki
10e75041e8 add className 'searchInput' 2017-05-30 18:54:13 +09:00
Sosuke Suzuki
2364348df4 fix indent 2017-05-30 18:49:39 +09:00
Sosuke Suzuki
e643443660 split search function to module. 2017-05-30 18:27:57 +09:00
Kevin Nadro
d72e876b23 fixing eslint 2017-05-28 17:58:10 -05:00
Kevin Nadro
f3612774ba clipboard button is added 2017-05-28 17:31:23 -05:00
SuenagaRyota
67d8b02c49 Merge pull request #598 from dangvanthanh/master
#595 - Resize an image with markdown
2017-05-27 07:08:31 +09:00
Sosuke Suzuki
7abf53009a change search ux 2017-05-26 18:01:44 +09:00
Kazu Yokomizo
90bb4632b6 Merge pull request #599 from BoostIO/update-slack-link
Update slack link
2017-05-26 13:59:50 +09:00
Kazu Yokomizo
630da00235 Update slack link 2017-05-26 13:57:01 +09:00
Kazu Yokomizo
245e0dd7ee update slack group link 2017-05-26 13:56:23 +09:00
Dang Van Thanh
824e288a80 #595 - Resize an image with markdown 2017-05-26 10:58:39 +07:00
asmsuechan
99228f2e60 ♻️ Refactor findeTitle() 2017-05-23 17:01:07 +09:00
asmsuechan
0f71139eba Delete a comma pointed by eslint 2017-05-23 15:51:21 +09:00
asmsuechan
2bbe7056d1 Add test cases 2017-05-23 15:43:54 +09:00
asmsuechan
005d8f84fd Change for to some 2017-05-23 15:43:48 +09:00
asmsuechan
908cd7a890 Edit the method 2017-05-23 15:31:32 +09:00
asmsuechan
d4865adf6a Change findNoteTitle.find() to findNoteTitle.findNoteTitle() in test 2017-05-23 15:23:16 +09:00
asmsuechan
20411a2fd5 Change the function name of findNoteTitle.js from find to findNoteTitle 2017-05-23 15:23:16 +09:00
asmsuechan
19f8930f5a Fix to use strict equal 2017-05-23 15:23:16 +09:00
asmsuechan
ffc4ca1dd4 Add an unit test for findNoteTitle 2017-05-23 15:23:16 +09:00
asmsuechan
0bc9c6fb5a Change to use the module instead of a function findTitle() implemented inside the file 2017-05-23 15:23:11 +09:00
asmsuechan
983f453afd Add findTitle as a module 2017-05-23 15:17:47 +09:00
Kazu Yokomizo
60a0293495 Merge pull request #594 from BoostIO/edit-noteList-layout
If there is no tag, narrow the width of NoteList.
2017-05-22 21:19:36 +09:00
Kazu Yokomizo
4807d22763 If there is no tag, narrow the width of NoteList. 2017-05-22 20:13:47 +09:00
Kazu Yokomizo
e8d451bcbb Merge pull request #587 from BoostIO/fix-task-bar-ui
Fix task bar UI
2017-05-20 15:18:50 +09:00
Kazu Yokomizo
4809bb6f06 Change the z-index postiion 2017-05-20 15:09:52 +09:00
Kazu Yokomizo
d6d694cf66 Change z-index position and delete unnecessary class 2017-05-20 15:07:21 +09:00
Kazu Yokomizo
132e3c3088 Fix task bar UI 2017-05-20 14:53:06 +09:00
SuenagaRyota
3c7ff78549 Merge pull request #374 from BoostIO/feature_move_note_between_folder
Move note between folders
2017-05-20 14:33:55 +09:00
SuenagaRyota
adbdc2cb1c Merge pull request #510 from BoostIO/fix-note-title-bug-for-h1
fix the bug when using markdown syntax h1
2017-05-20 14:27:52 +09:00
Kazu Yokomizo
e52b74bbc9 Merge pull request #557 from BoostIO/feature-todo-percentage
display percentage of achievement of todo in markdown
2017-05-20 13:51:30 +09:00
Sosuke Suzuki
575f1b4b33 change from forEach to some 2017-05-20 12:28:11 +09:00
Sosuke Suzuki
a528c99900 change from 'undefined' to undefined 2017-05-20 12:24:58 +09:00
Sosuke Suzuki
e6047ed383 use String template literal 2017-05-20 11:38:09 +09:00
Kazu Yokomizo
381b7d960a Merge pull request #584 from BoostIO/update-slack-invitation
Update slack invitation
2017-05-18 17:01:15 +09:00
Kazu Yokomizo
045a8bde22 Update slack group link 2017-05-18 16:57:06 +09:00
Kazu Yokomizo
e528182c2a Update slack group link 2017-05-18 16:56:30 +09:00
Sosuke Suzuki
b86cdb461a fix the spacing 2017-05-18 01:27:37 +09:00
Sosuke Suzuki
7265f76770 for loop change to forEach 2017-05-18 00:31:46 +09:00
Sosuke Suzuki
2ed092279d avoid undefined 2017-05-18 00:07:48 +09:00
SuenagaRyota
7d71819be0 Merge pull request #578 from asmsuechan/add-a-documentation-of-e2e-testiong
Add a documentation of e2e testiong
2017-05-16 15:20:59 +09:00
asmsuechan
e848c74511 Fix the document in English 2017-05-16 15:17:20 +09:00
asmsuechan
968ed146b2 Fix the document in Japanese 2017-05-16 15:16:55 +09:00
asmsuechan
5e03f12875 Add a documentation of e2e testing in Japanese 2017-05-16 15:13:56 +09:00
asmsuechan
09f8e5f25a Add a documentation of e2e testing in English 2017-05-16 15:11:20 +09:00
SuenagaRyota
89f4ed3006 Merge pull request #471 from BoostIO/escButton-to-ReactComponent
made escButton ReactComponent
2017-05-13 17:15:21 +09:00
Sosuke Suzuki
8d14a9557d fix the indent 2017-05-13 17:07:02 +09:00
Sosuke Suzuki
513042d769 made escButton ReactComponent 2017-05-13 17:00:34 +09:00
Sosuke Suzuki
c28980c2a9 fix the spacing 2017-05-12 19:54:52 +09:00
Sosuke Suzuki
1c31ff4e98 use string template literal 2017-05-10 23:47:09 +09:00
Sosuke Suzuki
7c9d3904b3 change to forEach from for 2017-05-10 23:10:25 +09:00
Sosuke Suzuki
e23707f0a0 add + and * to matching pattern 2017-05-10 22:49:15 +09:00
Kazu Yokomizo
a952b18b96 Merge pull request #560 from BoostIO/update-slack-link-1
Update slack link 1
2017-05-09 21:55:29 +09:00
Kazu Yokomizo
35cc07bf63 Update readme-ja.md 2017-05-09 21:49:07 +09:00
Kazu Yokomizo
118bf18434 Update slack link 2017-05-09 21:48:40 +09:00
Sosuke Suzuki
34da15208b fix spacing 2017-05-06 16:57:15 +09:00
Sosuke Suzuki
e0dc62c00b fix indents 2017-05-06 16:40:42 +09:00
Sosuke Suzuki
b58df2f172 display percentage of achievement of todo in markdown 2017-05-06 16:30:03 +09:00
Pavlo Tymchuk
90846fab81 Fix for #535:
- hide/show code editor based on markdown editor state (could be CODE or PREVIEW);
- changed styles for code editor;
2017-05-04 23:09:48 +02:00
SuenagaRyota
efd80d5c0c Merge pull request #545 from LeoLamCY/revamp-keypress-handling
Revamp handling of multiple key press
2017-05-04 10:59:28 -07:00
SuenagaRyota
e37e28a22e Merge pull request #541 from asmsuechan/fix-travis-for-spectron
Fix viriables for travis
2017-05-03 09:45:24 -07:00
Leo Lam
a1e71b318c fix lint error again 2017-05-03 01:20:57 -04:00
Leo Lam
aee6541d45 fix lint errors 2017-05-03 01:17:13 -04:00
Leo Lam
50ad5e3791 minor improvement on multi-key detection
-check if number of keys pressed is the same as number of keys in a
shortcut key combo before matching to avoid incorrect detection
2017-05-03 01:04:50 -04:00
Leo Lam
b34d72f21a revamp handling of multi-key presses
- use keyCodes instead of strings to detect keys
- delete key from keyPressed array on key up
- fix #540
2017-05-03 00:29:18 -04:00
asmsuechan
e5ae420ef6 Fix viriables for travis 2017-05-02 09:49:49 -07:00
Sota Sugiura
7269750264 Merge pull request #502 from asmsuechan/add-spectron
Add spectron
2017-05-02 18:23:02 +09:00
Sosuke Suzuki
11f7fcbaef fix the bug that is markdown titile starts with second letter 2017-04-26 18:32:46 +09:00
Sosuke Suzuki
6b8488ae0f fix the bug when using markdown syntax h1 2017-04-26 18:14:29 +09:00
asmsuechan
ee5268a07e Change to separation osx and linux 2017-04-25 17:05:10 -07:00
asmsuechan
28bc331318 Change to run only master branch 2017-04-25 11:48:45 -07:00
asmsuechan
098153b6ba Add osx support to .travis.yml
Change to run xvfb only linux
2017-04-25 09:19:46 -07:00
asmsuechan
878f31e91e Remove unnecessary break 2017-04-25 09:18:45 -07:00
asmsuechan
b40d09fa86 Edit .travis.yml to handle ES7 syntax problem 2017-04-25 00:08:59 -07:00
asmsuechan
eb48f48f6b Edit spectron.js to deal with linux 2017-04-25 00:08:49 -07:00
asmsuechan
7961008500 Edit gruntfile.js 2017-04-25 00:08:49 -07:00
asmsuechan
ff87c6b226 Fix .travis.yml to add grunt-cli because travis needs grunt to run $ grund pre-build for e2e test 2017-04-24 19:46:39 -07:00
asmsuechan
0c2807a08b Rename spectron-test.js to spectron.js because of avoidance from be ran it by npm run test 2017-04-24 19:42:02 -07:00
asmsuechan
f0cf369317 Edit .travis.yml for e2e test 2017-04-24 19:28:29 -07:00
asmsuechan
5e9954c060 Add a yarn task 2017-04-24 19:23:23 -07:00
asmsuechan
0c7bdf20af Add a smiple test to confirm Boostnote starts 2017-04-24 19:23:23 -07:00
Sosuke Suzuki
9c1179a6f9 Fixed indent which could not be corrected 2017-04-25 02:53:29 +09:00
Sosuke Suzuki
43cb290c80 fix the indent 2017-04-25 02:41:16 +09:00
Sosuke Suzuki
f8b7b7df9f change how to use dataApi and fix indent 2017-04-25 02:31:03 +09:00
Sosuke Suzuki
b73f0a8012 refactor some dirty code 2017-04-25 01:40:49 +09:00
asmsuechan
4e7204bdbc Add spectron 2017-04-23 12:13:58 -07:00
Sosuke Suzuki
25685dc8b0 Several small fixes 2017-04-06 21:52:23 +09:00
Sosuke Suzuki
50cd0b794b change color with hover during dragging 2017-04-05 10:08:09 +09:00
Sosuke Suzuki
720f07f62c Move note between folders 2017-04-01 02:36:48 +09:00
asmsuechan
893a92c87b Fix from reviews 2017-03-19 12:39:46 -07:00
asmsuechan
0f3230110c Change to use const instead of let 2017-03-10 20:40:11 -08:00
asmsuechan
65573bd4db Add comments 2017-03-10 20:33:16 -08:00
asmsuechan
928df018dc Fix the assginment of targetStorage 2017-03-10 20:33:16 -08:00
asmsuechan
b2187b72ab Fix let to const 2017-03-10 20:33:16 -08:00
asmsuechan
aae2bddd32 Improve the Promise of copyImage 2017-03-10 20:33:11 -08:00
asmsuechan
b6eddf0821 Add dataApi.copyImage for copying image to boostnote storage on an image to boostnote storage on an image doropped into CodeEditor 2017-03-10 20:31:54 -08:00
135 changed files with 3698 additions and 1763 deletions

33
.boostnoterc.sample Normal file
View File

@@ -0,0 +1,33 @@
{
"amaEnabled": true,
"editor": {
"fontFamily": "Monaco, Consolas",
"fontSize": "14",
"indentSize": "2",
"indentType": "space",
"keyMap": "vim",
"switchPreview": "BLUR",
"theme": "monokai"
},
"hotkey": {
"toggleFinder": "Cmd + Alt + S",
"toggleMain": "Cmd + Alt + L"
},
"isSideNavFolded": false,
"listStyle": "DEFAULT",
"listWidth": 174,
"navWidth": 200,
"preview": {
"codeBlockTheme": "dracula",
"fontFamily": "Lato",
"fontSize": "14",
"lineNumber": true
},
"sortBy": "UPDATED_AT",
"ui": {
"defaultNote": "ALWAYS_ASK",
"disableDirectWrite": false,
"theme": "default"
},
"zoom": 1
}

View File

@@ -1,10 +1,16 @@
{
"extends": ["standard", "standard-jsx"],
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
"plugins": ["react"],
"rules": {
"no-useless-escape": 0,
"prefer-const": "warn",
"no-unused-vars": "warn",
"no-undef": "warn",
"no-lone-blocks": "warn"
"no-lone-blocks": "warn",
"react/prop-types": 0,
"react/no-string-refs": 0,
"react/no-find-dom-node": "warn",
"react/no-render-return-value": "warn",
"react/no-deprecated": "warn"
}
}

9
Backers.md Normal file
View File

@@ -0,0 +1,9 @@
Become a [backer](https://salt.bountysource.com/teams/boostnote) and support Boostnote!
You can support Boostnote from $ 5 a month!
# Backers
[Kazu Yokomizo](https://twitter.com/kazup_bot)
kolchan11
RonWalker22

1
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1 @@
Please paste some **screenshots** with the **developer tool** open if you report a bug.

View File

@@ -2,6 +2,8 @@ import React, { PropTypes } from 'react'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -38,6 +40,7 @@ export default class CodeEditor extends React.Component {
}
this.props.onBlur != null && this.props.onBlur(e)
}
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
this.loadStyleHandler = (e) => {
this.editor.refresh()
}
@@ -83,7 +86,13 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'newlineAndIndentContinueMarkdownList'
Enter: 'newlineAndIndentContinueMarkdownList',
'Ctrl-C': (cm) => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
}
return CodeMirror.Pass
}
}
})
@@ -91,14 +100,25 @@ export default class CodeEditor extends React.Component {
this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler)
let editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler)
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
}
quitEditor () {
document.querySelector('textarea').blur()
}
componentWillUnmount () {
this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler)
this.editor.off('paste', this.pasteHandler)
let editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)
}
@@ -184,13 +204,40 @@ export default class CodeEditor extends React.Component {
e.preventDefault()
const imagePath = e.dataTransfer.files[0].path
const filename = path.basename(imagePath)
const imageMd = `![${encodeURI(filename)}](${encodeURI(imagePath)})`
this.insertImage(imageMd)
copyImage(imagePath, this.props.storageKey).then((imagePath) => {
const imageMd = `![${filename}](${path.join('/:storage', imagePath)})`
this.insertImageMd(imageMd)
})
}
insertImage (imageMd) {
insertImageMd (imageMd) {
const textarea = this.editor.getInputField()
textarea.value = textarea.value.substr(0, textarea.selectionStart) + imageMd + textarea.value.substr(textarea.selectionEnd)
const cm = this.editor
cm.replaceSelection(`${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`)
}
handlePaste (editor, e) {
const dataTransferItem = e.clipboardData.items[0]
if (!dataTransferItem.type.match('image')) return
const blob = dataTransferItem.getAsFile()
let reader = new FileReader()
let base64data
reader.readAsDataURL(blob)
reader.onloadend = () => {
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
const imageName = Math.random().toString(36).slice(-16)
const storagePath = findStorage(this.props.storageKey).path
const imagePath = path.join(`${storagePath}`, 'images', `${imageName}.png`)
require('fs').writeFile(imagePath, binaryData, 'binary')
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
}
render () {

View File

@@ -4,19 +4,23 @@ import styles from './MarkdownEditor.styl'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter'
import { findStorage } from 'browser/lib/findStorage'
const _ = require('lodash')
class MarkdownEditor extends React.Component {
constructor (props) {
super(props)
this.escapeFromEditor = ['Control', 'w']
// char codes for ctrl + w
this.escapeFromEditor = [17, 87]
this.supportMdSelectionBold = ['Control', ':']
// ctrl + shift + ;
this.supportMdSelectionBold = [16, 17, 186]
this.state = {
status: 'PREVIEW',
renderValue: props.value,
keyPressed: {},
keyPressed: new Set(),
isLocked: false
}
@@ -77,7 +81,6 @@ class MarkdownEditor extends React.Component {
if (newStatus === 'CODE') {
this.refs.code.focus()
} else {
this.refs.code.blur()
this.refs.preview.focus()
}
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
@@ -87,7 +90,7 @@ class MarkdownEditor extends React.Component {
handleBlur (e) {
if (this.state.isLocked) return
this.setState({ keyPressed: [] })
this.setState({ keyPressed: new Set() })
let { config } = this.props
if (config.editor.switchPreview === 'BLUR') {
let cursorPosition = this.refs.code.editor.getCursor()
@@ -160,16 +163,20 @@ class MarkdownEditor extends React.Component {
}
handleKeyDown (e) {
let { config } = this.props
if (this.state.status !== 'CODE') return false
const keyPressed = Object.assign(this.state.keyPressed, {
[e.key]: true
})
const keyPressed = this.state.keyPressed
keyPressed.add(e.keyCode)
this.setState({ keyPressed })
let isNoteHandlerKey = (el) => { return this.state.keyPressed[el] }
if (!this.state.isLocked && this.state.status === 'CODE' && this.escapeFromEditor.every(isNoteHandlerKey)) {
document.activeElement.blur()
let isNoteHandlerKey = (el) => { return keyPressed.has(el) }
// These conditions are for ctrl-e and ctrl-w
if (keyPressed.size === this.escapeFromEditor.length &&
!this.state.isLocked && this.state.status === 'CODE' &&
this.escapeFromEditor.every(isNoteHandlerKey)) {
this.handleContextMenu()
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
}
if (this.supportMdSelectionBold.every(isNoteHandlerKey)) {
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
this.addMdAroundWord('**')
}
}
@@ -190,9 +197,8 @@ class MarkdownEditor extends React.Component {
}
handleKeyUp (e) {
const keyPressed = Object.assign(this.state.keyPressed, {
[e.key]: false
})
const keyPressed = this.state.keyPressed
keyPressed.delete(e.keyCode)
this.setState({ keyPressed })
}
@@ -201,7 +207,7 @@ class MarkdownEditor extends React.Component {
}
render () {
let { className, value, config } = this.props
let { className, value, config, storageKey } = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -211,6 +217,8 @@ class MarkdownEditor extends React.Component {
let previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
const storage = findStorage(storageKey)
return (
<div className={className == null
? 'MarkdownEditor'
@@ -221,7 +229,10 @@ class MarkdownEditor extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
>
<CodeEditor styleName='codeEditor'
<CodeEditor styleName={this.state.status === 'CODE'
? 'codeEditor'
: 'codeEditor--hide'
}
ref='code'
mode='GitHub Flavored Markdown'
value={value}
@@ -231,6 +242,7 @@ class MarkdownEditor extends React.Component {
fontSize={editorFontSize}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
storageKey={storageKey}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
/>
@@ -254,6 +266,7 @@ class MarkdownEditor extends React.Component {
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
storagePath={storage.path}
/>
</div>
)

View File

@@ -4,8 +4,14 @@
.codeEditor
absolute top bottom left right
.hide
z-index 0
opacity 0
pointer-events none
.codeEditor--hide
@extend .codeEditor
@extend .hide
.preview
display block
@@ -17,7 +23,5 @@
.preview--hide
@extend .preview
z-index 0
opacity 0
pointer-events none
@extend .hide

View File

@@ -9,6 +9,7 @@ import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import copy from 'copy-to-clipboard'
const { remote } = require('electron')
const { app } = remote
@@ -46,6 +47,26 @@ code {
font-family: ${codeBlockFontFamily.join(', ')};
}
.clipboardButton {
color: rgba(147,147,149,0.8);;
fill: rgba(147,147,149,1);;
border-radius: 50%;
margin: 0px 10px;
border: none;
background-color: transparent;
outline: none;
height: 15px;
width: 15px;
cursor: pointer;
}
.clipboardButton:hover {
transition: 0.2s;
color: #939395;
fill: #939395;
background-color: rgba(0,0,0,0.1);
}
h1, h2 {
border: none;
}
@@ -87,6 +108,9 @@ export default class MarkdownPreview extends React.Component {
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd()
this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handlelinkClick.bind(this)
}
handlePreviewAnchorClick (e) {
@@ -139,6 +163,10 @@ export default class MarkdownPreview extends React.Component {
this.exportAsDocument('md')
}
handlePrint () {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType) {
const options = {
filters: [
@@ -175,6 +203,7 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('print', this.printHandler)
}
componentWillUnmount () {
@@ -185,6 +214,7 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('print', this.printHandler)
}
componentDidUpdate (prevProps) {
@@ -228,7 +258,11 @@ export default class MarkdownPreview extends React.Component {
el.removeEventListener('click', this.checkboxClickHandler)
})
let { value, theme, indentSize, codeBlockTheme } = this.props
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
el.removeEventListener('click', this.linkClickHandler)
})
let { value, theme, indentSize, codeBlockTheme, storagePath } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
@@ -252,6 +286,16 @@ export default class MarkdownPreview extends React.Component {
el.addEventListener('click', this.checkboxClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.linkClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
el.src = markdown.normalizeLinkText(el.src)
if (!/\/:storage/.test(el.src)) return
el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
})
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
? codeBlockTheme
: 'default'
@@ -261,6 +305,16 @@ export default class MarkdownPreview extends React.Component {
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => {
let content = htmlTextHelper.decodeEntities(el.innerHTML)
const copyIcon = document.createElement('i')
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
copyIcon.onclick = (e) => {
copy(content)
this.notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!',
silent: true
})
}
el.parentNode.appendChild(copyIcon)
el.innerHTML = ''
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
CodeMirror.runMode(content, syntax.mime, el, {
@@ -335,6 +389,21 @@ export default class MarkdownPreview extends React.Component {
e.stopPropagation()
}
notify (title, options) {
if (global.process.platform === 'win32') {
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
}
return new window.Notification(title, options)
}
handlelinkClick (e) {
const noteHash = e.target.href.split('/').pop()
const regexIsNoteLink = /^(.{20})-(.{20})$/
if (regexIsNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash)
}
}
render () {
let { className, style, tabIndex } = this.props
return (
@@ -356,5 +425,6 @@ MarkdownPreview.propTypes = {
onMouseUp: PropTypes.func,
onMouseDown: PropTypes.func,
className: PropTypes.string,
value: PropTypes.string
value: PropTypes.string,
storagePath: PropTypes.string
}

View File

@@ -0,0 +1,18 @@
import React, {PropTypes} from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ModalEscButton.styl'
const ModalEscButton = ({
handleEscButtonClick
}) => (
<button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>×</div>
<div styleName='esc-text'>esc</div>
</button>
)
ModalEscButton.propTypes = {
handleEscButtonClick: PropTypes.func.isRequired
}
export default CSSModules(ModalEscButton, styles)

View File

@@ -0,0 +1,16 @@
.escButton
height 50px
position absolute
background-color transparent
color $ui-inactive-text-color
border none
top 1px
right 10px
text-align center
width top-bar--height
height top-bar-height
.esc-mark
font-size 28px
margin-top -5px
margin-bottom -7px

View File

@@ -4,7 +4,9 @@
import React, { PropTypes } from 'react'
import { isArray } from 'lodash'
import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl'
import TodoProcess from './TodoProcess'
/**
* @description Tag element component.
@@ -39,17 +41,18 @@ const TagElementList = (tags) => {
* @param {boolean} isActive
* @param {Object} note
* @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart
* @param {string} dateDisplay
*/
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu }) => (
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStart }) => (
<div styleName={isActive
? 'item--active'
: 'item'
}
key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)}
draggable='true'
>
<div styleName='item-wrapper'>
{note.type === 'SNIPPET_NOTE'
@@ -67,6 +70,10 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteCont
{note.isStarred
? <i styleName='item-star' className='fa fa-star' /> : ''
}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
}
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
@@ -88,10 +95,12 @@ NoteItem.propTypes = {
type: PropTypes.string.isRequired,
title: PropTypes.string.isrequired,
tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired
isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired
}),
handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired
handleDragStart: PropTypes.func.isRequired,
handleDragEnd: PropTypes.func.isRequired
}
export default CSSModules(NoteItem, styles)

View File

@@ -39,6 +39,7 @@ $control-height = 30px
.item-wrapper
padding 15px 0
border-bottom $ui-border
position relative
.item--active
@extend .item
@@ -66,7 +67,7 @@ $control-height = 30px
color $ui-inactive-text-color
.item-title
font-size 13px
font-size 14px
position relative
top -12px
left 20px
@@ -83,7 +84,6 @@ $control-height = 30px
position relative
bottom 0px
margin-top 2px
height 20px
font-size 12px
line-height 20px
overflow ellipsis
@@ -93,10 +93,12 @@ $control-height = 30px
flex 1
overflow ellipsis
line-height 20px
padding-top 7px
padding-left 2px
margin-right 27px
.item-bottom-tagList-item
font-size 10px
font-size 11px
margin-right 8px
padding 0
height 20px
@@ -109,14 +111,14 @@ $control-height = 30px
.item-bottom-time
color $ui-inactive-text-color
font-size 10px
font-size 11px
padding-left 2px
padding-bottom 2px
.item-star
position absolute
top 34px
left 5px
right -20px
bottom 2px
width 34px
height 34px
color alpha($ui-favorite-star-button-color, 60%)

View File

@@ -10,16 +10,17 @@ import styles from './NoteItemSimple.styl'
* @param {boolean} isActive
* @param {Object} note
* @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart
*/
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu }) => (
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleDragStart }) => (
<div styleName={isActive
? 'item-simple--active'
: 'item-simple'
}
key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)}
draggable='true'
>
<div styleName='item-simple-title'>
{note.type === 'SNIPPET_NOTE'
@@ -43,7 +44,7 @@ NoteItemSimple.propTypes = {
title: PropTypes.string.isrequired
}),
handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired
handleDragStart: PropTypes.func.isRequired
}
export default CSSModules(NoteItemSimple, styles)

View File

@@ -7,7 +7,7 @@ $control-height = 30px
.item-simple
position relative
padding 0 25px
padding 0 20px
user-select none
cursor pointer
background-color $ui-noteList-backgroundColor
@@ -40,7 +40,7 @@ $control-height = 30px
background-color $ui-button--active-backgroundColor
.item-simple-title
font-size 12px
font-size 13px
height 40px
box-sizing border-box
line-height 24px
@@ -48,6 +48,7 @@ $control-height = 30px
overflow ellipsis
color $ui-inactive-text-color
border-bottom $ui-border
position relative
.item-simple-title-icon
font-size 12px

View File

@@ -1,24 +0,0 @@
import React, { PropTypes } from 'react'
import md5 from 'md5'
export default class ProfileImage extends React.Component {
render () {
let className = this.props.className == null ? 'ProfileImage' : 'ProfileImage ' + this.props.className
let email = this.props.email != null ? this.props.email : ''
let src = 'http://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=' + this.props.size
return (
<img
className={className}
width={this.props.size}
height={this.props.size}
src={src} />
)
}
}
ProfileImage.propTypes = {
email: PropTypes.string,
size: PropTypes.string,
className: PropTypes.string
}

View File

@@ -0,0 +1,55 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RealtimeNotification.styl'
const electron = require('electron')
const { shell } = electron
class RealtimeNotification extends React.Component {
constructor (props) {
super(props)
this.state = {
notifications: []
}
}
componentDidMount () {
this.fetchNotifications()
}
fetchNotifications () {
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
fetch(notificationsUrl)
.then(response => {
return response.json()
})
.then(json => {
this.setState({notifications: json.notifications})
})
}
handleLinkClick (e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
render () {
const { notifications } = this.state
const link = notifications.length > 0
? <a styleName='notification-link' href={notifications[0].linkUrl}
onClick={(e) => this.handleLinkClick(e)}
>
{notifications[0].text}
</a>
: ''
return (
<div styleName='notification-area'>{link}</div>
)
}
}
RealtimeNotification.propTypes = {}
export default CSSModules(RealtimeNotification, styles)

View File

@@ -0,0 +1,35 @@
.notification-area
z-index 1000
font-size 12px
position absolute
bottom 0px
background-color #EBEBEB
width 100vw
height 30px
text-align center
.notification-link
text-decoration none
color #282A36
border 1px solid #6FA8E6
background-color alpha(#6FA8E6, 0.2)
padding 3px 9px
border-radius 2px
position absolute
bottom 4px
margin-left -10%
transition 0.2s
&:hover
color #1378BD
body[data-theme="dark"]
.notification-area
background-color #1E2124
.notification-link
color #fff
border 1px solid alpha(#5CB85C, 0.6)
background-color alpha(#5CB85C, 0.2)
transition 0.2s
&:hover
color #5CB85C

View File

@@ -15,7 +15,7 @@ import styles from './SideNavFilter.styl'
*/
const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick
}) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
@@ -30,6 +30,12 @@ const SideNavFilter = ({
<i className='fa fa-star fa-fw' />
<span styleName='menu-button-label'>Starred</span>
</button>
<button styleName={isTrashedActive ? 'menu-button--active' : 'menu-button'}
onClick={handleTrashedButtonClick}
>
<i className='fa fa-trash fa-fw' />
<span styleName='menu-button-label'>Trash</span>
</button>
</div>
)
@@ -38,7 +44,9 @@ SideNavFilter.propTypes = {
isHomeActive: PropTypes.bool.isRequired,
handleAllNotesButtonClick: PropTypes.func.isRequired,
isStarredActive: PropTypes.bool.isRequired,
handleStarredButtonClick: PropTypes.func.isRequired
isTrashedActive: PropTypes.bool.isRequired,
handleStarredButtonClick: PropTypes.func.isRequired,
handleTrashdButtonClick: PropTypes.func.isRequired
}
export default CSSModules(SideNavFilter, styles)

View File

@@ -14,11 +14,14 @@ import { isNumber } from 'lodash'
* @param {string} folderColor
* @param {boolean} isFolded
* @param {number} noteCount
* @param {Function} handleDrop
* @param {Function} handleDragEnter
* @param {Function} handleDragOut
* @return {React.Component}
*/
const StorageItem = ({
isActive, handleButtonClick, handleContextMenu, folderName,
folderColor, isFolded, noteCount
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
}) => (
<button styleName={isActive
? 'folderList-item--active'
@@ -26,6 +29,9 @@ const StorageItem = ({
}
onClick={handleButtonClick}
onContextMenu={handleContextMenu}
onDrop={handleDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
<span styleName={isFolded
? 'folderList-item-name--folded' : 'folderList-item-name'
@@ -52,6 +58,8 @@ StorageItem.propTypes = {
folderName: PropTypes.string.isRequired,
folderColor: PropTypes.string,
isFolded: PropTypes.bool.isRequired,
handleDragEnter: PropTypes.func.isRequired,
handleDragLeave: PropTypes.func.isRequired,
noteCount: PropTypes.number
}

View File

@@ -0,0 +1,27 @@
/**
* @fileoverview Percentage of todo achievement.
*/
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoListPercentage.styl'
/**
* @param {number} percentageOfTodo
*/
const TodoListPercentage = ({
percentageOfTodo
}) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
<p styleName='percentageText'>{percentageOfTodo}%</p>
</div>
</div>
)
TodoListPercentage.propTypes = {
percentageOfTodo: PropTypes.number.isRequired
}
export default CSSModules(TodoListPercentage, styles)

View File

@@ -0,0 +1,31 @@
.percentageBar
position absolute
top 58px
right: 0px
left 0px
background-color #DADFE1
width 100%
height: 15px
font-size: 12px
z-index 100
border-radius 2px
.progressBar
background-color: #6C7A89
height 15px
border-radius 2px
transition 0.3s
.percentageText
color #f4f4f4
padding: 2px 43%
body[data-theme="dark"]
.percentageBar
background-color #363A3D
.progressBar
background-color: alpha(#939395, 50%)
.percentageText
color $ui-dark-text-color

View File

@@ -0,0 +1,33 @@
/**
* @fileoverview Percentage of todo achievement.
*/
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoProcess.styl'
const TodoProcess = ({
todoStatus: {
total: totalTodo,
completed: completedTodo
}
}) => (
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
<div styleName='todo-process-text'>
<i className='fa fa-fw fa-check-square-o' />
{completedTodo} of {totalTodo}
</div>
<div styleName='todo-process-bar'>
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
</div>
</div>
)
TodoProcess.propTypes = {
todoStatus: {
total: PropTypes.number.isRequired,
completed: PropTypes.number.isRequired
}
}
export default CSSModules(TodoProcess, styles)

View File

@@ -0,0 +1,45 @@
.todo-process
font-size 12px
display flex
padding-top 15px
width 85%
.todo-process-text
display inline-block
padding-right 10px
white-space nowrap
text-overflow ellipsis
color $ui-inactive-text-color
i
color $ui-inactive-text-color
padding-right 5px
.todo-process-bar
display inline-block
margin auto
height 4px
border-radius 10px
background-color #DADFE1
border-radius 2px
flex-grow 1
border 1px solid alpha(#6C7A89, 10%)
.todo-process-bar--inner
height 100%
border-radius 5px
background-color #6C7A89
transition 0.3s
body[data-theme="dark"]
.todo-process
color $ui-dark-text-color
.todo-process-bar
background-color #363A3D
.todo-process-text
color $ui-inactive-text-color
.todo-process-bar--inner
background-color: alpha(#939395, 50%)

View File

@@ -270,7 +270,7 @@ table
border-right solid 1px borderColor
themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #DDDDDD
themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%)
themeDarkPreview = $ui-dark-noteDetail-backgroundColor
themeDarkTableOdd = themeDarkPreview
@@ -288,8 +288,9 @@ body[data-theme="dark"]
background-color alpha(lighten(brandColor, 30%), 0.2) !important
code
color #EA6730
border-color darken(themeDarkBorder, 10%)
background-color lighten(themeDarkPreview, 10%)
background-color lighten(themeDarkPreview, 5%)
pre
border-color lighten(#21252B, 20%)

View File

@@ -64,7 +64,7 @@ $list-width = 250px
.result-nav-storageList
absolute bottom left right
top 80px + 32px + 10px + 10px
top 110px + 32px + 10px + 10px
overflow-y auto
.result-list

View File

@@ -5,6 +5,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
import MarkdownEditor from 'browser/components/MarkdownEditor'
import CodeEditor from 'browser/components/CodeEditor'
import CodeMirror from 'codemirror'
import { findStorage } from 'browser/lib/findStorage'
const electron = require('electron')
const { clipboard } = electron
@@ -106,6 +107,8 @@ class NoteDetail extends React.Component {
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const storage = findStorage(note.storage)
if (note.type === 'SNIPPET_NOTE') {
let tabList = note.snippets.map((snippet, index) => {
let isActive = this.state.snippetIndex === index
@@ -143,6 +146,7 @@ class NoteDetail extends React.Component {
config={config}
value={snippet.content}
ref={'code-' + index}
storageKey={note.storage}
/>
: <CodeEditor styleName='tabView-content'
mode={snippet.mode}
@@ -192,6 +196,7 @@ class NoteDetail extends React.Component {
lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize}
value={note.content}
storagePath={storage.path}
/>
)
}

View File

@@ -2,6 +2,7 @@
.root
absolute top bottom left right
bottom 30px
left $note-detail-left-margin
right $note-detail-right-margin
height 100%

View File

@@ -70,7 +70,6 @@ class NoteList extends React.Component {
dateDisplay={dateDisplay}
key={key}
handleNoteClick={(e) => this.props.handleNoteClick(e, _index)}
handleNoteContextMenu={() => ''}
/>
)
})

View File

@@ -10,6 +10,7 @@ import StorageSection from './StorageSection'
import NoteList from './NoteList'
import NoteDetail from './NoteDetail'
import SideNavFilter from 'browser/components/SideNavFilter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
require('!!style!css!stylus?sourceMap!../main/global.styl')
require('../lib/customMeta')
@@ -94,6 +95,7 @@ class FinderMain extends React.Component {
if (e.keyCode === 13) {
this.refs.detail.saveToClipboard()
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('COPY_FINDER')
hideFinder()
e.preventDefault()
}

21
browser/lib/RcParser.js Normal file
View File

@@ -0,0 +1,21 @@
import path from 'path'
import sander from 'sander'
const BOOSTNOTERC = '.boostnoterc'
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
export function parse (boostnotercPath = _boostnotercPath) {
if (!sander.existsSync(boostnotercPath)) return {}
try {
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
} catch (e) {
console.warn(e)
console.warn('Your .boostnoterc is broken so it\'s not used.')
return {}
}
}
export default {
parse
}

View File

@@ -8,7 +8,7 @@ import moment from 'moment'
* @param {mixed}
* @return {string}
*/
export function getLastUpdated (date) {
export function formatDate (date) {
const m = moment(date)
if (!m.isValid()) {
throw Error('Invalid argument.')

View File

@@ -0,0 +1,33 @@
export function findNoteTitle (value) {
let splitted = value.split('\n')
let title = null
let isInsideCodeBlock = false
splitted.some((line, index) => {
let trimmedLine = line.trim()
let trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (trimmedLine.match('```')) {
isInsideCodeBlock = !isInsideCodeBlock
}
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
title = trimmedLine
return true
}
})
if (title === null) {
title = ''
splitted.some((line) => {
if (line.trim().length > 0) {
title = line.trim()
return true
}
})
}
return title
}
export default {
findNoteTitle
}

View File

@@ -0,0 +1,14 @@
const _ = require('lodash')
export function findStorage (storageKey) {
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
const storage = _.find(cachedStorageList, {key: storageKey})
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
return storage
}
export default {
findStorage
}

View File

@@ -0,0 +1,25 @@
export function getTodoStatus (content) {
let splitted = content.split('\n')
let numberOfTodo = 0
let numberOfCompletedTodo = 0
splitted.forEach((line) => {
let trimmedLine = line.trim()
if (trimmedLine.match(/^[\+\-\*] \[\s|x\] ./)) {
numberOfTodo++
}
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
numberOfCompletedTodo++
}
})
return {
total: numberOfTodo,
completed: numberOfCompletedTodo
}
}
export function getTodoPercentageOfCompleted (content) {
const state = getTodoStatus(content)
return Math.floor(state.completed / state.total * 100)
}

View File

@@ -13,7 +13,8 @@ export function decodeEntities (text) {
['amp', '&'],
['lt', '<'],
['gt', '>'],
['#63', '\\?']
['#63', '\\?'],
['#36', '\\$']
]
for (var i = 0, max = entities.length; i < max; ++i) {
@@ -28,7 +29,8 @@ export function encodeEntities (text) {
['\'', 'apos'],
['<', 'lt'],
['>', 'gt'],
['\\?', '#63']
['\\?', '#63'],
['\\$', '#36']
]
entities.forEach((entity) => {

View File

@@ -57,7 +57,17 @@ md.use(math, {
return output
}
})
md.use(require('markdown-it-imsize'))
md.use(require('markdown-it-footnote'))
md.use(require('markdown-it-multimd-table'))
md.use(require('markdown-it-named-headers'), {
slugify: (header) => {
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
}
})
// Override task item
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
@@ -155,11 +165,17 @@ function strip (input) {
return output
}
function normalizeLinkText (linkText) {
return md.normalizeLinkText(linkText)
}
const markdown = {
render: function markdown (content) {
if (!_.isString(content)) content = ''
return md.render(content)
const renderedContent = md.render(content)
return renderedContent
},
strip
strip,
normalizeLinkText
}
export default markdown

42
browser/lib/search.js Normal file
View File

@@ -0,0 +1,42 @@
import _ from 'lodash'
export default function searchFromNotes (notes, search) {
if (search.trim().length === 0) return []
let searchBlocks = search.split(' ')
searchBlocks.forEach((block) => {
if (block.match(/^#.+/)) {
notes = findByTag(notes, block)
} else {
notes = findByWord(notes, block)
}
})
return notes
}
function findByTag (notes, block) {
const tag = block.match(/#(.+)/)[1]
let regExp = new RegExp(_.escapeRegExp(tag), 'i')
return notes.filter((note) => {
if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => {
return _tag.match(regExp)
})
})
}
function findByWord (notes, block) {
let regExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => {
return _tag.match(regExp)
})) {
return true
}
if (note.type === 'SNIPPET_NOTE') {
return note.description.match(regExp)
} else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(regExp)
}
return false
})
}

View File

@@ -1,7 +1,7 @@
.root
position relative
border solid 1px transparent
line-height 34px
line-height 26px
vertical-align middle
border-radius 2px
transition 0.15s

View File

@@ -0,0 +1,19 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoButton.styl'
const InfoButton = ({
onClick
}) => (
<button styleName='control-infoButton'
onClick={onClick}
>
<i className='fa fa-info infoButton' styleName='info-button' />
</button>
)
InfoButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(InfoButton, styles)

View File

@@ -0,0 +1,21 @@
.control-infoButton
float right
topBarButtonLight()
.control-infoPanel
position fixed
pointer-events none
top 50px
z-index 200
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.infoButton
padding 0px
margin 15px 0
body[data-theme="dark"]
.control-infoButton
topBarButtonDark()

View File

@@ -0,0 +1,103 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
const InfoPanel = ({
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type
}) => (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div styleName='group-section'>
<div styleName='group-section-label'>
Storage
</div>
<div styleName='group-section-control'>
{storageName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Folder
</div>
<div styleName='group-section-control'>
{folderName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Created
</div>
<div styleName='group-section-control'>
{createdAt}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Updated
</div>
<div styleName='group-section-control'>
{updatedAt}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Note Link
</div>
<div styleName='group-section-control'>
<input value={noteLink} onClick={(e) => { e.target.select() }} />
</div>
</div>
{type === 'SNIPPET_NOTE'
? ''
: <div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Words
</div>
<div styleName='group-section-control'>
{wordCount}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Letters
</div>
<div styleName='group-section-control'>
{letterCount}
</div>
</div>
</div>
}
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o fa-fw' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o fa-fw' />
<p>.txt</p>
</button>
<button styleName='export--unable'>
<i className='fa fa-file-pdf-o fa-fw' />
<p>.pdf</p>
</button>
</div>
</div>
)
InfoPanel.propTypes = {
storageName: PropTypes.string.isRequired,
folderName: PropTypes.string.isRequired,
noteLink: PropTypes.string.isRequired,
updatedAt: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired,
wordCount: PropTypes.number,
letterCount: PropTypes.number,
type: PropTypes.string.isRequired
}
export default CSSModules(InfoPanel, styles)

View File

@@ -0,0 +1,124 @@
.control-infoPanel
position fixed
pointer-events none
top 50px
z-index 200
line-height normal
border-radius 4px
opacity 0
transition 0.2s
.control-infoButton-panel
z-index 200
margin-top 45px
margin-left -210px
position absolute
padding 20px 20px 0 20px
width 340px
background-color $ui-noteList-backgroundColor
border 1px solid $border-color
.control-infoButton-panel-trash
z-index 200
margin-top 45px
margin-left -230px
position absolute
padding 20px 20px 0 20px
width 320px
background-color $ui-noteList-backgroundColor
border 1px solid $border-color
.group-section
display flex
line-height 30px
font-size 13px
.group-section-label
width 70px
height 20px
text-align left
margin-right 50px
font-size 13px
color $ui-inactive-text-color
.group-section-control
flex 1
font-size 13px
color $ui-text-color
.group-section-control input
width 160px
height 25px
.group-section-control text
color #EA4447
font-weight 600
font-size 14px
width 70px
height 25px
background-color rgba(226,33,113,0.1)
border none
outline none
border-radius 2px
margin-right 5px
padding 2px 5px
[id=export-wrap]
height 90px
display flex
justify-content center
margin 20px 0 10px 0
button
outline none
font-size 48px
color #A0A0A0
background-color transparent
border none
margin 0 5px
border-radius 5px
&:hover
transition 0.2s
background-color alpha($ui-button--hover-backgroundColor, 30%)
color $ui-inactive-text-color
p
font-size 13px
color #A0A0A0
font-weight light
&:hover
color $ui-inactive-text-color
.export--enable
cursor pointer
.export--unable
cursor not-allowed
body[data-theme="dark"]
.control-infoButton-panel
background-color $ui-dark-noteList-backgroundColor
border 1px solid $ui-dark-borderColor
.control-infoButton-panel-trash
background-color $ui-dark-noteList-backgroundColor
border 1px solid $ui-dark-borderColor
.group-section-label
color $ui-inactive-text-color
.group-section-control
color $ui-dark-text-color
.group-section-control input
background-color alpha($ui-dark-borderColor, 80%)
color $ui-dark-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-dark-borderColor, 20%)
color $ui-dark-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-dark-text-color

View File

@@ -0,0 +1,70 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
}) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div styleName='group-section'>
<div styleName='group-section-label'>
Storage
</div>
<div styleName='group-section-control'>
{storageName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Folder
</div>
<div styleName='group-section-control'>
<text>Trash</text>{folderName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Created
</div>
<div styleName='group-section-control'>
{createdAt}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Updated
</div>
<div styleName='group-section-control'>
{updatedAt}
</div>
</div>
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o fa-fw' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o fa-fw' />
<p>.txt</p>
</button>
<button styleName='export--unable'>
<i className='fa fa-file-pdf-o fa-fw' />
<p>.pdf</p>
</button>
</div>
</div>
)
InfoPanelTrashed.propTypes = {
storageName: PropTypes.string.isRequired,
folderName: PropTypes.string.isRequired,
updatedAt: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired
}
export default CSSModules(InfoPanelTrashed, styles)

View File

@@ -1,27 +0,0 @@
/**
* @fileoverview Component for show updated date of the detail.
*/
import React, { PropTypes } from 'react'
import { getLastUpdated } from 'browser/lib/date-formatter'
import CSSModules from 'browser/lib/CSSModules'
import styles from './LastUpdatedString.styl'
const LastUpdatedString = ({ date }) => {
let text = ''
try {
text = `Last updated at ${getLastUpdated(date)}`
} catch (e) {
text = ''
}
return (
<p styleName='info-right-date'>{text}</p>
)
}
LastUpdatedString.propTypes = {
date: PropTypes.string
}
export default CSSModules(LastUpdatedString, styles)

View File

@@ -1,10 +0,0 @@
.info-right-date
display inline
line-height 24px
padding-right 25px
font-size 11px
color $ui-button-color
body[data-theme="dark"]
.info-right-date
color $ui-dark-button-color

View File

@@ -2,6 +2,7 @@ import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './MarkdownNoteDetail.styl'
import MarkdownEditor from 'browser/components/MarkdownEditor'
import TodoListPercentage from 'browser/components/TodoListPercentage'
import StarButton from './StarButton'
import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect'
@@ -11,6 +12,15 @@ import ee from 'browser/main/lib/eventEmitter'
import markdown from 'browser/lib/markdown'
import StatusBar from '../StatusBar'
import _ from 'lodash'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
const electron = require('electron')
const { remote } = electron
@@ -49,7 +59,7 @@ class MarkdownNoteDetail extends React.Component {
note: Object.assign({}, nextProps.note)
}, () => {
this.refs.content.reload()
this.refs.tags.reset()
if (this.refs.tags) this.refs.tags.reset()
})
}
}
@@ -62,45 +72,12 @@ class MarkdownNoteDetail extends React.Component {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
}
findTitle (value) {
let splitted = value.split('\n')
let title = null
let isMarkdownInCode = false
for (let i = 0; i < splitted.length; i++) {
let trimmedLine = splitted[i].trim()
if (trimmedLine.match('```')) {
isMarkdownInCode = !isMarkdownInCode
} else if (isMarkdownInCode === false && trimmedLine.match(/^# +/)) {
title = trimmedLine.substring(1, trimmedLine.length).trim()
break
}
}
if (title == null) {
for (let i = 0; i < splitted.length; i++) {
let trimmedLine = splitted[i].trim()
if (trimmedLine.length > 0) {
title = trimmedLine
break
}
}
if (title == null) {
title = ''
}
}
title = markdown.strip(title)
return title
}
handleChange (e) {
let { note } = this.state
note.content = this.refs.content.value
note.tags = this.refs.tags.value
note.title = this.findTitle(note.content)
if (this.refs.tags) note.tags = this.refs.tags.value
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
note.updatedAt = new Date()
this.setState({
@@ -129,6 +106,7 @@ class MarkdownNoteDetail extends React.Component {
type: 'UPDATE_NOTE',
note: note
})
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
})
}
@@ -167,6 +145,7 @@ class MarkdownNoteDetail extends React.Component {
handleStarButtonClick (e) {
let { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred
@@ -181,14 +160,26 @@ class MarkdownNoteDetail extends React.Component {
}
handleDeleteButtonClick (e) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
exportAsMd () {
ee.emit('export:save-md')
}
exportAsTxt () {
ee.emit('export:save-text')
}
handleTrashButtonClick (e) {
let { note } = this.state
const { isTrashed } = note
if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
let { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
@@ -201,9 +192,35 @@ class MarkdownNoteDetail extends React.Component {
})
}
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
})
} else {
note.isTrashed = true
this.setState({
note
}, () => {
this.save()
})
}
ee.emit('list:next')
}
handleUndoButtonClick (e) {
let { note } = this.state
note.isTrashed = false
this.setState({
note
}, () => {
this.save()
this.refs.content.reload()
ee.emit('list:next')
})
}
handleFullScreenButton (e) {
ee.emit('editor:fullscreen')
}
handleLockButtonMouseDown (e) {
@@ -214,7 +231,7 @@ class MarkdownNoteDetail extends React.Component {
}
getToggleLockButton () {
return this.state.isLocked ? 'fa-lock' : 'fa-unlock-alt'
return this.state.isLocked ? 'fa-lock' : 'fa-unlock'
}
handleDeleteKeyDown (e) {
@@ -234,68 +251,123 @@ class MarkdownNoteDetail extends React.Component {
this.focus()
}
handleInfoButtonClick (e) {
const infoPanel = document.querySelector('.infoPanel')
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
}
render () {
let { data, config } = this.props
let { data, config, location } = this.props
let { note } = this.state
let storageKey = note.storage
let folderKey = note.folder
let options = []
data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => {
options.push({
storage: storage,
folder: folder
})
})
})
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
<div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanelTrashed
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt}
/>
</div>
</div>
const detailTopBar = <div styleName='info'>
<div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<TagSelect
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
<TodoListPercentage
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
/>
</div>
<div styleName='info-right'>
{(() => {
const faClassName = `fa ${this.getToggleLockButton()}`
const lockButtonComponent =
<button styleName='control-lockButton'
onFocus={(e) => this.handleFocus(e)}
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
>
<i className={faClassName} styleName='lock-button' />
<span styleName='control-lockButton-tooltip'>
{this.state.isLocked ? 'Unlock' : 'Lock'}
</span>
</button>
return (
this.state.isLockButtonShown ? lockButtonComponent : ''
)
})()}
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-window-maximize' styleName='fullScreen-button' />
</button>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt}
wordCount={note.content.split(' ').length}
letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type}
/>
</div>
</div>
return (
<div className='NoteDetail'
style={this.props.style}
styleName='root'
>
<div styleName='info'>
<div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<TagSelect
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
</div>
<div styleName='info-right'>
{(() => {
const faClassName = `fa ${this.getToggleLockButton()}`
const lockButtonComponent =
<button styleName='control-lockButton'
onFocus={(e) => this.handleFocus(e)}
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
>
<i className={faClassName} styleName='lock-button' />
<span styleName='control-lockButton-tooltip'>
{this.state.isLocked ? 'Unlock' : 'Lock'}
</span>
</button>
return (
this.state.isLockButtonShown ? lockButtonComponent : ''
)
})()}
<button styleName='control-trashButton'
onClick={(e) => this.handleDeleteButtonClick(e)}
>
<svg height='14px' id='Capa_1' style={{enableBackground: 'new 0 0 753.23 753.23'}} width='14px' version='1.1' viewBox='0 0 753.23 753.23' x='0px' y='0px' xmlSpace='preserve'>
<g>
<g id='_x34__19_'>
<g>
<path d='M494.308,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077c0-12.993-10.545-23.539-23.538-23.539&#xA;&#x9;&#x9;&#x9;&#x9;s-23.538,10.545-23.538,23.539v282.461C470.77,648.531,481.314,659.077,494.308,659.077z M635.538,94.154h-141.23V47.077&#xA;&#x9;&#x9;&#x9;&#x9;C494.308,21.067,473.24,0,447.23,0H306c-26.01,0-47.077,21.067-47.077,47.077v47.077h-141.23&#xA;&#x9;&#x9;&#x9;&#x9;c-26.01,0-47.077,21.067-47.077,47.077v47.077c0,25.986,21.067,47.077,47.077,47.077v423.692&#xA;&#x9;&#x9;&#x9;&#x9;c0,51.996,42.157,94.153,94.154,94.153h329.539c51.996,0,94.153-42.157,94.153-94.153V235.385&#xA;&#x9;&#x9;&#x9;&#x9;c26.01,0,47.077-21.091,47.077-47.077V141.23C682.615,115.221,661.548,94.154,635.538,94.154z M306,70.615&#xA;&#x9;&#x9;&#x9;&#x9;c0-12.993,10.545-23.539,23.538-23.539h94.154c12.993,0,23.538,10.545,23.538,23.539v23.539c-22.809,0-141.23,0-141.23,0V70.615z&#xA;&#x9;&#x9;&#x9;&#x9; M588.461,659.077c0,25.986-21.066,47.076-47.076,47.076H211.846c-26.01,0-47.077-21.09-47.077-47.076V235.385h423.692V659.077z&#xA;&#x9;&#x9;&#x9;&#x9; M612,188.308H141.23c-12.993,0-23.538-10.545-23.538-23.539s10.545-23.539,23.538-23.539H612&#xA;&#x9;&#x9;&#x9;&#x9;c12.993,0,23.538,10.545,23.538,23.539S624.993,188.308,612,188.308z M258.923,659.077c12.993,0,23.539-10.546,23.539-23.539&#xA;&#x9;&#x9;&#x9;&#x9;V353.077c0-12.993-10.545-23.539-23.539-23.539s-23.539,10.545-23.539,23.539v282.461&#xA;&#x9;&#x9;&#x9;&#x9;C235.384,648.531,245.93,659.077,258.923,659.077z M376.615,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077&#xA;&#x9;&#x9;&#x9;&#x9;c0-12.993-10.545-23.539-23.538-23.539s-23.539,10.545-23.539,23.539v282.461C353.077,648.531,363.622,659.077,376.615,659.077z' />
</g>
</g>
</g>
</svg>
</button>
</div>
</div>
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>
<MarkdownEditor
@@ -303,6 +375,7 @@ class MarkdownNoteDetail extends React.Component {
styleName='body-noteEditor'
config={config}
value={this.state.note.content}
storageKey={this.state.note.storage}
onChange={(e) => this.handleChange(e)}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
/>

View File

@@ -27,8 +27,9 @@
opacity 0
transition 0.1s
.control-trashButton
.control-fullScreenButton
float right
padding 0 0 2px 0
topBarButtonLight()
.body
@@ -53,5 +54,5 @@ body[data-theme="dark"]
.control-lockButton-tooltip
darkTooltip()
.control-trashButton
.control-fullScreenButton
topBarButtonDark()

View File

@@ -15,26 +15,26 @@ $info-margin-under-border = 27px
float left
padding 0 5px
margin 0px 2px
.info-left-top
display inline-block
height $info-height
line-height $info-height
.info-left-top-folderSelect
display inline-block
padding 0 3px
height 34px
line-height 34px
vertical-align middle
line-height 26px
display flex
align-items center
justify-content center
border-radius 3px
.info-left-button
width 34px
height 34px
navButtonColor()
color $ui-favorite-star-button-color
font-size 14px
line-height 0
margin 13px 2px
padding 0
border-radius 17px
@@ -44,7 +44,7 @@ $info-margin-under-border = 27px
border-color $ui-favorite-star-button-color
&:active, &:active:hover
background-color $ui-favorite-star-button-color
color $ui-button--active-color
color $ui-button--color
.info-right
position absolute
@@ -54,6 +54,27 @@ $info-margin-under-border = 27px
bottom 1px
padding-left 30px
.undo-button
width 34px
height 34px
border-radius 17px
font-size 14px
margin 15px 7px
border none
color $ui-button-color
display flex
align-items center
justify-content center
fill $ui-button-color
background-color transparent
cursor pointer
&:active
border-color $ui-button--active-backgroundColor
&:hover
background-color alpha($ui-button--hover-backgroundColor, 60%)
.control-lockButton-tooltip
opacity 1
body[data-theme="dark"]
.info
border-color $ui-dark-borderColor
@@ -73,3 +94,6 @@ body[data-theme="dark"]
.info-right
background-color $ui-dark-noteDetail-backgroundColor
.undo-button
topBarButtonDark()

View File

@@ -15,6 +15,13 @@ import StatusBar from '../StatusBar'
import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
function pass (name) {
switch (name) {
@@ -51,7 +58,7 @@ class SnippetNoteDetail extends React.Component {
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
let nextNote = Object.assign({
description: ''
@@ -66,7 +73,7 @@ class SnippetNoteDetail extends React.Component {
snippets.forEach((snippet, index) => {
this.refs['code-' + index].reload()
})
this.refs.tags.reset()
if (this.refs.tags) this.refs.tags.reset()
})
}
}
@@ -75,41 +82,13 @@ class SnippetNoteDetail extends React.Component {
if (this.saveQueue != null) this.saveNow()
}
findTitle (value) {
let splitted = value.split('\n')
let title = null
for (let i = 0; i < splitted.length; i++) {
let trimmedLine = splitted[i].trim()
if (trimmedLine.match(/^# .+/)) {
title = trimmedLine.substring(1, trimmedLine.length).trim()
break
}
}
if (title == null) {
for (let i = 0; i < splitted.length; i++) {
let trimmedLine = splitted[i].trim()
if (trimmedLine.length > 0) {
title = trimmedLine
break
}
}
if (title == null) {
title = ''
}
}
return title
}
handleChange (e) {
let { note } = this.state
note.tags = this.refs.tags.value
if (this.refs.tags) note.tags = this.refs.tags.value
note.description = this.refs.description.value
note.updatedAt = new Date()
note.title = this.findTitle(note.description)
note.title = findNoteTitle(note.description)
this.setState({
note
@@ -137,6 +116,7 @@ class SnippetNoteDetail extends React.Component {
type: 'UPDATE_NOTE',
note: note
})
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
})
}
@@ -175,6 +155,7 @@ class SnippetNoteDetail extends React.Component {
handleStarButtonClick (e) {
let { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred
@@ -189,14 +170,18 @@ class SnippetNoteDetail extends React.Component {
}
handleDeleteButtonClick (e) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
handleTrashButtonClick (e) {
let { note } = this.state
const { isTrashed } = note
if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
let { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
@@ -209,9 +194,34 @@ class SnippetNoteDetail extends React.Component {
})
}
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
})
} else {
note.isTrashed = true
this.setState({
note
}, () => {
this.save()
})
}
ee.emit('list:next')
}
handleUndoButtonClick (e) {
let { note } = this.state
note.isTrashed = false
this.setState({
note
}, () => {
this.save()
ee.emit('list:next')
})
}
handleFullScreenButton (e) {
ee.emit('editor:fullscreen')
}
handleTabPlusButtonClick (e) {
@@ -261,7 +271,7 @@ class SnippetNoteDetail extends React.Component {
let syntax = CodeMirror.findModeByFileName(name.trim())
let mode = syntax != null ? syntax.name : null
if (mode != null) snippets[index].mode = mode
this.state.note.snippets = snippets
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
@@ -274,7 +284,7 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
let snippets = this.state.note.snippets.slice()
snippets[index].mode = name
this.state.note.snippets = snippets
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
@@ -288,7 +298,7 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
let snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
this.state.note.snippets = snippets
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
}, () => {
@@ -452,10 +462,27 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + this.state.snippetIndex].focus()
}
handleInfoButtonClick (e) {
const infoPanel = document.querySelector('.infoPanel')
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
}
showWarning () {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.',
buttons: ['OK']
})
}
render () {
let { data, config } = this.props
let { data, config, location } = this.props
let { note } = this.state
let storageKey = note.storage
let folderKey = note.folder
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
@@ -493,6 +520,7 @@ class SnippetNoteDetail extends React.Component {
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
storageKey={storageKey}
/>
: <CodeEditor styleName='tabView-content'
mode={snippet.mode}
@@ -510,49 +538,91 @@ class SnippetNoteDetail extends React.Component {
</div>
})
let options = []
data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => {
options.push({
storage: storage,
folder: folder
})
})
})
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
<div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanelTrashed
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
/>
</div>
</div>
const detailTopBar = <div styleName='info'>
<div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<TagSelect
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
</div>
<div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-window-maximize' styleName='fullScreen-button' />
</button>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
type={note.type}
/>
</div>
</div>
return (
<div className='NoteDetail'
style={this.props.style}
styleName='root'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='info'>
<div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<TagSelect
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
</div>
<div styleName='info-right'>
<button styleName='control-trashButton'
onClick={(e) => this.handleDeleteButtonClick(e)}
>
<svg height='14px' id='Capa_1' style={{enableBackground: 'new 0 0 753.23 753.23'}} width='14px' version='1.1' viewBox='0 0 753.23 753.23' x='0px' y='0px' xmlSpace='preserve'>
<g>
<g id='_x34__19_'>
<g>
<path d='M494.308,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077c0-12.993-10.545-23.539-23.538-23.539&#xA;&#x9;&#x9;&#x9;&#x9;s-23.538,10.545-23.538,23.539v282.461C470.77,648.531,481.314,659.077,494.308,659.077z M635.538,94.154h-141.23V47.077&#xA;&#x9;&#x9;&#x9;&#x9;C494.308,21.067,473.24,0,447.23,0H306c-26.01,0-47.077,21.067-47.077,47.077v47.077h-141.23&#xA;&#x9;&#x9;&#x9;&#x9;c-26.01,0-47.077,21.067-47.077,47.077v47.077c0,25.986,21.067,47.077,47.077,47.077v423.692&#xA;&#x9;&#x9;&#x9;&#x9;c0,51.996,42.157,94.153,94.154,94.153h329.539c51.996,0,94.153-42.157,94.153-94.153V235.385&#xA;&#x9;&#x9;&#x9;&#x9;c26.01,0,47.077-21.091,47.077-47.077V141.23C682.615,115.221,661.548,94.154,635.538,94.154z M306,70.615&#xA;&#x9;&#x9;&#x9;&#x9;c0-12.993,10.545-23.539,23.538-23.539h94.154c12.993,0,23.538,10.545,23.538,23.539v23.539c-22.809,0-141.23,0-141.23,0V70.615z&#xA;&#x9;&#x9;&#x9;&#x9; M588.461,659.077c0,25.986-21.066,47.076-47.076,47.076H211.846c-26.01,0-47.077-21.09-47.077-47.076V235.385h423.692V659.077z&#xA;&#x9;&#x9;&#x9;&#x9; M612,188.308H141.23c-12.993,0-23.538-10.545-23.538-23.539s10.545-23.539,23.538-23.539H612&#xA;&#x9;&#x9;&#x9;&#x9;c12.993,0,23.538,10.545,23.538,23.539S624.993,188.308,612,188.308z M258.923,659.077c12.993,0,23.539-10.546,23.539-23.539&#xA;&#x9;&#x9;&#x9;&#x9;V353.077c0-12.993-10.545-23.539-23.539-23.539s-23.539,10.545-23.539,23.539v282.461&#xA;&#x9;&#x9;&#x9;&#x9;C235.384,648.531,245.93,659.077,258.923,659.077z M376.615,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077&#xA;&#x9;&#x9;&#x9;&#x9;c0-12.993-10.545-23.539-23.538-23.539s-23.539,10.545-23.539,23.539v282.461C353.077,648.531,363.622,659.077,376.615,659.077z' />
</g>
</g>
</g>
</svg>
</button>
</div>
</div>
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>
<div styleName='description'>

View File

@@ -19,7 +19,7 @@
.body .description
absolute top left right
height 80px
height 50px
.body .description textarea
outline none
@@ -27,14 +27,14 @@
height 100%
width 100%
resize none
border none
padding 10px
border 1px solid $ui-borderColor
padding 2px 5px
line-height 1.6
background-color $ui-noteDetail-backgroundColor
.tabList
absolute left right
top 80px
top 55px
height 30px
display flex
background-color $ui-noteDetail-backgroundColor
@@ -50,24 +50,26 @@
.tabView
absolute left right bottom
top 130px
top 100px
.tabView-content
absolute top left right bottom
.override
absolute bottom left
bottom 30px
left 60px
height 23px
z-index 1
z-index 101
button
navButtonColor()
height 24px
&:active .update-icon
color white
.control-trashButton
.control-fullScreenButton
float right
padding 0 0 2px 0
topBarButtonLight()
body[data-theme="dark"]
@@ -82,6 +84,7 @@ body[data-theme="dark"]
.body .description textarea
background-color $ui-dark-noteDetail-backgroundColor
color $ui-dark-text-color
border 1px solid $ui-dark-borderColor
.tabList
background-color $ui-button--active-backgroundColor
@@ -100,5 +103,5 @@ body[data-theme="dark"]
transition 0.15s
color $ui-dark-text-color
.control-trashButton
.control-fullScreenButton
topBarButtonDark()

View File

@@ -2,6 +2,7 @@ import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl'
import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
class TagSelect extends React.Component {
constructor (props) {
@@ -56,6 +57,7 @@ class TagSelect extends React.Component {
}
submitTag () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')

View File

@@ -1,7 +1,7 @@
.root
display inline-block
top 19px
user-select none
height 23px
vertical-align middle
width 300px
overflow-x scroll
@@ -14,7 +14,6 @@
display inline-block
margin 1px 3px
padding 0
vertical-align middle
height 20px
background-color alpha($ui-tag-backgroundColor, 10%)
border-radius 3px
@@ -49,9 +48,9 @@
.newTag
display inline-block
margin 0 2px
margin 2px 0 15px 2px
vertical-align middle
height 24px
height 18px
box-sizing borde-box
border none
background-color transparent

View File

@@ -0,0 +1,19 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
const TrashButton = ({
onClick
}) => (
<button styleName='control-trashButton'
onClick={(e) => onClick(e)}
>
<i className='fa fa-trash trashButton' styleName='info-button' />
</button>
)
TrashButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(TrashButton, styles)

View File

@@ -0,0 +1,11 @@
.control-trashButton
float right
topBarButtonLight()
.trashButton
padding 0px
margin 15px 0
body[data-theme="dark"]
.control-trashButton
topBarButtonDark()

View File

@@ -17,7 +17,7 @@ class Detail extends React.Component {
this.refs.root != null && this.refs.root.focus()
}
this.deleteHandler = () => {
this.refs.root != null && this.refs.root.handleDeleteButtonClick()
this.refs.root != null && this.refs.root.handleTrashButtonClick()
}
}
@@ -49,7 +49,7 @@ class Detail extends React.Component {
tabIndex='0'
>
<div styleName='empty'>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new post</div>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new note</div>
</div>
<StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])}

View File

@@ -12,6 +12,9 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
import modal from 'browser/main/lib/modal'
import InitModal from 'browser/main/modals/InitModal'
import mixpanel from 'browser/main/lib/mixpanel'
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
import eventEmitter from 'browser/main/lib/eventEmitter'
import RealtimeNotification from 'browser/components/RealtimeNotification'
function focused () {
mixpanel.track('MAIN_FOCUSED')
@@ -21,14 +24,23 @@ class Main extends React.Component {
constructor (props) {
super(props)
if (process.env.NODE_ENV === 'production') {
mobileAnalytics.initAwsMobileAnalytics()
}
let { config } = props
this.state = {
isRightSliderFocused: false,
listWidth: config.listWidth,
navWidth: config.navWidth,
isLeftSliderFocused: false
isLeftSliderFocused: false,
fullScreen: false,
noteDetailWidth: 0,
mainBodyWidth: 0
}
this.toggleFullScreen = () => this.handleFullScreenButton()
}
getChildContext () {
@@ -63,11 +75,13 @@ class Main extends React.Component {
}
})
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
window.addEventListener('focus', focused)
}
componentWillUnmount () {
window.removeEventListener('focus', focused)
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
}
handleLeftSlideMouseDown (e) {
@@ -144,6 +158,34 @@ class Main extends React.Component {
}
}
handleFullScreenButton (e) {
this.setState({ fullScreen: !this.state.fullScreen }, () => {
const noteDetail = document.querySelector('.NoteDetail')
const noteList = document.querySelector('.NoteList')
const mainBody = document.querySelector('#main-body')
if (this.state.fullScreen) {
this.hideLeftLists(noteDetail, noteList, mainBody)
} else {
this.showLeftLists(noteDetail, noteList, mainBody)
}
})
}
hideLeftLists (noteDetail, noteList, mainBody) {
this.setState({noteDetailWidth: noteDetail.style.left})
this.setState({mainBodyWidth: mainBody.style.left})
noteDetail.style.left = '0px'
mainBody.style.left = '0px'
noteList.style.display = 'none'
}
showLeftLists (noteDetail, noteList, mainBody) {
noteDetail.style.left = this.state.noteDetailWidth
mainBody.style.left = this.state.mainBodyWidth
noteList.style.display = 'inline'
}
render () {
let { config } = this.props
@@ -173,6 +215,7 @@ class Main extends React.Component {
</div>
}
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
id='main-body'
ref='body'
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
>
@@ -213,6 +256,7 @@ class Main extends React.Component {
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
/>
</div>
<RealtimeNotification />
</div>
)
}

View File

@@ -13,10 +13,12 @@
absolute top bottom
top -2px
width 0
z-index 0
.slider-right
@extend .slider
width 1px
z-index 0
.slider--active
@extend .slider

View File

@@ -0,0 +1,68 @@
.root
position relative
background-color $ui-noteList-backgroundColor
height $topBar-height - 1
margin-left: auto;
width: 64px;
margin-right: -15px;
.root--expanded
@extend .root
$control-height = 34px
.control
position absolute
top 13px
left 8px
right 8px
height $control-height
overflow hidden
display flex
.control-newNoteButton
display block
width 32px
height $control-height - 2
navButtonColor()
font-size 16px
line-height 28px
padding 0
&:active
border-color $ui-button--active-backgroundColor
&:hover .control-newNoteButton-tooltip
opacity 1
.control-newNoteButton-tooltip
tooltip()
position fixed
pointer-events none
top 50px
left 433px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.root, .root--expanded
background-color $ui-dark-noteList-backgroundColor
.control
border-color $ui-dark-borderColor
.control-newNoteButton
color $ui-inactive-text-color
border-color $ui-dark-borderColor
background-color $ui-dark-noteList-backgroundColor
&:hover
transition 0.15s
color $ui-dark-text-color
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
border-color $ui-dark-button--active-backgroundColor
.control-newNoteButton-tooltip
darkTooltip()

View File

@@ -0,0 +1,106 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteButton.styl'
import _ from 'lodash'
import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal'
import { hashHistory } from 'react-router'
import eventEmitter from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
const { remote } = require('electron')
const { dialog } = remote
const OSX = window.process.platform === 'darwin'
class NewNoteButton extends React.Component {
constructor (props) {
super(props)
this.state = {
}
this.newNoteHandler = () => {
this.handleNewNoteButtonClick()
}
}
componentDidMount () {
eventEmitter.on('top:new-note', this.newNoteHandler)
}
componentWillUnmount () {
eventEmitter.off('top:new-note', this.newNoteHandler)
}
handleNewNoteButtonClick (e) {
const { config, location, dispatch } = this.props
const { storage, folder } = this.resolveTargetFolder()
modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location
})
}
resolveTargetFolder () {
const { data, params } = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
if (storage == null) {
for (let kv of data.storageMap) {
storage = kv[1]
break
}
}
if (storage == null) this.showMessageBox('No storage to create a note')
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
if (folder == null) this.showMessageBox('No folder to create a note')
return {
storage,
folder
}
}
showMessageBox (message) {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: message,
buttons: ['OK']
})
}
render () {
const { config, style } = this.props
return (
<div className='NewNoteButton'
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
style={style}
>
<div styleName='control'>
<button styleName='control-newNoteButton'
onClick={(e) => this.handleNewNoteButtonClick(e)}>
<i className='fa fa-pencil-square-o' />
<span styleName='control-newNoteButton-tooltip'>
Make a Note {OSX ? '⌘' : '^'} + n
</span>
</button>
</div>
</div>
)
}
}
NewNoteButton.propTypes = {
dispatch: PropTypes.func,
config: PropTypes.shape({
isSideNavFolded: PropTypes.bool
})
}
export default CSSModules(NewNoteButton, styles)

View File

@@ -2,6 +2,7 @@ $control-height = 30px
.root
absolute left bottom
bottom 30px
top $topBar-height - 1
background-color $ui-noteList-backgroundColor
@@ -17,7 +18,7 @@ $control-height = 30px
.control-sortBy
flex 1
padding-left 25px
padding-left 22px
.control-sortBy-select
appearance: none;
@@ -28,11 +29,14 @@ $control-height = 30px
background-color transparent
outline none
cursor pointer
font-size 10px
font-size 11px
&:hover
transition 0.2s
color $ui-text-color
.control-button-area
margin-right 12px
.control-button
width 25px
padding 0

View File

@@ -8,6 +8,12 @@ import dataApi from 'browser/main/lib/dataApi'
import ConfigManager from 'browser/main/lib/ConfigManager'
import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple'
import searchFromNotes from 'browser/lib/search'
import fs from 'fs'
import { hashHistory } from 'react-router'
import markdown from 'browser/lib/markdown'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import stripgtags from 'striptags'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
@@ -41,10 +47,8 @@ class NoteList extends React.Component {
this.alertIfSnippetHandler = () => {
this.alertIfSnippet()
}
this.jumpToTopHandler = () => {
this.jumpToTop()
}
this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.state = {
}
@@ -56,8 +60,8 @@ class NoteList extends React.Component {
ee.on('list:prior', this.selectPriorNoteHandler)
ee.on('list:focus', this.focusHandler)
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.on('list:top', this.jumpToTopHandler)
ee.on('list:jumpToTop', this.jumpToTopHandler)
ee.on('import:file', this.importFromFileHandler)
ee.on('list:jump', this.jumpNoteByHash)
}
componentWillReceiveProps (nextProps) {
@@ -77,8 +81,8 @@ class NoteList extends React.Component {
ee.off('list:prior', this.selectPriorNoteHandler)
ee.off('list:focus', this.focusHandler)
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.off('list:top', this.jumpToTopHandler)
ee.off('list:jumpToTop', this.jumpToTopHandler)
ee.off('import:file', this.importFromFileHandler)
ee.off('list:jump', this.jumpNoteByHash)
}
componentDidUpdate (prevProps) {
@@ -171,6 +175,31 @@ class NoteList extends React.Component {
ee.emit('list:moved')
}
jumpNoteByHashHandler (event, noteHash) {
// first argument event isn't used.
if (this.notes === null || this.notes.length === 0) {
return
}
const { router } = this.context
const { location } = this.props
let targetIndex = _.findIndex(this.notes, (note) => {
return note.storage + '-' + note.key === noteHash
})
if (targetIndex < 0) targetIndex = 0
router.push({
pathname: location.pathname,
query: {
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
}
})
ee.emit('list:moved')
}
handleNoteListKeyDown (e) {
if (e.metaKey || e.ctrlKey) return true
@@ -202,6 +231,7 @@ class NoteList extends React.Component {
getNotes () {
let { data, params, location } = this.props
let { router } = this.context
if (location.pathname.match(/\/home/)) {
return data.noteMap.map((note) => note)
@@ -212,6 +242,19 @@ class NoteList extends React.Component {
.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
if (location.pathname.match(/\/searched/)) {
const searchInputText = document.getElementsByClassName('searchInput')[0].value
if (searchInputText === '') {
router.push('/home')
}
return searchFromNotes(this.notes, searchInputText)
}
if (location.pathname.match(/\/trashed/)) {
return data.trashedSet.toJS()
.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
let storageKey = params.storageKey
let folderKey = params.folderKey
let storage = data.storageMap.get(storageKey)
@@ -247,49 +290,6 @@ class NoteList extends React.Component {
})
}
handleNoteContextMenu (e, uniqueKey) {
let menu = new Menu()
menu.append(new MenuItem({
label: 'Delete Note',
click: (e) => this.handleDeleteNote(e, uniqueKey)
}))
menu.popup()
}
handleDeleteNote (e, uniqueKey) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
let { dispatch, location } = this.props
let splitted = uniqueKey.split('-')
let storageKey = splitted.shift()
let noteKey = splitted.shift()
dataApi
.deleteNote(storageKey, noteKey)
.then((data) => {
let dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
if (location.query.key === uniqueKey) {
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
} else {
dispatchHandler()
}
})
}
}
handleSortByChange (e) {
let { dispatch } = this.props
@@ -327,30 +327,78 @@ class NoteList extends React.Component {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.'
detail: 'md/text import is available only a markdown note.',
buttons: ['OK', 'Cancel']
})
}
}
jumpToTop () {
if (this.notes === null || this.notes.length === 0) {
return
handleDragStart (e, note) {
const noteData = JSON.stringify(note)
e.dataTransfer.setData('note', noteData)
}
importFromFile () {
const { dispatch, location } = this.props
const options = {
filters: [
{ name: 'Documents', extensions: ['md', 'txt'] }
],
properties: ['openFile', 'multiSelections']
}
let { router } = this.context
let { location } = this.props
const targetIndex = 0
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
this.addNotesFromFiles(filepaths)
})
}
router.push({
pathname: location.pathname,
query: {
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
}
handleDrop (e) {
e.preventDefault()
const { location } = this.props
const filepaths = Array.from(e.dataTransfer.files).map(file => { return file.path })
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
}
// Add notes to the current folder
addNotesFromFiles (filepaths) {
const { dispatch, location } = this.props
const targetIndex = _.findIndex(this.notes, (note) => {
return note !== null && `${note.storage}-${note.key}` === location.query.key
})
const storageKey = this.notes[targetIndex].storage
const folderKey = this.notes[targetIndex].folder
if (filepaths === undefined) return
filepaths.forEach((filepath) => {
fs.readFile(filepath, (err, data) => {
if (err) throw Error('File reading error: ', err)
const content = data.toString()
const newNote = {
content: content,
folder: folderKey,
title: markdown.strip(findNoteTitle(content)),
type: 'MARKDOWN_NOTE'
}
dataApi.createNote(storageKey, newNote)
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: `${note.storage}-${note.key}`}
})
})
})
})
}
render () {
let { location, notes, config } = this.props
let { location, notes, config, dispatch } = this.props
let sortFunc = config.sortBy === 'CREATED_AT'
? sortByCreatedAt
: config.sortBy === 'ALPHABETICAL'
@@ -358,6 +406,10 @@ class NoteList extends React.Component {
: sortByUpdatedAt
this.notes = notes = this.getNotes()
.sort(sortFunc)
.filter((note) => {
// this is for the trash box
if (note.isTrashed !== true || location.pathname === '/trashed') return true
})
let noteList = notes
.map(note => {
@@ -381,7 +433,7 @@ class NoteList extends React.Component {
dateDisplay={dateDisplay}
key={key}
handleNoteClick={this.handleNoteClick.bind(this)}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
/>
)
}
@@ -392,7 +444,7 @@ class NoteList extends React.Component {
note={note}
key={key}
handleNoteClick={this.handleNoteClick.bind(this)}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
/>
)
})
@@ -401,6 +453,7 @@ class NoteList extends React.Component {
<div className='NoteList'
styleName='root'
style={this.props.style}
onDrop={(e) => this.handleDrop(e)}
>
<div styleName='control'>
<div styleName='control-sortBy'>
@@ -409,27 +462,29 @@ class NoteList extends React.Component {
value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)}
>
<option value='UPDATED_AT'>Updated Time</option>
<option value='CREATED_AT'>Created Time</option>
<option value='ALPHABETICAL'>Alphabetical</option>
<option value='UPDATED_AT'>Last Updated</option>
<option value='CREATED_AT'>Creation Time</option>
<option value='ALPHABETICAL'>Alphabetically</option>
</select>
</div>
<button styleName={config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
>
<i className='fa fa-th-large' />
</button>
<button styleName={config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
>
<i className='fa fa-list-ul' />
</button>
<div styleName='control-button-area'>
<button styleName={config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
>
<i className='fa fa-th-large' />
</button>
<button styleName={config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
>
<i className='fa fa-list-ul' />
</button>
</div>
</div>
<div styleName='list'
ref='list'

View File

@@ -1,7 +1,7 @@
.root
absolute top left bottom
width $sideNav-width
background-color $ui-backgroundColor
background-color #f9f9f9
user-select none
color $ui-text-color

View File

@@ -7,6 +7,7 @@ import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
import dataApi from 'browser/main/lib/dataApi'
import StorageItemChild from 'browser/components/StorageItem'
import eventEmitter from 'browser/main/lib/eventEmitter'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
@@ -113,7 +114,7 @@ class StorageItem extends React.Component {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete Folder',
detail: 'This work will deletes all notes in the folder and can not be undone.',
detail: 'This will delete all notes in the folder and can not be undone.',
buttons: ['Confirm', 'Cancel']
})
@@ -131,16 +132,68 @@ class StorageItem extends React.Component {
}
}
handleDragEnter (e) {
e.dataTransfer.setData('defaultColor', e.target.style.backgroundColor)
e.target.style.backgroundColor = 'rgba(129, 130, 131, 0.08)'
}
handleDragLeave (e) {
e.target.style.opacity = '1'
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
}
handleDrop (e, storage, folder, dispatch, location) {
e.target.style.opacity = '1'
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
const noteData = JSON.parse(e.dataTransfer.getData('note'))
const newNoteData = Object.assign({}, noteData, {storage: storage, folder: folder.key})
if (folder.key === noteData.folder) return
dataApi
.createNote(storage.key, newNoteData)
.then((note) => {
dataApi
.deleteNote(noteData.storage, noteData.key)
.then((data) => {
let dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
eventEmitter.once('list:moved', dispatchHandler)
eventEmitter.emit('list:next')
})
.catch((err) => {
console.error(err)
})
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: `${note.storage}-${note.key}`}
})
})
}
render () {
let { storage, location, isFolded, data } = this.props
let { folderNoteMap } = data
let { storage, location, isFolded, data, dispatch } = this.props
let { folderNoteMap, trashedSet } = data
let folderList = storage.folders.map((folder) => {
let isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
let noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = noteSet != null
? noteSet.size
: 0
let noteCount = 0
if (noteSet) {
let trashedNoteCount = 0
const noteKeys = noteSet.map(noteKey => { return noteKey })
trashedSet.toJS().forEach(trashedKey => {
if (noteKeys.some(noteKey => { return noteKey === trashedKey })) trashedNoteCount++
})
noteCount = noteSet.size - trashedNoteCount
}
return (
<StorageItemChild
key={folder.key}
@@ -151,6 +204,9 @@ class StorageItem extends React.Component {
folderColor={folder.color}
isFolded={isFolded}
noteCount={noteCount}
handleDrop={(e) => this.handleDrop(e, storage, folder, dispatch, location)}
handleDragEnter={this.handleDragEnter}
handleDragLeave={this.handleDragLeave}
/>
)
})

View File

@@ -1,6 +1,7 @@
.root
width 100%
user-select none
padding-top 20px
.header
position relative

View File

@@ -33,12 +33,18 @@ class SideNav extends React.Component {
})
}
handleTrashedButtonClick (e) {
let { router } = this.context
router.push('/trashed')
}
render () {
let { data, location, config, dispatch } = this.props
let isFolded = config.isSideNavFolded
let isHomeActive = !!location.pathname.match(/^\/home$/)
let isStarredActive = !!location.pathname.match(/^\/starred$/)
let isTrashedActive = !!location.pathname.match(/^\/trashed$/)
let storageList = data.storageMap.map((storage, key) => {
return <StorageItem
@@ -62,8 +68,8 @@ class SideNav extends React.Component {
<button styleName='top-menu'
onClick={(e) => this.handleMenuButtonClick(e)}
>
<i className='fa fa-navicon fa-fw' />
<span styleName='top-menu-label'>Menu</span>
<i className='fa fa-wrench fa-fw' />
<span styleName='top-menu-label'>Preferences</span>
</button>
</div>
@@ -72,7 +78,9 @@ class SideNav extends React.Component {
isHomeActive={isHomeActive}
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
isStarredActive={isStarredActive}
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
/>
<div styleName='storageList'>

View File

@@ -3,6 +3,8 @@
.root
absolute bottom left right
height $statusBar-height
bottom 16px
z-index 100
background-color $ui-noteDetail-backgroundColor
display flex

View File

@@ -2,7 +2,6 @@ import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StatusBar.styl'
import ZoomManager from 'browser/main/lib/ZoomManager'
import LastUpdatedString from '../Detail/LastUpdatedString'
const electron = require('electron')
const { remote, ipcRenderer } = electron
@@ -60,16 +59,12 @@ class StatusBar extends React.Component {
{Math.floor(config.zoom * 100)}%
</button>
<div styleName='blank' />
{status.updateReady
? <button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
</button>
: null
}
<LastUpdatedString date={this.props.date} />
</div>
)
}

View File

@@ -19,26 +19,25 @@ $control-height = 34px
.control-search
height 32px
width 1px
flex 1
background-color white
position relative
.control-search-icon
absolute top bottom left
line-height 32px
width 35px
color $ui-inactive-text-color
background-color $ui-noteList-backgroundColor
.control-search-input
display block
absolute top bottom right
left 30px
width 100%
padding-left 12px
background-color $ui-noteList-backgroundColor
input
width 100%
height 100%
outline none
border none
color $ui-text-color
font-size 16px
padding-bottom 2px
background-color $ui-noteList-backgroundColor
.control-search-optionList
@@ -92,9 +91,7 @@ $control-height = 34px
width 32px
height $control-height - 2
navButtonColor()
border $ui-border
border-radius 32px
font-size 14px
font-size 16px
line-height 28px
padding 0
&:active
@@ -132,6 +129,7 @@ body[data-theme="dark"]
background-color $ui-dark-noteList-backgroundColor
.control-search-input
background-color $ui-dark-noteList-backgroundColor
input
background-color $ui-dark-noteList-backgroundColor
color $ui-dark-text-color

View File

@@ -2,12 +2,12 @@ import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TopBar.styl'
import _ from 'lodash'
import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import ConfigManager from 'browser/main/lib/ConfigManager'
import dataApi from 'browser/main/lib/dataApi'
import NewNoteButton from 'browser/main/NewNoteButton'
const { remote } = require('electron')
const { dialog } = remote
const OSX = window.process.platform === 'darwin'
@@ -18,11 +18,7 @@ class TopBar extends React.Component {
this.state = {
search: '',
searchOptions: [],
searchPopupOpen: false
}
this.newNoteHandler = () => {
this.handleNewPostButtonClick()
isSearching: false
}
this.focusSearchHandler = () => {
@@ -31,150 +27,24 @@ class TopBar extends React.Component {
}
componentDidMount () {
ee.on('top:new-note', this.newNoteHandler)
ee.on('top:focus-search', this.focusSearchHandler)
}
componentWillUnmount () {
ee.off('top:new-note', this.newNoteHandler)
ee.off('top:focus-search', this.focusSearchHandler)
}
handleNewPostButtonClick (e) {
let { config } = this.props
switch (config.ui.defaultNote) {
case 'MARKDOWN_NOTE':
this.createNote('MARKDOWN_NOTE')
break
case 'SNIPPET_NOTE':
this.createNote('SNIPPET_NOTE')
break
case 'ALWAYS_ASK':
default:
let { dispatch, location } = this.props
let { storage, folder } = this.resolveTargetFolder()
modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location
})
}
}
resolveTargetFolder () {
let { data, params } = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
if (storage == null) {
for (let kv of data.storageMap) {
storage = kv[1]
break
}
}
if (storage == null) window.alert('No storage to create a note')
let folder = _.find(storage.folders, {key: params.folderKey})
if (folder == null) folder = storage.folders[0]
if (folder == null) window.alert('No folder to create a note')
return {
storage,
folder
}
}
handleSearchChange (e) {
let { router } = this.context
router.push('/searched')
this.setState({
search: this.refs.searchInput.value
})
}
getOptions () {
let { data } = this.props
let { search } = this.state
let notes = data.noteMap.map((note) => note)
if (search.trim().length === 0) return []
let searchBlocks = search.split(' ')
searchBlocks.forEach((block) => {
if (block.match(/^!#.+/)) {
let tag = block.match(/^!#(.+)/)[1]
let regExp = new RegExp(_.escapeRegExp(tag), 'i')
notes = notes
.filter((note) => {
if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => {
return _tag.match(regExp)
})
})
} else if (block.match(/^!.+/)) {
let block = block.match(/^!(.+)/)[1]
let regExp = new RegExp(_.escapeRegExp(block), 'i')
notes = notes.filter((note) => {
if (!_.isArray(note.tags) || !note.tags.some((_tag) => {
return _tag.match(regExp)
})) {
return true
}
if (note.type === 'SNIPPET_NOTE') {
return !note.description.match(regExp)
} else if (note.type === 'MARKDOWN_NOTE') {
return !note.content.match(regExp)
}
return false
})
} else if (block.match(/^#.+/)) {
let tag = block.match(/#(.+)/)[1]
let regExp = new RegExp(_.escapeRegExp(tag), 'i')
notes = notes
.filter((note) => {
if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => {
return _tag.match(regExp)
})
})
} else {
let regExp = new RegExp(_.escapeRegExp(block), 'i')
notes = notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => {
return _tag.match(regExp)
})) {
return true
}
if (note.type === 'SNIPPET_NOTE') {
return note.description.match(regExp)
} else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(regExp)
}
return false
})
}
})
return notes
}
handleOptionClick (uniqueKey) {
return (e) => {
this.setState({
searchPopupOpen: false
}, () => {
let { location } = this.props
hashHistory.push({
pathname: location.pathname,
query: {
key: uniqueKey
}
})
})
}
}
handleSearchFocus (e) {
this.setState({
searchPopupOpen: true
isSearching: true
})
}
handleSearchBlur (e) {
@@ -191,67 +61,13 @@ class TopBar extends React.Component {
}
if (!isStillFocused) {
this.setState({
searchPopupOpen: false
isSearching: false
})
}
}
createNote (noteType) {
let { dispatch, location } = this.props
if (noteType !== 'MARKDOWN_NOTE' && noteType !== 'SNIPPET_NOTE') throw new Error('Invalid note type.')
let { storage, folder } = this.resolveTargetFolder()
let newNote = noteType === 'MARKDOWN_NOTE'
? {
type: 'MARKDOWN_NOTE',
folder: folder.key,
title: '',
content: ''
}
: {
type: 'SNIPPET_NOTE',
folder: folder.key,
title: '',
description: '',
snippets: [{
name: '',
mode: 'text',
content: ''
}]
}
dataApi
.createNote(storage.key, newNote)
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: note.storage + '-' + note.key}
})
ee.emit('detail:focus')
})
}
setDefaultNote (defaultNote) {
let { config, dispatch } = this.props
let ui = Object.assign(config.ui)
ui.defaultNote = defaultNote
ConfigManager.set({
ui
})
dispatch({
type: 'SET_UI',
config: ConfigManager.get()
})
}
handleOnSearchFocus () {
if (this.state.searchPopupOpen) {
if (this.state.isSearching) {
this.refs.search.childNodes[0].blur()
} else {
this.refs.search.childNodes[0].focus()
@@ -259,28 +75,7 @@ class TopBar extends React.Component {
}
render () {
let { config, style, data } = this.props
let searchOptionList = this.getOptions()
.map((note) => {
let storage = data.storageMap.get(note.storage)
let folder = _.find(storage.folders, {key: note.folder})
return <div styleName='control-search-optionList-item'
key={note.storage + '-' + note.key}
onClick={(e) => this.handleOptionClick(note.storage + '-' + note.key)(e)}
>
<div styleName='control-search-optionList-item-folder'
style={{borderColor: folder.color}}>
{folder.name}
<span styleName='control-search-optionList-item-folder-surfix'>in {storage.name}</span>
</div>
{note.type === 'SNIPPET_NOTE'
? <i styleName='control-search-optionList-item-type' className='fa fa-code' />
: <i styleName='control-search-optionList-item-type' className='fa fa-file-text-o' />
}&nbsp;
{note.title}
</div>
})
let { config, style, data, location } = this.props
return (
<div className='TopBar'
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
@@ -288,7 +83,6 @@ class TopBar extends React.Component {
>
<div styleName='control'>
<div styleName='control-search'>
<i styleName='control-search-icon' className='fa fa-search fa-fw' />
<div styleName='control-search-input'
onFocus={(e) => this.handleSearchFocus(e)}
onBlur={(e) => this.handleSearchBlur(e)}
@@ -301,15 +95,8 @@ class TopBar extends React.Component {
onChange={(e) => this.handleSearchChange(e)}
placeholder='Search'
type='text'
className='searchInput'
/>
{this.state.searchPopupOpen &&
<div styleName='control-search-optionList'>
{searchOptionList.length > 0
? searchOptionList
: <div styleName='control-search-optionList-empty'>Empty List</div>
}
</div>
}
</div>
{this.state.search > 0 &&
<button styleName='left-search-clearButton'
@@ -320,14 +107,17 @@ class TopBar extends React.Component {
}
</div>
<button styleName='control-newPostButton'
onClick={(e) => this.handleNewPostButtonClick(e)}>
<i className='fa fa-plus' />
<span styleName='control-newPostButton-tooltip'>
Make a Note {OSX ? '⌘' : '^'} + n
</span>
</button>
</div>
{location.pathname === '/trashed' ? ''
: <NewNoteButton
{..._.pick(this.props, [
'dispatch',
'data',
'config',
'params',
'location'
])}
/>}
</div>
)
}

View File

@@ -64,7 +64,7 @@ textarea.block-input
fullsize()
modalZIndex= 1000
modalBackColor = transparentify(white, 65%)
modalBackColor = white
.ace_focus
outline-color rgb(59, 153, 252)
outline-offset 0px
@@ -86,12 +86,12 @@ modalBackColor = transparentify(white, 65%)
body[data-theme="dark"]
.ModalBase
.modalBack
background-color alpha(black, 60%)
background-color $ui-dark-backgroundColor
.CodeMirror
font-family inherit !important
line-height 1.4em
height 100%
height 96%
.CodeMirror > div > textarea
margin-bottom -1em
.CodeMirror-focused .CodeMirror-selected

View File

@@ -23,6 +23,16 @@ document.addEventListener('dragover', function (e) {
e.stopPropagation()
})
document.addEventListener('click', function (e) {
const className = e.target.className
if (!className && typeof (className) !== 'string') return
const isInfoButton = className.includes('infoButton')
const isInfoPanel = e.target.offsetParent.className.includes('infoPanel')
if (isInfoButton || isInfoPanel) return
const infoPanel = document.querySelector('.infoPanel')
if (infoPanel) infoPanel.style.display = 'none'
})
let el = document.getElementById('content')
const history = syncHistoryWithStore(hashHistory, store)
@@ -50,6 +60,8 @@ ReactDOM.render((
<IndexRedirect to='/home' />
<Route path='home' />
<Route path='starred' />
<Route path='searched' />
<Route path='trashed' />
<Route path='storages'>
<IndexRedirect to='/home' />
<Route path=':storageKey'>

View File

@@ -0,0 +1,42 @@
const AWS = require('aws-sdk')
const AMA = require('aws-sdk-mobile-analytics')
const ConfigManager = require('browser/main/lib/ConfigManager')
AWS.config.region = 'us-east-1'
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
})
const mobileAnalyticsClient = new AMA.Manager({
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
appTitle: 'xxxxxxxxxx'
})
}
function initAwsMobileAnalytics () {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
AWS.config.credentials.get((err) => {
if (!err) {
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
recordDynamicCustomEvent('APP_STARTED')
recordStaticCustomEvent()
}
})
}
function recordDynamicCustomEvent (type) {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
mobileAnalyticsClient.recordEvent(type)
}
function recordStaticCustomEvent () {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
uiColorTheme: ConfigManager.default.get().ui.theme
})
}
module.exports = {
initAwsMobileAnalytics,
recordDynamicCustomEvent
}

View File

@@ -1,9 +1,13 @@
import _ from 'lodash'
import RcParser from 'browser/lib/RcParser'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
const electron = require('electron')
const { ipcRenderer } = electron
const consts = require('browser/lib/consts')
const path = require('path')
const fs = require('fs')
let isInitialized = false
@@ -14,6 +18,7 @@ export const DEFAULT_CONFIG = {
navWidth: 200,
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true,
hotkey: {
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
@@ -24,18 +29,18 @@ export const DEFAULT_CONFIG = {
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
},
editor: {
theme: 'default',
theme: 'base16-light',
keyMap: 'sublime',
fontSize: '14',
fontFamily: 'Monaco, Consolas',
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
indentType: 'space',
indentSize: '2',
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
},
preview: {
fontSize: '14',
fontFamily: 'Lato',
codeBlockTheme: 'elegant',
fontFamily: win ? 'Segoe UI' : 'Lato',
codeBlockTheme: 'dracula',
lineNumber: true
}
}
@@ -55,17 +60,17 @@ function _save (config) {
}
function get () {
let config = window.localStorage.getItem('config')
const rawStoredConfig = window.localStorage.getItem('config')
const storedConfig = Object.assign({}, DEFAULT_CONFIG, JSON.parse(rawStoredConfig))
let config = storedConfig
try {
config = Object.assign({}, DEFAULT_CONFIG, JSON.parse(config))
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, config.hotkey)
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, config.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, config.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, config.preview)
const boostnotercConfig = RcParser.parse()
config = assignConfigValues(storedConfig, boostnotercConfig)
if (!validate(config)) throw new Error('INVALID CONFIG')
} catch (err) {
console.warn('Boostnote resets the malformed configuration.')
console.warn('Boostnote resets the invalid configuration.')
config = DEFAULT_CONFIG
_save(config)
}
@@ -124,6 +129,15 @@ function set (updates) {
})
}
function assignConfigValues (originalConfig, rcConfig) {
let config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
return config
}
export default {
get,
set,

View File

@@ -0,0 +1,33 @@
const fs = require('fs')
const path = require('path')
const _ = require('lodash')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
/**
* @description To copy an image and return the path.
* @param {String} filePath
* @param {String} storageKey
* @return {String} an image path
*/
function copyImage (filePath, storageKey) {
return new Promise((resolve, reject) => {
try {
const targetStorage = findStorage(storageKey)
const inputImage = fs.createReadStream(filePath)
const imageExt = path.extname(filePath)
const imageName = Math.random().toString(36).slice(-16)
const basename = `${imageName}${imageExt}`
const imageDir = path.join(targetStorage.path, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
const outputImage = fs.createWriteStream(path.join(imageDir, basename))
inputImage.pipe(outputImage)
resolve(basename)
} catch (e) {
return reject(e)
}
})
}
module.exports = copyImage

View File

@@ -3,6 +3,7 @@ const keygen = require('browser/lib/keygen')
const path = require('path')
const resolveStorageData = require('./resolveStorageData')
const CSON = require('@rokt33r/season')
const { findStorage } = require('browser/lib/findStorage')
/**
* @param {String} storageKey
@@ -29,11 +30,7 @@ function createFolder (storageKey, input) {
if (!_.isString(input.name)) throw new Error('Name must be a string.')
if (!_.isString(input.color)) throw new Error('Color must be a string.')
rawStorages = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.')
targetStorage = _.find(rawStorages, {key: storageKey})
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}

View File

@@ -4,12 +4,14 @@ const _ = require('lodash')
const keygen = require('browser/lib/keygen')
const path = require('path')
const CSON = require('@rokt33r/season')
const { findStorage } = require('browser/lib/findStorage')
function validateInput (input) {
if (!_.isArray(input.tags)) input.tags = []
input.tags = input.tags.filter((tag) => _.isString(tag) && tag.trim().length > 0)
if (!_.isString(input.title)) input.title = ''
input.isStarred = !!input.isStarred
input.isTrashed = !!input.isTrashed
switch (input.type) {
case 'MARKDOWN_NOTE':
@@ -37,11 +39,7 @@ function createNote (storageKey, input) {
input = Object.assign({}, input)
validateInput(input)
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
targetStorage = _.find(cachedStorageList, {key: storageKey})
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}

View File

@@ -4,6 +4,7 @@ const resolveStorageData = require('./resolveStorageData')
const resolveStorageNotes = require('./resolveStorageNotes')
const CSON = require('@rokt33r/season')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
/**
* @param {String} storageKey
@@ -21,11 +22,7 @@ function deleteFolder (storageKey, folderKey) {
let rawStorages
let targetStorage
try {
rawStorages = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.')
targetStorage = _.find(rawStorages, {key: storageKey})
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}

View File

@@ -2,15 +2,12 @@ const resolveStorageData = require('./resolveStorageData')
const _ = require('lodash')
const path = require('path')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
function deleteNote (storageKey, noteKey) {
let targetStorage
try {
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
targetStorage = _.find(cachedStorageList, {key: storageKey})
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}

View File

@@ -41,7 +41,7 @@ function init () {
.then((notes) => {
let unknownCount = 0
notes.forEach((note) => {
if (!storage.folders.some((folder) => note.folder === folder.key)) {
if (note && !storage.folders.some((folder) => note.folder === folder.key)) {
unknownCount++
storage.folders.push({
key: note.folder,

View File

@@ -4,17 +4,13 @@ const path = require('path')
const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage
try {
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('Storage doesn\'t exist.')
oldStorage = _.find(cachedStorageList, {key: storageKey})
if (oldStorage == null) throw new Error('Storage doesn\'t exist.')
newStorage = _.find(cachedStorageList, {key: newStorageKey})
oldStorage = findStorage(storageKey)
newStorage = findStorage(newStorageKey)
if (newStorage == null) throw new Error('Target storage doesn\'t exist.')
} catch (e) {
return Promise.reject(e)

View File

@@ -1,5 +1,7 @@
const _ = require('lodash')
const resolveStorageData = require('./resolveStorageData')
const { findStorage } = require('browser/lib/findStorage')
/**
* @param {String} key
* @param {String} name

View File

@@ -28,7 +28,6 @@ function resolveStorageNotes (storage) {
return data
} catch (err) {
console.error(notePath)
throw err
}
})

View File

@@ -2,6 +2,7 @@ const resolveStorageData = require('./resolveStorageData')
const _ = require('lodash')
const path = require('path')
const CSON = require('@rokt33r/season')
const { findStorage } = require('browser/lib/findStorage')
function validateInput (input) {
let validatedInput = {}
@@ -21,6 +22,10 @@ function validateInput (input) {
validatedInput.isStarred = !!input.isStarred
}
if (input.isTrashed != null) {
validatedInput.isTrashed = !!input.isTrashed
}
validatedInput.type = input.type
switch (input.type) {
case 'MARKDOWN_NOTE':
@@ -64,11 +69,7 @@ function updateNote (storageKey, noteKey, input) {
if (input == null) throw new Error('No input found.')
input = validateInput(input)
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
targetStorage = _.find(cachedStorageList, {key: storageKey})
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}
@@ -101,6 +102,7 @@ function updateNote (storageKey, noteKey, input) {
noteData.createdAt = new Date()
noteData.updatedAt = new Date()
noteData.isStarred = false
noteData.isTrashed = false
noteData.tags = []
}

View File

@@ -4,6 +4,8 @@ import styles from './CreateFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import consts from 'browser/lib/consts'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
class CreateFolderModal extends React.Component {
constructor (props) {
@@ -47,6 +49,7 @@ class CreateFolderModal extends React.Component {
}
confirm () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_FOLDER')
if (this.state.name.trim().length > 0) {
let { storage } = this.props
let input = {
@@ -77,11 +80,7 @@ class CreateFolderModal extends React.Component {
<div styleName='header'>
<div styleName='title'>Create new folder</div>
</div>
<button styleName='close' onClick={(e) => this.handleCloseButtonClick(e)}>
<div styleName='close-mark'>×</div>
<div styleName='close-text'>esc</div>
</button>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<div styleName='control-folder'>
<div styleName='control-folder-label'>Folder name</div>

View File

@@ -4,10 +4,9 @@
height 270px
overflow hidden
position relative
padding 0 40px
.header
height 70px
height 80px
margin-bottom 10px
margin-top 20px
font-size 18px
@@ -15,38 +14,27 @@
background-color $ui-backgroundColor
color $ui-text-color
.close-mark
font-size 15px
.close
height 70px
position absolute
background-color transparent
color $ui-inactive-text-color
border none
top 7px
right 30px
text-align center
width top-bar--height
height top-bar--height
.title
font-size 36px
font-weight 600
.control-folder-label
text-align left
font-size 12px
font-size 14px
color $ui-text-color
.control-folder-input
display block
height 30px
width 420px
height 40px
width 490px
padding 0 5px
margin 10px auto 15px
margin 10px 0
border 1px solid #C9C9C9 // TODO: use variable.
border-radius 2px
background-color transparent
outline none
vertical-align middle
font-size 14px
font-size 16px
&:disabled
background-color $ui-input--disabled-backgroundColor
&:focus, &:active
@@ -54,14 +42,13 @@
.control-confirmButton
display block
float right
height 30px
width 100px
height 35px
width 140px
border none
border-radius 2px
padding 0 25px
margin 20px auto
font-size 12px
font-size 14px
colorPrimaryButton()
body[data-theme="dark"]
@@ -71,7 +58,6 @@ body[data-theme="dark"]
height 270px
overflow hidden
position relative
padding 0 40px
.header
background-color transparent
@@ -85,13 +71,8 @@ body[data-theme="dark"]
border 1px solid #C9C9C9 // TODO: use variable.
color white
.closeButton
border-color $ui-dark-borderColor
color $ui-dark-text-color
colorDarkDefaultButton()
.description
.description
color $ui-inactive-text-color
.control-confirmButton
colorDarkPrimaryButton()
colorDarkPrimaryButton()

View File

@@ -1,51 +0,0 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
const electron = require('electron')
const ipc = electron.ipcRenderer
export default class DeleteArticleModal extends React.Component {
constructor (props) {
super(props)
this.confirmHandler = (e) => this.handleYesButtonClick()
}
componentDidMount () {
ReactDOM.findDOMNode(this.refs.no).focus()
ipc.on('modal-confirm', this.confirmHandler)
}
componentWillUnmount () {
ipc.removeListener('modal-confirm', this.confirmHandler)
}
handleNoButtonClick (e) {
this.props.close()
}
handleYesButtonClick (e) {
this.props.close()
}
render () {
return (
<div className='DeleteArticleModal modal'>
<div className='title'><i className='fa fa-fw fa-trash' /> Delete an article.</div>
<div className='message'>Do you really want to delete?</div>
<div className='control'>
<button ref='no' onClick={(e) => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close' /> No</button>
<button ref='yes' onClick={(e) => this.handleYesButtonClick(e)} className='danger'><i className='fa fa-fw fa-check' /> Yes</button>
</div>
</div>
)
}
}
DeleteArticleModal.propTypes = {
action: PropTypes.object,
articleKey: PropTypes.string,
close: PropTypes.func
}

View File

@@ -5,6 +5,7 @@ import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { hashHistory } from 'react-router'
import _ from 'lodash'
import ModalEscButton from 'browser/components/ModalEscButton'
const CSON = require('@rokt33r/season')
const path = require('path')
@@ -102,7 +103,7 @@ class InitModal extends React.Component {
} else {
return dataApi
.createFolder(data.storage.key, {
color: '#6AA5E9',
color: '#1278BD',
name: 'Default'
})
.then((_data) => {
@@ -121,19 +122,6 @@ class InitModal extends React.Component {
notes: data.notes
})
let defaultMarkdownNote = dataApi
.createNote(data.storage.key, {
type: 'MARKDOWN_NOTE',
folder: data.storage.folders[0].key,
title: 'Welcome to Boostnote :)',
content: '# Welcome to Boostnote :)\nThis is a markdown note.\n\nClick to edit this note.'
})
.then((note) => {
store.dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
let defaultSnippetNote = dataApi
.createNote(data.storage.key, {
type: 'SNIPPET_NOTE',
@@ -144,12 +132,12 @@ class InitModal extends React.Component {
{
name: 'example.html',
mode: 'html',
content: '<html>\n<body>\n<h1 id=\'hello\'>Hello World</h1>\n</body>\n</html>'
content: '<html>\n<body>\n<h1 id=\'hello\'>Enjoy Boostnote!</h1>\n</body>\n</html>'
},
{
name: 'example.js',
mode: 'javascript',
content: 'var html = document.getElementById(\'hello\').innerHTML\n\nconsole.log(html)'
content: 'var boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)'
}
]
})
@@ -159,6 +147,19 @@ class InitModal extends React.Component {
note: note
})
})
let defaultMarkdownNote = dataApi
.createNote(data.storage.key, {
type: 'MARKDOWN_NOTE',
folder: data.storage.folders[0].key,
title: 'Welcome to Boostnote!',
content: '# Welcome to Boostnote! \n### _Click to edit this note._\n\n---\n\nBoostnote is an *open source* note-taking app. \nRepository is published on [GitHub](https://github.com/BoostIO/Boostnote), and tweeting everyday on [@Boostnoteapp](https://twitter.com/boostnoteapp)!\n\n## Features \n- [x] No Internet and Registration Required. \n- [ ] Quick search and copy the content of note. `macOS: Cmd + Alt + S / windows: Ctrl + Alt + S` \n- [ ] Markdown & Snippet note. \n- [ ] Available for `vim` and `emacs` mode. \n- [ ] Choose your favorite theme on UI, Editor and Code Block! \n--- \n\n- Copy Codeblock on Markdown Preview.\n```javascript\nvar boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)\n```'
})
.then((note) => {
store.dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
return Promise.resolve(defaultSnippetNote)
.then(defaultMarkdownNote)
@@ -199,6 +200,7 @@ class InitModal extends React.Component {
<div styleName='header'>
<div styleName='header-title'>Initialize Storage</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='body'>
<div styleName='body-welcome'>
Welcome!

View File

@@ -22,15 +22,6 @@
border-bottom solid 1px $ui-borderColor
color $ui-text-color
.closeButton
position absolute
top 10px
right 10px
height 30px
padding 0 25px
color $ui-text-color
colorDefaultButton()
.body
padding 30px

View File

@@ -4,6 +4,8 @@ import styles from './NewNoteModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
class NewNoteModal extends React.Component {
constructor (props) {
@@ -22,6 +24,8 @@ class NewNoteModal extends React.Component {
}
handleMarkdownNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let { storage, folder, dispatch, location } = this.props
dataApi
.createNote(storage, {
@@ -52,6 +56,8 @@ class NewNoteModal extends React.Component {
}
handleSnippetNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let { storage, folder, dispatch, location } = this.props
dataApi
@@ -102,11 +108,7 @@ class NewNoteModal extends React.Component {
<div styleName='header'>
<div styleName='title'>Make a Note</div>
</div>
<button styleName='closeButton' onClick={(e) => this.handleCloseButtonClick(e)}>
<div styleName='close-mark'>×</div>
<div styleName='close-text'>esc</div>
</button>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<button styleName='control-button'
onClick={(e) => this.handleMarkdownNoteButtonClick(e)}

View File

@@ -9,31 +9,19 @@
font-size 18px
line-height 50px
padding 0 15px
background-color $ui-backgroundColor
border-bottom solid 1px $ui-borderColor
color $ui-text-color
margin-bottom 20px
.closeButton
height 50px
position absolute
background-color transparent
color $ui-inactive-text-color
border none
top 1px
right 10px
text-align center
width top-bar--height
height top-bar--height
.title
font-size 36px
font-weight 600
.control
padding 25px 15px 15px
padding 25px 0px
text-align center
.close-mark
font-size 15px
.control-button
width 220px
width 240px
height 220px
margin 0 15px
border $ui-border
@@ -45,8 +33,8 @@
colorPrimaryButton()
.control-button-icon
font-size 50px
margin-bottom 15px
font-size 48px
margin-bottom 25px
.control-button-label
font-size 18px
@@ -64,8 +52,6 @@ body[data-theme="dark"]
modalDark()
.header
background-color $ui-dark-button--hover-backgroundColor
border-color $ui-dark-borderColor
color $ui-dark-text-color
.control-button

View File

@@ -31,10 +31,15 @@
.group-section-control
flex 1
margin-left 5px
.group-section-control select
outline none
border 1px solid $ui-borderColor
font-size 16px
height 30px
width 250px
margin-bottom 5px
background-color transparent
.group-section-control-input
@@ -77,14 +82,16 @@
margin-right 10px
.group-control-rightButton
float right
position absolute
top 10px
right 20px
colorPrimaryButton()
border none
border-radius 2px
font-size $tab--button-font-size
height 35px
width 100px
margin-right 10px
height 40px
width 120px
padding 0 15px
.group-hint
border $ui-border

View File

@@ -1,6 +1,9 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
const electron = require('electron')
const { shell, remote } = electron
@@ -11,6 +14,7 @@ class InfoTab extends React.Component {
super(props)
this.state = {
config: this.props.config
}
}
@@ -19,6 +23,29 @@ class InfoTab extends React.Component {
e.preventDefault()
}
handleConfigChange (e) {
const newConfig = { amaEnabled: this.refs.amaEnabled.checked }
this.setState({ config: newConfig })
}
handleSaveButtonClick (e) {
let newConfig = {
amaEnabled: this.state.config.amaEnabled
}
if (!newConfig.amaEnabled) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
}
ConfigManager.set(newConfig)
store.dispatch({
type: 'SET_CONFIG',
config: newConfig
})
}
render () {
return (
<div styleName='root'>
@@ -54,7 +81,7 @@ class InfoTab extends React.Component {
<li>
<a href='https://github.com/BoostIO/Boostnote/issues'
onClick={(e) => this.handleLinkClick(e)}
>GitHub Issues</a> : We'd love to hear your feedback 🙌
>GitHub Issues</a> : We&apos;d love to hear your feedback 🙌
</li>
<li>
<a href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
@@ -68,6 +95,18 @@ class InfoTab extends React.Component {
License: GPL v3
</li>
</ul>
<hr />
<div styleName='policy'>Data collection policy</div>
<div>We collect only the number of DAU for Boostnote and **DO NOT collect** any detail information such as your note content.</div>
<div>You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
<div>These data are only used for Boostnote improvements.</div>
<input onChange={(e) => this.handleConfigChange(e)}
checked={this.state.config.amaEnabled}
ref='amaEnabled'
type='checkbox'
/>
Enable to send analytics to our servers<br />
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
</div>
)
}

View File

@@ -41,7 +41,15 @@
a
color #4E8EC6
text-decoration none
.policy
width 100%
font-size 20px
margin-bottom 10px
.policy-submit
margin-top 10px
body[data-theme="dark"]
.root
color alpha($tab--dark-text-color, 80%)
color alpha($tab--dark-text-color, 80%)

View File

@@ -4,9 +4,10 @@ top-bar--height = 50px
.root
modal()
max-width 800px
min-height 500px
height 80%
max-width 100vw
min-height 100vh
height 100vh
width 100vw
overflow hidden
position relative
@@ -20,38 +21,27 @@ top-bar--height = 50px
font-size 18px
line-height top-bar--height
.top-bar-close
position absolute
background-color transparent
color $ui-inactive-text-color
border none
top 0
right 0
text-align center
width top-bar--height
height top-bar--height
.nav
absolute top left right
top top-bar--height
left 0
width 140px
margin-left 30px
margin-left 10px
margin-top 20px
background-color $ui-backgroundColor
.nav-button
font-size 14px
text-align left
width 100px
margin 4px 0
padding 5px 0
width 120px
margin 5px 0
padding 7px 0
padding-left 10px
border none
border-radius 2px
background-color transparent
color $ui-text-color
font-size 14px
font-size 16px
.nav-button--active
@extend .nav-button
@@ -95,4 +85,4 @@ body[data-theme="dark"]
color white
background-color $dark-primary-button-background--active
&:hover
color white
color white

View File

@@ -298,8 +298,8 @@ class StorageItem extends React.Component {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Unlink Storage',
detail: 'This work just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
buttons: ['Confirm', 'Cancel']
detail: 'Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.',
buttons: ['Unlink', 'Cancel']
})
if (index === 0) {

View File

@@ -5,7 +5,7 @@ import dataApi from 'browser/main/lib/dataApi'
import StorageItem from './StorageItem'
const electron = require('electron')
const remote = electron.remote
const { shell, remote } = electron
function browseFolder () {
let dialog = remote.dialog
@@ -50,6 +50,11 @@ class StoragesTab extends React.Component {
})
}
handleLinkClick (e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
renderList () {
let { data, boundingBox } = this.props
@@ -161,7 +166,10 @@ class StoragesTab extends React.Component {
<option value='FILESYSTEM'>File System</option>
</select>
<div styleName='addStorage-body-section-type-description'>
3rd party cloud integration(such as Google Drive and Dropbox) will be available soon.
3rd party cloud integration:
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing'
onClick={(e) => this.handleLinkClick(e)}
>Cloud-Syncing</a>
</div>
</div>
</div>

View File

@@ -10,8 +10,8 @@ $tab--button-font-size = 14px
$tab--dark-text-color = #E5E5E5
.header
font-size 24px
margin-bottom 30px
font-size 36px
margin-bottom 60px
body[data-theme="dark"]
.header

View File

@@ -90,9 +90,8 @@ class UiTab extends React.Component {
<div styleName='group'>
<div styleName='group-header'>UI</div>
<div styleName='group-header2'>Theme</div>
<div styleName='group-section'>
Color Theme
<div styleName='group-section-control'>
<select value={config.ui.theme}
onChange={(e) => this.handleUIChange(e)}
@@ -192,7 +191,7 @@ class UiTab extends React.Component {
<div styleName='group-section'>
<div styleName='group-section-label'>
Switching Preview
Switch to Preview
</div>
<div styleName='group-section-control'>
<select value={config.editor.switchPreview}
@@ -200,7 +199,7 @@ class UiTab extends React.Component {
onChange={(e) => this.handleUIChange(e)}
>
<option value='BLUR'>When Editor Blurred</option>
<option value='RIGHTCLICK'>When Right Clicking</option>
<option value='RIGHTCLICK'>On Right Click</option>
</select>
</div>
</div>
@@ -216,8 +215,9 @@ class UiTab extends React.Component {
>
<option value='sublime'>default</option>
<option value='vim'>vim</option>
<option value='emacs'>emacs</option>
</select>
<span styleName='note-for-keymap'>Please reload boostnote after you change the keymap</span>
<span styleName='note-for-keymap'>Please restart boostnote after you change the keymap</span>
</div>
</div>
@@ -270,7 +270,7 @@ class UiTab extends React.Component {
ref='previewLineNumber'
type='checkbox'
/>&nbsp;
Code block line numbering
Show line numbers for preview code blocks
</label>
</div>

View File

@@ -5,6 +5,7 @@ import HotkeyTab from './HotkeyTab'
import UiTab from './UiTab'
import InfoTab from './InfoTab'
import StoragesTab from './StoragesTab'
import ModalEscButton from 'browser/components/ModalEscButton'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferencesModal.styl'
@@ -43,7 +44,12 @@ class Preferences extends React.Component {
switch (this.state.currentTab) {
case 'INFO':
return <InfoTab />
return (
<InfoTab
dispatch={dispatch}
config={config}
/>
)
case 'HOTKEY':
return (
<HotkeyTab
@@ -115,12 +121,9 @@ class Preferences extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='top-bar'>
<p>Your menu for Boostnote</p>
<p>Your preferences for Boostnote</p>
</div>
<button styleName='top-bar-close' onClick={(e) => this.handleEscButtonClick(e)}>
<div styleName='top-bar-close-mark'>×</div>
<div styleName='top-bar-close-text'>esc</div>
</button>
<ModalEscButton handleEscButtonClick={(e) => this.handleEscButtonClick(e)} />
<div styleName='nav'>
{navButtons}
</div>

View File

@@ -3,6 +3,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './RenameFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import ModalEscButton from 'browser/components/ModalEscButton'
class RenameFolderModal extends React.Component {
constructor (props) {
@@ -72,9 +73,7 @@ class RenameFolderModal extends React.Component {
<div styleName='header'>
<div styleName='title'>Rename Folder</div>
</div>
<button styleName='closeButton'
onClick={(e) => this.handleCloseButtonClick(e)}
>Close</button>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<input styleName='control-input'

Some files were not shown because too many files have changed in this diff Show More