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

Compare commits

..

395 Commits

Author SHA1 Message Date
Kohei TAKATA
a594332ffb Merge pull request #1027 from BoostIO/feature-v0-8-16
v0.8.16
2017-10-28 14:15:41 +09:00
Kohei TAKATA
ceb18ebf1c v0.8.16 2017-10-28 12:28:06 +09:00
Kazu Yokomizo
8910c26ee2 Merge pull request #1016 from PaulRosset/clear-message-HotKeyTab
ClearMessage notification HotKeyTab
2017-10-26 13:53:43 +09:00
Kazu Yokomizo
7549a7bbbe Merge pull request #1014 from asmsuechan/improve-lock-tooltip-text
Change the tooltip text of the lock button for easy to understand what its role
2017-10-26 13:49:57 +09:00
Kazu Yokomizo
17fbe6e232 Merge pull request #1020 from BoostIO/change-init-content
Make <kbd> on keyboard introduction
2017-10-26 10:15:39 +09:00
Kazu Yokomizo
ccdac8f604 Make <kbd> on keyboard introduction 2017-10-26 09:23:12 +09:00
Kazu Yokomizo
88a828c9ef Merge pull request #1019 from filoxo/add-keyboard-markdown
Add keyboard markdown
2017-10-26 08:58:45 +09:00
Carlos Filoteo
ae3291b90e Add keyboard styles 2017-10-25 11:07:03 -06:00
Carlos Filoteo
2c6f0452b8 Require markdown-it-kbd 2017-10-25 10:46:21 -06:00
Carlos Filoteo
4651acd6f4 Install markdown-it-kbd 2017-10-25 10:46:03 -06:00
Kazu Yokomizo
bba7babce3 Merge pull request #1017 from BoostIO/change-crowdfunding
Change crowdfunding Bountysource to OpenCollective.
2017-10-26 00:10:48 +09:00
Kazu Yokomizo
73dc6a4a92 Change crowdfunding Bountysource to OpenCollective. 2017-10-25 23:58:36 +09:00
Paul Rosset
992f5a525a Test build travis with change clearMessage feature 2017-10-25 13:21:30 +01:00
SuenagaRyota
b38d5789f3 Merge pull request #1013 from asmsuechan/change-ISSUETEMPLATE
Change ISSUETEMPLATE to add a sentence dealing with if a user tries to open an issue regarding boostnote mobile
2017-10-25 19:56:25 +09:00
asmsuechan
47b5945e17 Change the tooltip text of the lock button for easy to understand what its role 2017-10-25 19:49:16 +09:00
asmsuechan
73544b0f06 Change ISSUETEMPLATE to add a sentence dealing with if a user tries to open an issue regarding boostnote mobile 2017-10-25 19:45:32 +09:00
Kazu Yokomizo
e7d9311e23 Merge pull request #1010 from PaulRosset/save-notification
Adjust notification HotKeyTab
2017-10-25 10:28:58 +09:00
Paul Rosset
c97c65b707 Delete clearMessage due to problem wih test [WIP] 2017-10-24 23:54:14 +01:00
Paul Rosset
1c02b4e62a Delete setTimeout() due to problem wih test 2017-10-24 23:50:29 +01:00
Paul Rosset
d23156d11a Correction build error 2017-10-24 23:40:15 +01:00
Paul Rosset
bd013adb4d correction eslint syntax 2017-10-24 23:09:21 +01:00
Paul Rosset
c0368ce713 correction 2017-10-24 23:01:16 +01:00
Paul Rosset
80283b5f55 Adjust notification HotKeyTab 2017-10-24 22:27:03 +01:00
Kohei TAKATA
4078645958 Merge pull request #1002 from BoostIO/add-ama-event-for-snippet-lang
Add ama event for snippet lang
2017-10-23 22:33:35 +09:00
Sosuke Suzuki
955ade0b8a add space 2017-10-23 21:12:42 +09:00
Sosuke Suzuki
4b158af9f6 send snippet_lang 2017-10-23 16:35:15 +09:00
Sosuke Suzuki
642fae3ac7 some let => const 2017-10-23 13:51:32 +09:00
SuenagaRyota
d249967aee Merge pull request #1004 from ysshah/patch-1
Fix typo in "Markdown"
2017-10-23 10:47:25 +09:00
Yash Shah
7b6b7f05e0 Fix typo in "Markdown" 2017-10-22 11:10:13 -07:00
Sosuke Suzuki
35b9bf5d34 enable recordDynamicEvent func to passed attribute arg 2017-10-21 18:18:02 +09:00
Sosuke Suzuki
59f0cc4f98 send lang_name of snippet 2017-10-21 16:59:33 +09:00
Kazu Yokomizo
a29ca73fb4 Merge pull request #1000 from BoostIO/fix-sideNav-style
start point of absolute to the right.
2017-10-21 16:18:51 +09:00
Sosuke Suzuki
59b658f059 start point of absolute to the right. 2017-10-21 15:49:47 +09:00
Kazu Yokomizo
3397b3108f Merge pull request #999 from BoostIO/fix-sideNav-style
Fix side nav style
2017-10-21 15:04:26 +09:00
Sosuke Suzuki
cae7baa5e1 change from premitive value to variable 2017-10-21 14:45:26 +09:00
Sosuke Suzuki
4af71fd1dd fix Folded-SideNav size 2017-10-21 14:29:29 +09:00
Sosuke Suzuki
4194b61373 text-align of button-lebels in SideNavFilder are center 2017-10-21 14:11:35 +09:00
Sosuke Suzuki
c91fd6783d fix preference button style 2017-10-21 13:56:21 +09:00
SuenagaRyota
89bbed1dfd Merge pull request #978 from aquibm/master
Fix URIs decoded with mdurl
2017-10-21 13:40:36 +09:00
Sosuke Suzuki
2aeb53920c enable tooltip-label of Preference 2017-10-21 13:37:10 +09:00
Sosuke Suzuki
fe51c232b6 fix typo 2017-10-21 13:27:45 +09:00
Sosuke Suzuki
57b054794c fix position of preference button 2017-10-21 13:25:28 +09:00
SuenagaRyota
8318c56046 Merge pull request #998 from asmsuechan/change-issue-template
Change ISSUE_TEMPLATE because it seems unfriendly
2017-10-21 13:10:54 +09:00
Sosuke Suzuki
0d52417ee7 disable switch-buttons, when NavToggle 2017-10-21 13:06:04 +09:00
asmsuechan
6f3b1b8d6f Change ISSUE_TEMPLATE because it seems unfriendly 2017-10-21 13:05:14 +09:00
Kazu Yokomizo
a460d7722e Merge pull request #996 from BoostIO/update-readme
Update readme
2017-10-21 00:19:00 +09:00
Kazu Yokomizo
d770208d4c Update Backers 2017-10-21 00:14:59 +09:00
Kazu Yokomizo
0434109908 Update Bakcers 2017-10-21 00:11:29 +09:00
Kazu Yokomizo
289d3a4e6b Update Readme 2017-10-21 00:07:39 +09:00
Kazu Yokomizo
ffb9be63c7 Merge pull request #920 from BoostIO/add-ama-options
enable to get appVerion and platformName
2017-10-20 23:05:35 +09:00
Kazu Yokomizo
bf2b53cbce Merge pull request #994 from Visovsiouk/master
Adjust tooltip (#963)
2017-10-19 13:53:40 +09:00
visovsiouk
1d9bf65c31 Adjust tooltip (#963) 2017-10-18 20:29:38 +03:00
Kazu Yokomizo
4744b918d3 Merge pull request #988 from ringohub/fix/982-can_not_scroll_tag_list
Fix #982: Tag list cannot be scroll
2017-10-18 16:35:19 +09:00
Kazu Yokomizo
588b1809a9 Merge pull request #992 from mrseanbaines/sean/fix-grammar
Grammar fix
2017-10-18 16:13:24 +09:00
Sean Baines
dc1c19293d Grammar fix
"Quick search and copy the content of note" to "Quick search and copy the content of a note"
2017-10-17 19:49:51 +01:00
Yoshiki Aoki
1f548959e3 Fix #928: Tag list cannot be scroll 2017-10-17 11:47:06 +09:00
asmsuechan
8cae5670fc Change travis_snapcraft 2017-10-16 15:09:28 +09:00
SuenagaRyota
07c0982d4f Merge pull request #986 from asmsuechan/snapcraft
Snapcraft
2017-10-16 14:07:20 +09:00
SuenagaRyota
2f9e4b3198 Merge pull request #950 from clone1612/catchAnalyticsError
Add try/catch logic to recording of analytics events
2017-10-16 14:06:49 +09:00
asmsuechan
89dba149a3 Change to run build script only master branch 2017-10-16 14:03:19 +09:00
asmsuechan
aa71b4c1b8 Change to snap on travisCI 2017-10-16 13:57:39 +09:00
asmsuechan
43110f8f2a Add boostnote.desktop 2017-10-16 13:57:39 +09:00
asmsuechan
e48540713d Edit snapcraft.yaml to snap Boostnote 2017-10-16 13:57:35 +09:00
asmsuechan
cfd13139e0 snapcraft init 2017-10-16 13:55:32 +09:00
SuenagaRyota
ac5cdf384f Merge pull request #984 from asmsuechan/remove-unused-vars
Remove unused vars
2017-10-15 23:39:50 +09:00
asmsuechan
e9d858d902 Remove unused vars 2017-10-15 23:33:22 +09:00
Kazu Yokomizo
1beae4403a Merge pull request #979 from BoostIO/fix-layout
Fix layout
2017-10-15 17:54:59 +09:00
Kazu Yokomizo
dedf36f704 Position of navToggle button at Sidebar 2017-10-15 17:44:02 +09:00
Kazu Yokomizo
1477de3899 Fix tag layout 2017-10-15 17:40:19 +09:00
Kazu Yokomizo
0d947c7dd8 Fix CI error 2017-10-15 17:25:40 +09:00
Kazu Yokomizo
ebfd8f40e3 Pin to top layout 2017-10-15 17:20:55 +09:00
Kazu Yokomizo
3159cc0ded Add crowdfunding page in Preference 2017-10-15 17:10:58 +09:00
Kazu Yokomizo
10dcbfb891 Fix tags and folder list layout 2017-10-15 16:17:06 +09:00
Kazu Yokomizo
19dc16e14a Change sort icon and string 2017-10-15 15:52:16 +09:00
Kazu Yokomizo
95586b3156 Tag list layout 2017-10-15 15:46:30 +09:00
Kazu Yokomizo
0637daf645 Fix topbar height 2017-10-15 15:31:26 +09:00
Kazu Yokomizo
fdcd62617d Fix position of preference button 2017-10-15 15:30:03 +09:00
Kazu Yokomizo
0f3e5ee4ed Tag button at topbar in sidenav 2017-10-15 15:25:31 +09:00
Kazu Yokomizo
7b171ecc67 Fix layout
- [ ] Pin to top
- [ ] Tag area
- [ ] Preference button
- [ ] Position of New post button
- [ ] Icon of sort button at NoteList and change name of "Updated, Created"
- [ ] Set donation button at side bar in Preference
2017-10-15 11:46:37 +09:00
Sota Sugiura
7a4052ede3 Merge pull request #922 from sosukesuzuki/feature-tag-area
Feature tag area
2017-10-14 16:58:28 +09:00
Aquib Master
3f53a1f629 Fix linting issues 2017-10-14 19:28:11 +13:00
Aquib Master
31daec5fe2 Fix URIs decoded with mdurl
This PR mitigates #918 by retroactively replacing link text decoded by mdurl with the original link.
2017-10-14 19:01:04 +13:00
Sosuke Suzuki
0d7155bda6 implement convertPlatformName function 2017-10-14 12:12:35 +09:00
Sosuke Suzuki
35beec3e39 insert necessary space 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
ff4b96b622 implement tagItem active styleName 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
9b60814292 fix bug : isTrashedActive and isStarredActive are inverted 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
3c4fa83161 modify the arg of arrayOf to the correct 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
e8564f6540 modify propTypes "array" -> "arrayOf" 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
a22e97d4bd add a space after "//" in comment 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
046e6af489 remove extra semicolon and fix indent 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
f805e8a688 cut out NavToggle component 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
2fddc32eb7 cut out StorageList component 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
6018cd5d81 get "data" from this.props 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
3533903be3 duplicate conditions as a variable 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
d867292f66 remove unnecesarry "e" variable 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
7691b662d6 remove unnecesarry "return" 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
86270dd856 add the comment fot complicated condition 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
012e2dde4f remove unnecessary "e" variables 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
ad7a3c49f9 modify to use "const" from "let" 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
e8abd43c8a modify to remove unnecessary "return" 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
3192ce9d39 modify to use PropTypes 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
d09de09fef use color variables 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
4689ddeb98 some rename plural form or easy to understand 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
e300b33a4f fix a mistake in function naming 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
0ca87ea407 fix type description parens () -> {} 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
2886da4f63 give the key props to tagList Item 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
bf9ecb02e5 remove extra parens 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
852617726c complete editting file 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
c2aa35104c remove button width style 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
95e237d4a3 fix style tagList 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
59e5c547e9 fix button style to change color when switch active/inactive 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
06bd2b2b79 add title "tags" 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
faede48217 feature tag search 2017-10-14 11:57:03 +09:00
Sosuke Suzuki
ad0ac19d3d handling tagButton click 2017-10-14 11:49:32 +09:00
Sosuke Suzuki
3154110de1 list tags 2017-10-14 11:49:32 +09:00
Sosuke Suzuki
5248c05e61 switching view and change routing 2017-10-14 11:49:32 +09:00
Sosuke Suzuki
8311030bec add the tag to routing 2017-10-14 11:49:32 +09:00
SuenagaRyota
c429fc6b2c Merge pull request #968 from adrichey/master
Fix: "Uncaught TypeError: Cannot read property 'className' of null"
2017-10-13 23:36:48 +09:00
Alan Richey
590aa9ab17 Fixed the "Uncaught TypeError: Cannot read property 'className' of null" bug that appears when creating folders (coding style adjustments) 2017-10-13 09:30:41 -05:00
SuenagaRyota
f9a7c2d457 Merge pull request #853 from PrimaMateria/feature-rearrange-storage
Feature rearrange storage
2017-10-13 20:16:39 +09:00
Kazu Yokomizo
b4506168fb Merge pull request #972 from asmsuechan/replace-pdf-to-print
Replace pdf to print
2017-10-13 18:11:07 +09:00
SuenagaRyota
f203ab3aaf Merge pull request #973 from asmsuechan/refactor-targetIndex
Add getTargetIndex() and replece them
2017-10-13 17:25:30 +09:00
asmsuechan
c197dd0a4b Add getTargetIndex() and replece them 2017-10-13 17:19:38 +09:00
SuenagaRyota
457e596851 Merge pull request #956 from asmsuechan/pinn-to-top
Pin to top
2017-10-13 17:02:59 +09:00
asmsuechan
d274563b2c Change order for pinned notes on /home, /trashed, /searched or /starred 2017-10-13 16:33:55 +09:00
asmsuechan
2003bea3cf Remove unnecessary lines 2017-10-13 16:24:13 +09:00
asmsuechan
f9b3284852 Fix a condition 2017-10-13 14:22:13 +09:00
asmsuechan
9bca133d88 Remove updateFolder because it's no longer used 2017-10-13 14:18:41 +09:00
asmsuechan
03fc453608 Change method names 2017-10-13 14:18:41 +09:00
asmsuechan
3027cc81b3 Add handleNoteDelete 2017-10-13 14:18:41 +09:00
asmsuechan
2415fbf676 Change to use const instead of let 2017-10-13 14:18:41 +09:00
asmsuechan
725c6a7ba9 Resolve conflicts 2017-10-13 14:18:41 +09:00
asmsuechan
c33da0cf8e Fix some pointed by eslint 2017-10-13 14:18:41 +09:00
asmsuechan
d915d19425 Add comment for NoteItem 2017-10-13 14:18:41 +09:00
asmsuechan
f3370242bf Add conditions to hide pin from /home, /starred or /trash 2017-10-13 14:18:41 +09:00
asmsuechan
0e312ba929 Change name Pinn to Pin 2017-10-13 14:18:41 +09:00
asmsuechan
6440395197 Change to use strict equation 2017-10-13 14:18:41 +09:00
asmsuechan
5433abddaf Remove console.log 2017-10-13 14:18:41 +09:00
asmsuechan
0ccb465288 Remove pinnedNotes from Folder 2017-10-13 14:18:41 +09:00
asmsuechan
8fd4deb3eb Add unpin 2017-10-13 14:18:41 +09:00
asmsuechan
fe8045c51d Add pinn to top 2017-10-13 14:18:41 +09:00
SuenagaRyota
b890c59134 Merge pull request #948 from wrhansen/master
Support for solarized dark and light themes
2017-10-13 13:29:10 +09:00
SuenagaRyota
f39caeb967 Merge pull request #954 from asmsuechan/improve-searching
Improve searching
2017-10-13 12:08:25 +09:00
asmsuechan
7ab482184b Improve tag search by changing the logic 2017-10-13 11:40:10 +09:00
asmsuechan
78b12ae686 Add test 2017-10-13 10:58:32 +09:00
asmsuechan
caa5deac4e Change searching by tag 2017-10-13 10:37:43 +09:00
Kazu Yokomizo
af3083825e Merge pull request #970 from Samatar26/fix-handleCloseButtonClick
Use the correct handler for closing the modal.
2017-10-13 09:35:13 +09:00
asmsuechan
5255708ff2 Replace pdf to print 2017-10-13 09:29:41 +09:00
Samatar26
9331f2034b Use the correct handler for closing the modal.
Relates to #967
2017-10-12 18:01:34 +01:00
Kazu Yokomizo
fc6a5c22bf Merge pull request #969 from Samatar26/fix-button-protrusion
Fixed button protrustion when creating a new folder.
2017-10-13 01:44:17 +09:00
Samatar26
51c397d177 Fixed button protrustion when creating a new folder.
Relates to #966
2017-10-12 17:33:18 +01:00
Alan Richey
7c9596308e Fixed the "Uncaught TypeError: Cannot read property 'className' of null" bug that appears when creating folders 2017-10-12 10:56:07 -05:00
asmsuechan
15dc424ade Change keycode handling scope due to global keyboard support 2017-10-12 23:27:48 +09:00
asmsuechan
49243a8010 Add to handle space key on search 2017-10-12 23:27:45 +09:00
asmsuechan
93e188d118 Change to be able to search on starred 2017-10-12 20:28:23 +09:00
asmsuechan
df3195fc1e Refactor getContextNotes() 2017-10-12 18:42:34 +09:00
asmsuechan
da6b8c30a0 Improve some 2017-10-12 18:42:34 +09:00
asmsuechan
70468b6b7d Change contextNotes a state to a variable because of lifesycle loop 2017-10-12 18:42:34 +09:00
asmsuechan
2511512d94 Improve context search 2017-10-12 18:42:34 +09:00
asmsuechan
4418617d3b Delete a unnecessary break 2017-10-12 18:42:34 +09:00
asmsuechan
6e480ba146 Add comments 2017-10-12 18:42:34 +09:00
asmsuechan
b4f5913a80 Fix to work inputting by IME on search 2017-10-12 18:42:34 +09:00
Kazu Yokomizo
6a3062709c Merge pull request #951 from mrseanbaines/sean/grammar-fix
Grammar and capitalization fix
2017-10-12 14:37:34 +09:00
Kazu Yokomizo
d66bc1faef Merge pull request #943 from voidsatisfaction/add_context_menu_delete_note
Add notelist right click(context menu) and delete note
2017-10-12 14:34:51 +09:00
Kazu Yokomizo
bef7d45c3e Merge pull request #961 from asmsuechan/fix-pasting-image
Fix to create imageDir if it does not exist
2017-10-12 11:48:20 +09:00
SuenagaRyota
bb9489a8d3 Merge pull request #955 from asmsuechan/iss-919
Change to apply font with single quotations
2017-10-12 11:34:42 +09:00
asmsuechan
700eeb8f5a Fix to create imageDir if it does not exist 2017-10-12 10:28:01 +09:00
asmsuechan
7e2f0049b6 Change to apply font with single quotations 2017-10-11 12:17:49 +09:00
Sean Baines
b2388544d8 Merge branch 'master' into sean/grammar-fix 2017-10-10 19:22:02 +01:00
Sosuke Suzuki
d772551c60 convert platformName from os.platfoem() form to AMA form 2017-10-10 14:58:52 +09:00
Wesley Hansen
31dca6f06b Fix failed build lint error 2017-10-09 20:55:04 -04:00
Sean Baines
83f68fe153 Grammar and capitalization fix 2017-10-09 19:15:52 +01:00
Kazu Yokomizo
08a2ae0fd3 Merge pull request #941 from voidsatisfaction/korean_trans_dev
Add recent translated korean developement & debug docs
2017-10-10 01:21:56 +09:00
Jannick Hemelhof
53d3f51c74 Cleaner catch 2017-10-09 14:04:02 +02:00
Jannick Hemelhof
f7cdafb087 Add try/catch logic to recording of analytics events 2017-10-09 10:45:09 +02:00
Wesley Hansen
5b17808569 Support for solarized dark and light themes
CodeMirror seems to have the two solarized themes within the
`node_modules/coemirror/theme/solarized.css`. So passing the string
"solarized light" and "solarized dark" to the editor theme appropriately
sets these themes.

The Boostnote app appears to be dynamically determining themes based
on the css files found within `node_modules/codemirror/theme/`.
So that's why there was just a single option of 'solarized' before.

The light and dark 'solarized' themes appear to be the *only* themes
that share a css style, whereas all other light and dark variarnt
themes get their own css file (ex: base16-dark.css and base16-light.css).

Weird!
2017-10-08 23:56:15 -04:00
voidSatisfaction
a7328e21f1 feat: add right click notelist delete 2017-10-08 18:20:31 +09:00
voidSatisfaction
e64370e9a2 fix: no need enter 2017-10-08 15:16:26 +09:00
voidSatisfaction
d5b37b2418 feat: add debug docs for korean and link on debug.md 2017-10-08 13:58:15 +09:00
voidSatisfaction
4da08d93fd feat: add translated korean developement docs 2017-10-08 13:39:32 +09:00
Kazu Yokomizo
c39e5c67f5 Merge pull request #937 from mrseanbaines/sean/improve-english
Sean/improve english
2017-10-06 13:21:35 +09:00
Sean Baines
00d5cf13c9 Merge branch 'master' into sean/improve-english 2017-10-05 07:46:02 +01:00
Kazu Yokomizo
1120bcfc0c Merge pull request #934 from mrseanbaines/sean/tag-select-border-box
Fix "borde-box" typo
2017-10-05 10:49:39 +09:00
Sean Baines
8e506cb7c2 Improve English
This would make more sense like this.
2017-10-04 21:50:15 +01:00
Sean Baines
145b66d375 Fix English mistake 2017-10-04 21:46:55 +01:00
Sean Baines
82e4a8bbc3 Fix borde-box typo
Mistake in code. Changed from `borde-box` to `border-box`
2017-10-04 21:33:49 +01:00
Kazu Yokomizo
bca9bfb960 Merge pull request #931 from BoostIO/fix-font-color-in-code
Fix font color in code block
2017-10-04 13:11:55 +09:00
Kazu Yokomizo
d8e19d9c17 Fix font color in code block 2017-10-04 11:39:27 +09:00
SuenagaRyota
95d74d1ca2 Merge pull request #897 from urda/urda/feature/copy-notify-toggle
Allow users to disable "Saved to clipboard" notification.
2017-10-01 19:33:17 +09:00
Peter Urda
ebdd6d77f7 Allow users to disable "Saved to clipboard" notification. 2017-10-01 02:48:54 -07:00
Sosuke Suzuki
d56bcc4fdf enable to get appVerion and platformName 2017-09-30 12:19:51 +09:00
Kazu Yokomizo
4bb18cfc9a Merge pull request #915 from cofcool/fix_doc
change grand to grunt
2017-09-27 13:01:45 +09:00
CofCool
6f30692534 grand -> grunt 2017-09-27 11:49:05 +08:00
Kazu Yokomizo
e249c1ec65 Merge pull request #911 from sferra/place-notification-to-the-right
Place notifications on the right side of the notification bar
2017-09-26 18:37:49 +09:00
Cristian Beskid
c2b4c77003 Place notifications on the right side 2017-09-26 10:35:58 +02:00
Kazu Yokomizo
e64733827a Merge pull request #906 from sferra/fix-collapse-sidebar-button-not-visible
Align notification bar with the navigation bar
2017-09-26 13:02:24 +09:00
Cristian Beskid
ea81b0d414 Fix notification bar offset in full screen mode 2017-09-25 23:08:12 +02:00
Cristian Beskid
000cf2a864 Center notification in the notification bar 2017-09-25 18:44:22 +02:00
Cristian Beskid
dc13b919b3 Fix incorrect notification bar width 2017-09-25 17:34:40 +02:00
Cristian Beskid
a0c8ec3171 Remove redundant semicolon 2017-09-25 17:26:08 +02:00
Cristian Beskid
80c13f7c4f Align notification bar with the navigation bar 2017-09-25 17:17:34 +02:00
Kazu Yokomizo
1a6f3d808b Merge pull request #899 from BoostIO/change-font-color-at-code-in-default
Change font color in code at default theme
2017-09-24 13:35:21 +09:00
Kazu Yokomizo
ec8fac1199 Change font color in code at default theme 2017-09-24 13:24:14 +09:00
Kazu Yokomizo
98c93d3248 Merge pull request #895 from Overload119/fix_grammar
Fix grammer in InfoTab
2017-09-24 12:00:18 +09:00
Amir Sharif
a053706c24 Fix grammer in InfoTab 2017-09-23 09:10:33 -07:00
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
Matus Benko
43c49f54d2 used findStorage function in the updateFolder 2017-09-11 21:10:44 +02:00
Matus Benko
a15dfffa44 used findStorage function in the reorderFolder 2017-09-11 21:09:51 +02:00
Matus Benko
59985dee72 fixed test title 2017-09-09 21:53:10 +02:00
Matus Benko
6b7132f134 fixed style of dragged folder item for dark theme 2017-09-09 21:50:06 +02: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
Matus Benko
5c2c99282d added test for reorderFolder 2017-09-08 18:58:35 +02:00
Matus Benko
94e6f89d07 fixed errors from lint 2017-09-08 18:46:17 +02:00
Matus Benko
3804a746df connected sortable folder list to data api 2017-09-08 12:07:59 +02:00
Matus Benko
5c2d7e2d2a fixed typo in the useDragHandle attribute for the folder list 2017-09-08 00:59:38 +02:00
Matus Benko
c34dd462b6 made sortable item helper visible by setting proper zindex 2017-09-08 00:41:20 +02:00
Matus Benko
9141b1a641 saving new order in the storage only for now 2017-09-08 00:09:13 +02:00
Matus Benko
0fea85e2f2 added drag handle to the folder item 2017-09-07 23:52:27 +02:00
Kazu Yokomizo
0ca41fbdb4 Merge pull request #844 from BoostIO/fix-layout
Fix layout
2017-09-08 01:39:02 +09:00
Matus Benko
a58c191ded used react-sortable-hoc on folderList and folderItem 2017-09-06 22:56:32 +02:00
Matus Benko
77089a1178 extracted folder list and item to separate components 2017-09-06 20:02:47 +02: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
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
120 changed files with 2901 additions and 1017 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"
}
}

