1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 02:06:29 +00:00

Compare commits

...

311 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
118 changed files with 2440 additions and 1561 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"
}
}

View File

@@ -1,26 +1,6 @@
language: node_js
# To fix the npm version in 4.x
# refs: https://github.com/travis-ci/travis-ci/issues/4653#issuecomment-194051953
before_install: 'if [[ `npm -v` != 4* ]]; then npm i -g npm@4; fi'
matrix:
include:
- os: osx
install:
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then yarn; fi'
node_js:
- 'stable'
- 'lts/*'
script:
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" && ${TRAVIS_NODE_VERSION} = "stable" ]]; then
npm run lint;
npm run test;
./script/e2e-runner.sh;
fi'
- os: linux
node_js:
- 'stable'
- 'lts/*'
script:
- 'npm run lint'
- 'npm run test'
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" && ${TRAVIS_NODE_VERSION} = "stable" ]]; then ./script/e2e-runner.sh; fi'
node_js:
- 'stable'
- 'lts/*'
script: npm run lint && npm run test

View File

@@ -3,3 +3,7 @@ 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

@@ -3,6 +3,7 @@ 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'
@@ -39,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()
}
@@ -98,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)
}
@@ -192,15 +205,39 @@ export default class CodeEditor extends React.Component {
const imagePath = e.dataTransfer.files[0].path
const filename = path.basename(imagePath)
copyImage(imagePath, this.props.storageKey).then((imagePathInTheStorage) => {
const imageMd = `![${encodeURI(filename)}](${imagePathInTheStorage})`
copyImage(imagePath, this.props.storageKey).then((imagePath) => {
const imageMd = `![${filename}](${path.join('/:storage', imagePath)})`
this.insertImageMd(imageMd)
})
}
insertImageMd (imageMd) {
const textarea = this.editor.getInputField()
const cm = this.editor
cm.setValue(cm.getValue() + imageMd)
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,6 +4,8 @@ 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) {
@@ -79,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)
@@ -162,15 +163,18 @@ class MarkdownEditor extends React.Component {
}
handleKeyDown (e) {
let { config } = this.props
if (this.state.status !== 'CODE') return false
const keyPressed = this.state.keyPressed
keyPressed.add(e.keyCode)
this.setState({ keyPressed })
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)) {
document.activeElement.blur()
this.handleContextMenu()
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
}
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
this.addMdAroundWord('**')
@@ -213,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'
@@ -260,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

@@ -51,12 +51,12 @@ code {
color: rgba(147,147,149,0.8);;
fill: rgba(147,147,149,1);;
border-radius: 50%;
margin: 7px;
margin: 0px 10px;
border: none;
background-color: transparent;
outline: none;
height: 32px;
width: 32px;
height: 15px;
width: 15px;
cursor: pointer;
}
@@ -108,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) {
@@ -160,6 +163,10 @@ export default class MarkdownPreview extends React.Component {
this.exportAsDocument('md')
}
handlePrint () {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType) {
const options = {
filters: [
@@ -196,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 () {
@@ -206,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) {
@@ -249,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)
@@ -273,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'
@@ -373,6 +396,14 @@ export default class MarkdownPreview extends React.Component {
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 (
@@ -394,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

@@ -6,7 +6,7 @@ const ModalEscButton = ({
handleEscButtonClick
}) => (
<button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>x</div>
<div styleName='esc-mark'>×</div>
<div styleName='esc-text'>esc</div>
</button>
)

View File

@@ -11,4 +11,6 @@
height top-bar-height
.esc-mark
font-size 15px
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,18 +41,16 @@ 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, handleDragStart }) => (
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'
>
@@ -70,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
@@ -91,10 +95,10 @@ 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
}

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
@@ -92,11 +93,12 @@ $control-height = 30px
flex 1
overflow ellipsis
line-height 20px
padding-top 2px
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,17 +10,15 @@ 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, handleNoteContextMenum, handleDragStart }) => (
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'
>
@@ -46,7 +44,6 @@ NoteItemSimple.propTypes = {
title: PropTypes.string.isrequired
}),
handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired,
handleDragStart: PropTypes.func.isRequired
}

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

@@ -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

@@ -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

@@ -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

@@ -95,7 +95,7 @@ class FinderMain extends React.Component {
if (e.keyCode === 13) {
this.refs.detail.saveToClipboard()
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('COPY_FINDER')
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,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

@@ -59,6 +59,15 @@ md.use(math, {
})
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
@@ -156,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

View File

@@ -1,7 +1,6 @@
import _ from 'lodash'
export default function searchFromNotes (data, search) {
let notes = data.noteMap.map((note) => note)
export default function searchFromNotes (notes, search) {
if (search.trim().length === 0) return []
let searchBlocks = search.split(' ')
searchBlocks.forEach((block) => {

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

@@ -14,6 +14,13 @@ 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
@@ -52,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()
})
}
}
@@ -65,30 +72,12 @@ class MarkdownNoteDetail extends React.Component {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
}
getPercentageOfCompleteTodo (noteContent) {
let splitted = noteContent.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 Math.floor(numberOfCompletedTodo / numberOfTodo * 100)
}
handleChange (e) {
let { note } = this.state
note.content = this.refs.content.value
note.tags = this.refs.tags.value
note.title = markdown.strip(findNoteTitle(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({
@@ -117,7 +106,7 @@ class MarkdownNoteDetail extends React.Component {
type: 'UPDATE_NOTE',
note: note
})
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('EDIT_NOTE')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
})
}
@@ -156,7 +145,7 @@ class MarkdownNoteDetail extends React.Component {
handleStarButtonClick (e) {
let { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_STAR')
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred
@@ -171,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)
@@ -191,9 +192,31 @@ 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) {
@@ -208,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) {
@@ -228,76 +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)}
/>
<TodoListPercentage
percentageOfTodo={this.getPercentageOfCompleteTodo(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 : ''
)
})()}
<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>
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-arrows-alt' styleName='fullScreen-button' />
</button>
</div>
</div>
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>
<MarkdownEditor

View File

@@ -27,13 +27,9 @@
opacity 0
transition 0.1s
.control-trashButton
float right
topBarButtonLight()
.control-fullScreenButton
float right
padding 7px
padding 0 0 2px 0
topBarButtonLight()
.body
@@ -58,8 +54,5 @@ body[data-theme="dark"]
.control-lockButton-tooltip
darkTooltip()
.control-trashButton
topBarButtonDark()
.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

@@ -17,6 +17,11 @@ 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) {
@@ -53,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: ''
@@ -68,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()
})
}
}
@@ -80,7 +85,7 @@ class SnippetNoteDetail extends React.Component {
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 = findNoteTitle(note.description)
@@ -111,7 +116,7 @@ class SnippetNoteDetail extends React.Component {
type: 'UPDATE_NOTE',
note: note
})
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('EDIT_NOTE')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
})
}
@@ -150,7 +155,7 @@ class SnippetNoteDetail extends React.Component {
handleStarButtonClick (e) {
let { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_STAR')
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred
@@ -165,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)
@@ -185,9 +194,30 @@ 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) {
@@ -241,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
@@ -254,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
@@ -268,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
}, () => {
@@ -432,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)
@@ -473,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}
@@ -490,54 +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>
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-arrows-alt' styleName='fullScreen-button' />
</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,29 +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
float right
topBarButtonLight()
.control-fullScreenButton
float right
padding 7px
padding 0 0 2px 0
topBarButtonLight()
body[data-theme="dark"]
@@ -87,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
@@ -105,8 +103,5 @@ body[data-theme="dark"]
transition 0.15s
color $ui-dark-text-color
.control-trashButton
topBarButtonDark()
.control-fullScreenButton
topBarButtonDark()