Binary file not shown.

View File

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

View File

@@ -1,5 +1,39 @@
Become a [backer](https://salt.bountysource.com/teams/boostnote) and support Boostnote!
You can support Boostnote from $ 5 a month!
Dear all,
# Backers
[Kazu Yokomizo](https://twitter.com/kazup_bot)
Thanks for your using!
Boostnote is used in about 200 countries and regions, it is a awesome developer community.
To continue supporting this growth, and to satisfy community expectations,
we would like to invest more time in this project.
If you like this project and see its potential, you can help!
Thanks,
Boostnote maintainers.
### >> [Support via OpenCollective](https://opencollective.com/boostnoteio)
---
## Backers
[Kazz](https://twitter.com/kazup_bot) - $65
Intense Raiden - $45
ravy22 - $25
trentpolack - $20
hikariru - $10
kolchan11 - $10
RonWalker22 - $10
hocchuc - $5
Adam - $5
Steve - $5
evmin - $5

View File

@@ -1 +1,3 @@
Please paste some **screenshots** with opening the developer tool if you report a bug.
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.

View File

@@ -3,6 +3,8 @@ 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'
import fs from 'fs'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -39,6 +41,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 +101,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)
}
@@ -201,7 +215,31 @@ export default class CodeEditor extends React.Component {
insertImageMd (imageMd) {
const textarea = this.editor.getInputField()
const cm = this.editor
textarea.value = `${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`
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 imageDir = path.join(storagePath, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
const imagePath = path.join(imageDir, `${imageName}.png`)
fs.writeFile(imagePath, binaryData, 'binary')
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
}
render () {

View File

@@ -4,6 +4,7 @@ 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 {
@@ -80,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)
@@ -163,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('**')
@@ -214,10 +217,7 @@ class MarkdownEditor extends React.Component {
let previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
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 storage = findStorage(storageKey)
return (
<div className={className == null
@@ -266,6 +266,7 @@ class MarkdownEditor extends React.Component {
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>
</div>

View File

@@ -10,6 +10,7 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
const { remote } = require('electron')
const { app } = remote
@@ -34,13 +35,12 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
}
${markdownStyle}
body {
font-family: ${fontFamily.join(', ')};
font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px;
}
code {
font-family: ${codeBlockFontFamily.join(', ')};
background-color: rgba(0,0,0,0.04);
color: #CC305F;
}
.lineNumber {
${lineNumber && 'display: block !important;'}
@@ -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,7 @@ 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)
}
@@ -162,6 +163,10 @@ export default class MarkdownPreview extends React.Component {
this.exportAsDocument('md')
}
handlePrint () {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType) {
const options = {
filters: [
@@ -179,6 +184,14 @@ export default class MarkdownPreview extends React.Component {
})
}
fixDecodedURI (node) {
const { innerText, href } = node
node.innerText = mdurl.decode(href) === innerText
? href
: innerText
}
componentDidMount () {
this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
@@ -198,6 +211,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 () {
@@ -208,6 +222,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) {
@@ -217,6 +232,7 @@ export default class MarkdownPreview extends React.Component {
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
prevProps.lineNumber !== this.props.lineNumber ||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
prevProps.theme !== this.props.theme) {
this.applyStyle()
this.rewriteIframe()
@@ -255,7 +271,7 @@ export default class MarkdownPreview extends React.Component {
el.removeEventListener('click', this.linkClickHandler)
})
let { value, theme, indentSize, codeBlockTheme, storagePath } = this.props
let { value, theme, indentSize, codeBlockTheme, showCopyNotification, storagePath } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
@@ -272,6 +288,7 @@ export default class MarkdownPreview extends React.Component {
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
el.addEventListener('click', this.anchorClickHandler)
})
@@ -284,8 +301,9 @@ export default class MarkdownPreview extends React.Component {
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
el.src = markdown.normalizeLinkText(el.src)
if (!/\/:storage/.test(el.src)) return
el.src = `file:///${path.join(storagePath, 'images', path.basename(el.src))}`
el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
})
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
@@ -301,10 +319,12 @@ export default class MarkdownPreview extends React.Component {
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
copyIcon.onclick = (e) => {
copy(content)
this.notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!',
silent: true
})
if (showCopyNotification) {
this.notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!',
silent: true
})
}
}
el.parentNode.appendChild(copyIcon)
el.innerHTML = ''
@@ -418,5 +438,6 @@ MarkdownPreview.propTypes = {
onMouseDown: PropTypes.func,
className: PropTypes.string,
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
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

@@ -0,0 +1,29 @@
/**
* @fileoverview Micro component for toggle SideNav
*/
import React, { PropTypes } from 'react'
import styles from './NavToggleButton.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {boolean} isFolded
* @param {Function} handleToggleButtonClick
*/
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
<button styleName='navToggle'
onClick={(e) => handleToggleButtonClick(e)}
>
{isFolded
? <i className='fa fa-angle-double-right' />
: <i className='fa fa-angle-double-left' />
}
</button>
)
NavToggleButton.propTypes = {
isFolded: PropTypes.bool.isRequired,
handleToggleButtonClick: PropTypes.func.isRequired
}
export default CSSModules(NavToggleButton, styles)

View File

@@ -0,0 +1,19 @@
.navToggle
navButtonColor()
display block
position absolute
left 5px
bottom 5px
border-radius 16.5px
height 34px
width 34px
line-height 32px
padding 0
body[data-theme="dark"]
.navToggle
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
transition 0.15s
color $ui-dark-text-color

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,16 +41,18 @@ const TagElementList = (tags) => {
* @param {boolean} isActive
* @param {Object} note
* @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart
* @param {string} dateDisplay
*/
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStart }) => (
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
<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'
>
@@ -68,6 +72,13 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStar
{note.isStarred
? <i styleName='item-star' className='fa fa-star' /> : ''
}
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
}
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
@@ -93,6 +104,7 @@ NoteItem.propTypes = {
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
@@ -116,8 +117,8 @@ $control-height = 30px
.item-star
position absolute
right 5px
bottom 0px
right -20px
bottom 2px
width 34px
height 34px
color alpha($ui-favorite-star-button-color, 60%)
@@ -125,6 +126,17 @@ $control-height = 30px
padding 0
border-radius 17px
.item-pin
position absolute
right -21px
bottom 28px
width 34px
height 34px
color #E54D42
font-size 14px
padding 0
border-radius 17px
body[data-theme="dark"]
.root
border-color $ui-dark-borderColor

View File

@@ -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' style={this.props.style}>{link}</div>
)
}
}
RealtimeNotification.propTypes = {}
export default CSSModules(RealtimeNotification, styles)

View File

@@ -0,0 +1,34 @@
.notification-area
z-index 1000
font-size 12px
position absolute
bottom 0px
right 0px
background-color #EBEBEB
height 30px
.notification-link
position absolute
right 5px
top 5px
text-decoration none
color #282A36
border 1px solid #6FA8E6
background-color alpha(#6FA8E6, 0.2)
padding 3px 9px
border-radius 2px
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

@@ -17,12 +17,12 @@
.menu-button-label
color $ui-text-color
&:hover
background-color $ui-button--active-backgroundColor
background-color alpha($ui-button--active-backgroundColor, 50%)
color #e74c3c
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color $ui-button--active-backgroundColor
background-color alpha($ui-button--active-backgroundColor, 50%)
color #e74c3c
.menu-button-label
color $ui-text-color
@@ -34,12 +34,12 @@
.menu-button-label
color $ui-text-color
&:hover
background-color $ui-button--active-backgroundColor
background-color alpha($ui-button--active-backgroundColor, 50%)
color #F9BF3B
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color $ui-button--active-backgroundColor
background-color alpha($ui-button--active-backgroundColor, 50%)
color #F9BF3B
.menu-button-label
color $ui-text-color
@@ -88,7 +88,7 @@ body[data-theme="dark"]
.menu-button-label
color $ui-dark-text-color
&:hover
background-color $ui-dark-button--active-backgroundColor
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
color #c0392b
.menu-button-label
color $ui-dark-text-color
@@ -99,7 +99,7 @@ body[data-theme="dark"]
.menu-button-label
color $ui-dark-text-color
&:hover
background-color $ui-dark-button--active-backgroundColor
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
color $ui-favorite-star-button-color
.menu-button-label
color $ui-dark-text-color
color $ui-dark-text-color

View File

@@ -30,7 +30,7 @@
background-color $ui-button--active-backgroundColor
&:hover
color $ui-text-color
background-color $ui-button--active-backgroundColor
background-color alpha($ui-button--active-backgroundColor, 50%)
.folderList-item-name
display block
@@ -86,7 +86,7 @@ body[data-theme="dark"]
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
&:active
background-color $ui-dark-button--active-backgroundColor
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
&:hover
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
background-color alpha($ui-dark-button--active-backgroundColor, 50%)

View File

@@ -0,0 +1,23 @@
/**
* @fileoverview Micro component for showing StorageList
*/
import React, { PropTypes } from 'react'
import styles from './StorgaeList.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {Array} storgaeList
*/
const StorageList = ({storageList}) => (
<div styleName='storageList'>
{storageList.length > 0 ? storageList : (
<div styleName='storgaeList-empty'>No storage mount.</div>
)}
</div>
)
StorageList.propTypes = {
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
}
export default CSSModules(StorageList, styles)

View File

@@ -0,0 +1,20 @@
.storageList
absolute left right
bottom 37px
top 160px
overflow-y auto
.storageList-empty
padding 0 10px
margin-top 15px
line-height 24px
color $ui-inactive-text-color
body[data-theme="dark"]
.storageList-empty
color $ui-dark-inactive-text-color
.root-folded
.storageList-empty
white-space nowrap
transform rotate(90deg)

View File

@@ -0,0 +1,27 @@
/**
* @fileoverview Micro component for showing TagList.
*/
import React, { PropTypes } from 'react'
import styles from './TagListItem.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {string} name
* @param {Function} handleClickTagListItem
* @param {bool} isActive
*/
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-name'>
{`# ${name}`}
</span>
</button>
)
TagListItem.propTypes = {
name: PropTypes.string.isRequired,
handleClickTagListItem: PropTypes.func.isRequired
}
export default CSSModules(TagListItem, styles)

View File

@@ -0,0 +1,67 @@
.tagList-item
display flex
width 100%
height 26px
background-color transparent
color $ui-inactive-text-color
padding 0
margin-bottom 5px
text-align left
border none
overflow ellipsis
font-size 12px
&:first-child
margin-top 0
&:hover
color $ui-text-color
background-color alpha($ui-button--active-backgroundColor, 20%)
transition background-color 0.15s
&:active
color $ui-text-color
background-color $ui-button--active-backgroundColor
.tagList-item-active
background-color $ui-button--active-backgroundColor
display flex
width 100%
height 26px
padding 0
margin-bottom 5px
text-align left
border none
overflow ellipsis
font-size 12px
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
transition 0.2s
.tagList-item-name
display block
flex 1
padding 0 25px
height 26px
line-height 26px
border-width 0 0 0 2px
border-style solid
border-color transparent
overflow hidden
text-overflow ellipsis
body[data-theme="dark"]
.tagList-item
color $ui-dark-inactive-text-color
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.tagList-item-active
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)

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

@@ -193,6 +193,7 @@ ol
&>li>ul, &>li>ol
margin 0
code
color #CC305F
padding 0.2em 0.4em
background-color #f7f7f7
border-radius 3px
@@ -268,6 +269,16 @@ table
border-color borderColor
&:last-child
border-right solid 1px borderColor
kbd
background-color #fafbfc
border solid 1px borderColor
border-bottom-color btnColor
border-radius 3px
box-shadow inset 0 -1px 0 #959da5
display inline-block
font-size .8em
line-height 1
padding 3px 5px
themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9
@@ -316,3 +327,6 @@ body[data-theme="dark"]
border-color themeDarkTableBorder
&:last-child
border-right solid 1px themeDarkTableBorder
kbd
background-color themeDarkBorder
color themeDarkText

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,10 +107,7 @@ class NoteDetail extends React.Component {
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
const storage = _.find(cachedStorageList, {key: note.storage})
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
const storage = findStorage(note.storage)
if (note.type === 'SNIPPET_NOTE') {
let tabList = note.snippets.map((snippet, index) => {
@@ -148,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}
@@ -197,6 +196,7 @@ class NoteDetail extends React.Component {
lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize}
value={note.content}
showCopyNotification={config.ui.showCopyNotification}
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%

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

@@ -10,6 +10,7 @@ const themes = fs.readdirSync(themePath)
.map((themePath) => {
return themePath.substring(0, themePath.lastIndexOf('.'))
})
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
const consts = {
FOLDER_COLORS: [

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,16 @@ 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(/\-+$/, '')
}
})
md.use(require('markdown-it-kbd'))
// Override task item
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
@@ -156,12 +166,17 @@ function strip (input) {
return output
}
function normalizeLinkText (linkText) {
return md.normalizeLinkText(linkText)
}
const markdown = {
render: function markdown (content) {
if (!_.isString(content)) content = ''
const renderedContent = md.render(content)
return md.normalizeLinkText(renderedContent)
return renderedContent
},
strip
strip,
normalizeLinkText
}
export default markdown

View File

@@ -1,17 +1,17 @@
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(' ')
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
let foundNotes = findByWord(notes, searchBlocks[0])
searchBlocks.forEach((block) => {
foundNotes = findByWord(foundNotes, block)
if (block.match(/^#.+/)) {
notes = findByTag(notes, block)
} else {
notes = findByWord(notes, block)
foundNotes = foundNotes.concat(findByTag(notes, block))
}
})
return notes
return foundNotes
}
function findByTag (notes, block) {

View File

@@ -3,7 +3,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
const InfoPanel = ({
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
}) => (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div styleName='group-section'>
@@ -24,7 +24,7 @@ const InfoPanel = ({
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Created at
Created
</div>
<div styleName='group-section-control'>
{createdAt}
@@ -32,7 +32,7 @@ const InfoPanel = ({
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Updated at
Updated
</div>
<div styleName='group-section-control'>
{updatedAt}
@@ -46,6 +46,27 @@ const InfoPanel = ({
<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)}>
@@ -58,9 +79,9 @@ const InfoPanel = ({
<p>.txt</p>
</button>
<button styleName='export--unable'>
<i className='fa fa-file-pdf-o fa-fw' />
<p>.pdf</p>
<button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-print fa-fw' />
<p>Print</p>
</button>
</div>
</div>
@@ -73,7 +94,11 @@ InfoPanel.propTypes = {
updatedAt: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired
exportAsTxt: PropTypes.func.isRequired,
wordCount: PropTypes.number,
letterCount: PropTypes.number,
type: PropTypes.string.isRequired,
print: PropTypes.func.isRequired
}
export default CSSModules(InfoPanel, styles)

View File

@@ -18,6 +18,16 @@
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
@@ -40,6 +50,19 @@
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
@@ -75,6 +98,10 @@ body[data-theme="dark"]
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

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

@@ -17,7 +17,10 @@ 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
@@ -69,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
if (this.refs.tags) note.tags = this.refs.tags.value
note.title = markdown.strip(findNoteTitle(note.content))
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
note.updatedAt = new Date()
this.setState({
@@ -190,8 +175,8 @@ class MarkdownNoteDetail extends React.Component {
if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
@@ -271,6 +256,10 @@ class MarkdownNoteDetail extends React.Component {
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
}
print (e) {
ee.emit('print')
}
render () {
let { data, config, location } = this.props
let { note } = this.state
@@ -297,6 +286,17 @@ class MarkdownNoteDetail extends React.Component {
</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>
@@ -321,7 +321,7 @@ class MarkdownNoteDetail extends React.Component {
onChange={(e) => this.handleChange(e)}
/>
<TodoListPercentage
percentageOfTodo={this.getPercentageOfCompleteTodo(note.content)}
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
/>
</div>
<div styleName='info-right'>
@@ -334,7 +334,7 @@ class MarkdownNoteDetail extends React.Component {
>
<i className={faClassName} styleName='lock-button' />
<span styleName='control-lockButton-tooltip'>
{this.state.isLocked ? 'Unlock' : 'Lock'}
{this.state.isLocked ? 'Unlock Editor' : 'Keep Editor Locked'}
</span>
</button>
return (
@@ -345,7 +345,7 @@ class MarkdownNoteDetail extends React.Component {
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-expand' styleName='fullScreen-button' />
<i className='fa fa-window-maximize' styleName='fullScreen-button' />
</button>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
@@ -358,6 +358,10 @@ class MarkdownNoteDetail extends React.Component {
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}
print={this.print}
/>
</div>
</div>

View File

@@ -20,6 +20,7 @@ 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) {
@@ -176,8 +177,8 @@ class SnippetNoteDetail extends React.Component {
if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
@@ -265,12 +266,17 @@ class SnippetNoteDetail extends React.Component {
}
renameSnippetByIndex (index, name) {
let snippets = this.state.note.snippets.slice()
const snippets = this.state.note.snippets.slice()
snippets[index].name = name
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
const syntax = CodeMirror.findModeByFileName(name.trim())
const mode = syntax != null ? syntax.name : null
if (mode != null) {
snippets[index].mode = mode
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SNIPPET_LANG', {
name: mode
})
}
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
@@ -283,13 +289,17 @@ 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
}, () => {
this.save()
})
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
name
})
}
}
@@ -297,7 +307,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
}, () => {
@@ -519,6 +529,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}
@@ -556,6 +567,17 @@ class SnippetNoteDetail extends React.Component {
</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>
@@ -585,7 +607,7 @@ class SnippetNoteDetail extends React.Component {
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-expand' styleName='fullScreen-button' />
<i className='fa fa-window-maximize' styleName='fullScreen-button' />
</button>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
@@ -598,6 +620,7 @@ class SnippetNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
type={note.type}
/>
</div>
</div>

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,16 +50,17 @@
.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
@@ -83,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

View File

@@ -51,7 +51,7 @@
margin 2px 0 15px 2px
vertical-align middle
height 18px
box-sizing borde-box
box-sizing border-box
border none
background-color transparent
outline none

View File

@@ -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,12 +14,14 @@ 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')
}
class Main extends React.Component {
constructor (props) {
super(props)
@@ -172,8 +174,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'
@@ -188,6 +190,17 @@ class Main extends React.Component {
render () {
let { config } = this.props
// the width of the navigation bar when it is folded/collapsed
const foldedNavigationWidth = 44
let notificationBarOffsetLeft
if (this.state.fullScreen) {
notificationBarOffsetLeft = 0
} else if (config.isSideNavFolded) {
notificationBarOffsetLeft = foldedNavigationWidth
} else {
notificationBarOffsetLeft = this.state.navWidth
}
return (
<div
className='Main'
@@ -216,7 +229,7 @@ class Main extends React.Component {
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
id='main-body'
ref='body'
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
>
<TopBar style={{width: this.state.listWidth}}
{..._.pick(this.props, [
@@ -255,6 +268,9 @@ class Main extends React.Component {
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
/>
</div>
<RealtimeNotification
style={{left: notificationBarOffsetLeft}}
/>
</div>
)
}

View File

@@ -0,0 +1,66 @@
.root
position relative
background-color $ui-noteList-backgroundColor
height $topBar-height - 1
margin-left: auto;
width: 64px;
.root--expanded
@extend .root
$control-height = 34px
.control
position absolute
top 13px
right 7px
height $control-height
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 absolute
pointer-events none
top 26px
right -43px
width 124px
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

View File

@@ -13,6 +13,8 @@ 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'
import store from 'browser/main/store'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
@@ -51,6 +53,8 @@ class NoteList extends React.Component {
this.state = {
}
this.contextNotes = []
}
componentDidMount () {
@@ -89,6 +93,7 @@ class NoteList extends React.Component {
if (this.notes.length > 0 && location.query.key == null) {
let { router } = this.context
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
router.replace({
pathname: location.pathname,
query: {
@@ -100,9 +105,7 @@ class NoteList extends React.Component {
// Auto scroll
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
let targetIndex = _.findIndex(this.notes, (note) => {
return note != null && note.storage + '-' + note.key === location.query.key
})
const targetIndex = this.getTargetIndex()
if (targetIndex > -1) {
let list = this.refs.list
let item = list.childNodes[targetIndex]
@@ -128,9 +131,7 @@ class NoteList extends React.Component {
let { router } = this.context
let { location } = this.props
let targetIndex = _.findIndex(this.notes, (note) => {
return note.storage + '-' + note.key === location.query.key
})
let targetIndex = this.getTargetIndex()
if (targetIndex === 0) {
return
@@ -153,9 +154,7 @@ class NoteList extends React.Component {
let { router } = this.context
let { location } = this.props
let targetIndex = _.findIndex(this.notes, (note) => {
return note.storage + '-' + note.key === location.query.key
})
let targetIndex = this.getTargetIndex()
if (targetIndex === this.notes.length - 1) {
targetIndex = 0
@@ -183,9 +182,7 @@ class NoteList extends React.Component {
const { router } = this.context
const { location } = this.props
let targetIndex = _.findIndex(this.notes, (note) => {
return note.storage + '-' + note.key === noteHash
})
let targetIndex = this.getTargetIndex()
if (targetIndex < 0) targetIndex = 0
@@ -232,49 +229,67 @@ class NoteList extends React.Component {
let { data, params, location } = this.props
let { router } = this.context
if (location.pathname.match(/\/home/)) {
return data.noteMap.map((note) => note)
if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) {
const allNotes = data.noteMap.map((note) => note)
this.contextNotes = allNotes
return allNotes
}
if (location.pathname.match(/\/starred/)) {
return data.starredSet.toJS()
.map((uniqueKey) => data.noteMap.get(uniqueKey))
const starredNotes = data.starredSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
this.contextNotes = starredNotes
return starredNotes
}
if (location.pathname.match(/\/searched/)) {
const searchInputText = document.getElementsByClassName('searchInput')[0].value
if (searchInputText === '') {
router.push('/home')
return this.sortByPin(this.contextNotes)
}
return searchFromNotes(this.props.data, searchInputText)
return searchFromNotes(this.contextNotes, searchInputText)
}
if (location.pathname.match(/\/trashed/)) {
return data.trashedSet.toJS()
.map((uniqueKey) => data.noteMap.get(uniqueKey))
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
this.contextNotes = trashedNotes
return trashedNotes
}
let storageKey = params.storageKey
let folderKey = params.folderKey
let storage = data.storageMap.get(storageKey)
if (storage == null) return []
let folder = _.find(storage.folders, {key: folderKey})
if (folder == null) {
let storageNoteSet = data.storageNoteMap
.get(storage.key)
if (storageNoteSet == null) storageNoteSet = []
return storageNoteSet
.map((uniqueKey) => data.noteMap.get(uniqueKey))
if (location.pathname.match(/\/tags/)) {
return data.noteMap.map(note => {
return note
}).filter(note => {
return note.tags.includes(params.tagname)
})
}
let folderNoteKeyList = data.folderNoteMap
.get(storage.key + '-' + folder.key)
return this.getContextNotes()
}
return folderNoteKeyList != null
? folderNoteKeyList
.map((uniqueKey) => data.noteMap.get(uniqueKey))
: []
// get notes in the current folder
getContextNotes () {
const { data, params } = this.props
const storageKey = params.storageKey
const folderKey = params.folderKey
const storage = data.storageMap.get(storageKey)
if (storage === undefined) return []
const folder = _.find(storage.folders, {key: folderKey})
if (folder === undefined) {
const storageNoteSet = data.storageNoteMap.get(storage.key) || []
return storageNoteSet.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
const folderNoteKeyList = data.folderNoteMap.get(`${storage.key}-${folder.key}`) || []
return folderNoteKeyList.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
sortByPin (unorderedNotes) {
const pinnedNotes = unorderedNotes.filter((note) => {
return note.isPinned
})
return pinnedNotes.concat(unorderedNotes)
}
handleNoteClick (e, uniqueKey) {
@@ -318,10 +333,7 @@ class NoteList extends React.Component {
}
alertIfSnippet () {
let { location } = this.props
const targetIndex = _.findIndex(this.notes, (note) => {
return `${note.storage}-${note.key}` === location.query.key
})
const targetIndex = this.getTargetIndex()
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
@@ -337,6 +349,53 @@ class NoteList extends React.Component {
e.dataTransfer.setData('note', noteData)
}
handleNoteContextMenu (e, uniqueKey) {
const { location } = this.props
const targetIndex = this.getTargetIndex()
let note = this.notes[targetIndex]
const label = note.isPinned ? 'Remove pin' : 'Pin to Top'
let menu = new Menu()
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
menu.append(new MenuItem({
label: label,
click: (e) => this.pinToTop(e, uniqueKey)
}))
}
menu.append(new MenuItem({
label: 'Delete Note',
click: (e) => this.deleteNote(e, uniqueKey)
}))
menu.popup()
}
pinToTop (e, uniqueKey) {
const { data, params } = this.props
const storageKey = params.storageKey
const folderKey = params.folderKey
const currentStorage = data.storageMap.get(storageKey)
const currentFolder = _.find(currentStorage.folders, {key: folderKey})
const targetIndex = this.getTargetIndex()
let note = this.notes[targetIndex]
note.isPinned = !note.isPinned
dataApi
.updateNote(note.storage, note.key, note)
.then((note) => {
store.dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
}
deleteNote (e, uniqueKey) {
this.handleNoteClick(e, uniqueKey)
ee.emit('detail:delete')
}
importFromFile () {
const { dispatch, location } = this.props
@@ -347,41 +406,61 @@ class NoteList extends React.Component {
properties: ['openFile', 'multiSelections']
}
const targetIndex = _.findIndex(this.notes, (note) => {
return note !== null && `${note.storage}-${note.key}` === location.query.key
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 = this.getTargetIndex()
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}`}
})
})
})
})
}
getTargetIndex () {
const { location } = this.props
const targetIndex = _.findIndex(this.notes, (note) => {
return `${note.storage}-${note.key}` === location.query.key
})
return targetIndex
}
render () {
let { location, notes, config, dispatch } = this.props
let sortFunc = config.sortBy === 'CREATED_AT'
@@ -389,12 +468,13 @@ class NoteList extends React.Component {
: config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical
: 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
})
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
? this.getNotes().sort(sortFunc)
: this.sortByPin(this.getNotes().sort(sortFunc))
this.notes = notes = sortedNotes.filter((note) => {
// this is for the trash box
if (note.isTrashed !== true || location.pathname === '/trashed') return true
})
let noteList = notes
.map(note => {
@@ -417,8 +497,10 @@ class NoteList extends React.Component {
note={note}
dateDisplay={dateDisplay}
key={key}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
/>
)
}
@@ -438,17 +520,18 @@ 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'>
<i className='fa fa-bolt' />
<i className='fa fa-angle-down' />
<select styleName='control-sortBy-select'
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'>Updated</option>
<option value='CREATED_AT'>Created</option>
<option value='ALPHABETICAL'>Alphabetically</option>
</select>
</div>
<div styleName='control-button-area'>

View File

@@ -4,59 +4,81 @@
background-color #f9f9f9
user-select none
color $ui-text-color
height: 100vh
display: flex
flex-direction column
.top
height $topBar-height
padding-bottom 15px
.top-menu
navButtonColor()
height $topBar-height
padding 0 15px
font-size 12px
width 100%
text-align left
position absolute
top 22px
right 5px
height 23px
width 2em
&:hover
color $ui-text-color
&:active, &:active:hover
color $ui-text-color
background-color alpha($ui-button--active-backgroundColor, 20%)
.switch-buttons
background-color transparent
border 1px solid $ui-borderColor
width 110px
height 25px
margin 20px auto 0px auto
border-radius 1px
.non-active-button
navButtonColor()
font-weight 600
width 54px
height 23px
.active-button
@extend .non-active-button
background-color $ui-button--active-backgroundColor
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 70%)
color alpha($ui-text-color, 70%)
&:active, &:active:hover
background-color $ui-button--active-backgroundColor
.top-menu-label
margin-left 5px
overflow ellipsis
opacity 0
.storageList
absolute left right
bottom 37px
top 160px
.tabBody
flex 1
display flex
flex-direction column
.tag-title
padding-left 15px
padding-bottom 13px
p
color $ui-text-color
.tagList
overflow-y auto
.storageList-empty
padding 0 10px
margin-top 15px
line-height 24px
color $ui-inactive-text-color
.navToggle
navButtonColor()
display block
position absolute
right 5px
bottom 5px
border-radius 16.5px
height 34px
width 34px
line-height 32px
padding 0
flex: 1
.root--folded
@extend .root
width 44px
.storageList-empty
white-space nowrap
transform rotate(90deg)
height 100vh
width $sideNav--folded-width
.switch-buttons
display none
.top
height 60px
.top-menu
width 44px - 1
position static
width $sideNav--folded-width
height 60px
text-align center
&:hover .top-menu-label
transition opacity 0.15s
@@ -65,7 +87,7 @@
position fixed
display inline-block
height 30px
left 32px
left $sideNav--folded-width
padding 0 10px
margin-top -8px
opacity 0
@@ -79,30 +101,6 @@
border-bottom-right-radius 2px
pointer-events none
font-size 12px
.menu-button, .menu-button--active
text-align center
&:hover .menu-button-label
transition opacity 0.15s
opacity 1
.menu-button-label
position fixed
display inline-block
height 32px
left 44px
padding 0 10px
margin-top -8px
margin-left 0
overflow ellipsis
background-color $ui-tooltip-backgroundColor
z-index 10
color white
line-height 32px
border-top-right-radius 2px
border-bottom-right-radius 2px
pointer-events none
opacity 0
font-size 12px
body[data-theme="dark"]
.root, .root--folded
@@ -120,11 +118,26 @@ body[data-theme="dark"]
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
.storageList-empty
color $ui-dark-inactive-text-color
.switch-buttons
border-color $ui-dark-borderColor
.navToggle
.non-active-button
navDarkButtonColor()
.active-button
@extend .non-active-button
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
transition 0.15s
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 70%)
color alpha($ui-dark-text-color, 70%)
&:active
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
&:active, &:active:hover
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.tag-title
p
color alpha($ui-dark-text-color, 60%)

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

@@ -5,7 +5,10 @@ import { openModal } from 'browser/main/lib/modal'
import PreferencesModal from '../modals/PreferencesModal'
import ConfigManager from 'browser/main/lib/ConfigManager'
import StorageItem from './StorageItem'
import TagListItem from 'browser/components/TagListItem'
import SideNavFilter from 'browser/components/SideNavFilter'
import StorageList from 'browser/components/StorageList'
import NavToggleButton from 'browser/components/NavToggleButton'
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
@@ -38,13 +41,85 @@ class SideNav extends React.Component {
router.push('/trashed')
}
handleSwitchFoldersButtonClick () {
const { router } = this.context
router.push('/home')
}
handleSwitchTagsButtonClick () {
const { router } = this.context
router.push('/alltags')
}
SideNavComponent (isFolded, storageList) {
let { location, data } = this.props
const isHomeActive = !!location.pathname.match(/^\/home$/)
const isStarredActive = !!location.pathname.match(/^\/starred$/)
const isTrashedActive = !!location.pathname.match(/^\/trashed$/)
let component
// TagsMode is not selected
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
component = (
<div>
<SideNavFilter
isFolded={isFolded}
isHomeActive={isHomeActive}
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
isStarredActive={isStarredActive}
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
/>
<StorageList storageList={storageList} />
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
</div>
)
} else {
component = (
<div styleName='tabBody'>
<div styleName='tag-title'>
<p>Tags</p>
</div>
<div styleName='tagList'>
{this.tagListComponent(data)}
</div>
</div>
)
}
return component
}
tagListComponent () {
const { data, location } = this.props
let tagList = data.tagNoteMap.map((tag, key) => {
return key
})
return (
tagList.map(tag => (
<TagListItem
name={tag}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={!!location.pathname.match(tag)}
key={tag}
/>
))
)
}
handleClickTagListItem (name) {
const { router } = this.context
router.push(`/tags/${name}`)
}
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
@@ -58,6 +133,7 @@ class SideNav extends React.Component {
})
let style = {}
if (!isFolded) style.width = this.props.width
const isTagActive = location.pathname.match(/tag/)
return (
<div className='SideNav'
styleName={isFolded ? 'root--folded' : 'root'}
@@ -65,37 +141,20 @@ class SideNav extends React.Component {
style={style}
>
<div styleName='top'>
<button styleName='top-menu'
onClick={(e) => this.handleMenuButtonClick(e)}
>
<i className='fa fa-wrench fa-fw' />
<span styleName='top-menu-label'>Preferences</span>
</button>
<div styleName='switch-buttons'>
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}>Folders</button>
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={this.handleSwitchTagsButtonClick.bind(this)}>Tags</button>
</div>
<div>
<button styleName='top-menu'
onClick={(e) => this.handleMenuButtonClick(e)}
>
<i className='fa fa-wrench fa-fw' />
<span styleName='top-menu-label'>Preferences</span>
</button>
</div>
</div>
<SideNavFilter
isFolded={isFolded}
isHomeActive={isHomeActive}
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
isStarredActive={isStarredActive}
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
/>
<div styleName='storageList'>
{storageList.length > 0 ? storageList : (
<div styleName='storageList-empty'>No storage mount.</div>
)}
</div>
<button styleName='navToggle'
onClick={(e) => this.handleToggleButtonClick(e)}
>
{isFolded
? <i className='fa fa-angle-double-right' />
: <i className='fa fa-angle-double-left' />
}
</button>
{this.SideNavComponent(isFolded, storageList)}
</div>
)
}

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

@@ -59,8 +59,6 @@ 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!

View File

@@ -2,12 +2,9 @@ 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
@@ -21,11 +18,10 @@ class TopBar extends React.Component {
this.state = {
search: '',
searchOptions: [],
isSearching: false
}
this.newNoteHandler = () => {
this.handleNewPostButtonClick()
isSearching: false,
isAlphabet: false,
isIME: false,
isConfirmTranslation: false
}
this.focusSearchHandler = () => {
@@ -34,95 +30,64 @@ class TopBar extends React.Component {
}
componentDidMount () {
ee.on('top:new-note', this.newNoteHandler)
ee.on('top:focus-search', this.focusSearchHandler)
}
componentWillUnmount () {
ee.off('top:new-note', this.newNoteHandler)
ee.off('top:focus-search', this.focusSearchHandler)
}
handleNewPostButtonClick (e) {
let { config, location } = this.props
handleKeyDown (e) {
// reset states
this.setState({
isAlphabet: false,
isIME: false
})
if (location.pathname === '/trashed') {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Cannot create new note',
detail: 'You cannot create new note in trash box.',
buttons: ['OK']
// When the key is an alphabet, del, enter or ctr
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
this.setState({
isAlphabet: true
})
// When the key is an IME input (Japanese, Chinese)
} else if (e.keyCode === 229) {
this.setState({
isIME: true
})
return
}
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)
handleKeyUp (e) {
const { router } = this.context
// reset states
this.setState({
isConfirmTranslation: false
})
// 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
// When the key is translation confirmation (Enter, Space)
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
this.setState({
isConfirmTranslation: true
})
router.push('/searched')
this.setState({
search: this.refs.searchInput.value
})
}
}
handleSearchChange (e) {
let { router } = this.context
router.push('/searched')
const { router } = this.context
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched')
} else {
e.preventDefault()
}
this.setState({
search: this.refs.searchInput.value
})
}
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
@@ -147,60 +112,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()
@@ -210,7 +121,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'}
@@ -228,6 +139,8 @@ class TopBar extends React.Component {
ref='searchInput'
value={this.state.search}
onChange={(e) => this.handleSearchChange(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
placeholder='Search'
type='text'
className='searchInput'
@@ -242,14 +155,17 @@ class TopBar extends React.Component {
}
</div>
<button styleName='control-newPostButton'
onClick={(e) => this.handleNewPostButtonClick(e)}>
<i className='fa fa-pencil-square-o' />
<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
@@ -102,3 +102,10 @@ body[data-theme="dark"]
background #B1D7FE
::selection
background #B1D7FE
.sortableItemHelper
z-index modalZIndex + 5
body[data-theme="dark"]
.sortableItemHelper
color: $ui-dark-text-color

View File

@@ -27,7 +27,10 @@ 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')
const offsetParent = e.target.offsetParent
const isInfoPanel = offsetParent !== null
? offsetParent.className.includes('infoPanel')
: false
if (isInfoButton || isInfoPanel) return
const infoPanel = document.querySelector('.infoPanel')
if (infoPanel) infoPanel.style.display = 'none'
@@ -62,6 +65,11 @@ ReactDOM.render((
<Route path='starred' />
<Route path='searched' />
<Route path='trashed' />
<Route path='alltags' />
<Route path='tags'>
<IndexRedirect to='/alltags' />
<Route path=':tagname' />
</Route>
<Route path='storages'>
<IndexRedirect to='/home' />
<Route path=':storageKey'>

View File

@@ -2,37 +2,70 @@ const AWS = require('aws-sdk')
const AMA = require('aws-sdk-mobile-analytics')
const ConfigManager = require('browser/main/lib/ConfigManager')
const remote = require('electron').remote
const os = require('os')
AWS.config.region = 'us-east-1'
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
})
const validPlatformName = convertPlatformName(os.platform())
const mobileAnalyticsClient = new AMA.Manager({
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
appTitle: 'xxxxxxxxxx'
appTitle: 'xxxxxxxxxx',
appVersionName: remote.app.getVersion().toString(),
platform: validPlatformName
})
}
function convertPlatformName (platformName) {
if (platformName === 'darwin') {
return 'MacOS'
} else if (platformName === 'win32') {
return 'Windows'
} else if (platformName === 'linux') {
return 'Linux'
} else {
return ''
}
}
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 recordDynamicCustomEvent (type) {
function recordDynamicCustomEvent (type, options = {}) {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
mobileAnalyticsClient.recordEvent(type)
try {
mobileAnalyticsClient.recordEvent(type, options)
} catch (analyticsError) {
if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message)
}
}
}
function recordStaticCustomEvent () {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
uiColorTheme: ConfigManager.default.get().ui.theme
})
try {
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
uiColorTheme: ConfigManager.default.get().ui.theme
})
} catch (analyticsError) {
if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message)
}
}
}
module.exports = {

View File

@@ -1,10 +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
@@ -22,6 +25,7 @@ export const DEFAULT_CONFIG = {
},
ui: {
theme: 'default',
showCopyNotification: true,
disableDirectWrite: false,
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
},
@@ -57,17 +61,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)
}
@@ -126,6 +130,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,11 +13,7 @@ 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 imageExt = path.extname(filePath)

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,6 +4,7 @@ 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 = []
@@ -38,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

@@ -6,6 +6,7 @@ const dataApi = {
createFolder: require('./createFolder'),
updateFolder: require('./updateFolder'),
deleteFolder: require('./deleteFolder'),
reorderFolder: require('./reorderFolder'),
createNote: require('./createNote'),
updateNote: require('./updateNote'),
deleteNote: require('./deleteNote'),

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

@@ -0,0 +1,43 @@
const _ = require('lodash')
_.move = require('lodash-move').default
const path = require('path')
const resolveStorageData = require('./resolveStorageData')
const CSON = require('@rokt33r/season')
const { findStorage } = require('browser/lib/findStorage')
/**
* @param {String} storageKey
* @param {number} oldIndex
* @param {number} newIndex
*
* @return {Object}
* ```
* {
* storage: Object
* }
* ```
*/
function reorderFolder (storageKey, oldIndex, newIndex) {
let rawStorages
let targetStorage
try {
if (!_.isNumber(oldIndex)) throw new Error('oldIndex must be a number.')
if (!_.isNumber(newIndex)) throw new Error('newIndex must be a number.')
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}
return resolveStorageData(targetStorage)
.then(function reorderFolder (storage) {
storage.folders = _.move(storage.folders, oldIndex, newIndex)
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
return {
storage
}
})
}
module.exports = reorderFolder

View File

@@ -2,6 +2,7 @@ const _ = require('lodash')
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 updateFolder (storageKey, folderKey, 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

@@ -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 = {}
@@ -25,6 +26,10 @@ function validateInput (input) {
validatedInput.isTrashed = !!input.isTrashed
}
if (input.isPinned !== undefined) {
validatedInput.isPinned = !!input.isPinned
}
validatedInput.type = input.type
switch (input.type) {
case 'MARKDOWN_NOTE':
@@ -68,11 +73,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)
}
@@ -107,6 +108,7 @@ function updateNote (storageKey, noteKey, input) {
noteData.isStarred = false
noteData.isTrashed = false
noteData.tags = []
noteData.isPinned = false
}
if (noteData.type === 'SNIPPET_NOTE') {

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

@@ -152,7 +152,7 @@ class InitModal extends React.Component {
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 Makrdown Preview.\n```javascript\nvar boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)\n```'
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 or registration required. \n- [ ] Quick search and copy the content of note. macOS: <kbd>Cmd</kbd> + <kbd>Alt</kbd> + <kbd>S</kbd> / windows: <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>S</kbd> \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({
@@ -194,13 +194,13 @@ class InitModal extends React.Component {
return (
<div styleName='root'
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyDown={this.props.close}
>
<div styleName='header'>
<div styleName='header-title'>Initialize Storage</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<ModalEscButton handleEscButtonClick={this.props.close} />
<div styleName='body'>
<div styleName='body-welcome'>
Welcome!

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
@@ -62,10 +67,17 @@
text-align right
:global
.alert
font-size 12px
line-height 30px
padding 0 5px
float right
display inline-block
position absolute
top 60px
right 15px
font-size 14px
.success
color green
.error
color red
.group-control-leftButton
colorDefaultButton()
@@ -77,14 +89,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

@@ -0,0 +1,49 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './Crowdfunding.styl'
const electron = require('electron')
const { shell } = electron
class Crowdfunding extends React.Component {
constructor (props) {
super(props)
this.state = {
}
}
handleLinkClick (e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
render () {
return (
<div styleName='root'>
<div styleName='header'>Crowdfunding</div>
<p>Dear all,</p>
<br />
<p>Thanks for your using!</p>
<p>Boostnote is used in about 200 countries and regions, it is a awesome developer community.</p>
<br />
<p>To continue supporting this growth, and to satisfy community expectations,</p>
<p>we would like to invest more time in this project.</p>
<br />
<p>If you like this project and see its potential, you can help!</p>
<br />
<p>Thanks,</p>
<p>Boostnote maintainers.</p>
<br />
<button styleName='cf-link'>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>
</button>
</div>
)
}
}
Crowdfunding.propTypes = {
}
export default CSSModules(Crowdfunding, styles)

View File

@@ -0,0 +1,30 @@
@import('./Tab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
p
font-size 16px
.cf-link
width 250px
height 35px
border-radius 2px
border none
background-color alpha(#1EC38B, 90%)
&:hover
background-color #1EC38B
transition 0.2s
a
text-decoration none
color white
font-weight 600
font-size 16px
body[data-theme="dark"]
p
color $ui-dark-text-color

View File

@@ -0,0 +1,303 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import ReactDOM from 'react-dom'
import styles from './FolderItem.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { SketchPicker } from 'react-color'
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
class FolderItem extends React.Component {
constructor (props) {
super(props)
this.state = {
status: 'IDLE',
folder: {
showColumnPicker: false,
colorPickerPos: { left: 0, top: 0 },
color: props.color,
name: props.name
}
}
}
handleEditChange (e) {
let { folder } = this.state
folder.name = this.refs.nameInput.value
this.setState({
folder
})
}
handleConfirmButtonClick (e) {
this.confirm()
}
confirm () {
let { storage, folder } = this.props
dataApi
.updateFolder(storage.key, folder.key, {
color: this.state.folder.color,
name: this.state.folder.name
})
.then((data) => {
store.dispatch({
type: 'UPDATE_FOLDER',
storage: data.storage
})
this.setState({
status: 'IDLE'
})
})
}
handleColorButtonClick (e) {
const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } })
this.setState({ folder }, function () {
// After the color picker has been painted, re-calculate its position
// by comparing its dimensions to the host dimensions.
const { hostBoundingBox } = this.props
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
const colorPickerBox = colorPickerNode.getBoundingClientRect()
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom
const folder = Object.assign({}, this.state.folder, {
colorPickerPos: {
left: 25,
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
}
})
this.setState({ folder })
})
}
handleColorChange (color) {
const folder = Object.assign({}, this.state.folder, { color: color.hex })
this.setState({ folder })
}
handleColorPickerClose (event) {
const folder = Object.assign({}, this.state.folder, { showColumnPicker: false })
this.setState({ folder })
}
handleCancelButtonClick (e) {
this.setState({
status: 'IDLE'
})
}
handleFolderItemBlur (e) {
let el = e.relatedTarget
while (el != null) {
if (el === this.refs.root) {
return false
}
el = el.parentNode
}
this.confirm()
}
renderEdit (e) {
const popover = { position: 'absolute', zIndex: 2 }
const cover = {
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0
}
const pickerStyle = Object.assign({}, {
position: 'absolute'
}, this.state.folder.colorPickerPos)
return (
<div styleName='folderItem'
onBlur={(e) => this.handleFolderItemBlur(e)}
tabIndex='-1'
ref='root'
>
<div styleName='folderItem-left'>
<button styleName='folderItem-left-colorButton' style={{color: this.state.folder.color}}
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
>
{this.state.folder.showColumnPicker
? <div style={popover}>
<div style={cover}
onClick={() => this.handleColorPickerClose()}
/>
<div style={pickerStyle}>
<SketchPicker
ref='colorPicker'
color={this.state.folder.color}
onChange={(color) => this.handleColorChange(color)}
onChangeComplete={(color) => this.handleColorChange(color)}
/>
</div>
</div>
: null
}
<i className='fa fa-square' />
</button>
<input styleName='folderItem-left-nameInput'
value={this.state.folder.name}
ref='nameInput'
onChange={(e) => this.handleEditChange(e)}
/>
</div>
<div styleName='folderItem-right'>
<button styleName='folderItem-right-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)}
>
Confirm
</button>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleCancelButtonClick(e)}
>
Cancel
</button>
</div>
</div>
)
}
handleDeleteConfirmButtonClick (e) {
let { storage, folder } = this.props
dataApi
.deleteFolder(storage.key, folder.key)
.then((data) => {
store.dispatch({
type: 'DELETE_FOLDER',
storage: data.storage,
folderKey: data.folderKey
})
})
}
renderDelete () {
return (
<div styleName='folderItem'>
<div styleName='folderItem-left'>
Are you sure to <span styleName='folderItem-left-danger'>delete</span> this folder?
</div>
<div styleName='folderItem-right'>
<button styleName='folderItem-right-dangerButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
>
Confirm
</button>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleCancelButtonClick(e)}
>
Cancel
</button>
</div>
</div>
)
}
handleEditButtonClick (e) {
let { folder: propsFolder } = this.props
let { folder: stateFolder } = this.state
const folder = Object.assign({}, stateFolder, propsFolder)
this.setState({
status: 'EDIT',
folder
}, () => {
this.refs.nameInput.select()
})
}
handleDeleteButtonClick (e) {
this.setState({
status: 'DELETE'
})
}
renderIdle () {
let { folder } = this.props
return (
<div styleName='folderItem'
onDoubleClick={(e) => this.handleEditButtonClick(e)}
>
<div styleName='folderItem-left'
style={{borderColor: folder.color}}
>
<span styleName='folderItem-left-name'>{folder.name}</span>
<span styleName='folderItem-left-key'>({folder.key})</span>
</div>
<div styleName='folderItem-right'>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleEditButtonClick(e)}
>
Edit
</button>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleDeleteButtonClick(e)}
>
Delete
</button>
</div>
</div>
)
}
render () {
switch (this.state.status) {
case 'DELETE':
return this.renderDelete()
case 'EDIT':
return this.renderEdit()
case 'IDLE':
default:
return this.renderIdle()
}
}
}
FolderItem.propTypes = {
hostBoundingBox: PropTypes.shape({
bottom: PropTypes.number,
height: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
top: PropTypes.number,
width: PropTypes.number
}),
storage: PropTypes.shape({
key: PropTypes.string
}),
folder: PropTypes.shape({
key: PropTypes.string,
color: PropTypes.string,
name: PropTypes.string
})
}
class Handle extends React.Component {
render () {
return (
<div styleName='folderItem-drag-handle'>
<i className='fa fa-reorder' />
</div>
)
}
}
class SortableFolderItemComponent extends React.Component {
render () {
const StyledHandle = CSSModules(Handle, this.props.styles)
const DragHandle = SortableHandle(StyledHandle)
const StyledFolderItem = CSSModules(FolderItem, this.props.styles)
return (
<div>
<DragHandle />
<StyledFolderItem {...this.props} />
</div>
)
}
}
export default CSSModules(SortableElement(SortableFolderItemComponent), styles)

View File

@@ -0,0 +1,103 @@
.folderItem
height 35px
box-sizing border-box
padding 2.5px 15px
&:hover
background-color darken(white, 3%)
.folderItem-drag-handle
height 35px
border none
padding 0 10px
line-height 35px
float left
cursor row-resize
.folderItem-left
height 30px
border-left solid 2px transparent
padding 0 10px
line-height 30px
float left
.folderItem-left-danger
color $danger-color
font-weight bold
.folderItem-left-key
color $ui-inactive-text-color
font-size 10px
margin 0 5px
border none
.folderItem-left-colorButton
colorDefaultButton()
height 25px
width 25px
line-height 23px
padding 0
box-sizing border-box
vertical-align middle
border $ui-border
border-radius 2px
margin-right 5px
margin-left -15px
.folderItem-left-nameInput
height 25px
box-sizing border-box
vertical-align middle
border $ui-border
border-radius 2px
padding 0 5px
outline none
.folderItem-right
float right
.folderItem-right-button
vertical-align middle
height 25px
margin-top 2.5px
colorDefaultButton()
border-radius 2px
border $ui-border
margin-right 5px
padding 0 5px
&:last-child
margin-right 0
.folderItem-right-confirmButton
@extend .folderItem-right-button
border none
colorPrimaryButton()
.folderItem-right-dangerButton
@extend .folderItem-right-button
border none
colorDangerButton()
body[data-theme="dark"]
.folderItem
&:hover
background-color lighten($ui-dark-button--hover-backgroundColor, 5%)
.folderItem-left-danger
color $danger-color
font-weight bold
.folderItem-left-key
color $ui-dark-inactive-text-color
.folderItem-left-colorButton
colorDarkDefaultButton()
border-color $ui-dark-borderColor
.folderItem-right-button
colorDarkDefaultButton()
border-color $ui-dark-borderColor
.folderItem-right-confirmButton
colorDarkPrimaryButton()
.folderItem-right-dangerButton
colorDarkDangerButton()

View File

@@ -0,0 +1,84 @@
import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi'
import styles from './FolderList.styl'
import store from 'browser/main/store'
import FolderItem from './FolderItem'
import { SortableContainer, arrayMove } from 'react-sortable-hoc'
class FolderList extends React.Component {
render () {
let { storage, hostBoundingBox } = this.props
let folderList = storage.folders.map((folder, index) => {
return <FolderItem key={folder.key}
folder={folder}
storage={storage}
index={index}
hostBoundingBox={hostBoundingBox}
/>
})
return (
<div styleName='folderList'>
{folderList.length > 0
? folderList
: <div styleName='folderList-empty'>No Folders</div>
}
</div>
)
}
}
FolderList.propTypes = {
hostBoundingBox: PropTypes.shape({
bottom: PropTypes.number,
height: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
top: PropTypes.number,
width: PropTypes.number
}),
storage: PropTypes.shape({
key: PropTypes.string
}),
folder: PropTypes.shape({
key: PropTypes.number,
color: PropTypes.string,
name: PropTypes.string
})
}
class SortableFolderListComponent extends React.Component {
constructor (props) {
super(props)
this.onSortEnd = ({oldIndex, newIndex}) => {
let { storage } = this.props
dataApi
.reorderFolder(storage.key, oldIndex, newIndex)
.then((data) => {
store.dispatch({
type: 'REORDER_FOLDER',
storage: data.storage
})
this.setState()
})
}
}
render () {
const StyledFolderList = CSSModules(FolderList, this.props.styles)
const SortableFolderList = SortableContainer(StyledFolderList)
return (
<SortableFolderList
helperClass='sortableItemHelper'
onSortEnd={this.onSortEnd}
useDragHandle
{...this.props}
/>
)
}
}
export default CSSModules(SortableFolderListComponent, styles)

View File

@@ -3,6 +3,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import _ from 'lodash'
const electron = require('electron')
const ipc = electron.ipcRenderer
@@ -50,6 +51,7 @@ class HotkeyTab extends React.Component {
type: 'SET_UI',
config: newConfig
})
this.clearMessage()
}
handleHintToggleButtonClick (e) {
@@ -69,6 +71,14 @@ class HotkeyTab extends React.Component {
})
}
clearMessage () {
_.debounce(() => {
this.setState({
keymapAlert: null
})
}, 2000)()
}
render () {
let keymapAlert = this.state.keymapAlert
let keymapAlertElement = keymapAlert != null
@@ -94,7 +104,7 @@ class HotkeyTab extends React.Component {
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>Toggle Finder(popup)</div>
<div styleName='group-section-label'>Toggle Finder (Quick search)</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}

View File

@@ -34,15 +34,16 @@ class InfoTab extends React.Component {
amaEnabled: this.state.config.amaEnabled
}
if (!newConfig.amaEnabled) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
}
ConfigManager.set(newConfig)
store.dispatch({
type: 'SET_CONFIG',
config: newConfig
})
if (!newConfig.amaEnabled) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
}
}
render () {
@@ -73,14 +74,14 @@ class InfoTab extends React.Component {
>Boostnote Shop</a> : Products are shipped to all over the world 🌏
</li>
<li>
<a href='https://salt.bountysource.com/teams/boostnote'
<a href='https://opencollective.com/boostnoteio'
onClick={(e) => this.handleLinkClick(e)}
>Donate via Bountysource</a> : Thank you for your support 🎉
>Crowdfunding</a> : Thank you for your support 🎉
</li>
<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'
@@ -96,9 +97,9 @@ class InfoTab extends React.Component {
</ul>
<hr />
<div styleName='policy'>Data collection policy</div>
<p>We collect only the number of DAU for Boostnote and DO NOT collect any detail information</p>
<p>such as your note content. You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</p>
<p>These data are only used for Boostnote improvements.</p>
<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>This data is only used for Boostnote improvements.</div>
<input onChange={(e) => this.handleConfigChange(e)}
checked={this.state.config.amaEnabled}
ref='amaEnabled'

View File

@@ -43,6 +43,7 @@
text-decoration none
.policy
width 100%
font-size 20px
margin-bottom 10px

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
@@ -24,23 +25,23 @@ top-bar--height = 50px
absolute top left right
top top-bar--height
left 0
width 140px
margin-left 30px
width 170px
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 150px
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
@@ -55,7 +56,7 @@ top-bar--height = 50px
.content
absolute left right bottom
top top-bar--height
left 140px
left 170px
margin-top 10px
overflow-y auto

View File

@@ -1,265 +1,13 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageItem.styl'
import consts from 'browser/lib/consts'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import FolderList from './FolderList'
const { shell, remote } = require('electron')
const { dialog } = remote
import { SketchPicker } from 'react-color'
class UnstyledFolderItem extends React.Component {
constructor (props) {
super(props)
this.state = {
status: 'IDLE',
folder: {
showColumnPicker: false,
colorPickerPos: { left: 0, top: 0 },
color: props.color,
name: props.name
}
}
}
handleEditChange (e) {
let { folder } = this.state
folder.name = this.refs.nameInput.value
this.setState({
folder
})
}
handleConfirmButtonClick (e) {
this.confirm()
}
confirm () {
let { storage, folder } = this.props
dataApi
.updateFolder(storage.key, folder.key, {
color: this.state.folder.color,
name: this.state.folder.name
})
.then((data) => {
store.dispatch({
type: 'UPDATE_FOLDER',
storage: data.storage
})
this.setState({
status: 'IDLE'
})
})
}
handleColorButtonClick (e) {
const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } })
this.setState({ folder }, function () {
// After the color picker has been painted, re-calculate its position
// by comparing its dimensions to the host dimensions.
const { hostBoundingBox } = this.props
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
const colorPickerBox = colorPickerNode.getBoundingClientRect()
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom
const folder = Object.assign({}, this.state.folder, {
colorPickerPos: {
left: 25,
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
}
})
this.setState({ folder })
})
}
handleColorChange (color) {
const folder = Object.assign({}, this.state.folder, { color: color.hex })
this.setState({ folder })
}
handleColorPickerClose (event) {
const folder = Object.assign({}, this.state.folder, { showColumnPicker: false })
this.setState({ folder })
}
handleCancelButtonClick (e) {
this.setState({
status: 'IDLE'
})
}
handleFolderItemBlur (e) {
let el = e.relatedTarget
while (el != null) {
if (el === this.refs.root) {
return false
}
el = el.parentNode
}
this.confirm()
}
renderEdit (e) {
const popover = { position: 'absolute', zIndex: 2 }
const cover = {
position: 'fixed',
top: 0,
right: 0,
bottom: 0,
left: 0
}
const pickerStyle = Object.assign({}, {
position: 'absolute'
}, this.state.folder.colorPickerPos)
return (
<div styleName='folderList-item'
onBlur={(e) => this.handleFolderItemBlur(e)}
tabIndex='-1'
ref='root'
>
<div styleName='folderList-item-left'>
<button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}}
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
>
{this.state.folder.showColumnPicker
? <div style={popover}>
<div style={cover}
onClick={() => this.handleColorPickerClose()}
/>
<div style={pickerStyle}>
<SketchPicker
ref='colorPicker'
color={this.state.folder.color}
onChange={(color) => this.handleColorChange(color)}
onChangeComplete={(color) => this.handleColorChange(color)}
/>
</div>
</div>
: null
}
<i className='fa fa-square' />
</button>
<input styleName='folderList-item-left-nameInput'
value={this.state.folder.name}
ref='nameInput'
onChange={(e) => this.handleEditChange(e)}
/>
</div>
<div styleName='folderList-item-right'>
<button styleName='folderList-item-right-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)}
>
Confirm
</button>
<button styleName='folderList-item-right-button'
onClick={(e) => this.handleCancelButtonClick(e)}
>
Cancel
</button>
</div>
</div>
)
}
handleDeleteConfirmButtonClick (e) {
let { storage, folder } = this.props
dataApi
.deleteFolder(storage.key, folder.key)
.then((data) => {
store.dispatch({
type: 'DELETE_FOLDER',
storage: data.storage,
folderKey: data.folderKey
})
})
}
renderDelete () {
return (
<div styleName='folderList-item'>
<div styleName='folderList-item-left'>
Are you sure to <span styleName='folderList-item-left-danger'>delete</span> this folder?
</div>
<div styleName='folderList-item-right'>
<button styleName='folderList-item-right-dangerButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
>
Confirm
</button>
<button styleName='folderList-item-right-button'
onClick={(e) => this.handleCancelButtonClick(e)}
>
Cancel
</button>
</div>
</div>
)
}
handleEditButtonClick (e) {
let { folder: propsFolder } = this.props
let { folder: stateFolder } = this.state
const folder = Object.assign({}, stateFolder, propsFolder)
this.setState({
status: 'EDIT',
folder
}, () => {
this.refs.nameInput.select()
})
}
handleDeleteButtonClick (e) {
this.setState({
status: 'DELETE'
})
}
renderIdle () {
let { folder } = this.props
return (
<div styleName='folderList-item'
onDoubleClick={(e) => this.handleEditButtonClick(e)}
>
<div styleName='folderList-item-left'
style={{borderColor: folder.color}}
>
<span styleName='folderList-item-left-name'>{folder.name}</span>
<span styleName='folderList-item-left-key'>({folder.key})</span>
</div>
<div styleName='folderList-item-right'>
<button styleName='folderList-item-right-button'
onClick={(e) => this.handleEditButtonClick(e)}
>
Edit
</button>
<button styleName='folderList-item-right-button'
onClick={(e) => this.handleDeleteButtonClick(e)}
>
Delete
</button>
</div>
</div>
)
}
render () {
switch (this.state.status) {
case 'DELETE':
return this.renderDelete()
case 'EDIT':
return this.renderEdit()
case 'IDLE':
default:
return this.renderIdle()
}
}
}
const FolderItem = CSSModules(UnstyledFolderItem, styles)
class StorageItem extends React.Component {
constructor (props) {
@@ -298,8 +46,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) {
@@ -349,13 +97,7 @@ class StorageItem extends React.Component {
render () {
let { storage, hostBoundingBox } = this.props
let folderList = storage.folders.map((folder) => {
return <FolderItem key={folder.key}
folder={folder}
storage={storage}
hostBoundingBox={hostBoundingBox}
/>
})
return (
<div styleName='root' key={storage.key}>
<div styleName='header'>
@@ -404,12 +146,9 @@ class StorageItem extends React.Component {
</button>
</div>
</div>
<div styleName='folderList'>
{folderList.length > 0
? folderList
: <div styleName='folderList-empty'>No Folders</div>
}
</div>
<FolderList storage={storage}
hostBoundingBox={hostBoundingBox}
/>
</div>
)
}
@@ -426,11 +165,6 @@ StorageItem.propTypes = {
}),
storage: PropTypes.shape({
key: PropTypes.string
}),
folder: PropTypes.shape({
key: PropTypes.string,
color: PropTypes.string,
name: PropTypes.string
})
}

View File

@@ -63,75 +63,6 @@
z-index 10
white-space nowrap
.folderList-item
height 35px
box-sizing border-box
padding 2.5px 15px
&:hover
background-color darken(white, 3%)
.folderList-item-left
height 30px
border-left solid 2px transparent
padding 0 10px
line-height 30px
float left
.folderList-item-left-danger
color $danger-color
font-weight bold
.folderList-item-left-key
color $ui-inactive-text-color
font-size 10px
margin 0 5px
border none
.folderList-item-left-colorButton
colorDefaultButton()
height 25px
width 25px
line-height 23px
padding 0
box-sizing border-box
vertical-align middle
border $ui-border
border-radius 2px
margin-right 5px
margin-left -15px
.folderList-item-left-nameInput
height 25px
box-sizing border-box
vertical-align middle
border $ui-border
border-radius 2px
padding 0 5px
outline none
.folderList-item-right
float right
.folderList-item-right-button
vertical-align middle
height 25px
margin-top 2.5px
colorDefaultButton()
border-radius 2px
border $ui-border
margin-right 5px
padding 0 5px
&:last-child
margin-right 0
.folderList-item-right-confirmButton
@extend .folderList-item-right-button
border none
colorPrimaryButton()
.folderList-item-right-dangerButton
@extend .folderList-item-right-button
border none
colorDangerButton()
body[data-theme="dark"]
.header
border-color $ui-dark-borderColor
@@ -153,28 +84,3 @@ body[data-theme="dark"]
top 25px
z-index 10
white-space nowrap
.folderList-item
&:hover
background-color lighten($ui-dark-button--hover-backgroundColor, 5%)
.folderList-item-left-danger
color $danger-color
font-weight bold
.folderList-item-left-key
color $ui-dark-inactive-text-color
.folderList-item-left-colorButton
colorDarkDefaultButton()
border-color $ui-dark-borderColor
.folderList-item-right-button
colorDarkDefaultButton()
border-color $ui-dark-borderColor
.folderList-item-right-confirmButton
colorDarkPrimaryButton()
.folderList-item-right-dangerButton
colorDarkDangerButton()

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

@@ -36,6 +36,7 @@ class UiTab extends React.Component {
const newConfig = {
ui: {
theme: this.refs.uiTheme.value,
showCopyNotification: this.refs.showCopyNotification.checked,
disableDirectWrite: this.refs.uiD2w != null
? this.refs.uiD2w.checked
: false
@@ -60,7 +61,7 @@ class UiTab extends React.Component {
const newCodemirrorTheme = this.refs.editorTheme.value
if (newCodemirrorTheme !== codemirrorTheme) {
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme}.css`)
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
}
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme })
@@ -90,9 +91,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)}
@@ -103,6 +103,16 @@ class UiTab extends React.Component {
</select>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.showCopyNotification}
ref='showCopyNotification'
type='checkbox'
/>&nbsp;
Show &quot;Saved to Clipboard&quot; notification when copying
</label>
</div>
{
global.process.platform === 'win32'
? <div styleName='group-checkBoxSection'>
@@ -192,7 +202,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 +210,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 +228,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 +281,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

@@ -4,6 +4,7 @@ import { connect } from 'react-redux'
import HotkeyTab from './HotkeyTab'
import UiTab from './UiTab'
import InfoTab from './InfoTab'
import Crowdfunding from './Crowdfunding'
import StoragesTab from './StoragesTab'
import ModalEscButton from 'browser/components/ModalEscButton'
import CSSModules from 'browser/lib/CSSModules'
@@ -64,6 +65,10 @@ class Preferences extends React.Component {
config={config}
/>
)
case 'CROWDFUNDING':
return (
<Crowdfunding />
)
case 'STORAGES':
default:
return (
@@ -94,7 +99,8 @@ class Preferences extends React.Component {
{target: 'STORAGES', label: 'Storages'},
{target: 'HOTKEY', label: 'Hotkey'},
{target: 'UI', label: 'UI'},
{target: 'INFO', label: 'Info'}
{target: 'INFO', label: 'Info'},
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
]
let navButtons = tabs.map((tab) => {

View File

@@ -346,6 +346,13 @@ function data (state = defaultDataMap(), action) {
state.storageMap.set(action.storage.key, action.storage)
}
return state
case 'REORDER_FOLDER':
{
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
}
return state
case 'DELETE_FOLDER':
{
state = Object.assign({}, state)

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,10 +150,13 @@ 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
@@ -260,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,10 +1,11 @@
# 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
* node: 7.x
You should use `npm v4.x` because `$ grand pre-build` fails on `v5.x`.
You should use `npm v4.x` because `$ grunt pre-build` fails on `v5.x`.
## Development
@@ -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), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/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,42 +1,50 @@
# Build
## 환경
* npm: 4.x
* node: 7.x
`$ grunt pre-build``npm v5.x`에서 실행할 수 없기 때문에, 반드시 `npm v4.x`를 사용하셔야 합니다.
## 개발
Webpack HRM을 개발을 위해 사용합니다.
다음 명령을 통해 저희가 해둔 설정을 사용 할 수 있습니다.
개발에 있어서 Webpack HRM을 사용합니다.
다음과 같은 명령을 프로젝트 디렉토리에서 실행하면, 기본 설정을 사용 할 수 있습니다.
먼저, yarn을 이용해서 필요한 패키지들을 설치합니다.
```
yarn run webpack
$ yarn
```
몇 초 후, 다음 메세지를 보게 될겁니다.
그 다음, 아래의 명령으로 빌드를 끝내고 자동적으로 어플리케이션을 실행합니다.
```
webpack: bundle is now VALID.
$ yarn run dev-start
```
그럼 앱을 실행합시다.
이 명령은 `yarn run webpack``yarn run hot`을 동시에 실행합니다. 이는 두개의 터미널에서 각각의 명령을 동시에 실행하는 것과 같습니다.
```
yarn run hot
```
`Webpack`은 코드의 변화를 자동으로 탐지하여 적용시키는 역할을 합니다.
> 원래 앱은 `yarn start`로 실행가능합니다. 하지만 이 경우, 컴파일된 스크립트를 사용할 것입니다.
만약, `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)
> ### 주의
> 가끔 직접 리프레쉬를 해주어야 하는 경우가 있습니다.
> 1. 콤포넌트의 컨스트럭터 함수를 수정할 경우
> 1. 콤포넌트의 컨스트럭터 함수를 수정할 경우
> 2. 새로운 CSS코드를 추가할 경우(1.과 같은 이유: CSS클래스는 콤포넌트마다 다시 만들어 지는데, 이 작업은 컨스트럭터에서 일어납니다.)
## 배포
그런트를 사용합니다.
실제 디플로이`grunt`로 실행할 수 있습니다. 하지만, 여기엔 Codesign과 Authenticode의 과정이 포함되어있기 때문에 사용 하셔선 안됩니다.
Boostnote에서는 배포 자동화를 위하여 그런트를 사용합니다.
실제 배포`grunt`로 실행할 수 있습니다. 하지만, 여기엔 Codesign과 Authenticode의 과정이 포함되어있기 때문에 사용 하셔선 안됩니다.
그래서, 실행파일만을 만드는 스크립트를 준비해 뒀습니다.
이 빌드는 npm v5.3.0에서는 작동하지 않습니다. 그러므로, 성공적으로 빌드하기 위해서는 v5.2.0을 사용해야 합니다.
```
grunt pre-build
```

21
docs/ko/debug.md Normal file
View File

@@ -0,0 +1,21 @@
# Boostnote의 디버그 방법(Electron app)
Boostnote는 Electron 애플리케이션이므로 Chromium위에서 작동합니다. 그렇기 때문에 개발자분들은 Google Chrome 브라우저에서 처럼 `Developer Tools`를 사용하실 수 있습니다.
다음과 같이 `Developer Tools`를 실행할 수 있습니다:
![how_to_toggle_devTools](https://cloud.githubusercontent.com/assets/11307908/24343585/162187e2-127c-11e7-9c01-23578db03ecf.png)
`Developer Tools`는 다음과 같이 나타납니다:
![Developer_Tools](https://cloud.githubusercontent.com/assets/11307908/24343545/eff9f3a6-127b-11e7-94cf-cb67bfda634a.png)
에러가 발생할 때에는, 에러메시지가 `console`위에 표시 됩니다.
## 디버깅
예를들면 `debugger`를 사용하여 코드 안에서 다음과 같이 일시 정지지점을 설정할 수 있습니다:
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png)
이는 단순한 하나의 예시에 불과합니다. 자기자신에게 가장 잘 맞는 디버그 방법을 찾는 것도 좋을 것 입니다.
## 참고
* [디버그에 관한 Google Chrome의 공식 문서](https://developer.chrome.com/devtools)

View File

@@ -4,7 +4,7 @@
* npm: 4.x
* node: 7.x
Вы должны использовать `npm v4.x`, так как `$ grand pre-build` не работает в `v5.x`.
Вы должны использовать `npm v4.x`, так как `$ grunt pre-build` не работает в `v5.x`.
## Разработка
@@ -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
```

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