View File

@@ -57,7 +57,7 @@ class TagSelect extends React.Component {
}
submitTag () {
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_TAG')
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

@@ -14,6 +14,7 @@ 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')
@@ -172,8 +173,8 @@ class Main extends React.Component {
}
hideLeftLists (noteDetail, noteList, mainBody) {
this.state.noteDetailWidth = noteDetail.style.left
this.state.mainBodyWidth = mainBody.style.left
this.setState({noteDetailWidth: noteDetail.style.left})
this.setState({mainBodyWidth: mainBody.style.left})
noteDetail.style.left = '0px'
mainBody.style.left = '0px'
noteList.style.display = 'none'
@@ -255,6 +256,7 @@ class Main extends React.Component {
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
/>
</div>
<RealtimeNotification />
</div>
)
}

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

@@ -13,6 +13,7 @@ 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
@@ -47,10 +48,7 @@ class NoteList extends React.Component {
this.alertIfSnippet()
}
this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpToTopHandler = () => {
this.jumpToTop()
}
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.state = {
}
@@ -62,9 +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) {
@@ -84,9 +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) {
@@ -179,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
@@ -226,7 +247,12 @@ class NoteList extends React.Component {
if (searchInputText === '') {
router.push('/home')
}
return searchFromNotes(this.props.data, searchInputText)
return searchFromNotes(this.notes, searchInputText)
}
if (location.pathname.match(/\/trashed/)) {
return data.trashedSet.toJS()
.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
let storageKey = params.storageKey
@@ -264,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
@@ -350,23 +333,6 @@ class NoteList extends React.Component {
}
}
jumpToTop () {
if (this.notes === null || this.notes.length === 0) {
return
}
let { router } = this.context
let { location } = this.props
const targetIndex = 0
router.push({
pathname: location.pathname,
query: {
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
}
})
}
handleDragStart (e, note) {
const noteData = JSON.stringify(note)
e.dataTransfer.setData('note', noteData)
@@ -382,6 +348,22 @@ class NoteList extends React.Component {
properties: ['openFile', 'multiSelections']
}
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
this.addNotesFromFiles(filepaths)
})
}
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
})
@@ -389,28 +371,26 @@ class NoteList extends React.Component {
const storageKey = this.notes[targetIndex].storage
const folderKey = this.notes[targetIndex].folder
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
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}`}
})
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}`}
})
})
})
@@ -426,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 => {
@@ -449,7 +433,6 @@ class NoteList extends React.Component {
dateDisplay={dateDisplay}
key={key}
handleNoteClick={this.handleNoteClick.bind(this)}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
/>
)
@@ -461,7 +444,6 @@ class NoteList extends React.Component {
note={note}
key={key}
handleNoteClick={this.handleNoteClick.bind(this)}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
/>
)
@@ -471,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'>
@@ -479,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

@@ -114,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']
})
@@ -180,14 +180,20 @@ class StorageItem extends React.Component {
render () {
let { storage, location, isFolded, data, dispatch } = this.props
let { folderNoteMap } = data
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}

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'
@@ -21,71 +21,19 @@ class TopBar extends React.Component {
isSearching: false
}
this.newNoteHandler = () => {
this.handleNewPostButtonClick()
}
this.focusSearchHandler = () => {
this.handleOnSearchFocus()
}
}
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')
@@ -94,22 +42,6 @@ class TopBar extends React.Component {
})
}
handleOptionClick (uniqueKey) {
return (e) => {
this.setState({
isSearching: false
}, () => {
let { location } = this.props
hashHistory.push({
pathname: location.pathname,
query: {
key: uniqueKey
}
})
})
}
}
handleSearchFocus (e) {
this.setState({
isSearching: true
@@ -134,60 +66,6 @@ class TopBar extends React.Component {
}
}
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.isSearching) {
this.refs.search.childNodes[0].blur()
@@ -197,7 +75,7 @@ class TopBar extends React.Component {
}
render () {
let { config, style, data } = this.props
let { config, style, data, location } = this.props
return (
<div className='TopBar'
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
@@ -205,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)}
@@ -230,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)
@@ -51,6 +61,7 @@ ReactDOM.render((
<Route path='home' />
<Route path='starred' />
<Route path='searched' />
<Route path='trashed' />
<Route path='storages'>
<IndexRedirect to='/home' />
<Route path=':storageKey'>

View File

@@ -3,29 +3,34 @@ const AMA = require('aws-sdk-mobile-analytics')
const ConfigManager = require('browser/main/lib/ConfigManager')
AWS.config.region = 'us-east-1'
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
})
const mobileAnalyticsClient = new AMA.Manager({
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
appTitle: 'xxxxxxxxxx'
})
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()
}
})
recordStaticCustomEvent()
}
function recordDynamitCustomEvent (type) {
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
})
@@ -33,5 +38,5 @@ function recordStaticCustomEvent () {
module.exports = {
initAwsMobileAnalytics,
recordDynamitCustomEvent
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

@@ -2,6 +2,7 @@ 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.
@@ -12,17 +13,17 @@ const sander = require('sander')
function copyImage (filePath, storageKey) {
return new Promise((resolve, reject) => {
try {
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.')
const targetStorage = storage
const targetStorage = findStorage(storageKey)
const inputImage = fs.createReadStream(filePath)
const imageName = path.basename(filePath)
const outputImage = fs.createWriteStream(path.join(targetStorage.path, 'images', imageName))
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(`${encodeURI(targetStorage.path)}/images/${encodeURI(imageName)}`)
resolve(basename)
} catch (e) {
return reject(e)
}

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

@@ -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

@@ -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

@@ -49,7 +49,7 @@ class CreateFolderModal extends React.Component {
}
confirm () {
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_FOLDER')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_FOLDER')
if (this.state.name.trim().length > 0) {
let { storage } = this.props
let input = {

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,23 +14,27 @@
background-color $ui-backgroundColor
color $ui-text-color
.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
@@ -39,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"]
@@ -56,7 +58,6 @@ body[data-theme="dark"]
height 270px
overflow hidden
position relative
padding 0 40px
.header
background-color transparent

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

@@ -103,7 +103,7 @@ class InitModal extends React.Component {
} else {
return dataApi
.createFolder(data.storage.key, {
color: '#6AA5E9',
color: '#1278BD',
name: 'Default'
})
.then((_data) => {
@@ -122,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',
@@ -145,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)'
}
]
})
@@ -160,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)

View File

@@ -24,8 +24,8 @@ class NewNoteModal extends React.Component {
}
handleMarkdownNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_ALLNOTE')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let { storage, folder, dispatch, location } = this.props
dataApi
.createNote(storage, {
@@ -56,8 +56,8 @@ class NewNoteModal extends React.Component {
}
handleSnippetNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamitCustomEvent('ADD_ALLNOTE')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let { storage, folder, dispatch, location } = this.props
dataApi

View File

@@ -9,16 +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
.title
font-size 36px
font-weight 600
.control
padding 25px 15px 15px
padding 25px 0px
text-align center
.control-button
width 220px
width 240px
height 220px
margin 0 15px
border $ui-border
@@ -30,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
@@ -49,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
@@ -25,22 +26,22 @@ top-bar--height = 50px
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

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>
@@ -218,7 +217,7 @@ class UiTab extends React.Component {
<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>
@@ -271,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

@@ -44,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
@@ -116,7 +121,7 @@ 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>
<ModalEscButton handleEscButtonClick={(e) => this.handleEscButtonClick(e)} />
<div styleName='nav'>

View File

@@ -11,7 +11,8 @@ function defaultDataMap () {
starredSet: new Set(),
storageNoteMap: new Map(),
folderNoteMap: new Map(),
tagNoteMap: new Map()
tagNoteMap: new Map(),
trashedSet: new Set()
}
}
@@ -34,6 +35,10 @@ function data (state = defaultDataMap(), action) {
state.starredSet.add(uniqueKey)
}
if (note.isTrashed) {
state.trashedSet.add(uniqueKey)
}
let storageNoteList = state.storageNoteMap.get(note.storage)
if (storageNoteList == null) {
storageNoteList = new Set(storageNoteList)
@@ -78,6 +83,15 @@ function data (state = defaultDataMap(), action) {
}
}
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
if (note.isTrashed) {
state.trashedSet.add(uniqueKey)
} else {
state.trashedSet.delete(uniqueKey)
}
}
// Update storageNoteMap if oldNote doesn't exist
if (oldNote == null) {
state.storageNoteMap = new Map(state.storageNoteMap)
@@ -163,6 +177,11 @@ function data (state = defaultDataMap(), action) {
state.starredSet.delete(originKey)
}
if (originNote.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
state.trashedSet.delete(originKey)
}
// From storageNoteMap
state.storageNoteMap = new Map(state.storageNoteMap)
let noteSet = state.storageNoteMap.get(originNote.storage)
@@ -199,6 +218,15 @@ function data (state = defaultDataMap(), action) {
}
}
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
if (note.isTrashed) {
state.trashedSet.add(uniqueKey)
} else {
state.trashedSet.delete(uniqueKey)
}
}
// Update storageNoteMap if oldNote doesn't exist
if (oldNote == null) {
state.storageNoteMap = new Map(state.storageNoteMap)
@@ -283,6 +311,11 @@ function data (state = defaultDataMap(), action) {
state.starredSet.delete(uniqueKey)
}
if (targetNote.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
state.trashedSet.delete(uniqueKey)
}
// From folderNoteMap
let folderKey = targetNote.storage + '-' + targetNote.folder
state.folderNoteMap = new Map(state.folderNoteMap)
@@ -348,6 +381,11 @@ function data (state = defaultDataMap(), action) {
state.starredSet.delete(noteKey)
}
if (note.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
state.trashedSet.delete(noteKey)
}
// Delete key from tag map
state.tagNoteMap = new Map(state.tagNoteMap)
note.tags.forEach((tag) => {

View File

@@ -5,7 +5,7 @@ $danger-color = #c9302c
$danger-lighten-color = lighten(#c9302c, 5%)
// Layouts
$statusBar-height = 24px
$statusBar-height = 36px
$sideNav-width = 200px
$sideNav--folded-width = 44px
$topBar-height = 60px
@@ -150,18 +150,20 @@ modal()
position relative
z-index $modal-z-index
width 100%
margin-left 80px
margin-right 80px
margin-bottom 80px
margin-top 100px
background-color $modal-background
overflow hidden
border-radius $modal-border-radius
box-shadow 0 0 1px rgba(76,86,103,.15), 0 2px 18px rgba(31,37,50,.22)
topBarButtonLight()
width 34px
height 34px
border-radius 17px
font-size 14px
margin 13px 7px
padding-top 7px
margin 15px 7px
border none
color $ui-button-color
fill $ui-button-color
@@ -169,7 +171,7 @@ topBarButtonLight()
&:active
border-color $ui-button--active-backgroundColor
&:hover
background-color $ui-button--hover-backgroundColor
background-color alpha($ui-button--hover-backgroundColor, 60%)
.control-lockButton-tooltip
opacity 1
@@ -182,7 +184,7 @@ $ui-dark-noteDetail-backgroundColor = #2D3033
$ui-dark-tag-backgroundColor = #3A404C
$dark-background-color = lighten($ui-dark-backgroundColor, 10%)
$ui-dark-text-color = #DDDDDD
$ui-dark-button--active-color = white
$ui-dark-button--active-color = #f4f4f4
$ui-dark-button--active-backgroundColor = #3A404C
$ui-dark-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
$ui-dark-button--focus-borderColor = lighten(#369DCD, 25%)
@@ -232,10 +234,12 @@ topBarButtonDark()
color $ui-dark-topbar-button-color
&:hover
background-color $dark-default-button-background--hover
color $ui-dark-tooltip-text-color
&:active
border-color $ui-dark-button--focus-borderColor
&:active:hover
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-tooltip-text-color
&:focus
border-color $ui-button--focus-borderColor
@@ -259,4 +263,3 @@ modalDark()
background-color $ui-dark-backgroundColor
overflow hidden
border-radius $modal-border-radius
box-shadow 2px 2px 10px black

View File

@@ -69,3 +69,21 @@ Pull requestをすることはその変化分のコードの著作権をMaisin&C
これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。
もし、このアプリケーションに料金が発生する時は、Boostnote専用のCloud storageの提供やMobile appとの連動、何か特殊なプレミアム機能の提供など形になります。
現在考えられているのは、GPL v3の場合、他のライセンスとの互換が不可能であるため、もしより自由なLicense(BSD, MIT)に変える時に改めて著作権者としてライセンスし直す選択肢を残すイメージです。
---
# Contributing to Boostnote (Simplified Chinese)
### 当您创建一个issue的时候
我们对您的issue格式没有要求但是我们有一个请求
**如果可能,请在开发者模式打开的情况下,为我们提供屏幕截图**
(您可以通过`Ctrl+Shift+I`打开开发者模式)。
感谢您对我们的支持。
### 关于您提供的Pull Request的著作权版权问题
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给Maisin&Co。
这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益我们会尝试一些其他的方法比如说云存储、绑定手机软件等。
因为GPLv3过于严格不能和其他的一些协议兼容所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议比如说BSD、MIT。

View File

@@ -1,4 +1,5 @@
# Build
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), and [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md).
## Environments
* npm: 4.x
@@ -43,6 +44,8 @@ You can build the program by using `grunt`. However, we don't recommend this bec
So, we've prepared a separate script which just makes an executable file.
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
```
grunt pre-build
```

View File

@@ -1,4 +1,6 @@
# How to debug Boostnote (Electron app)
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), and [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md)
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.
You can toggle the `Developer Tools` like this:

View File

@@ -37,6 +37,8 @@ Gruntを使います。
それで、実行ファイルを作るスクリプトを用意しておきました。
このビルドはnpm v5.3.0では動かないのでv5.2.0で動かす必要があります。
```
grunt pre-build
```

View File

@@ -1,15 +0,0 @@
# Testing for Boostnote
## e2eテスト
Boostnoteには[ava](https://github.com/avajs/ava)と[spectron](https://github.com/electron/spectron)で書かれたe2eテストがあります。
### 実行方法
以下のe2eテストのコマンドがあります。
```
$ yarn run test:e2e
```
もう一つのテストコマンドと分けた理由は、travisCIの都合です。
### travisCIでは
travisCIではmasterブランチで飲みe2eテストを実行するようにしています。もし興味がある方は `.travis.yml`をご覧ください。

View File

@@ -37,6 +37,8 @@ yarn run hot
그래서, 실행파일만을 만드는 스크립트를 준비해 뒀습니다.
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
```
grunt pre-build
```

View File

@@ -43,6 +43,8 @@ $ yarn run dev-start
Мы подготовили отдельный скрипт, который просто создает исполняемый файл:
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
```
grunt pre-build
```

View File

@@ -1,20 +0,0 @@
# Тестирование для Boostnote
## Тестирование e2e
Существуют тесты e2e для Boostnote, написанные на [ava](https://github.com/avajs/ava) и [spectron](https://github.com/electron/spectron).
### Как запустить
Для тестирование e2e существует команда:
```
$ yarn run test:e2e
```
Причина, по которой я использую другую команду тестирования - это удобство travisCI.
### TravisCI
Я установил тесты e2e, запущенные на travisCI, только в ветке master. Если вас это интересует, ознакомьтесь с файлом .travis.yml
---
Special thanks:
Translated by @AlexanderBelkevich

View File

@@ -1,15 +0,0 @@
# Testing for Boostnote
## e2e testing
There is e2e tests for Boostnote written in [ava](https://github.com/avajs/ava) and [spectron](https://github.com/electron/spectron).
### How to run
There is a command for e2e testing bellow:
```
$ yarn run test:e2e
```
The reason why I seperate aother test command is because of convenience of travisCI.
### On travisCI
I set e2e tests running on travisCI only master branch. If you're interested in it, please take a look at .travis.yml

56
docs/zh_CN/build.md Normal file
View File

@@ -0,0 +1,56 @@
# 构建Boostnote
## 环境
* npm: 4.x
* node: 7.x
因为`$ grand pre-build`的问题,您只能使用`npm v4.x`而不能使用`npm v5.x`
## 开发
我们使用Webpack HMR来开发Boostnote。
在代码根目录下运行下列指令可以以默认配置运行Boostnote。
### 首先使用yarn安装所需的依赖包。
```
$ yarn
```
### 接着编译并且运行Boostnote。
```
$ yarn run dev-start
```
这个指令相当于在两个终端内同时运行`yarn run webpack``yarn run hot`
如果出现错误`Failed to load resource: net::ERR_CONNECTION_REFUSED`请尝试重新运行Boostnote。
![net::ERR_CONNECTION_REFUSED](https://cloud.githubusercontent.com/assets/11307908/24343004/081e66ae-1279-11e7-8d9e-7f478043d835.png)
### 然后您就可以进行开发了
当您对代码作出更改的时候,`webpack`会自动抓取并应用所有代码更改。
> ### 提示
> 在如下情况中您可能需要重新运行Boostnote才能应用代码更改
> 1. 当您在修改了一个组件的构造函数的时候When editing a constructor method of a component
> 2. 当您新建了一个CSS类的时候其实这和第1项是相同的因为每个CSS类都需在组件的构造函数中被重写
## 部署
我们使用Grunt来自动部署Boostnote。
因为部署需要协同设计(codesign)与验证码(authenticode),所以您可以但我们不建议通过`grunt`来部署。
所以我们准备了一个脚本文件来生成执行文件。
```
grunt pre-build
```
您只能使用`npm v5.2.0`而不能使用`npm v5.3.0`
接下来您就可以在`dist`目录中找到可执行文件。
> ### 提示
> 因为此可执行文件并没有被注册,所以自动更新不可用。
> 如果需要,您也可将协同设计(codesign)与验证码(authenticode)使用于这个可执行文件中。

15
docs/zh_CN/debug.md Normal file
View File

@@ -0,0 +1,15 @@
# 在Boostnote上Debug
Boostnote基于Electron所以Boostnote上的开发者工具和Google Chrome相同。
您可以像这样或者按下快捷键`Ctrl+Shift+I`打开开发者工具:
![how_to_toggle_devTools](https://cloud.githubusercontent.com/assets/11307908/24343585/162187e2-127c-11e7-9c01-23578db03ecf.png)
开发者工具大概形如这样:
![Developer_Tools](https://cloud.githubusercontent.com/assets/11307908/24343545/eff9f3a6-127b-11e7-94cf-cb67bfda634a.png)
您可以在`console`选项卡中找到运行错误,
也可以像这样在`debugger`选项卡中设置断点去分步Debug
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png)
关于具体如何使用开发者工具,详见[Chrome 官档](https://developer.chrome.com/devtools)。如果您在中国大陆您可能需要一个VPN才能正常访问

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