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

Compare commits

...

163 Commits

Author SHA1 Message Date
Masahide Morio
9d98f0cb03 for v0.9 2018-02-04 05:51:39 +09:00
Masahide Morio
3503233631 Merge remote-tracking branch 'upstream/master' 2018-02-04 05:40:24 +09:00
Masahide Morio
c39393c453 for Windows 32bit 2018-02-04 05:40:15 +09:00
Junyoung Choi
8e1bf48cd1 Close immediately on windows 2018-02-04 00:59:54 +09:00
Junyoung Choi
8dd82e1a3b Disable uglify 2018-02-04 00:12:38 +09:00
Junyoung Choi
4418bfe965 v0.9.0 2018-02-03 23:50:49 +09:00
Junyoung Choi
39c4d710bc Remove unnecessary logging 2018-02-03 23:45:46 +09:00
Junyoung Choi (Sai)
51a8c47afd Discard finder (#1497)
* Discard finder

* Upgrade electron

* Discard anything related with finder

* Fix lint errors

* Run test serial

* Test on v6

* Test on v6 only
2018-02-03 23:39:53 +09:00
Kazz Yokomizo
922570bb5c Merge pull request #1495 from BoostIO/fix-todo-style
Fix percentage bar in markdown preview
2018-02-03 16:32:37 +09:00
Kazu Yokomizo
ca282d5635 Fix percentage bar in markdown preview 2018-02-03 16:32:02 +09:00
Junyoung Choi
ff67043210 Bump version 2018-02-03 15:52:51 +09:00
Junyoung Choi
31da231c1c Rollback .travis.yml 2018-02-03 15:28:19 +09:00
Junyoung Choi
eb698a7430 Fix lint error 2018-02-03 15:10:16 +09:00
Junyoung Choi
03be809ba9 Text stable version only
The current version of boostnote doesn't support the latest stable version, v9.x
2018-02-03 15:03:33 +09:00
Junyoung Choi
69601bf15a Fix but: line numbers of editor isnt applied properly 2018-02-03 15:02:26 +09:00
Junyoung Choi
7cad3d403b Discard unnecessary logging 2018-02-03 15:00:06 +09:00
Junyoung Choi (Sai)
cc84af3346 Merge pull request #1458 from cyalins/cyalins-patch-1
Improved overall wording and grammar of preferences
2018-02-03 14:27:55 +09:00
Junyoung Choi
33ef54a162 Fix lint errors 2018-02-03 14:16:26 +09:00
Junyoung Choi (Sai)
ac43ff886a Merge pull request #1494 from Paalon/katex_update
Update KaTeX library from 0.7.1 to 0.8.3
2018-02-03 13:57:30 +09:00
Junyoung Choi (Sai)
a64f73ca0c Merge pull request #879 from xxdavid/feature-editor-line-lines
Make line numbers in the editor optional
2018-02-03 13:57:00 +09:00
Paalon
314477d2fc Update yarn.lock 2018-02-02 17:01:15 +09:00
Paalon
c63fc93daa Update katex from 0.7.1 to 0.8.3. 2018-02-02 16:45:33 +09:00
Kazz Yokomizo
2183c4bda6 Merge pull request #1480 from allejo/feature-preview-scroll
Allow preview area to scroll past end of file
2018-01-31 18:01:32 +09:00
Kazz Yokomizo
6ba91c1515 Merge pull request #1472 from Antogin/feat/add-ability-to-clone-note
Feat/add ability to clone note
2018-01-31 17:55:46 +09:00
Vladimir Jimenez
d0559c16b5 Pass correct preview prop to MarkdownEditor 2018-01-30 07:42:16 -08:00
Kazz Yokomizo
eb693f7b48 Merge pull request #1481 from andyklimczak/1476-tag-check
Remove leading # when creating tag
2018-01-30 21:50:12 +09:00
Georges Indrianjafy
ef5639ff4b chore: removing space 2018-01-30 11:56:59 +02:00
Georges Indrianjafy
34a335797c chore: remove trailing space 2018-01-30 11:49:39 +02:00
Georges Indrianjafy
edda3a4d23 chore: cleanup 2018-01-30 11:45:47 +02:00
Georges Indrianjafy
184839423f fix(NoteList): remove router 2018-01-30 10:37:58 +02:00
Kazz Yokomizo
c0f3600a52 Merge pull request #1484 from BoostIO/fix-#1477
Fix #1477
2018-01-30 01:00:13 -05:00
Kazu Yokomizo
2b1302aa07 Fix #1477 2018-01-30 14:38:53 +09:00
Andy Klimczak
470c071344 Remove leading # when creating tag
- Remove leading # from tags that are created with leading #
- Convenience method for users who tend to type them, but did not want a
tag with double #
- If a user wants a tag with a leading #, then can double it (ie: ##OfPeople)
2018-01-29 20:42:59 -05:00
Vladimir Jimenez
4bd639c6c4 Allow preview area to scroll past end of file 2018-01-29 17:11:42 -08:00
Georges Indrianjafy
4cb7e63421 chore: clean up 2018-01-29 09:25:01 +02:00
Georges Indrianjafy
9f14a503d8 fix(history): use hashHistory 2018-01-29 09:24:12 +02:00
Georges Indrianjafy
d5da6de86c fix(NoteList): fix router issue 2018-01-29 09:13:09 +02:00
Georges Indrianjafy
4c2b233722 fix: resolve ci issues 2018-01-27 17:45:50 +02:00
Georges Indrianjafy
ca5b1eea13 chore: removing console.log 2018-01-27 17:33:22 +02:00
Georges Indrianjafy
614e9b6d55 feat(NoteList): add ability to clone note 2018-01-27 14:16:29 +02:00
cyalins
27b2530b8d Undoing an accidental edit 2018-01-26 21:12:10 +11:00
Kazz Yokomizo
2259167200 Merge pull request #1464 from BoostIO/fix-editor-lock-btn
Fix the editor lock button layout
2018-01-25 13:38:33 -05:00
Kazu Yokomizo
2e05214828 Fix the editor lock button layout 2018-01-25 13:30:36 -05:00
Kazz Yokomizo
cfb996039b Merge pull request #1453 from BoostIO/fix-info-right-layout
WIP - Fix info right buttons layout
2018-01-25 09:37:24 -05:00
Kazz Yokomizo
44c4d56214 Merge pull request #1457 from stevequinn/1238
Allows keyboard text selection in Finder search box
2018-01-25 09:34:59 -05:00
cyalins
8e6be91f7c Improved consistency of wording in Storage tab 2018-01-25 13:57:15 +11:00
cyalins
00816fb2c8 Improved clarity of wording in UI Tab 2018-01-25 13:56:11 +11:00
cyalins
535356b77f Improved clarity of tab titles 2018-01-25 13:53:50 +11:00
cyalins
5e558746ce Reworded Hotkey preferences 2018-01-25 13:52:04 +11:00
cyalins
f7bd52ac0c Improved clarity of crowdfunding message 2018-01-25 13:46:08 +11:00
cyalins
9165f518a9 Improved wording of data collection 2018-01-25 13:41:24 +11:00
Steve Quinn
01605aa221 Allows keyboard text selection in Finder search box 2018-01-25 13:27:43 +11:00
cyalins
8b0b29c424 Improved clarity of wording 2018-01-25 13:27:02 +11:00
Kazu Yokomizo
7a116966fa Add tooltip to full-screen-btn on the snippet note detail 2018-01-24 16:16:14 -05:00
Kazu Yokomizo
e7e8f11a74 Fix tooltips position 2018-01-24 16:08:44 -05:00
Kazu Yokomizo
f235d832d5 Fix note detail layout 2018-01-24 16:03:50 -05:00
Kazu Yokomizo
7730b5e20b Change size of icons on note detail 2018-01-24 15:39:27 -05:00
Kazu Yokomizo
8c3ba4ce48 Fix infopanel 2018-01-24 15:26:45 -05:00
Kazu Yokomizo
e9a126f586 Fix multiple colors 2018-01-24 15:19:13 -05:00
Kazu Yokomizo
097e7d2ff2 Fix tooltip 2018-01-24 15:13:45 -05:00
Kazu Yokomizo
81265f1238 Reorder of buttons 2018-01-23 21:02:19 -05:00
Kazu Yokomizo
2b507e6e20 Zoom button to display none 2018-01-23 20:57:57 -05:00
Kazu Yokomizo
747d3a8f13 Fix note detail width 2018-01-23 20:55:29 -05:00
Kazu Yokomizo
30f6f07434 Fix info right buttons layout 2018-01-23 20:50:48 -05:00
Kazz Yokomizo
6de5488a15 Merge pull request #1451 from BoostIO/update-readme
Update readme
2018-01-23 11:50:32 -05:00
Kazz Yokomizo
5413647166 Update readme 2018-01-23 11:46:22 -05:00
Kazz Yokomizo
e83fe73b18 Merge pull request #1271 from mslourens/utf8-uml
convert uml to utf8 before converting to base64
2018-01-17 09:47:56 -05:00
Kazz Yokomizo
87a289ec65 Merge pull request #1429 from yamash723/fix-markdown-list-link-style
Remove inline-style from markdown link list
2018-01-17 09:33:51 -05:00
yamash723
8a0a118dba Remove inline-style from markdown link list 2018-01-17 16:32:43 +09:00
Masahide Morio
564cc80ef7 for Windows 32bit 2018-01-17 01:01:45 +09:00
Kazz Yokomizo
687126ce87 Merge pull request #1419 from mslourens/html-export-encoding
added encoding meta tag
2018-01-16 08:06:30 -05:00
Kazz Yokomizo
8a05d577da Merge pull request #1425 from BoostIO/fixed-save-button
make save button position:fixed
2018-01-16 08:05:08 -05:00
Kazz Yokomizo
4c3ebfc0f8 Merge pull request #1418 from mslourens/import-note-name
name note to imported file
2018-01-16 08:04:25 -05:00
Sosuke Suzuki
6093f25f9a make save button position:fixed 2018-01-16 19:37:01 +09:00
Maurits Lourens
ecab68d676 removed unused imports 2018-01-16 10:09:02 +01:00
Masahide.MORIO
77f7144fbf test 2018-01-16 10:01:20 +09:00
Maurits Lourens
1cb4f37c95 moved the import outside the use config section 2018-01-15 22:27:38 +01:00
Maurits Lourens
14318528b9 added encoding meta tag 2018-01-15 22:18:23 +01:00
Maurits Lourens
9c0e1f8f1a name note to imported file 2018-01-15 21:33:17 +01:00
Kazz Yokomizo
2034ce9e4d Merge pull request #1416 from mslourens/copy-note-link
added button for copy note link
2018-01-15 15:17:17 -05:00
Maurits Lourens
657489caf6 added button for copy note link 2018-01-15 21:01:18 +01:00
Kazz Yokomizo
94be3d1fe5 Merge pull request #1413 from BoostIO/fix-folded-layout
Fix the folded layout on side bar
2018-01-15 08:39:32 -05:00
Kazz Yokomizo
f6eae41cee Merge pull request #1412 from BoostIO/fix-1409
Fix the Solarized Dark Styling
2018-01-15 08:34:04 -05:00
Kazu Yokomizo
69c64434e3 Fix the folded layout on side bar 2018-01-15 08:33:23 -05:00
Kazz Yokomizo
256cabfce1 Merge pull request #1411 from BoostIO/change-ctrl-icon-windows
Change the control icon on Windows
2018-01-15 08:26:41 -05:00
Kazu Yokomizo
e8b8272cf9 Fix #1409 - Fix the Solarized Dark Styling 2018-01-15 08:22:02 -05:00
Kazu Yokomizo
bd5ab4881c Change the control icon on Windows 2018-01-15 07:57:36 -05:00
Junyoung Choi (Sai)
9630744bdb Merge pull request #1395 from BoostIO/feature-v0-8-20
v0.8.20
2018-01-13 19:43:49 +09:00
Junyoung Choi
ab3ad0eb97 v0.8.20 2018-01-13 19:01:53 +09:00
Junyoung Choi (Sai)
2393889028 Merge pull request #1394 from BoostIO/fix-export-folder-test
Fix exportFolder error
2018-01-13 18:58:45 +09:00
Junyoung Choi
c36ecb1ed1 Fix exportFolder error
writeFileSync doesn't require any errors
2018-01-13 18:48:34 +09:00
Junyoung Choi (Sai)
3e9b28ff0c Merge pull request #1384 from BoostIO/fix-background-color-bug
fix bug happen on solarized-dark CreateFolderModal.
2018-01-13 18:12:59 +09:00
Junyoung Choi (Sai)
d4eec461a9 Merge pull request #1393 from BoostIO/fix-update
Fix windows update
2018-01-13 18:00:44 +09:00
Junyoung Choi (Sai)
b584f37087 Merge pull request #1392 from BoostIO/cm-close-brackets
Cm close brackets
2018-01-13 18:00:12 +09:00
Junyoung Choi
5c75644db2 Check upate every day rather than every hour 2018-01-13 17:36:33 +09:00
Junyoung Choi
72d9e3e00b Fix update error
This error was caused by using super old version of
electron-winstaller.
2018-01-13 17:35:23 +09:00
Sosuke Suzuki
b3d3362f34 set autoCloseBrackets to cm defaultConfigurations 2018-01-13 15:37:14 +09:00
Sosuke Suzuki
1cbaf55cee import the addon for autoCloseBrackets 2018-01-13 15:10:18 +09:00
Sosuke Suzuki
7771875d57 Change CreateFolderInput style from raw color code to variable 2018-01-13 11:37:47 +09:00
Kazz Yokomizo
85937d8e23 Merge pull request #1324 from mslourens/confirmation-dialog
show delete confirmation dialog
2018-01-12 09:37:05 +09:00
Sosuke Suzuki
1480986a3a change submit button style 2018-01-11 18:34:00 +09:00
Sosuke Suzuki
bc968736df add colors for SolarizedDark 2018-01-11 18:30:57 +09:00
Kazz Yokomizo
ad80b8e8b6 Merge pull request #1379 from BoostIO/destroy-initmodal
remove initmodal
2018-01-10 23:36:35 +09:00
Sosuke Suzuki
d44d220c55 change default storage name from debugging to production 2018-01-10 19:40:04 +09:00
Sosuke Suzuki
f6bcef0789 remove unnecessary variables 2018-01-10 19:36:45 +09:00
Sosuke Suzuki
a28ec752e8 remove InitModalComponent 2018-01-10 19:36:13 +09:00
Sosuke Suzuki
48ea5746d9 move init function to Main.js 2018-01-10 19:33:58 +09:00
Kazz Yokomizo
f473d31970 Merge pull request #1368 from marcusstenbeck/patch-1
fix(code-editor): use correct prop for font family
2018-01-05 19:06:57 +09:00
Marcus Stenbeck
9cd1d4b466 fix(code-editor): use correct prop for font family 2018-01-05 10:54:48 +01:00
Kazz Yokomizo
4f02065eaf Merge pull request #1354 from trinode/feature-elixir-support
Elixir syntax highlighting
2018-01-04 20:31:45 +09:00
Kazz Yokomizo
4b85e3e8fb Merge pull request #1363 from BoostIO/fix-note-disppaer-bug
fix bug when add tag, disappear note.
2018-01-04 19:57:40 +09:00
Sosuke Suzuki
e4e8c2205e splid update functions 2018-01-04 18:12:27 +09:00
Maurits Lourens
18649dd074 fixed lint errors 2018-01-04 08:32:42 +01:00
Maurits Lourens
9f9463f0e8 fixed review comments 2018-01-03 23:08:42 +01:00
Kazz Yokomizo
6cf9bc5de2 Merge pull request #1361 from BoostIO/fix-ci-errror
Fix CI error at #1147
2018-01-04 04:07:49 +09:00
Kazu Yokomizo
297b4346c5 Fix CI error at #1147 2018-01-04 03:51:49 +09:00
Kazz Yokomizo
767a203439 Merge pull request #1147 from mslourens/export-folder
export folder as md or text
2018-01-04 03:50:24 +09:00
Kazz Yokomizo
c564c253b1 Merge pull request #1302 from mslourens/open-finder
fixed opening finder on Windows and Linux
2018-01-04 03:39:14 +09:00
Anthony Graham
b4e138e21b Elixir Syntax Highlighting 2018-01-01 21:17:29 +00:00
Kazz Yokomizo
8ca01921c4 Merge pull request #1349 from BoostIO/fix-live-preview-indent-bug
fix bug that config of editorIndentSize is not working.
2017-12-30 16:38:58 +09:00
Sosuke Suzuki
c8b97ffde3 pass fontSize and indentSize to CodeEditor as props 2017-12-30 11:04:13 +09:00
Kazz Yokomizo
abc84e5710 Merge pull request #1347 from BoostIO/fix-solarized-dark-infopanel
fix style
2017-12-29 21:56:55 +09:00
Sosuke Suzuki
d732d195f3 fix style 2017-12-29 21:38:40 +09:00
Kazz Yokomizo
789975abb0 Merge pull request #1343 from BoostIO/fix-table-backgroud-color
add table style in solarized-dark-theme
2017-12-28 22:57:39 +09:00
Sosuke Suzuki
ed1eab7fcc add table style in solarized-dark-theme 2017-12-28 22:48:59 +09:00
Kazz Yokomizo
29b31c114a Merge pull request #1337 from BoostIO/update-readme
Update readme
2017-12-28 11:13:48 +09:00
Kazz Yokomizo
c8cf353c21 Merge pull request #1220 from PaulRosset/add-notification-onChangeUi
Add notification on change ui
2017-12-28 10:45:21 +09:00
Kazz Yokomizo
e82e2c71f1 Update readme 2017-12-28 10:44:07 +09:00
Kazz Yokomizo
dd729c406f Merge pull request #1256 from mslourens/export-html
first attempt to export html
2017-12-28 10:38:55 +09:00
Kazz Yokomizo
3127e85900 Merge pull request #1329 from robbawebba/fix/jump-notes-by-link
Fix linking to other notes
2017-12-26 11:51:51 +09:00
Kazz Yokomizo
304eeb3158 Merge pull request #1331 from ncaron/master
Fix starred item count
2017-12-26 11:41:28 +09:00
Niko Caron
bf781c6b50 Fix starred item count
* Deleting a starred item will now update the starred count in the side nav. (delete from starred set)
* Restoring a starred item will add it the the starred set again.

Fix: #1326
2017-12-23 18:17:48 -04:00
David Pavlík
da1098e441 Merge branch 'master' into feature-editor-line-lines 2017-12-23 23:06:26 +01:00
David Pavlík
85065357e2 Make the option to disable line numbers in editor affect snippet notes too 2017-12-23 22:52:50 +01:00
David Pavlík
1f5f6c3b0e Rename 'lineNumber' to 'displayLineNumbers' 2017-12-23 22:51:38 +01:00
Rob Weber
4f7026969f Fix linking to other notes 2017-12-23 01:33:03 -08:00
Kazz Yokomizo
16d264ebfa Merge pull request #1328 from BoostIO/fix-style
Fix style
2017-12-23 15:32:02 +09:00
Kazu Yokomizo
04ffbe945b Delete unnecessary style 2017-12-23 15:25:45 +09:00
Kazu Yokomizo
974a1a1e7d Fix style 2017-12-23 15:18:16 +09:00
Kazz Yokomizo
ca2c07244f Merge pull request #1321 from mslourens/tooltips
added tooltips like the new note button
2017-12-23 15:12:17 +09:00
Maurits Lourens
e262d2f19b fixed lint error 2017-12-21 19:15:34 +01:00
Maurits Lourens
aabfe820ac refactored to prevent duplicate code 2017-12-21 17:41:08 +01:00
Maurits Lourens
3bba5442bd show delete confirmation dialog 2017-12-21 15:57:27 +01:00
Maurits Lourens
6ce1922fb3 added tooltips like the new note button (and consequently extracted some buttons and styles) 2017-12-21 15:23:13 +01:00
Maurits Lourens
9367a404ef converted line-endings back to lf 2017-12-21 09:37:17 +01:00
Maurits Lourens
775200bdd6 fixed opening finder on Windows and Linux
fixes #1291 - tested on Ubuntu Linux Mate and Windows 10
2017-12-15 12:57:18 +01:00
Paul Rosset
795fe8ae1d Add isEqual and changing haveToSaveNotif method 2017-12-14 12:26:05 +00:00
Maurits Lourens
6fba62d062 fixed review comments 2017-12-13 17:20:22 +01:00
Maurits Lourens
6906c0ab0d changed var into const 2017-12-13 17:16:28 +01:00
Maurits Lourens
5d46adf8fd fixed review comments 2017-12-13 17:11:43 +01:00
Maurits Lourens
4cdfc738c0 forgot to run eslint (again) 2017-12-11 15:14:02 +01:00
Maurits Lourens
46d46f21e4 convert uml to utf8 before converting to base64 2017-12-11 15:02:42 +01:00
Maurits Lourens
8c8a0ab46d forgot to run lint 2017-12-08 16:21:31 +01:00
Maurits Lourens
959b75bddd export folder as md or text 2017-12-08 16:21:31 +01:00
Maurits Lourens
6a9d4ae0fd first attempt to export html 2017-12-08 11:43:50 +01:00
Paul Rosset
cb59458c79 refactor 2017-12-05 18:28:59 +00:00
Paul Rosset
125a493400 Change name for state 2017-12-05 18:24:30 +00:00
Paul Rosset
83910b55d2 Correction eslint code format 2017-12-05 18:18:12 +00:00
Paul Rosset
f4fd131100 Requested Review 2017-12-05 18:16:42 +00:00
Paul Rosset
d1e5781c24 Correction UiTab 2017-12-01 19:03:04 +00:00
Paul Rosset
c86e451198 Merge branch 'master' of github.com:BoostIO/Boostnote into add-notification-onChangeUi 2017-12-01 17:34:16 +00:00
Paul Rosset
b4a7b547f0 Add notification when not saved 2017-12-01 15:23:23 +00:00
David Pavlík
7a4258bb20 Make line numbers in the editor optional 2017-09-20 22:34:18 +02:00
109 changed files with 2526 additions and 2645 deletions

View File

@@ -10,7 +10,6 @@
"theme": "monokai" "theme": "monokai"
}, },
"hotkey": { "hotkey": {
"toggleFinder": "Cmd + Alt + S",
"toggleMain": "Cmd + Alt + L" "toggleMain": "Cmd + Alt + L"
}, },
"isSideNavFolded": false, "isSideNavFolded": false,

2
.gitignore vendored
View File

@@ -8,5 +8,5 @@ node_modules/*
/compiled /compiled
/secret /secret
*.log *.log
.vscode
.idea .idea
.vscode

View File

@@ -1,7 +1,6 @@
language: node_js language: node_js
node_js: node_js:
- stable - 6
- lts/*
script: script:
- npm run lint && npm run test - 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' - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
@@ -18,3 +17,4 @@ deploy:
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq 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 && cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
skip_cleanup: true skip_cleanup: true

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import _ from 'lodash' import _ from 'lodash'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import path from 'path' import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage' import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
@@ -50,9 +51,10 @@ export default class CodeEditor extends React.Component {
componentDidMount () { componentDidMount () {
this.value = this.props.value this.value = this.props.value
this.editor = CodeMirror(this.refs.root, { this.editor = CodeMirror(this.refs.root, {
value: this.props.value, value: this.props.value,
lineNumbers: true, lineNumbers: this.props.displayLineNumbers,
lineWrapping: true, lineWrapping: true,
theme: this.props.theme, theme: this.props.theme,
indentUnit: this.props.indentSize, indentUnit: this.props.indentSize,
@@ -62,6 +64,7 @@ export default class CodeEditor extends React.Component {
scrollPastEnd: this.props.scrollPastEnd, scrollPastEnd: this.props.scrollPastEnd,
inputStyle: 'textarea', inputStyle: 'textarea',
dragDrop: false, dragDrop: false,
autoCloseBrackets: true,
extraKeys: { extraKeys: {
Tab: function (cm) { Tab: function (cm) {
const cursor = cm.getCursor() const cursor = cm.getCursor()
@@ -69,7 +72,7 @@ export default class CodeEditor extends React.Component {
if (cm.somethingSelected()) cm.indentSelection('add') if (cm.somethingSelected()) cm.indentSelection('add')
else { else {
const tabs = cm.getOption('indentWithTabs') const tabs = cm.getOption('indentWithTabs')
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)\] )?$/)) { if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
cm.execCommand('goLineStart') cm.execCommand('goLineStart')
if (tabs) { if (tabs) {
cm.execCommand('insertTab') cm.execCommand('insertTab')
@@ -154,6 +157,10 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space') this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
} }
if (prevProps.displayLineNumbers !== this.props.displayLineNumbers) {
this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
}
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) { if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd) this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
} }
@@ -228,7 +235,7 @@ export default class CodeEditor extends React.Component {
if (!dataTransferItem.type.match('image')) return if (!dataTransferItem.type.match('image')) return
const blob = dataTransferItem.getAsFile() const blob = dataTransferItem.getAsFile()
const reader = new FileReader() const reader = new window.FileReader()
let base64data let base64data
reader.readAsDataURL(blob) reader.readAsDataURL(blob)
@@ -249,7 +256,7 @@ export default class CodeEditor extends React.Component {
render () { render () {
const { className, fontSize } = this.props const { className, fontSize } = this.props
let fontFamily = this.props.className let fontFamily = this.props.fontFamily
fontFamily = _.isString(fontFamily) && fontFamily.length > 0 fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily) ? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily : defaultEditorFontFamily

View File

@@ -242,6 +242,7 @@ class MarkdownEditor extends React.Component {
fontSize={editorFontSize} fontSize={editorFontSize}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
@@ -260,7 +261,7 @@ class MarkdownEditor extends React.Component {
codeBlockFontFamily={config.editor.fontFamily} codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize} indentSize={editorIndentSize}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.preview.scrollPastEnd}
ref='preview' ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
tabIndex='0' tabIndex='0'

View File

@@ -3,6 +3,7 @@ import React from 'react'
import markdown from 'browser/lib/markdown' import markdown from 'browser/lib/markdown'
import _ from 'lodash' import _ from 'lodash'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import consts from 'browser/lib/consts' import consts from 'browser/lib/consts'
import Raphael from 'raphael' import Raphael from 'raphael'
import flowchart from 'flowchart' import flowchart from 'flowchart'
@@ -23,7 +24,7 @@ const appPath = 'file://' + (process.env.NODE_ENV === 'production'
? app.getAppPath() ? app.getAppPath()
: path.resolve()) : path.resolve())
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) { function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
return ` return `
@font-face { @font-face {
font-family: 'Lato'; font-family: 'Lato';
@@ -47,6 +48,7 @@ ${markdownStyle}
body { body {
font-family: '${fontFamily.join("','")}'; font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px; font-size: ${fontSize}px;
${scrollPastEnd && 'padding-bottom: 90vh;'}
} }
code { code {
font-family: '${codeBlockFontFamily.join("','")}'; font-family: '${codeBlockFontFamily.join("','")}';
@@ -118,6 +120,7 @@ export default class MarkdownPreview extends React.Component {
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e) this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText() this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd() this.saveAsMdHandler = () => this.handleSaveAsMd()
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.printHandler = () => this.handlePrint() this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handlelinkClick.bind(this) this.linkClickHandler = this.handlelinkClick.bind(this)
@@ -176,21 +179,29 @@ export default class MarkdownPreview extends React.Component {
this.exportAsDocument('md') this.exportAsDocument('md')
} }
handleSaveAsHtml () {
this.exportAsDocument('html', (value) => {
return this.refs.root.contentWindow.document.documentElement.outerHTML
})
}
handlePrint () { handlePrint () {
this.refs.root.contentWindow.print() this.refs.root.contentWindow.print()
} }
exportAsDocument (fileType) { exportAsDocument (fileType, formatter) {
const options = { const options = {
filters: [ filters: [
{ name: 'Documents', extensions: [fileType] } { name: 'Documents', extensions: [fileType] }
], ],
properties: ['openFile', 'createDirectory'] properties: ['openFile', 'createDirectory']
} }
const value = formatter ? formatter.call(this, this.props.value) : this.props.value
dialog.showSaveDialog(remote.getCurrentWindow(), options, dialog.showSaveDialog(remote.getCurrentWindow(), options,
(filename) => { (filename) => {
if (filename) { if (filename) {
fs.writeFile(filename, this.props.value, (err) => { fs.writeFile(filename, value, (err) => {
if (err) throw err if (err) throw err
}) })
} }
@@ -216,6 +227,7 @@ export default class MarkdownPreview extends React.Component {
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css"> <link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" id="codeTheme"> <link rel="stylesheet" id="codeTheme">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
` `
this.rewriteIframe() this.rewriteIframe()
this.applyStyle() this.applyStyle()
@@ -226,6 +238,7 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler) this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler) eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler) eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
eventEmitter.on('print', this.printHandler) eventEmitter.on('print', this.printHandler)
} }
@@ -237,6 +250,7 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler) this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
eventEmitter.off('export:save-text', this.saveAsTextHandler) eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler) eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
eventEmitter.off('print', this.printHandler) eventEmitter.off('print', this.printHandler)
} }
@@ -248,14 +262,15 @@ export default class MarkdownPreview extends React.Component {
prevProps.codeBlockTheme !== this.props.codeBlockTheme || prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
prevProps.lineNumber !== this.props.lineNumber || prevProps.lineNumber !== this.props.lineNumber ||
prevProps.showCopyNotification !== this.props.showCopyNotification || prevProps.showCopyNotification !== this.props.showCopyNotification ||
prevProps.theme !== this.props.theme) { prevProps.theme !== this.props.theme ||
prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
this.applyStyle() this.applyStyle()
this.rewriteIframe() this.rewriteIframe()
} }
} }
applyStyle () { applyStyle () {
const { fontSize, lineNumber, codeBlockTheme } = this.props const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
let { fontFamily, codeBlockFontFamily } = this.props let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0 fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily) ? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
@@ -265,7 +280,7 @@ export default class MarkdownPreview extends React.Component {
: defaultCodeBlockFontFamily : defaultCodeBlockFontFamily
this.setCodeTheme(codeBlockTheme) this.setCodeTheme(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber) this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
} }
setCodeTheme (theme) { setCodeTheme (theme) {

View File

@@ -45,6 +45,10 @@ class MarkdownSplitEditor extends React.Component {
render () { render () {
const { config, value, storageKey } = this.props const { config, value, storageKey } = this.props
const storage = findStorage(storageKey) const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {} const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none' if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
return ( return (
@@ -57,7 +61,10 @@ class MarkdownSplitEditor extends React.Component {
theme={config.editor.theme} theme={config.editor.theme}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize}
displayLineNumbers={config.editor.displayLineNumbers}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
onChange={this.handleOnChange.bind(this)} onChange={this.handleOnChange.bind(this)}
@@ -72,6 +79,7 @@ class MarkdownSplitEditor extends React.Component {
codeBlockTheme={config.preview.codeBlockTheme} codeBlockTheme={config.preview.codeBlockTheme}
codeBlockFontFamily={config.editor.fontFamily} codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd}
ref='preview' ref='preview'
tabInde='0' tabInde='0'
value={value} value={value}

View File

@@ -92,7 +92,6 @@ body[data-theme="white"]
color $ui-inactive-text-color color $ui-inactive-text-color
.menu-button--active .menu-button--active
@extend .menu-button
color #e74c3c color #e74c3c
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
.menu-button-label .menu-button-label
@@ -109,7 +108,6 @@ body[data-theme="white"]
color $ui-text-color color $ui-text-color
.menu-button-star--active .menu-button-star--active
@extend .menu-button
color #F9BF3B color #F9BF3B
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
.menu-button-label .menu-button-label
@@ -126,7 +124,6 @@ body[data-theme="white"]
color $ui-text-color color $ui-text-color
.menu-button-trash--active .menu-button-trash--active
@extend .menu-button
color #5D9E36 color #5D9E36
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
.menu-button-label .menu-button-label

View File

@@ -1,6 +1,6 @@
.percentageBar .percentageBar
position absolute position absolute
top 50px top 72px
right 0px right 0px
left 0px left 0px
background-color #DADFE1 background-color #DADFE1

View File

@@ -105,7 +105,6 @@ a
border-radius 5px border-radius 5px
margin -5px margin -5px
transition .1s transition .1s
display inline-block
img img
vertical-align sub vertical-align sub
&:hover &:hover
@@ -336,8 +335,29 @@ body[data-theme="dark"]
background-color themeDarkBorder background-color themeDarkBorder
color themeDarkText color themeDarkText
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
themeSolarizedDarkTableBorder = themeDarkBorder
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
border-color themeDarkBorder border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
table
thead
tr
background-color themeSolarizedDarkTableHead
th
border-color themeSolarizedDarkTableBorder
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeSolarizedDarkTableOdd
tr:nth-child(2n)
background-color themeSolarizedDarkTableEven
td
border-color themeSolarizedDarkTableBorder
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder

View File

@@ -1,156 +0,0 @@
$search-height = 50px
$nav-width = 175px
$list-width = 250px
.root
absolute top left right bottom
.search
height $search-height
padding 10px
box-sizing border-box
border-bottom $ui-border
text-align center
.search-input
height 30px
width 100%
margin 0 auto
font-size 18px
border none
outline none
text-align center
background-color transparent
.result
absolute left right bottom
top $search-height
background-color $ui-noteDetail-backgroundColor
.result-nav
user-select none
absolute left top bottom
width $nav-width
background-color $ui-backgroundColor
.result-nav-filter
margin-bottom 10px
.result-nav-filter-option
height 25px
line-height 25px
padding 0 10px
label
cursor pointer
.result-nav-menu
navButtonColor()
height 32px
padding 0 10px
font-size 14px
width 100%
outline none
text-align left
line-height 32px
box-sizing border-box
cursor pointer
.result-nav-menu--active
@extend .result-nav-menu
background-color $ui-button--active-backgroundColor
color $ui-button--active-color
&:hover
background-color $ui-button--active-backgroundColor
.result-nav-storageList
absolute bottom left right
top 110px + 32px + 10px + 10px + 20px
overflow-y auto
.result-list
user-select none
absolute top bottom
left $nav-width
width $list-width
box-sizing border-box
overflow-y auto
box-shadow 2px 0 15px -8px #b1b1b1
z-index 1
.result-detail
absolute top bottom right
left $nav-width + $list-width
background-color $ui-noteDetail-backgroundColor
body[data-theme="dark"]
.root
background-color $ui-dark-backgroundColor
.search
border-color $ui-dark-borderColor
.search-input
color $ui-dark-text-color
.result
background-color $ui-dark-noteList-backgroundColor
.result-nav
background-color $ui-dark-backgroundColor
label
color $ui-dark-text-color
.result-nav-menu
navDarkButtonColor()
.result-nav-menu--active
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-button--active-color
&:hover
background-color $ui-dark-button--active-backgroundColor
.result-list
border-color $ui-dark-borderColor
box-shadow none
top 0
.result-detail
absolute top bottom right
left $nav-width + $list-width
background-color $ui-dark-noteDetail-backgroundColor
body[data-theme="solarized-dark"]
.root
background-color $ui-solarized-dark-backgroundColor
.search
border-color $ui-solarized-dark-borderColor
.search-input
color $ui-dark-text-color
.result
background-color $ui-solarized-dark-backgroundColor
.result-nav
background-color $ui-solarized-dark-backgroundColor
label
color $ui-dark-text-color
.result-nav-menu
navDarkButtonColor()
.result-nav-menu--active
background-color $ui-solarized-dark-button-backgroundColor
color $ui-dark-button--active-color
&:hover
background-color $ui-dark-button--active-backgroundColor
.result-list
border-color $ui-solarized-dark-borderColor
box-shadow none
top 0
.result-detail
absolute top bottom right
left $nav-width + $list-width
background-color $ui-solarized-dark-backgroundColor

View File

@@ -1,210 +0,0 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './NoteDetail.styl'
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
const path = require('path')
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
function notify (title, options) {
if (global.process.platform === 'win32') {
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
}
return new window.Notification(title, options)
}
class NoteDetail extends React.Component {
constructor (props) {
super(props)
this.state = {
snippetIndex: 0
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.note !== this.props.note) {
this.setState({
snippetIndex: 0
}, () => {
if (nextProps.note.type === 'SNIPPET_NOTE') {
nextProps.note.snippets.forEach((snippet, index) => {
this.refs['code-' + index].reload()
})
}
})
}
}
selectPriorSnippet () {
const { note } = this.props
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
this.setState({
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
})
}
}
selectNextSnippet () {
const { note } = this.props
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
this.setState({
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
})
}
}
saveToClipboard () {
const { note } = this.props
if (note.type === 'MARKDOWN_NOTE') {
clipboard.writeText(note.content)
} else {
clipboard.writeText(note.snippets[this.state.snippetIndex].content)
}
notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!',
silent: true
})
}
handleTabButtonClick (e, index) {
this.setState({
snippetIndex: index
})
}
render () {
const { note, config } = this.props
if (note == null) {
return (
<div styleName='root' />
)
}
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const storage = findStorage(note.storage)
if (note.type === 'SNIPPET_NOTE') {
const tabList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
return <div styleName={isActive
? 'tabList-item--active'
: 'tabList-item'
}
key={index}
>
<button styleName='tabList-item-button'
onClick={(e) => this.handleTabButtonClick(e, index)}
>
{snippet.name.trim().length > 0
? snippet.name
: <span styleName='tabList-item-unnamed'>
Unnamed
</span>
}
</button>
</div>
})
const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return <div styleName='tabView'
key={index}
style={{zIndex: isActive ? 5 : 4}}
>
{snippet.mode === 'markdown'
? <MarkdownEditor styleName='tabView-content'
config={config}
value={snippet.content}
ref={'code-' + index}
storageKey={note.storage}
/>
: <CodeEditor styleName='tabView-content'
mode={snippet.mode}
value={snippet.content}
theme={config.editor.theme}
fontFamily={config.editor.fontFamily}
fontSize={editorFontSize}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
readOnly
ref={'code-' + index}
/>
}
</div>
})
return (
<div styleName='root'>
<div styleName='description'>
<textarea styleName='description-textarea'
style={{
fontFamily: config.preview.fontFamily,
fontSize: parseInt(config.preview.fontSize, 10)
}}
ref='description'
placeholder='Description...'
value={note.description}
readOnly
/>
</div>
<div styleName='tabList'>
{tabList}
</div>
{viewList}
</div>
)
}
return (
<MarkdownPreview styleName='root'
theme={config.ui.theme}
fontSize={config.preview.fontSize}
fontFamily={config.preview.fontFamily}
codeBlockTheme={config.preview.codeBlockTheme}
codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize}
value={note.content}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>
)
}
}
NoteDetail.propTypes = {
}
export default CSSModules(NoteDetail, styles)

View File

@@ -1,129 +0,0 @@
@import('../main/Detail/DetailVars.styl')
.root
absolute top bottom left right
bottom 30px
margin 0 25px
height 100%
width 365px
background-color $ui-noteDetail-backgroundColor
.description
absolute top left right
height 80px
box-sizing border-box
.description-textarea
display block
height 100%
width 100%
resize none
border none
padding 10px
line-height 1.6
box-sizing border-box
background-color $ui-noteDetail-backgroundColor
.tabList
absolute left right
top 80px
box-sizing border-box
height 30px
display flex
background-color $ui-noteDetail-backgroundColor
.tabList-item
position relative
flex 1
overflow hidden
&:hover
background-color $ui-button--hover-backgroundColorg
.tabList-item--active
@extend .tabList-item
border-bottom $ui-border
.tabList-item-button
width 100%
height 29px
overflow ellipsis
text-align left
padding-right 30px
padding-left 10px
border none
background-color transparent
transition 0.15s
&:hover
background-color $ui-button--hover-backgroundColor
.tabView
absolute left right bottom
top 130px
.tabView-content
absolute top left right bottom
box-sizing border-box
height 100%
width 100%
body[data-theme="dark"]
.root
background-color $ui-dark-noteDetail-backgroundColor
.description
border-color $ui-dark-borderColor
background-color $ui-dark-noteDetail-backgroundColor
.description-textarea
background-color $ui-dark-noteDetail-backgroundColor
color white
.tabList
background-color $ui-dark-noteDetail-backgroundColor
.tabList-item
border-color $ui-dark-borderColor
&:hover
background-color $ui-dark-button--hover-backgroundColor
.tabList-item-button
border none
color $ui-dark-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
&:hover
color white
background-color $ui-dark-button--hover-backgroundColor
body[data-theme="solarized-dark"]
.root
background-color $ui-solarized-dark-backgroundColor
.description
border-color $ui-dark-borderColor
background-color $ui-solarized-dark-backgroundColor
.description-textarea
background-color $ui-solarized-dark-backgroundColor
color white
.tabList
background-color $ui-solarized-dark-backgroundColor
.tabList-item
border-color $ui-dark-borderColor
&:hover
background-color $ui-dark-button--hover-backgroundColor
.tabList-item-button
border none
color $ui-dark-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
&:hover
color white
background-color $ui-dark-button--hover-backgroundColor

View File

@@ -1,90 +0,0 @@
import React from 'react'
import NoteItem from 'browser/components/NoteItem'
import moment from 'moment'
class NoteList extends React.Component {
constructor (props) {
super(props)
this.state = {
range: 0
}
}
componentWillReceiveProps (nextProps) {
if (this.props.search !== nextProps.search) {
this.resetScroll()
}
}
componentDidUpdate () {
const { index } = this.props
if (index > -1) {
const list = this.refs.root
const item = list.childNodes[index]
if (item == null) return null
const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
if (overflowBelow) {
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
}
const overflowAbove = list.scrollTop > item.offsetTop
if (overflowAbove) {
list.scrollTop = item.offsetTop
}
}
}
resetScroll () {
this.refs.root.scrollTop = 0
this.setState({
range: 0
})
}
handleScroll (e) {
const { notes } = this.props
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
this.setState({
range: this.state.range + 1
})
}
}
render () {
const { notes, index } = this.props
const notesList = notes
.slice(0, 10 + 10 * this.state.range)
.map((note, _index) => {
const isActive = (index === _index)
const key = `${note.storage}-${note.key}`
const dateDisplay = moment(note.updatedAt).fromNow()
return (
<NoteItem
isActive={isActive}
note={note}
dateDisplay={dateDisplay}
key={key}
handleNoteClick={(e) => this.props.handleNoteClick(e, _index)}
/>
)
})
return (
<div className={this.props.className}
onScroll={(e) => this.handleScroll(e)}
ref='root'
>
{notesList}
</div>
)
}
}
NoteList.propTypes = {
}
export default NoteList

View File

@@ -1,77 +0,0 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageSection.styl'
import StorageItem from 'browser/components/StorageItem'
class StorageSection extends React.Component {
constructor (props) {
super(props)
this.state = {
isOpen: true
}
}
handleToggleButtonClick (e) {
this.setState({
isOpen: !this.state.isOpen
})
}
handleHeaderClick (e) {
const { storage } = this.props
this.props.handleStorageButtonClick(e, storage.key)
}
handleFolderClick (e, folder) {
const { storage } = this.props
this.props.handleFolderButtonClick(e, storage.key, folder.key)
}
render () {
const { storage, filter } = this.props
const folderList = storage.folders
.map(folder => (
<StorageItem
key={folder.key}
isActive={filter.type === 'FOLDER' && filter.folder === folder.key && filter.storage === storage.key}
handleButtonClick={(e) => this.handleFolderClick(e, folder)}
folderName={folder.name}
folderColor={folder.color}
isFolded={false}
/>
))
return (
<div styleName='root'>
<div styleName='header'>
<button styleName='header-toggleButton'
onClick={(e) => this.handleToggleButtonClick(e)}
>
<i className={this.state.isOpen
? 'fa fa-caret-down'
: 'fa fa-caret-right'
}
/>
</button>
<button styleName={filter.type === 'STORAGE' && filter.storage === storage.key
? 'header-name--active'
: 'header-name'
}
onClick={(e) => this.handleHeaderClick(e)}
>{storage.name}</button>
</div>
{this.state.isOpen &&
<div styleName='folderList'>
{folderList}
</div>
}
</div>
)
}
}
StorageSection.propTypes = {
}
export default CSSModules(StorageSection, styles)

View File

@@ -1,85 +0,0 @@
.root
position relative
.header
height 26px
.header-toggleButton
absolute top left
width 25px
height 26px
navButtonColor()
border none
outline none
.header-name
display block
height 26px
navButtonColor()
padding 0 10px 0 25px
font-size 14px
width 100%
text-align left
line-height 26px
box-sizing border-box
cursor pointer
outline none
.header-name--active
@extend .header-name
background-color $ui-button--active-backgroundColor
color $ui-button--active-color
&:hover
background-color $ui-button--active-backgroundColor
.folderList-item
display block
width 100%
height 26px
navButtonColor()
padding 0 10px 0 25px
font-size 14px
width 100%
text-align left
line-height 26px
box-sizing border-box
cursor pointer
outline none
padding 0 10px
margin 2px 0
height 26px
line-height 26px
border-width 0 0 0 6px
border-style solid
border-color transparent
.folderList-item--active
@extend .folderList-item
background-color $ui-button--active-backgroundColor
color $ui-button--active-color
&:hover
background-color $ui-button--active-backgroundColor
body[data-theme="dark"]
.header-toggleButton
navDarkButtonColor()
.header-name
navDarkButtonColor()
.header-name--active
@extend .header-name
background-color $ui-button--active-backgroundColor
color $ui-button--active-color
&:hover
background-color $ui-button--active-backgroundColor
.folderList-item
navDarkButtonColor()
border-width 0 0 0 6px
border-style solid
border-color transparent
.folderList-item--active
@extend .folderList-item
background-color $ui-button--active-backgroundColor
color $ui-button--active-color
&:hover
background-color $ui-button--active-backgroundColor

View File

@@ -1,357 +0,0 @@
import PropTypes from 'prop-types'
import React from 'react'
import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux'
import _ from 'lodash'
import store from './store'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FinderMain.styl'
import StorageSection from './StorageSection'
import NoteList from './NoteList'
import NoteDetail from './NoteDetail'
import SideNavFilter from 'browser/components/SideNavFilter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
require('!!style!css!stylus?sourceMap!../main/global.styl')
require('../lib/customMeta')
require('./ipcClient.js')
const electron = require('electron')
const { remote } = electron
const { Menu } = remote
function hideFinder () {
const finderWindow = remote.getCurrentWindow()
if (global.process.platform === 'win32') {
finderWindow.blur()
finderWindow.hide()
}
if (global.process.platform === 'darwin') {
Menu.sendActionToFirstResponder('hide:')
}
remote.getCurrentWindow().hide()
}
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
class FinderMain extends React.Component {
constructor (props) {
super(props)
this.state = {
search: '',
index: 0,
filter: {
includeSnippet: true,
includeMarkdown: false,
type: 'ALL',
storage: null,
folder: null
}
}
this.focusHandler = (e) => this.handleWindowFocus(e)
this.blurHandler = (e) => this.handleWindowBlur(e)
}
componentDidMount () {
this.refs.search.focus()
window.addEventListener('focus', this.focusHandler)
window.addEventListener('blur', this.blurHandler)
}
componentWillUnmount () {
window.removeEventListener('focus', this.focusHandler)
window.removeEventListener('blur', this.blurHandler)
}
handleWindowFocus (e) {
this.refs.search.focus()
}
handleWindowBlur (e) {
this.setState({
search: ''
})
}
handleKeyDown (e) {
this.refs.search.focus()
if (e.keyCode === 9) {
if (e.shiftKey) {
this.refs.detail.selectPriorSnippet()
} else {
this.refs.detail.selectNextSnippet()
}
e.preventDefault()
}
if (e.keyCode === 38) {
this.selectPrevious()
e.preventDefault()
}
if (e.keyCode === 40) {
this.selectNext()
e.preventDefault()
}
if (e.keyCode === 13) {
this.refs.detail.saveToClipboard()
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('COPY_FINDER')
hideFinder()
e.preventDefault()
}
if (e.keyCode === 27) {
hideFinder()
e.preventDefault()
}
if (e.keyCode === 91 || e.metaKey) {
return
}
}
handleSearchChange (e) {
this.setState({
search: e.target.value,
index: 0
})
}
selectArticle (article) {
this.setState({currentArticle: article})
}
selectPrevious () {
if (this.state.index > 0) {
this.setState({
index: this.state.index - 1
})
}
}
selectNext () {
if (this.state.index < this.noteCount - 1) {
this.setState({
index: this.state.index + 1
})
}
}
handleOnlySnippetCheckboxChange (e) {
const { filter } = this.state
filter.includeSnippet = e.target.checked
this.setState({
filter: filter,
index: 0
}, () => {
this.refs.search.focus()
})
}
handleOnlyMarkdownCheckboxChange (e) {
const { filter } = this.state
filter.includeMarkdown = e.target.checked
this.refs.list.resetScroll()
this.setState({
filter: filter,
index: 0
}, () => {
this.refs.search.focus()
})
}
handleAllNotesButtonClick (e) {
const { filter } = this.state
filter.type = 'ALL'
this.refs.list.resetScroll()
this.setState({
filter,
index: 0
}, () => {
this.refs.search.focus()
})
}
handleStarredButtonClick (e) {
const { filter } = this.state
filter.type = 'STARRED'
this.refs.list.resetScroll()
this.setState({
filter,
index: 0
}, () => {
this.refs.search.focus()
})
}
handleStorageButtonClick (e, storage) {
const { filter } = this.state
filter.type = 'STORAGE'
filter.storage = storage
this.refs.list.resetScroll()
this.setState({
filter,
index: 0
}, () => {
this.refs.search.focus()
})
}
handleFolderButtonClick (e, storage, folder) {
const { filter } = this.state
filter.type = 'FOLDER'
filter.storage = storage
filter.folder = folder
this.refs.list.resetScroll()
this.setState({
filter,
index: 0
}, () => {
this.refs.search.focus()
})
}
handleNoteClick (e, index) {
this.setState({
index
}, () => {
this.refs.search.focus()
})
}
render () {
const { data, config } = this.props
const { filter, search } = this.state
const storageList = []
for (const key in data.storageMap) {
const storage = data.storageMap[key]
const item = (
<StorageSection
filter={filter}
storage={storage}
key={storage.key}
handleStorageButtonClick={(e, storage) => this.handleStorageButtonClick(e, storage)}
handleFolderButtonClick={(e, storage, folder) => this.handleFolderButtonClick(e, storage, folder)}
/>
)
storageList.push(item)
}
let notes = []
let noteIds
switch (filter.type) {
case 'STORAGE':
noteIds = data.storageNoteMap[filter.storage]
break
case 'FOLDER':
noteIds = data.folderNoteMap[filter.storage + '-' + filter.folder]
break
case 'STARRED':
noteIds = data.starredSet
}
if (noteIds != null) {
noteIds.forEach((id) => {
notes.push(data.noteMap[id])
})
} else {
for (const key in data.noteMap) {
notes.push(data.noteMap[key])
}
}
if (!filter.includeSnippet && filter.includeMarkdown) {
notes = notes.filter((note) => note.type === 'MARKDOWN_NOTE')
} else if (filter.includeSnippet && !filter.includeMarkdown) {
notes = notes.filter((note) => note.type === 'SNIPPET_NOTE')
}
if (search.trim().length > 0) {
const needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
notes = notes.filter((note) => note.title.match(needle))
}
notes = notes
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
const activeNote = notes[this.state.index]
this.noteCount = notes.length
return (
<div className='Finder'
styleName='root'
ref='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='search'>
<input
styleName='search-input'
ref='search'
value={search}
placeholder='Search...'
onChange={(e) => this.handleSearchChange(e)}
/>
</div>
<div styleName='result'>
<div styleName='result-nav'>
<div styleName='result-nav-filter'>
<div styleName='result-nav-filter-option'>
<label>
<input type='checkbox'
checked={filter.includeSnippet}
onChange={(e) => this.handleOnlySnippetCheckboxChange(e)}
/> Only Snippets</label>
</div>
<div styleName='result-nav-filter-option'>
<label>
<input type='checkbox'
checked={filter.includeMarkdown}
onChange={(e) => this.handleOnlyMarkdownCheckboxChange(e)}
/> Only Markdown</label>
</div>
</div>
<SideNavFilter
isHomeActive={filter.type === 'ALL'}
handleAllNotesButtonClick={(e) => this.handleAllNotesButtonClick(e)}
isStarredActive={filter.type === 'STARRED'}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
/>
<div styleName='result-nav-storageList'>
{storageList}
</div>
</div>
<NoteList styleName='result-list'
storageMap={data.storageMap}
notes={notes}
ref='list'
search={search}
index={this.state.index}
handleNoteClick={(e, _index) => this.handleNoteClick(e, _index)}
/>
<div styleName='result-detail'>
<NoteDetail
note={activeNote}
config={config}
ref='detail'
/>
</div>
</div>
</div>
)
}
}
FinderMain.propTypes = {
dispatch: PropTypes.func
}
var Finder = connect((x) => x)(CSSModules(FinderMain, styles))
function refreshData () {
// let data = dataStore.getData(true)
}
ReactDOM.render((
<Provider store={store}>
<Finder />
</Provider>
), document.getElementById('content'), function () {
refreshData()
})

View File

@@ -1,126 +0,0 @@
const nodeIpc = require('node-ipc')
const { remote, ipcRenderer } = require('electron')
const { app, Menu } = remote
const path = require('path')
const store = require('./store')
const consts = require('browser/lib/consts')
nodeIpc.config.id = 'finder'
nodeIpc.config.retry = 1500
nodeIpc.config.silent = true
function killFinder () {
const finderWindow = remote.getCurrentWindow()
finderWindow.removeAllListeners()
if (global.process.platform === 'darwin') {
// Only OSX has another app process.
nodeIpc.of.node.emit('quit-from-finder')
} else {
finderWindow.close()
}
}
function toggleFinder () {
const finderWindow = remote.getCurrentWindow()
if (global.process.platform === 'darwin') {
if (finderWindow.isVisible()) {
finderWindow.hide()
Menu.sendActionToFirstResponder('hide:')
} else {
nodeIpc.of.node.emit('request-data-from-finder')
finderWindow.show()
}
} else {
if (finderWindow.isVisible()) {
finderWindow.blur()
finderWindow.hide()
} else {
nodeIpc.of.node.emit('request-data-from-finder')
finderWindow.show()
finderWindow.focus()
}
}
}
nodeIpc.connectTo(
'node',
path.join(app.getPath('userData'), 'boostnote.service'),
function () {
nodeIpc.of.node.on('error', function (err) {
console.log(err)
})
nodeIpc.of.node.on('connect', function () {
console.log('Conncted successfully')
})
nodeIpc.of.node.on('disconnect', function () {
console.log('disconnected')
})
nodeIpc.of.node.on('open-finder', function () {
toggleFinder()
})
ipcRenderer.on('open-finder-from-tray', function () {
toggleFinder()
})
ipcRenderer.on('open-main-from-tray', function () {
nodeIpc.of.node.emit('open-main-from-finder')
})
ipcRenderer.on('quit-from-tray', function () {
nodeIpc.of.node.emit('quit-from-finder')
killFinder()
})
nodeIpc.of.node.on('throttle-data', function (payload) {
console.log('Received data from Main renderer')
store.default.dispatch({
type: 'THROTTLE_DATA',
data: payload
})
})
nodeIpc.of.node.on('config-renew', function (payload) {
const { config } = payload
if (config.ui.theme === 'dark') {
document.body.setAttribute('data-theme', 'dark')
} else if (config.ui.theme === 'white') {
document.body.setAttribute('data-theme', 'white')
} else if (config.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
} else {
document.body.setAttribute('data-theme', 'default')
}
let editorTheme = document.getElementById('editorTheme')
if (editorTheme == null) {
editorTheme = document.createElement('link')
editorTheme.setAttribute('id', 'editorTheme')
editorTheme.setAttribute('rel', 'stylesheet')
document.head.appendChild(editorTheme)
}
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme)
? config.editor.theme
: 'default'
if (config.editor.theme !== 'default') {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
}
store.default.dispatch({
type: 'SET_CONFIG',
config: config
})
})
nodeIpc.of.node.on('quit-finder-app', function () {
nodeIpc.of.node.emit('quit-finder-app-confirm')
killFinder()
})
}
)
const ipc = {}
module.exports = ipc

View File

@@ -1,51 +0,0 @@
import { combineReducers, createStore } from 'redux'
import { routerReducer } from 'react-router-redux'
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
const defaultData = {
storageMap: {},
noteMap: {},
starredSet: [],
storageNoteMap: {},
folderNoteMap: {},
tagNoteMap: {}
}
function data (state = defaultData, action) {
switch (action.type) {
case 'THROTTLE_DATA':
console.log(action)
state = action.data
}
return state
}
function config (state = DEFAULT_CONFIG, action) {
switch (action.type) {
case 'INIT_CONFIG':
case 'SET_CONFIG':
return Object.assign({}, state, action.config)
case 'SET_IS_SIDENAV_FOLDED':
state.isSideNavFolded = action.isFolded
return Object.assign({}, state)
case 'SET_ZOOM':
state.zoom = action.zoom
return Object.assign({}, state)
case 'SET_LIST_WIDTH':
state.listWidth = action.listWidth
return Object.assign({}, state)
case 'SET_UI':
return Object.assign({}, state, action.config)
}
return state
}
const reducer = combineReducers({
data,
config,
routing: routerReducer
})
const store = createStore(reducer)
export default store

View File

@@ -2,7 +2,7 @@ const { remote } = require('electron')
const { Menu, MenuItem } = remote const { Menu, MenuItem } = remote
function popup (templates) { function popup (templates) {
let menu = new Menu() const menu = new Menu()
templates.forEach((item) => { templates.forEach((item) => {
menu.append(new MenuItem(item)) menu.append(new MenuItem(item))
}) })

View File

@@ -1,3 +1,5 @@
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']}) CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})

View File

@@ -31,10 +31,10 @@ var md = markdownit({
return `<pre class="sequence">${str}</pre>` return `<pre class="sequence">${str}</pre>`
} }
return '<pre class="code">' + return '<pre class="code">' +
createGutter(str) + createGutter(str) +
'<code class="' + lang + '">' + '<code class="' + lang + '">' +
str + str +
'</code></pre>' '</code></pre>'
} }
}) })
md.use(emoji, { md.use(emoji, {
@@ -57,7 +57,7 @@ md.use(math, {
blockRenderer: function (str) { blockRenderer: function (str) {
let output = '' let output = ''
try { try {
output = katex.renderToString(str.trim(), {displayMode: true}) output = katex.renderToString(str.trim(), { displayMode: true })
} catch (err) { } catch (err) {
output = `<div class="katex-error">${err.message}</div>` output = `<div class="katex-error">${err.message}</div>`
} }
@@ -76,7 +76,17 @@ md.use(require('markdown-it-named-headers'), {
} }
}) })
md.use(require('markdown-it-kbd')) md.use(require('markdown-it-kbd'))
md.use(require('markdown-it-plantuml'))
const deflate = require('markdown-it-plantuml/lib/deflate')
md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
}
})
// Override task item // Override task item
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) { md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
@@ -110,7 +120,7 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
state.line = nextLine state.line = nextLine
token = state.push('paragraph_open', 'p', 1) token = state.push('paragraph_open', 'p', 1)
token.map = [ startLine, state.line ] token.map = [startLine, state.line]
if (state.parentType === 'list') { if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i) const match = content.match(/^\[( |x)\] ?(.+)/i)
@@ -121,7 +131,7 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
token = state.push('inline', '', 0) token = state.push('inline', '', 0)
token.content = content token.content = content
token.map = [ startLine, state.line ] token.map = [startLine, state.line]
token.children = [] token.children = []
token = state.push('paragraph_close', 'p', -1) token = state.push('paragraph_close', 'p', -1)

View File

@@ -20,11 +20,13 @@
body[data-theme="dark"] body[data-theme="dark"]
.root .root
background-color $ui-dark-backgroundColor background-color $ui-dark-backgroundColor
border-left 1px solid $ui-dark-borderColor
.empty-message .empty-message
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
border-left 1px solid $ui-solarized-dark-borderColor
.empty-message .empty-message
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color

View File

@@ -3,20 +3,14 @@
border solid 1px transparent border solid 1px transparent
vertical-align middle vertical-align middle
border-radius 2px border-radius 2px
height 30px
transition 0.15s transition 0.15s
user-select none user-select none
margin-right 10px margin-right 10px
&:hover
background-color $ui-button--hover-backgroundColor
.root--search, .root--focus .root--search, .root--focus
@extend .root @extend .root
background-color $ui-noteDetail-backgroundColor = #fff
border-color $ui-input--focus-borderColor border-color $ui-input--focus-borderColor
width 154px
height 30px
&:hover
border-color $ui-input--focus-borderColor = #fff
.idle .idle
position relative position relative

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl'
const FullscreenButton = ({
onClick
}) => (
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
</button>
)
FullscreenButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(FullscreenButton, styles)

View File

@@ -0,0 +1,22 @@
.control-fullScreenButton
top 80px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 70px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()

View File

@@ -10,6 +10,7 @@ const InfoButton = ({
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img className='infoButton' src='../resources/icon/icon-info.svg' /> <img className='infoButton' src='../resources/icon/icon-info.svg' />
<span styleName='tooltip'>Info</span>
</button> </button>
) )

View File

@@ -1,6 +1,21 @@
.control-infoButton .control-infoButton
top 10px top 10px
topBarButtonRight() topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 20px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.infoButton .infoButton
padding 0px padding 0px

View File

@@ -2,77 +2,97 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl' import styles from './InfoPanel.styl'
import copy from 'copy-to-clipboard'
const InfoPanel = ({ class InfoPanel extends React.Component {
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print copyNoteLink () {
}) => ( const {noteLink} = this.props
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}> this.refs.noteLink.select()
<div> copy(noteLink)
<p styleName='modification-date'>{updatedAt}</p> }
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div>
<hr /> render () {
const {
{type === 'SNIPPET_NOTE' storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
? '' } = this.props
: <div styleName='count-wrap'> return (
<div styleName='count-number'> <div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<p styleName='infoPanel-defaul-count'>{wordCount}</p> <div>
<p styleName='infoPanel-sub-count'>Words</p> <p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div> </div>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p> <hr />
<p styleName='infoPanel-sub-count'>Letters</p>
{type === 'SNIPPET_NOTE'
? ''
: <div styleName='count-wrap'>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
<p styleName='infoPanel-sub-count'>Words</p>
</div>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
<p styleName='infoPanel-sub-count'>Letters</p>
</div>
</div>
}
{type === 'SNIPPET_NOTE'
? ''
: <hr />
}
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div>
<div>
<p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
</div>
<div>
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<i className='fa fa-clipboard' />
</button>
<p styleName='infoPanel-sub'>NOTE LINK</p>
</div>
<hr />
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o' />
<p>.txt</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>.html</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-print' />
<p>Print</p>
</button>
</div> </div>
</div> </div>
} )
}
{type === 'SNIPPET_NOTE' }
? ''
: <hr />
}
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div>
<div>
<p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
</div>
<div>
<input styleName='infoPanel-noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
<p styleName='infoPanel-sub'>NOTE LINK</p>
</div>
<hr />
<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--enable' onClick={(e) => print(e)}>
<i className='fa fa-print fa-fw' />
<p>Print</p>
</button>
</div>
</div>
)
InfoPanel.propTypes = { InfoPanel.propTypes = {
storageName: PropTypes.string.isRequired, storageName: PropTypes.string.isRequired,
@@ -82,6 +102,7 @@ InfoPanel.propTypes = {
createdAt: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired,
wordCount: PropTypes.number, wordCount: PropTypes.number,
letterCount: PropTypes.number, letterCount: PropTypes.number,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,

View File

@@ -11,11 +11,10 @@
.control-infoButton-panel .control-infoButton-panel
z-index 200 z-index 200
margin-top 0px margin-top 0px
right 0 right 25px
position absolute position absolute
padding 20px 25px 0 25px padding 20px 25px 0 25px
width 300px width 300px
height 350px
overflow auto overflow auto
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1) box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
@@ -70,15 +69,30 @@
color $ui-text-color color $ui-text-color
.infoPanel-sub .infoPanel-sub
font-size 14px font-size 12px
font-weight 600
color $ui-inactive-text-color color $ui-inactive-text-color
padding-bottom 8px padding-bottom 8px
.infoPanel-noteLink .infoPanel-noteLink
padding-right 5px padding-right 5px
width 200px width 210px
height 25px height 25px
margin-bottom 6px margin 6px 0
.infoPanel-copyButton
outline none
font-size 16px
color #A0A0A0
background-color transparent
border none
margin 0 5px
border-radius 5px
cursor pointer
&:hover
transition 0.2s
background-color alpha($ui-button--hover-backgroundColor, 30%)
color $ui-inactive-text-color
.infoPanel-trash .infoPanel-trash
color #EA4447 color #EA4447
@@ -161,3 +175,43 @@ body[data-theme="dark"]
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
&:hover &:hover
color $ui-dark-text-color color $ui-dark-text-color
body[data-theme="solarized-dark"]
.control-infoButton-panel
background-color $ui-solarized-dark-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-solarized-ark-noteList-backgroundColor
.modification-date
color $ui-solarized-ark-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-solarized-dark-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-solarized-ark-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-solarized-dark-borderColor, 20%)
color $ui-solarized-dark-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-solarized-dark-borderColor, 20%)
color $ui-solarized-ark-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-solarized-ark-text-color

View File

@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl' import styles from './InfoPanel.styl'
const InfoPanelTrashed = ({ const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
}) => ( }) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div> <div>
@@ -31,17 +31,22 @@ const InfoPanelTrashed = ({
<div id='export-wrap'> <div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}> <button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o fa-fw' /> <i className='fa fa-file-code-o' />
<p>.md</p> <p>.md</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}> <button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o fa-fw' /> <i className='fa fa-file-text-o' />
<p>.txt</p> <p>.txt</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>.html</p>
</button>
<button styleName='export--unable'> <button styleName='export--unable'>
<i className='fa fa-file-pdf-o fa-fw' /> <i className='fa fa-file-pdf-o' />
<p>.pdf</p> <p>.pdf</p>
</button> </button>
</div> </div>
@@ -54,7 +59,8 @@ InfoPanelTrashed.propTypes = {
updatedAt: PropTypes.string.isRequired, updatedAt: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired
} }
export default CSSModules(InfoPanelTrashed, styles) export default CSSModules(InfoPanelTrashed, styles)

View File

@@ -18,18 +18,16 @@ import { findNoteTitle } from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton'
import PermanentDeleteButton from './PermanentDeleteButton' import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton' import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton'
import InfoPanel from './InfoPanel' import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed' import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter' import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus' import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags' import striptags from 'striptags'
const electron = require('electron')
const { remote } = electron
const { dialog } = remote
class MarkdownNoteDetail extends React.Component { class MarkdownNoteDetail extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -77,17 +75,22 @@ class MarkdownNoteDetail extends React.Component {
ee.off('topbar:togglelockbutton', this.toggleLockButton) ee.off('topbar:togglelockbutton', this.toggleLockButton)
} }
handleChange (e) { handleUpdateTag () {
const { note } = this.state const { note } = this.state
note.content = this.refs.content.value
if (this.refs.tags) note.tags = this.refs.tags.value if (this.refs.tags) note.tags = this.refs.tags.value
note.title = markdown.strip(striptags(findNoteTitle(note.content))) this.updateNote(note)
note.updatedAt = new Date() }
this.setState({ handleUpdateContent () {
note const { note } = this.state
}, () => { note.content = this.refs.content.value
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
this.updateNote(note)
}
updateNote (note) {
note.updatedAt = new Date()
this.setState({note}, () => {
this.save() this.save()
}) })
} }
@@ -173,41 +176,44 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('export:save-text') ee.emit('export:save-text')
} }
exportAsHtml () {
ee.emit('export:save-html')
}
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
const { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
const { confirmDeletion } = this.props
if (isTrashed) { if (isTrashed) {
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), { if (confirmDeletion(true)) {
type: 'warning', const {note, dispatch} = this.props
message: 'Confirm note deletion', dataApi
detail: 'This will permanently remove this note.', .deleteNote(note.storage, note.key)
buttons: ['Confirm', 'Cancel'] .then((data) => {
}) const dispatchHandler = () => {
if (dialogueButtonIndex === 1) return dispatch({
const { note, dispatch } = this.props type: 'DELETE_NOTE',
dataApi storageKey: data.storageKey,
.deleteNote(note.storage, note.key) noteKey: data.noteKey
.then((data) => { })
const dispatchHandler = () => { }
dispatch({ ee.once('list:moved', dispatchHandler)
type: 'DELETE_NOTE', })
storageKey: data.storageKey, }
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
})
} else { } else {
note.isTrashed = true if (confirmDeletion()) {
note.isTrashed = true
this.setState({ this.setState({
note note
}, () => { }, () => {
this.save() this.save()
}) })
ee.emit('list:next')
}
} }
ee.emit('list:next')
} }
handleUndoButtonClick (e) { handleUndoButtonClick (e) {
@@ -283,7 +289,7 @@ class MarkdownNoteDetail extends React.Component {
config={config} config={config}
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
onChange={(e) => this.handleChange(e)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
} else { } else {
@@ -292,7 +298,7 @@ class MarkdownNoteDetail extends React.Component {
config={config} config={config}
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
onChange={(e) => this.handleChange(e)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
} }
@@ -332,6 +338,7 @@ class MarkdownNoteDetail extends React.Component {
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsHtml={this.exportAsHtml}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
/> />
@@ -352,27 +359,14 @@ class MarkdownNoteDetail extends React.Component {
<TagSelect <TagSelect
ref='tags' ref='tags'
value={this.state.note.tags} value={this.state.note.tags}
onChange={(e) => this.handleChange(e)} onChange={this.handleUpdateTag.bind(this)}
/> />
<div styleName='mode-tab'> <ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => this.handleSwitchMode('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-split-on.svg' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => this.handleSwitchMode('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : '../resources/icon/icon-mode-markdown-off.svg'} />
</div>
</div>
<TodoListPercentage <TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
/>
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<StarButton <StarButton
onClick={(e) => this.handleStarButtonClick(e)} onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred} isActive={note.isStarred}
@@ -386,6 +380,7 @@ class MarkdownNoteDetail extends React.Component {
onMouseDown={(e) => this.handleLockButtonMouseDown(e)} onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
> >
<img styleName='iconInfo' src={imgSrc} /> <img styleName='iconInfo' src={imgSrc} />
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
</button> </button>
return ( return (
@@ -393,14 +388,14 @@ class MarkdownNoteDetail extends React.Component {
) )
})()} })()}
<button styleName='control-fullScreenButton' <FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
@@ -409,6 +404,7 @@ class MarkdownNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsHtml={this.exportAsHtml}
wordCount={note.content.split(' ').length} wordCount={note.content.split(' ').length}
letterCount={note.content.replace(/\r?\n/g, '').length} letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type} type={note.type}
@@ -447,7 +443,8 @@ MarkdownNoteDetail.propTypes = {
style: PropTypes.shape({ style: PropTypes.shape({
left: PropTypes.number left: PropTypes.number
}), }),
ignorePreviewPointerEvents: PropTypes.bool ignorePreviewPointerEvents: PropTypes.bool,
confirmDeletion: PropTypes.bool.isRequired
} }
export default CSSModules(MarkdownNoteDetail, styles) export default CSSModules(MarkdownNoteDetail, styles)

View File

@@ -12,47 +12,39 @@
padding-bottom 3px padding-bottom 3px
.control-lockButton .control-lockButton
top 150px
topBarButtonRight() topBarButtonRight()
position absolute
right 225px
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 35px
right -10px
width 50px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.trashed-infopanel .trashed-infopanel
top 40px
position relative position relative
.control-fullScreenButton
top 80px
topBarButtonRight()
.body .body
absolute left right absolute left right
left 0 left 0
right 0 right 0
top $info-height + $info-margin-under-border top $info-height + $info-margin-under-border
bottom $statusBar-height bottom $statusBar-height
margin 0 45px margin 0 30px
.body-noteEditor .body-noteEditor
absolute top bottom left right absolute top bottom left right
.mode-tab
border 1px solid #eee
height 34px
display flex
align-items center
div
width 40px
height 100%
background-color #f9f9f9
display flex
align-items center
justify-content center
cursor pointer
&:first-child
border-right 1px solid #eee
.active
background-color #fff
box-shadow 2px 0px 7px #eee
z-index 1
body[data-theme="white"] body[data-theme="white"]
.root .root
box-shadow $note-detail-box-shadow box-shadow $note-detail-box-shadow
@@ -73,27 +65,8 @@ body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()
.mode-tab
border 1px solid #444444
div
background-color $ui-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #444444
.active
background-color #3A404C
box-shadow 2px 0px 7px #444444
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
border-left 1px solid $ui-solarized-dark-borderColor border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
.mode-tab
border 1px solid #586E75
div
background-color $ui-solarized-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #586E75
.active
background-color #002B36
box-shadow 2px 0px 7px #222222

View File

@@ -1,6 +1,6 @@
@import('DetailVars') @import('DetailVars')
$info-height = 50px $info-height = 60px
$info-margin-under-border = 30px $info-margin-under-border = 30px
.info .info
@@ -8,11 +8,11 @@ $info-margin-under-border = 30px
left 0 left 0
right 0 right 0
height $info-height height $info-height
border-bottom 1px solid #eee
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
width 100% width 100%
display flex display flex
align-items center align-items center
padding 0 20px
.info-left .info-left
padding 0 10px padding 0 10px
@@ -20,7 +20,6 @@ $info-margin-under-border = 30px
display flex display flex
align-items center align-items center
.info-left-top-folderSelect .info-left-top-folderSelect
display flex display flex
align-items center align-items center
@@ -45,12 +44,9 @@ $info-margin-under-border = 30px
color $ui-button--color color $ui-button--color
.info-right .info-right
position absolute
right 40px
top 60px
bottom 1px
padding-left 30px
z-index 101 z-index 101
display inline-flex
margin-top 3px
.undo-button .undo-button
width 34px width 34px

View File

@@ -10,6 +10,7 @@ const PermanentDeleteButton = ({
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Permanent Delete</span>
</button> </button>
) )

View File

@@ -11,6 +11,7 @@ import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import SnippetTab from 'browser/components/SnippetTab' import SnippetTab from 'browser/components/SnippetTab'
import StatusBar from '../StatusBar' import StatusBar from '../StatusBar'
import context from 'browser/lib/context' import context from 'browser/lib/context'
@@ -175,38 +176,37 @@ class SnippetNoteDetail extends React.Component {
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
const { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
const { confirmDeletion } = this.props
if (isTrashed) { if (isTrashed) {
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), { if (confirmDeletion(true)) {
type: 'warning', const {note, dispatch} = this.props
message: 'Confirm note deletion', dataApi
detail: 'This will permanently remove this note.', .deleteNote(note.storage, note.key)
buttons: ['Confirm', 'Cancel'] .then((data) => {
}) const dispatchHandler = () => {
if (dialogueButtonIndex === 1) return dispatch({
const { note, dispatch } = this.props type: 'DELETE_NOTE',
dataApi storageKey: data.storageKey,
.deleteNote(note.storage, note.key) noteKey: data.noteKey
.then((data) => { })
const dispatchHandler = () => { }
dispatch({ ee.once('list:moved', dispatchHandler)
type: 'DELETE_NOTE', })
storageKey: data.storageKey, }
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
})
} else { } else {
note.isTrashed = true if (confirmDeletion()) {
note.isTrashed = true
this.setState({ this.setState({
note note
}, () => { }, () => {
this.save() this.save()
}) })
ee.emit('list:next')
}
} }
ee.emit('list:next')
} }
handleUndoButtonClick (e) { handleUndoButtonClick (e) {
@@ -380,7 +380,7 @@ class SnippetNoteDetail extends React.Component {
handleModeButtonClick (e, index) { handleModeButtonClick (e, index) {
const menu = new Menu() const menu = new Menu()
CodeMirror.modeInfo.forEach((mode) => { CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
menu.append(new MenuItem({ menu.append(new MenuItem({
label: mode.name, label: mode.name,
click: (e) => this.handleModeOptionClick(index, mode.name)(e) click: (e) => this.handleModeOptionClick(index, mode.name)(e)
@@ -564,6 +564,7 @@ class SnippetNoteDetail extends React.Component {
fontSize={editorFontSize} fontSize={editorFontSize}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
@@ -603,6 +604,7 @@ class SnippetNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning}
/> />
</div> </div>
</div> </div>
@@ -625,21 +627,23 @@ class SnippetNoteDetail extends React.Component {
/> />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<StarButton <StarButton
onClick={(e) => this.handleStarButtonClick(e)} onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred} isActive={note.isStarred}
/> />
<button styleName='control-fullScreenButton' <button styleName='control-fullScreenButton' title='Fullscreen'
onMouseDown={(e) => this.handleFullScreenButton(e)}> onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
</button> </button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
@@ -729,7 +733,8 @@ SnippetNoteDetail.propTypes = {
style: PropTypes.shape({ style: PropTypes.shape({
left: PropTypes.number left: PropTypes.number
}), }),
ignorePreviewPointerEvents: PropTypes.bool ignorePreviewPointerEvents: PropTypes.bool,
confirmDeletion: PropTypes.bool.isRequired
} }
export default CSSModules(SnippetNoteDetail, styles) export default CSSModules(SnippetNoteDetail, styles)

View File

@@ -9,8 +9,7 @@
.body .body
absolute left right absolute left right
left $snippet-note-detail-left-margin margin 0 30px
right $snippet-note-detail-right-margin
top $info-height + $info-margin-under-border top $info-height + $info-margin-under-border
bottom $statusBar-height bottom $statusBar-height
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
@@ -70,6 +69,21 @@
top 80px top 80px
margin-bottom 10px margin-bottom 10px
topBarButtonRight() topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 70px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="white"] body[data-theme="white"]
.root .root

View File

@@ -46,14 +46,14 @@ class StarButton extends React.Component {
onMouseDown={(e) => this.handleMouseDown(e)} onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)} onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)} onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick} onClick={this.props.onClick}>
>
<img styleName='icon' <img styleName='icon'
src={this.state.isActive || this.props.isActive src={this.state.isActive || this.props.isActive
? '../resources/icon/icon-starred.svg' ? '../resources/icon/icon-starred.svg'
: '../resources/icon/icon-star.svg' : '../resources/icon/icon-star.svg'
} }
/> />
<span styleName='tooltip'>Star</span>
</button> </button>
) )
} }

View File

@@ -4,6 +4,22 @@
&:hover &:hover
transition 0.2s transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6) color alpha($ui-favorite-star-button-color, 0.6)
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 115px
width 40px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.root--active .root--active
@extend .root @extend .root

View File

@@ -64,7 +64,8 @@ class TagSelect extends React.Component {
submitTag () { submitTag () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG') AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props let { value } = this.props
const newTag = this.refs.newTag.value.trim().replace(/ +/g, '_') let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
if (newTag.length <= 0) { if (newTag.length <= 0) {
this.setState({ this.setState({

View File

@@ -6,7 +6,8 @@
width 100% width 100%
overflow-x scroll overflow-x scroll
white-space nowrap white-space nowrap
margin-right 10px margin-top 31px
position absolute
.root::-webkit-scrollbar .root::-webkit-scrollbar
display none display none

View File

@@ -0,0 +1,25 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleModeButton.styl'
const ToggleModeButton = ({
onClick, editorType
}) => (
<div styleName='control-toggleModeButton'>
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
</div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<span styleName='tooltip'>Toggle Mode</span>
</div>
)
ToggleModeButton.propTypes = {
onClick: PropTypes.func.isRequired,
editorType: PropTypes.string.Required
}
export default CSSModules(ToggleModeButton, styles)

View File

@@ -0,0 +1,58 @@
.control-toggleModeButton
height 25px
border-radius 50px
background-color #F4F4F4
width 52px
display flex
align-items center
position absolute
right 165px
.active
background-color #1EC38B
width 33px
height 24px
box-shadow 2px 0px 7px #eee
z-index 1
div
width 40px
height 100%
border-radius 50%
display flex
align-items center
justify-content center
cursor pointer
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 33px
left -10px
z-index 200
width 80px
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()
.control-toggleModeButton
background-color #3A404C
.active
background-color #1EC38B
box-shadow 2px 0px 7px #444444
body[data-theme="solarized-dark"]
.control-toggleModeButton
background-color #002B36
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222

View File

@@ -10,6 +10,7 @@ const TrashButton = ({
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Trash</span>
</button> </button>
) )

View File

@@ -1,6 +1,21 @@
.control-trashButton .control-trashButton
top 115px top 115px
topBarButtonRight() topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 50px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.control-trashButton--in-trash .control-trashButton--in-trash
top 60px top 60px

View File

@@ -32,6 +32,26 @@ class Detail extends React.Component {
ee.off('detail:delete', this.deleteHandler) ee.off('detail:delete', this.deleteHandler)
} }
confirmDeletion (permanent) {
if (this.props.config.ui.confirmDeletion || permanent) {
const electron = require('electron')
const { remote } = electron
const { dialog } = remote
const alertConfig = {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
}
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
return dialogueButtonIndex === 0
}
return true
}
render () { render () {
const { location, data, config } = this.props const { location, data, config } = this.props
let note = null let note = null
@@ -64,6 +84,7 @@ class Detail extends React.Component {
<SnippetNoteDetail <SnippetNoteDetail
note={note} note={note}
config={config} config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root' ref='root'
{..._.pick(this.props, [ {..._.pick(this.props, [
'dispatch', 'dispatch',
@@ -80,6 +101,7 @@ class Detail extends React.Component {
<MarkdownNoteDetail <MarkdownNoteDetail
note={note} note={note}
config={config} config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root' ref='root'
{..._.pick(this.props, [ {..._.pick(this.props, [
'dispatch', 'dispatch',

View File

@@ -10,10 +10,13 @@ import Detail from './Detail'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import _ from 'lodash' import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import modal from 'browser/main/lib/modal'
import InitModal from 'browser/main/modals/InitModal'
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig' import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import { hashHistory } from 'react-router'
import store from 'browser/main/store'
const path = require('path')
const electron = require('electron')
const { remote } = electron
class Main extends React.Component { class Main extends React.Component {
@@ -48,6 +51,91 @@ class Main extends React.Component {
} }
} }
init () {
dataApi
.addStorage({
name: 'My Storage',
path: path.join(remote.app.getPath('home'), 'Boostnote')
})
.then((data) => {
return data
})
.then((data) => {
if (data.storage.folders[0] != null) {
return data
} else {
return dataApi
.createFolder(data.storage.key, {
color: '#1278BD',
name: 'Default'
})
.then((_data) => {
return {
storage: _data.storage,
notes: data.notes
}
})
}
})
.then((data) => {
console.log(data)
store.dispatch({
type: 'ADD_STORAGE',
storage: data.storage,
notes: data.notes
})
const defaultSnippetNote = dataApi
.createNote(data.storage.key, {
type: 'SNIPPET_NOTE',
folder: data.storage.folders[0].key,
title: 'Snippet note example',
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
snippets: [
{
name: 'example.html',
mode: 'html',
content: '<html>\n<body>\n<h1 id=\'hello\'>Enjoy Boostnote!</h1>\n</body>\n</html>'
},
{
name: 'example.js',
mode: 'javascript',
content: 'var boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)'
}
]
})
.then((note) => {
store.dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
const defaultMarkdownNote = dataApi
.createNote(data.storage.key, {
type: 'MARKDOWN_NOTE',
folder: data.storage.folders[0].key,
title: 'Welcome to Boostnote!',
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
})
.then((note) => {
store.dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
return Promise.resolve(defaultSnippetNote)
.then(defaultMarkdownNote)
.then(() => data.storage)
})
.then((storage) => {
hashHistory.push('/storages/' + storage.key)
})
.catch((err) => {
throw err
})
}
componentDidMount () { componentDidMount () {
const { dispatch, config } = this.props const { dispatch, config } = this.props
@@ -71,7 +159,7 @@ class Main extends React.Component {
}) })
if (data.storages.length < 1) { if (data.storages.length < 1) {
modal.open(InitModal) this.init()
} }
}) })

View File

@@ -86,7 +86,7 @@ class NewNoteButton extends React.Component {
onClick={(e) => this.handleNewNoteButtonClick(e)}> onClick={(e) => this.handleNewNoteButtonClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' /> <img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'> <span styleName='control-newNoteButton-tooltip'>
Make a Note {OSX ? '⌘' : '^'} + n Make a note {OSX ? '⌘' : 'Ctrl'} + N
</span> </span>
</button> </button>
</div> </div>

View File

@@ -11,10 +11,8 @@ import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple' import NoteItemSimple from 'browser/components/NoteItemSimple'
import searchFromNotes from 'browser/lib/search' import searchFromNotes from 'browser/lib/search'
import fs from 'fs' import fs from 'fs'
import path from 'path'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import markdown from 'browser/lib/markdownTextHelper'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import store from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
const { remote } = require('electron') const { remote } = require('electron')
@@ -171,9 +169,8 @@ class NoteList extends React.Component {
if (this.notes == null || this.notes.length === 0) { if (this.notes == null || this.notes.length === 0) {
return return
} }
let { router } = this.context let { selectedNoteKeys } = this.state
let { location } = this.props const { shiftKeyDown } = this.state
let { selectedNoteKeys, shiftKeyDown } = this.state
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
@@ -199,9 +196,8 @@ class NoteList extends React.Component {
if (this.notes == null || this.notes.length === 0) { if (this.notes == null || this.notes.length === 0) {
return return
} }
let { router } = this.context let { selectedNoteKeys } = this.state
let { location } = this.props const { shiftKeyDown } = this.state
let { selectedNoteKeys, shiftKeyDown } = this.state
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
const isTargetLastNote = targetIndex === this.notes.length - 1 const isTargetLastNote = targetIndex === this.notes.length - 1
@@ -235,24 +231,13 @@ class NoteList extends React.Component {
return return
} }
const { router } = this.context const selectedNoteKeys = [noteHash]
const { location } = this.props this.focusNote(selectedNoteKeys, noteHash)
let targetIndex = this.getTargetIndex()
if (targetIndex < 0) targetIndex = 0
const selectedNoteKeys = []
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
selectedNoteKeys.push(nextNoteKey)
this.focusNote(selectedNoteKeys, nextNoteKey)
ee.emit('list:moved') ee.emit('list:moved')
} }
handleNoteListKeyDown (e) { handleNoteListKeyDown (e) {
const { shiftKeyDown } = this.state
if (e.metaKey || e.ctrlKey) return true if (e.metaKey || e.ctrlKey) return true
if (e.keyCode === 65 && !e.shiftKey) { if (e.keyCode === 65 && !e.shiftKey) {
@@ -294,7 +279,7 @@ class NoteList extends React.Component {
getNotes () { getNotes () {
const { data, params, location } = this.props const { data, params, location } = this.props
if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) { if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
const allNotes = data.noteMap.map((note) => note) const allNotes = data.noteMap.map((note) => note)
this.contextNotes = allNotes this.contextNotes = allNotes
return allNotes return allNotes
@@ -365,9 +350,10 @@ class NoteList extends React.Component {
} }
handleNoteClick (e, uniqueKey) { handleNoteClick (e, uniqueKey) {
let { router } = this.context const { router } = this.context
let { location } = this.props const { location } = this.props
let { shiftKeyDown, selectedNoteKeys } = this.state let { selectedNoteKeys } = this.state
const { shiftKeyDown } = this.state
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) { if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey) const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
@@ -453,6 +439,7 @@ class NoteList extends React.Component {
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top' const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = 'Delete Note' const deleteLabel = 'Delete Note'
const cloneNote = 'Clone Note'
const menu = new Menu() const menu = new Menu()
if (!location.pathname.match(/\/home|\/starred|\/trash/)) { if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
@@ -465,6 +452,10 @@ class NoteList extends React.Component {
label: deleteLabel, label: deleteLabel,
click: this.deleteNote click: this.deleteNote
})) }))
menu.append(new MenuItem({
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.popup() menu.popup()
} }
@@ -555,6 +546,42 @@ class NoteList extends React.Component {
this.setState({ selectedNoteKeys: [] }) this.setState({ selectedNoteKeys: [] })
} }
cloneNote () {
const { selectedNoteKeys } = this.state
const { dispatch, location } = this.props
const { storage, folder } = this.resolveTargetFolder()
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const eventName = firstNote.type === 'MARKDOWN_NOTE' ? 'ADD_MARKDOWN' : 'ADD_SNIPPET'
AwsMobileAnalyticsConfig.recordDynamicCustomEvent(eventName)
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
dataApi
.createNote(storage.key, {
type: firstNote.type,
folder: folder.key,
title: firstNote.title + ' copy',
content: firstNote.content
})
.then((note) => {
const uniqueKey = note.storage + '-' + note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
this.setState({
selectedNoteKeys: [uniqueKey]
})
hashHistory.push({
pathname: location.pathname,
query: {key: uniqueKey}
})
})
}
importFromFile () { importFromFile () {
const options = { const options = {
filters: [ filters: [
@@ -592,7 +619,7 @@ class NoteList extends React.Component {
const newNote = { const newNote = {
content: content, content: content,
folder: folder.key, folder: folder.key,
title: markdown.strip(findNoteTitle(content)), title: path.basename(filepath, path.extname(filepath)),
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
createdAt: birthtime, createdAt: birthtime,
updatedAt: mtime updatedAt: mtime
@@ -652,9 +679,10 @@ class NoteList extends React.Component {
} }
render () { render () {
let { location, notes, config, dispatch } = this.props const { location, config } = this.props
let { selectedNoteKeys } = this.state let { notes } = this.props
let sortFunc = config.sortBy === 'CREATED_AT' const { selectedNoteKeys } = this.state
const sortFunc = config.sortBy === 'CREATED_AT'
? sortByCreatedAt ? sortByCreatedAt
: config.sortBy === 'ALPHABETICAL' : config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical ? sortByAlphabetical
@@ -699,7 +727,6 @@ class NoteList extends React.Component {
config.sortBy === 'CREATED_AT' config.sortBy === 'CREATED_AT'
? note.createdAt : note.updatedAt ? note.createdAt : note.updatedAt
).fromNow('D') ).fromNow('D')
const key = `${note.storage}-${note.key}`
if (isDefault) { if (isDefault) {
return ( return (

View File

@@ -0,0 +1,24 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
const ListButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}>
<img src={isTagActive
? '../resources/icon/icon-list.svg'
: '../resources/icon/icon-list-active.svg'
}
/>
<span styleName='tooltip'>Notes</span>
</button>
)
ListButton.propTypes = {
onClick: PropTypes.func.isRequired,
isTagActive: PropTypes.bool.isRequired
}
export default CSSModules(ListButton, styles)

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferenceButton.styl'
const PreferenceButton = ({
onClick
}) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>Preferences</span>
</button>
)
PreferenceButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(PreferenceButton, styles)

View File

@@ -0,0 +1,51 @@
.top-menu-preference
navButtonColor()
position absolute
top 22px
right 10px
width 2em
background-color transparent
&:hover
color $ui-button-default--active-backgroundColor
background-color transparent
.tooltip
opacity 1
&:active, &:active:hover
color $ui-button-default--active-backgroundColor
body[data-theme="white"]
.top-menu-preference
navWhiteButtonColor()
background-color transparent
&:hover
color #0B99F1
background-color transparent
&:active, &:active:hover
color #0B99F1
background-color transparent
body[data-theme="dark"]
.top-menu-preference
navDarkButtonColor()
background-color transparent
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
left -20px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s

View File

@@ -11,19 +11,6 @@
.top .top
padding-bottom 15px padding-bottom 15px
.top-menu-preference
navButtonColor()
position absolute
top 22px
right 10px
width 2em
background-color transparent
&:hover
color $ui-button-default--active-backgroundColor
background-color transparent
&:active, &:active:hover
color $ui-button-default--active-backgroundColor
.switch-buttons .switch-buttons
background-color transparent background-color transparent
border 0 border 0
@@ -31,21 +18,7 @@
display flex display flex
text-align center text-align center
.non-active-button
color $ui-inactive-text-color
font-size 16px
border 0
background-color transparent
transition 0.2s
display flex
text-align center
margin-right 4px;
&:hover
color alpha(#239F86, 60%)
.active-button
@extend .non-active-button
color $ui-button-default--active-backgroundColor
.top-menu-label .top-menu-label
margin-left 5px margin-left 5px
@@ -109,33 +82,6 @@ body[data-theme="white"]
background-color #f9f9f9 background-color #f9f9f9
color $ui-text-color color $ui-text-color
.top-menu-preference
navWhiteButtonColor()
background-color transparent
&:hover
color #0B99F1
background-color transparent
&:active, &:active:hover
color #0B99F1
background-color transparent
.non-active-button
color $ui-inactive-text-color
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color $ui-text-color
.non-active-button
&:hover
color alpha(#0B99F1, 60%)
.active-button
@extend .non-active-button
color #0B99F1
body[data-theme="dark"] body[data-theme="dark"]
.root, .root--folded .root, .root--folded
border-right 1px solid $ui-dark-borderColor border-right 1px solid $ui-dark-borderColor
@@ -145,25 +91,6 @@ body[data-theme="dark"]
.top .top
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
.top-menu-preference
navDarkButtonColor()
background-color transparent
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
.non-active-button
color alpha($ui-dark-text-color, 60%)
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color alpha($ui-dark-text-color, 60%)
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root, .root--folded .root, .root--folded
background-color $ui-solarized-dark-backgroundColor background-color $ui-solarized-dark-backgroundColor

View File

@@ -8,11 +8,10 @@ import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
import RenameFolderModal from 'browser/main/modals/RenameFolderModal' import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import StorageItemChild from 'browser/components/StorageItem' import StorageItemChild from 'browser/components/StorageItem'
import eventEmitter from 'browser/main/lib/eventEmitter'
import _ from 'lodash' import _ from 'lodash'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote const { Menu, dialog } = remote
class StorageItem extends React.Component { class StorageItem extends React.Component {
constructor (props) { constructor (props) {
@@ -24,18 +23,20 @@ class StorageItem extends React.Component {
} }
handleHeaderContextMenu (e) { handleHeaderContextMenu (e) {
const menu = new Menu() const menu = Menu.buildFromTemplate([
menu.append(new MenuItem({ {
label: 'Add Folder', label: 'Add Folder',
click: (e) => this.handleAddFolderButtonClick(e) click: (e) => this.handleAddFolderButtonClick(e)
})) },
menu.append(new MenuItem({ {
type: 'separator' type: 'separator'
})) },
menu.append(new MenuItem({ {
label: 'Unlink Storage', label: 'Unlink Storage',
click: (e) => this.handleUnlinkStorageClick(e) click: (e) => this.handleUnlinkStorageClick(e)
})) }
])
menu.popup() menu.popup()
} }
@@ -89,18 +90,36 @@ class StorageItem extends React.Component {
} }
handleFolderButtonContextMenu (e, folder) { handleFolderButtonContextMenu (e, folder) {
const menu = new Menu() const menu = Menu.buildFromTemplate([
menu.append(new MenuItem({ {
label: 'Rename Folder', label: 'Rename Folder',
click: (e) => this.handleRenameFolderClick(e, folder) click: (e) => this.handleRenameFolderClick(e, folder)
})) },
menu.append(new MenuItem({ {
type: 'separator' type: 'separator'
})) },
menu.append(new MenuItem({ {
label: 'Delete Folder', label: 'Export Folder',
click: (e) => this.handleFolderDeleteClick(e, folder) submenu: [
})) {
label: 'Export as txt',
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
},
{
label: 'Export as md',
click: (e) => this.handleExportFolderClick(e, folder, 'md')
}
]
},
{
type: 'separator'
},
{
label: 'Delete Folder',
click: (e) => this.handleFolderDeleteClick(e, folder)
}
])
menu.popup() menu.popup()
} }
@@ -112,6 +131,31 @@ class StorageItem extends React.Component {
}) })
} }
handleExportFolderClick (e, folder, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: 'Select directory',
title: 'Select a folder to export the files to',
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
(paths) => {
if (paths && paths.length === 1) {
const { storage, dispatch } = this.props
dataApi
.exportFolder(storage.key, folder.key, fileType, paths[0])
.then((data) => {
dispatch({
type: 'EXPORT_FOLDER',
storage: data.storage,
folderKey: data.folderKey,
fileType: data.fileType
})
})
}
})
}
handleFolderDeleteClick (e, folder) { handleFolderDeleteClick (e, folder) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), { const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',

View File

@@ -0,0 +1,59 @@
.non-active-button
color $ui-inactive-text-color
font-size 16px
border 0
background-color transparent
transition 0.2s
display flex
text-align center
margin-right 4px
position relative
&:hover
color alpha(#239F86, 60%)
.tooltip
opacity 1
.active-button
@extend .non-active-button
color $ui-button-default--active-backgroundColor
.tooltip
tooltip()
position absolute
pointer-events none
top 22px
left -2px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="white"]
.non-active-button
color $ui-inactive-text-color
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color $ui-text-color
.non-active-button
&:hover
color alpha(#0B99F1, 60%)
.active-button
@extend .non-active-button
color #0B99F1
body[data-theme="dark"]
.non-active-button
color alpha($ui-dark-text-color, 60%)
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color alpha($ui-dark-text-color, 60%)

View File

@@ -0,0 +1,24 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
const TagButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}>
<img src={isTagActive
? '../resources/icon/icon-tag-active.svg'
: '../resources/icon/icon-tag.svg'
}
/>
<span styleName='tooltip'>Tags</span>
</button>
)
TagButton.propTypes = {
onClick: PropTypes.func.isRequired,
isTagActive: PropTypes.bool.isRequired
}
export default CSSModules(TagButton, styles)

View File

@@ -11,6 +11,9 @@ import SideNavFilter from 'browser/components/SideNavFilter'
import StorageList from 'browser/components/StorageList' import StorageList from 'browser/components/StorageList'
import NavToggleButton from 'browser/components/NavToggleButton' import NavToggleButton from 'browser/components/NavToggleButton'
import EventEmitter from 'browser/main/lib/eventEmitter' import EventEmitter from 'browser/main/lib/eventEmitter'
import PreferenceButton from './PreferenceButton'
import ListButton from './ListButton'
import TagButton from './TagButton'
class SideNav extends React.Component { class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7 // TODO: should not use electron stuff v0.7
@@ -162,27 +165,11 @@ class SideNav extends React.Component {
> >
<div styleName='top'> <div styleName='top'>
<div styleName='switch-buttons'> <div styleName='switch-buttons'>
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}> <ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
<img src={isTagActive <TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
? '../resources/icon/icon-list.svg'
: '../resources/icon/icon-list-active.svg'
}
/>
</button>
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={this.handleSwitchTagsButtonClick.bind(this)}>
<img src={isTagActive
? '../resources/icon/icon-tag-active.svg'
: '../resources/icon/icon-tag.svg'
}
/>
</button>
</div> </div>
<div> <div>
<button styleName='top-menu-preference' <PreferenceButton onClick={this.handleMenuButtonClick} />
onClick={(e) => this.handleMenuButtonClick(e)}
>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
</button>
</div> </div>
</div> </div>
{this.SideNavComponent(isFolded, storageList)} {this.SideNavComponent(isFolded, storageList)}

View File

@@ -21,19 +21,20 @@
color white color white
.zoom .zoom
navButtonColor() display none
color rgba(0,0,0,.54) // navButtonColor()
height 20px // color rgba(0,0,0,.54)
display flex // height 20px
padding 0 // display flex
align-items center // padding 0
background-color transparent // align-items center
&:hover // background-color transparent
color $ui-active-color // &:hover
&:active // color $ui-active-color
color $ui-active-color // &:active
span // color $ui-active-color
margin-left 5px // span
// margin-left 5px
.update .update
navButtonColor() navButtonColor()

View File

@@ -63,7 +63,7 @@ class StatusBar extends React.Component {
{status.updateReady {status.updateReady
? <button onClick={this.updateApp} styleName='update'> ? <button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update! <i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
</button> </button>
: null : null
} }
</div> </div>

View File

@@ -97,7 +97,7 @@ body[data-theme="dark"]
.CodeMirror .CodeMirror
font-family inherit !important font-family inherit !important
line-height 1.4em line-height 1.4em
height 96% height 100%
.CodeMirror > div > textarea .CodeMirror > div > textarea
margin-bottom -1em margin-bottom -1em
.CodeMirror-focused .CodeMirror-selected .CodeMirror-focused .CodeMirror-selected

View File

@@ -18,7 +18,6 @@ export const DEFAULT_CONFIG = {
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL' listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true, amaEnabled: true,
hotkey: { hotkey: {
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E' toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
}, },
ui: { ui: {
@@ -34,6 +33,7 @@ export const DEFAULT_CONFIG = {
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas', fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
indentType: 'space', indentType: 'space',
indentSize: '2', indentSize: '2',
displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false, scrollPastEnd: false,
type: 'SPLIT' type: 'SPLIT'
@@ -46,7 +46,8 @@ export const DEFAULT_CONFIG = {
latexInlineOpen: '$', latexInlineOpen: '$',
latexInlineClose: '$', latexInlineClose: '$',
latexBlockOpen: '$$', latexBlockOpen: '$$',
latexBlockClose: '$$' latexBlockClose: '$$',
scrollPastEnd: false
} }
} }

View File

@@ -0,0 +1,61 @@
import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes'
import * as path from 'path'
import * as fs from 'fs'
/**
* @param {String} storageKey
* @param {String} folderKey
* @param {String} fileType
* @param {String} exportDir
*
* @return {Object}
* ```
* {
* storage: Object,
* folderKey: String,
* fileType: String,
* exportDir: String
* }
* ```
*/
function exportFolder (storageKey, folderKey, fileType, exportDir) {
let targetStorage
try {
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}
return resolveStorageData(targetStorage)
.then(function assignNotes (storage) {
return resolveStorageNotes(storage)
.then((notes) => {
return {
storage,
notes
}
})
})
.then(function exportNotes (data) {
const { storage, notes } = data
notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(snippet => {
const notePath = path.join(exportDir, `${snippet.title}.${fileType}`)
fs.writeFileSync(notePath, snippet.content)
})
return {
storage,
folderKey,
fileType,
exportDir
}
})
}
module.exports = exportFolder

View File

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

View File

@@ -24,20 +24,6 @@ nodeIpc.connectTo(
nodeIpc.of.node.on('disconnect', function () { nodeIpc.of.node.on('disconnect', function () {
console.log('disconnected') console.log('disconnected')
}) })
nodeIpc.of.node.on('request-data-from-finder', function () {
console.log('throttle')
var { data } = store.getState()
console.log(data.starredSet.toJS())
nodeIpc.of.node.emit('throttle-data', {
storageMap: data.storageMap.toJS(),
noteMap: data.noteMap.toJS(),
starredSet: data.starredSet.toJS(),
storageNoteMap: data.storageNoteMap.toJS(),
folderNoteMap: data.folderNoteMap.toJS(),
tagNoteMap: data.tagNoteMap.toJS()
})
})
} }
) )

View File

@@ -29,7 +29,7 @@
width 490px width 490px
padding 0 5px padding 0 5px
margin 10px 0 margin 10px 0
border 1px solid #C9C9C9 // TODO: use variable. border 1px solid $ui-input--create-folder-modal
border-radius 2px border-radius 2px
background-color transparent background-color transparent
outline none outline none
@@ -68,7 +68,7 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
.control-folder-input .control-folder-input
border 1px solid #C9C9C9 // TODO: use variable. border 1px solid $ui-input--create-folder-modal
color white color white
.description .description
@@ -76,3 +76,29 @@ body[data-theme="dark"]
.control-confirmButton .control-confirmButton
colorDarkPrimaryButton() colorDarkPrimaryButton()
body[data-theme="solarized-dark"]
.root
modalSolarizedDark()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-solarized-dark-text-color
.control-folder-label
color $ui-solarized-dark-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorSolarizedDarkPrimaryButton()

View File

@@ -1,254 +0,0 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InitModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { hashHistory } from 'react-router'
import _ from 'lodash'
const CSON = require('@rokt33r/season')
const path = require('path')
const electron = require('electron')
const { remote } = electron
function browseFolder () {
const dialog = remote.dialog
const defaultPath = remote.app.getPath('home')
return new Promise((resolve, reject) => {
dialog.showOpenDialog({
title: 'Select Directory',
defaultPath,
properties: ['openDirectory', 'createDirectory']
}, function (targetPaths) {
if (targetPaths == null) return resolve('')
resolve(targetPaths[0])
})
})
}
class InitModal extends React.Component {
constructor (props) {
super(props)
this.state = {
path: path.join(remote.app.getPath('home'), 'Boostnote'),
migrationRequested: true,
isLoading: true,
data: null,
legacyStorageExists: false,
isSending: false
}
}
handlePathChange (e) {
this.setState({
path: e.target.value
})
}
componentDidMount () {
let data = null
try {
data = CSON.readFileSync(path.join(remote.app.getPath('userData'), 'local.json'))
} catch (err) {
console.error(err)
}
const newState = {
isLoading: false
}
if (data != null) {
newState.legacyStorageExists = true
newState.data = data
}
this.setState(newState, () => {
this.refs.createButton.focus()
})
}
handlePathBrowseButtonClick (e) {
browseFolder()
.then((targetPath) => {
if (targetPath.length > 0) {
this.setState({
path: targetPath
})
}
})
.catch((err) => {
console.error('BrowseFAILED')
console.error(err)
})
}
handleSubmitButtonClick (e) {
this.setState({
isSending: true
}, () => {
dataApi
.addStorage({
name: 'My Storage',
path: this.state.path
})
.then((data) => {
if (this.state.migrationRequested && _.isObject(this.state.data) && _.isArray(this.state.data.folders) && _.isArray(this.state.data.articles)) {
return dataApi.migrateFromV5Storage(data.storage.key, this.state.data)
}
return data
})
.then((data) => {
if (data.storage.folders[0] != null) {
return data
} else {
return dataApi
.createFolder(data.storage.key, {
color: '#1278BD',
name: 'Default'
})
.then((_data) => {
return {
storage: _data.storage,
notes: data.notes
}
})
}
})
.then((data) => {
console.log(data)
store.dispatch({
type: 'ADD_STORAGE',
storage: data.storage,
notes: data.notes
})
const defaultSnippetNote = dataApi
.createNote(data.storage.key, {
type: 'SNIPPET_NOTE',
folder: data.storage.folders[0].key,
title: 'Snippet note example',
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
snippets: [
{
name: 'example.html',
mode: 'html',
content: '<html>\n<body>\n<h1 id=\'hello\'>Enjoy Boostnote!</h1>\n</body>\n</html>'
},
{
name: 'example.js',
mode: 'javascript',
content: 'var boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)'
}
]
})
.then((note) => {
store.dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
const defaultMarkdownNote = dataApi
.createNote(data.storage.key, {
type: 'MARKDOWN_NOTE',
folder: data.storage.folders[0].key,
title: 'Welcome to Boostnote!',
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
})
.then((note) => {
store.dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
return Promise.resolve(defaultSnippetNote)
.then(defaultMarkdownNote)
.then(() => data.storage)
})
.then((storage) => {
hashHistory.push('/storages/' + storage.key)
this.props.close()
})
.catch((err) => {
this.setState({
isSending: false
})
throw err
})
})
}
handleMigrationRequestedChange (e) {
this.setState({
migrationRequested: e.target.checked
})
}
handleKeyDown (e) {
if (e.keyCode === 27) {
this.props.close()
}
}
render () {
if (this.state.isLoading) {
return <div styleName='root--loading'>
<i styleName='spinner' className='fa fa-spin fa-spinner' />
<div styleName='loadingMessage'>Preparing initialization...</div>
</div>
}
return (
<div styleName='root'
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='body'>
<div styleName='body-welcome'>
Welcome to Boostnote!
</div>
<div styleName='body-description'>
Please select a directory for data storage.
</div>
<div styleName='body-path'>
<input styleName='body-path-input'
placeholder='Select Folder'
value={this.state.path}
onChange={(e) => this.handlePathChange(e)}
/>
<button styleName='body-path-button'
onClick={(e) => this.handlePathBrowseButtonClick(e)}
>
...
</button>
</div>
{this.state.legacyStorageExists &&
<div styleName='body-migration'>
<label><input type='checkbox' checked={this.state.migrationRequested} onChange={(e) => this.handleMigrationRequestedChange(e)} /> Migrate old data from the legacy app v0.5</label>
</div>
}
<div styleName='body-control'>
<button styleName='body-control-createButton'
ref='createButton'
onClick={(e) => this.handleSubmitButtonClick(e)}
disabled={this.state.isSending}
>
{this.state.isSending
? <span>
<i className='fa fa-spin fa-spinner' /> Loading...
</span>
: 'CREATE'
}
</button>
</div>
</div>
</div>
)
}
}
InitModal.propTypes = {
}
export default CSSModules(InitModal, styles)

View File

@@ -1,76 +0,0 @@
.root
modal()
background-color #fff
max-width 100vw
max-height 100vh
overflow hidden
margin 0
padding 150px 0
position relative
.root--loading
@extend .root
text-align center
.spinner
font-size 100px
margin 35px auto
color $ui-text-color
.loadingMessage
color $ui-text-color
margin 15px auto 35px
.body
padding 30px
.body-welcome
text-align center
margin-bottom 25px
font-size 32px
color $ui-text-color
.body-description
font-size 16px
color $ui-text-color
text-align center
margin-bottom 25px
.body-path
margin 0 auto 25px
width 330px
.body-path-input
height 40px
vertical-align middle
width 300px
font-size 14px
border-style solid
border-width 1px 0 1px 1px
border-color $border-color
border-top-left-radius 2px
border-bottom-left-radius 2px
padding 0 5px
.body-path-button
height 42px
width 30px
font-size 16px
font-weight 600
border none
border-top-right-radius 2px
border-bottom-right-radius 2px
colorPrimaryButton()
vertical-align middle
.body-migration
margin 0 auto 25px
text-align center
.body-control
text-align center
.body-control-createButton
colorPrimaryButton()
font-size 14px
font-weight 600
border none
border-radius 2px
height 40px
padding 0 25px

View File

@@ -106,7 +106,7 @@ class NewNoteModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)} onKeyDown={(e) => this.handleKeyDown(e)}
> >
<div styleName='header'> <div styleName='header'>
<div styleName='title'>Make a Note</div> <div styleName='title'>Make a note</div>
</div> </div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} /> <ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'> <div styleName='control'>

View File

@@ -76,8 +76,8 @@
color #1EC38B color #1EC38B
.error .error
color red color red
.warning
color #FFA500
.group-control-leftButton .group-control-leftButton
colorDefaultButton() colorDefaultButton()
@@ -89,9 +89,9 @@
margin-right 10px margin-right 10px
.group-control-rightButton .group-control-rightButton
position absolute position fixed
top 10px top 80px
right 20px right 100px
colorPrimaryButton() colorPrimaryButton()
border none border none
border-radius 2px border-radius 2px

View File

@@ -22,18 +22,18 @@ class Crowdfunding extends React.Component {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='header'>Crowdfunding</div> <div styleName='header'>Crowdfunding</div>
<p>Dear all,</p> <p>Dear everyone,</p>
<br /> <br />
<p>Thanks for your using!</p> <p>Thank you for using Boostnote!</p>
<p>Boostnote is used in about 200 countries and regions, it is a awesome developer community.</p> <p>Boostnote is used in about 200 different countries and regions by an awesome community of developers.</p>
<br /> <br />
<p>To continue supporting this growth, and to satisfy community expectations,</p> <p>To continue supporting this growth, and to satisfy community expectations,</p>
<p>we would like to invest more time in this project.</p> <p>we would like to invest more time and resources in this project.</p>
<br /> <br />
<p>If you like this project and see its potential, you can help!</p> <p>If you like this project and see its potential, you can help by supporting us on OpenCollective!</p>
<br /> <br />
<p>Thanks,</p> <p>Thanks,</p>
<p>Boostnote maintainers.</p> <p>Boostnote maintainers</p>
<br /> <br />
<button styleName='cf-link'> <button styleName='cf-link'>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a> <a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>

View File

@@ -32,6 +32,7 @@ class HotkeyTab extends React.Component {
message: err.message != null ? err.message : 'Error occurs!' message: err.message != null ? err.message : 'Error occurs!'
}}) }})
} }
this.oldHotkey = this.state.config.hotkey
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError) ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
} }
@@ -53,6 +54,7 @@ class HotkeyTab extends React.Component {
config: newConfig config: newConfig
}) })
this.clearMessage() this.clearMessage()
this.props.haveToSave()
} }
handleHintToggleButtonClick (e) { handleHintToggleButtonClick (e) {
@@ -64,12 +66,20 @@ class HotkeyTab extends React.Component {
handleHotkeyChange (e) { handleHotkeyChange (e) {
const { config } = this.state const { config } = this.state
config.hotkey = { config.hotkey = {
toggleFinder: this.refs.toggleFinder.value,
toggleMain: this.refs.toggleMain.value toggleMain: this.refs.toggleMain.value
} }
this.setState({ this.setState({
config config
}) })
if (_.isEqual(this.oldHotkey, config.hotkey)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'Hotkey',
type: 'warning',
message: 'You have to save!'
})
}
} }
clearMessage () { clearMessage () {
@@ -92,9 +102,9 @@ class HotkeyTab extends React.Component {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='group'> <div styleName='group'>
<div styleName='group-header'>Hotkey</div> <div styleName='group-header'>Hotkeys</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'>Toggle Main</div> <div styleName='group-section-label'>Show/Hide Boostnote</div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)} onChange={(e) => this.handleHotkeyChange(e)}
@@ -104,24 +114,13 @@ class HotkeyTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<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)}
ref='toggleFinder'
value={config.hotkey.toggleFinder}
type='text'
/>
</div>
</div>
<div styleName='group-control'> <div styleName='group-control'>
<button styleName='group-control-leftButton' <button styleName='group-control-leftButton'
onClick={(e) => this.handleHintToggleButtonClick(e)} onClick={(e) => this.handleHintToggleButtonClick(e)}
> >
{this.state.isHotkeyHintOpen {this.state.isHotkeyHintOpen
? 'Hide Hint' ? 'Hide Help'
: 'Hint?' : 'Help'
} }
</button> </button>
<button styleName='group-control-rightButton' <button styleName='group-control-rightButton'
@@ -161,7 +160,8 @@ class HotkeyTab extends React.Component {
} }
HotkeyTab.propTypes = { HotkeyTab.propTypes = {
dispatch: PropTypes.func dispatch: PropTypes.func,
haveToSave: PropTypes.func
} }
export default CSSModules(HotkeyTab, styles) export default CSSModules(HotkeyTab, styles)

View File

@@ -31,7 +31,7 @@ class InfoTab extends React.Component {
} }
handleSaveButtonClick (e) { handleSaveButtonClick (e) {
let newConfig = { const newConfig = {
amaEnabled: this.state.config.amaEnabled amaEnabled: this.state.config.amaEnabled
} }
@@ -102,7 +102,7 @@ class InfoTab extends React.Component {
<hr /> <hr />
<div styleName='header--sub'>Info</div> <div styleName='header--sub'>About</div>
<div styleName='top'> <div styleName='top'>
<div styleName='icon-space'> <div styleName='icon-space'>
@@ -137,17 +137,19 @@ class InfoTab extends React.Component {
<hr styleName='separate-line' /> <hr styleName='separate-line' />
<div styleName='policy'>Data collection policy</div> <div styleName='policy'>Analytics</div>
<div>We collect only the number of DAU for Boostnote and **DO NOT collect** any detail information such as your note content.</div> <div>Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.</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>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> <br />
<div>You can choose to enable or disable this option.</div>
<input onChange={(e) => this.handleConfigChange(e)} <input onChange={(e) => this.handleConfigChange(e)}
checked={this.state.config.amaEnabled} checked={this.state.config.amaEnabled}
ref='amaEnabled' ref='amaEnabled'
type='checkbox' type='checkbox'
/> />
Enable to send analytics to our servers<br /> Enable analytics to help improve Boostnote<br />
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button> <button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
<br />
{this.infoMessage()} {this.infoMessage()}
</div> </div>
) )

View File

@@ -42,6 +42,8 @@ top-bar--height = 50px
background-color transparent background-color transparent
color $ui-text-color color $ui-text-color
font-size 16px font-size 16px
.saving--warning
haveToSave()
.nav-button--active .nav-button--active
@extend .nav-button @extend .nav-button
@@ -49,6 +51,8 @@ top-bar--height = 50px
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
&:hover &:hover
color $ui-text-color color $ui-text-color
.saving--warning
haveToSave()
.nav-button-icon .nav-button-icon
display block display block

View File

@@ -84,3 +84,17 @@ body[data-theme="dark"]
top 25px top 25px
z-index 10 z-index 10
white-space nowrap white-space nowrap
body[data-theme="solarized-dark"]
.header
border-color $ui-solarized-dark-button-backgroundColor
.header-label-path
color $ui-solarized-dark-text-color
.header-label-editButton
color $ui-solarized-dark-text-color
.header-control-button
border-color $ui-solarized-dark-button-backgroundColor
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color

View File

@@ -78,7 +78,7 @@ class StoragesTab extends React.Component {
<button styleName='list-control-addStorageButton' <button styleName='list-control-addStorageButton'
onClick={(e) => this.handleAddStorageButton(e)} onClick={(e) => this.handleAddStorageButton(e)}
> >
<i className='fa fa-plus' /> Add Storage <i className='fa fa-plus' /> Add Storage Location
</button> </button>
</div> </div>
</div> </div>
@@ -167,7 +167,7 @@ class StoragesTab extends React.Component {
<option value='FILESYSTEM'>File System</option> <option value='FILESYSTEM'>File System</option>
</select> </select>
<div styleName='addStorage-body-section-type-description'> <div styleName='addStorage-body-section-type-description'>
3rd party cloud integration: Setting up 3rd-party cloud storage integration:{' '}
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup' <a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
onClick={(e) => this.handleLinkClick(e)} onClick={(e) => this.handleLinkClick(e)}
>Cloud-Syncing-and-Backup</a> >Cloud-Syncing-and-Backup</a>
@@ -196,7 +196,7 @@ class StoragesTab extends React.Component {
<div styleName='addStorage-body-control'> <div styleName='addStorage-body-control'>
<button styleName='addStorage-body-control-createButton' <button styleName='addStorage-body-control-createButton'
onClick={(e) => this.handleAddStorageCreateButton(e)} onClick={(e) => this.handleAddStorageCreateButton(e)}
>Create</button> >Add</button>
<button styleName='addStorage-body-control-cancelButton' <button styleName='addStorage-body-control-cancelButton'
onClick={(e) => this.handleAddStorageCancelButton(e)} onClick={(e) => this.handleAddStorageCancelButton(e)}
>Cancel</button> >Cancel</button>

View File

@@ -20,3 +20,8 @@ $tab--dark-text-color = #E5E5E5
body[data-theme="dark"] body[data-theme="dark"]
.header .header
color $tab--dark-text-color color $tab--dark-text-color
haveToSave()
color #FFA500
font-size 10px
margin-top 3px

View File

@@ -7,11 +7,11 @@ import store from 'browser/main/store'
import consts from 'browser/lib/consts' import consts from 'browser/lib/consts'
import ReactCodeMirror from 'react-codemirror' import ReactCodeMirror from 'react-codemirror'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import _ from 'lodash'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
import _ from 'lodash'
const electron = require('electron') const electron = require('electron')
const ipc = electron.ipcRenderer const ipc = electron.ipcRenderer
@@ -62,6 +62,7 @@ class UiTab extends React.Component {
ui: { ui: {
theme: this.refs.uiTheme.value, theme: this.refs.uiTheme.value,
showCopyNotification: this.refs.showCopyNotification.checked, showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked,
disableDirectWrite: this.refs.uiD2w != null disableDirectWrite: this.refs.uiD2w != null
? this.refs.uiD2w.checked ? this.refs.uiD2w.checked
: false : false
@@ -72,6 +73,7 @@ class UiTab extends React.Component {
fontFamily: this.refs.editorFontFamily.value, fontFamily: this.refs.editorFontFamily.value,
indentType: this.refs.editorIndentType.value, indentType: this.refs.editorIndentType.value,
indentSize: this.refs.editorIndentSize.value, indentSize: this.refs.editorIndentSize.value,
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
switchPreview: this.refs.editorSwitchPreview.value, switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value, keyMap: this.refs.editorKeyMap.value,
scrollPastEnd: this.refs.scrollPastEnd.checked scrollPastEnd: this.refs.scrollPastEnd.checked
@@ -84,7 +86,8 @@ class UiTab extends React.Component {
latexInlineOpen: this.refs.previewLatexInlineOpen.value, latexInlineOpen: this.refs.previewLatexInlineOpen.value,
latexInlineClose: this.refs.previewLatexInlineClose.value, latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value, latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value latexBlockClose: this.refs.previewLatexBlockClose.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked
} }
} }
@@ -93,8 +96,19 @@ class UiTab extends React.Component {
if (newCodemirrorTheme !== codemirrorTheme) { if (newCodemirrorTheme !== codemirrorTheme) {
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`) checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
} }
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => {
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }) const {ui, editor, preview} = this.props.config
this.currentConfig = {ui, editor, preview}
if (_.isEqual(this.currentConfig, this.state.config)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'UI',
type: 'warning',
message: 'You have to save!'
})
}
})
} }
handleSaveUIClick (e) { handleSaveUIClick (e) {
@@ -111,6 +125,7 @@ class UiTab extends React.Component {
config: newConfig config: newConfig
}) })
this.clearMessage() this.clearMessage()
this.props.haveToSave()
} }
clearMessage () { clearMessage () {
@@ -135,10 +150,10 @@ class UiTab extends React.Component {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='group'> <div styleName='group'>
<div styleName='group-header'>UI</div> <div styleName='group-header'>Interface</div>
<div styleName='group-section'> <div styleName='group-section'>
Color Theme Interface Theme
<div styleName='group-section-control'> <div styleName='group-section-control'>
<select value={config.ui.theme} <select value={config.ui.theme}
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
@@ -161,6 +176,16 @@ class UiTab extends React.Component {
Show &quot;Saved to Clipboard&quot; notification when copying Show &quot;Saved to Clipboard&quot; notification when copying
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.confirmDeletion}
ref='confirmDeletion'
type='checkbox'
/>&nbsp;
Show a confirmation dialog when deleting notes
</label>
</div>
{ {
global.process.platform === 'win32' global.process.platform === 'win32'
? <div styleName='group-checkBoxSection'> ? <div styleName='group-checkBoxSection'>
@@ -170,7 +195,7 @@ class UiTab extends React.Component {
refs='uiD2w' refs='uiD2w'
disabled={OSX} disabled={OSX}
type='checkbox' type='checkbox'
/> />&nbsp;
Disable Direct Write(It will be applied after restarting) Disable Direct Write(It will be applied after restarting)
</label> </label>
</div> </div>
@@ -280,6 +305,17 @@ class UiTab extends React.Component {
</div> </div>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.displayLineNumbers}
ref='editorDisplayLineNumbers'
type='checkbox'
/>&nbsp;
Show line numbers in the editor
</label>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}
@@ -333,6 +369,16 @@ class UiTab extends React.Component {
</select> </select>
</div> </div>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.scrollPastEnd}
ref='previewScrollPastEnd'
type='checkbox'
/>&nbsp;
Allow preview to scroll past the last line
</label>
</div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}
@@ -412,7 +458,8 @@ UiTab.propTypes = {
user: PropTypes.shape({ user: PropTypes.shape({
name: PropTypes.string name: PropTypes.string
}), }),
dispatch: PropTypes.func dispatch: PropTypes.func,
haveToSave: PropTypes.func
} }
export default CSSModules(UiTab, styles) export default CSSModules(UiTab, styles)

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import HotkeyTab from './HotkeyTab' import HotkeyTab from './HotkeyTab'
import UiTab from './UiTab' import UiTab from './UiTab'
@@ -11,13 +10,16 @@ import ModalEscButton from 'browser/components/ModalEscButton'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferencesModal.styl' import styles from './PreferencesModal.styl'
import RealtimeNotification from 'browser/components/RealtimeNotification' import RealtimeNotification from 'browser/components/RealtimeNotification'
import _ from 'lodash'
class Preferences extends React.Component { class Preferences extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {
currentTab: 'STORAGES' currentTab: 'STORAGES',
UIAlert: '',
HotkeyAlert: ''
} }
} }
@@ -58,6 +60,7 @@ class Preferences extends React.Component {
<HotkeyTab <HotkeyTab
dispatch={dispatch} dispatch={dispatch}
config={config} config={config}
haveToSave={alert => this.setState({HotkeyAlert: alert})}
/> />
) )
case 'UI': case 'UI':
@@ -65,6 +68,7 @@ class Preferences extends React.Component {
<UiTab <UiTab
dispatch={dispatch} dispatch={dispatch}
config={config} config={config}
haveToSave={alert => this.setState({UIAlert: alert})}
/> />
) )
case 'CROWDFUNDING': case 'CROWDFUNDING':
@@ -90,23 +94,29 @@ class Preferences extends React.Component {
} }
getContentBoundingBox () { getContentBoundingBox () {
const node = ReactDOM.findDOMNode(this.refs.content) return this.refs.content.getBoundingClientRect()
return node.getBoundingClientRect() }
haveToSaveNotif (type, message) {
return (
<p styleName={`saving--${type}`}>{message}</p>
)
} }
render () { render () {
const content = this.renderContent() const content = this.renderContent()
const tabs = [ const tabs = [
{target: 'STORAGES', label: 'Storages'}, {target: 'STORAGES', label: 'Storage'},
{target: 'HOTKEY', label: 'Hotkey'}, {target: 'HOTKEY', label: 'Hotkeys', Hotkey: this.state.HotkeyAlert},
{target: 'UI', label: 'UI'}, {target: 'UI', label: 'Interface', UI: this.state.UIAlert},
{target: 'INFO', label: 'Community / Info'}, {target: 'INFO', label: 'About'},
{target: 'CROWDFUNDING', label: 'Crowdfunding'} {target: 'CROWDFUNDING', label: 'Crowdfunding'}
] ]
const navButtons = tabs.map((tab) => { const navButtons = tabs.map((tab) => {
const isActive = this.state.currentTab === tab.target const isActive = this.state.currentTab === tab.target
const isUiHotkeyTab = _.isObject(tab[tab.label]) && tab.label === tab[tab.label].tab
return ( return (
<button styleName={isActive <button styleName={isActive
? 'nav-button--active' ? 'nav-button--active'
@@ -118,6 +128,7 @@ class Preferences extends React.Component {
<span styleName='nav-button-label'> <span styleName='nav-button-label'>
{tab.label} {tab.label}
</span> </span>
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}
</button> </button>
) )
}) })

View File

@@ -87,8 +87,13 @@ function data (state = defaultDataMap(), action) {
state.trashedSet = new Set(state.trashedSet) state.trashedSet = new Set(state.trashedSet)
if (note.isTrashed) { if (note.isTrashed) {
state.trashedSet.add(uniqueKey) state.trashedSet.add(uniqueKey)
state.starredSet.delete(uniqueKey)
} else { } else {
state.trashedSet.delete(uniqueKey) state.trashedSet.delete(uniqueKey)
if (note.isStarred) {
state.starredSet.add(uniqueKey)
}
} }
} }
@@ -349,6 +354,11 @@ function data (state = defaultDataMap(), action) {
state.storageMap = new Map(state.storageMap) state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage) state.storageMap.set(action.storage.key, action.storage)
return state return state
case 'EXPORT_FOLDER':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
case 'DELETE_FOLDER': case 'DELETE_FOLDER':
{ {
state = Object.assign({}, state) state = Object.assign({}, state)

View File

@@ -1,129 +0,0 @@
@import '../../../node_modules/nib/lib/nib'
@import '../vars'
@import '../mixins/*'
global-reset()
@import '../shared/*'
@import '../theme/*'
iptBgColor = #E6E6E6
iptFocusBorderColor = #369DCD
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
body
font-family DEFAULT_FONTS
color textColor
font-size fontSize
width 100%
height 100%
overflow hidden
button, input
font-family DEFAULT_FONTS
.Finder
absolute top bottom left right
.FinderInput
padding 11px
margin 0 auto
height 55px
box-sizing border-box
border-bottom solid 1px borderColor
background-color iptBgColor
z-index 200
input
display block
width 100%
border solid 1px borderColor
padding 0 10px
font-size 1em
height 33px
border-radius 5px
box-sizing border-box
border-radius 5px
&:focus, &.focus
border-color iptFocusBorderColor
outline none
.FinderList
absolute left bottom
top 55px
border-right solid 1px borderColor
box-sizing border-box
width 250px
overflow-y auto
z-index 0
user-select none
&>ul>li
.articleItem
padding 10px
border solid 2px transparent
box-sizing border-box
cursor pointer
white-space nowrap
overflow-x hidden
text-overflow ellipsis
.divider
box-sizing border-box
border-bottom solid 1px borderColor
&.active
.articleItem
border-color brandColor
.FinderDetail
absolute right bottom
top 55px
left 250px
box-shadow 0px 0px 10px 0 #CCC
z-index 100
.header
absolute top left right
height 55px
box-sizing border-box
padding 0 10px
border-bottom solid 1px borderColor
line-height 55px
font-size 18px
white-space nowrap
text-overflow ellipsis
overflow-x hidden
clearfix()
.left
float left
.right
float right
button
border-radius 16.5px
cursor pointer
height 33px
width 33px
border none
margin-right 5px
font-size 18px
color inactiveTextColor
background-color transparent
padding 0
.tooltip
tooltip()
&.clipboardBtn .tooltip
margin-left -160px
margin-top 25px
&:hover
color textColor
.tooltip
opacity 1
.content
position absolute
top 55px
padding 10px
bottom 0
left 0
right 0
box-sizing border-box
overflow-y auto
.MarkdownPreview
marked()
&.empty
color lighten(inactiveTextColor, 10%)
user-select none
font-size 14px
.CodeEditor
absolute top bottom left right

View File

@@ -46,6 +46,7 @@ tooltip()
// UI Input // UI Input
$ui-input--focus-borderColor = #369DCD $ui-input--focus-borderColor = #369DCD
$ui-input--disabled-backgroundColor = #DDD $ui-input--disabled-backgroundColor = #DDD
$ui-input--create-folder-modal = #C9C9C9
// Parts // Parts
$ui-favorite-star-button-color = #FFC216 $ui-favorite-star-button-color = #FFC216
@@ -187,7 +188,6 @@ modal()
border-radius $modal-border-radius border-radius $modal-border-radius
topBarButtonRight() topBarButtonRight()
position absolute
width 34px width 34px
height 34px height 34px
border-radius 17px border-radius 17px
@@ -340,3 +340,11 @@ $ui-solarized-dark-button--active-color = #93a1a1
$ui-solarized-dark-button--active-backgroundColor = #073642 $ui-solarized-dark-button--active-backgroundColor = #073642
$ui-solarized-dark-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%) $ui-solarized-dark-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
$ui-solarized-dark-button--focus-borderColor = lighten(#369DCD, 25%) $ui-solarized-dark-button--focus-borderColor = lighten(#369DCD, 25%)
modalSolarizedDark()
position relative
z-index $modal-z-index
width 100%
background-color $ui-solarized-dark-backgroundColor
overflow hidden
border-radius $modal-border-radius

View File

@@ -378,52 +378,10 @@ body[data-theme="dark"]
&:hover &:hover
color themeDarkFocusButton color themeDarkFocusButton
.Finder
.FinderInput
color themeDarkText
border-color themeDarkBorder
background-color themeDarkBackground
input
color themeDarkText
border-color lighten(themeDarkBackground, 10%)
background-color lighten(themeDarkBackground, 10%)
&:focus
border-color themeDarkTopicColor
.FinderList
color themeDarkText
border-color themeDarkBorder
background-color themeDarkList
.divider
border-color themeDarkBorder
.FinderDetail
color themeDarkText
border-color themeDarkBorder
background-color themeDarkPreview
box-shadow 0px 0px 10px 0 darken(themeDarkBorder, 20%);
.header
border-color themeDarkBorder
.right
.clipboardBtn
transition 0.1s
&:hover
color themeDarkFocusButton
.tooltip
background-color themeDarkTooltip
.ArticleDetail-panel .ArticleDetail-panel
border-radius 0 border-radius 0
// Markdown Preview // Markdown Preview
.Finder .FinderDetail .content,
.ArticleDetail .ArticleDetail-panel .ArticleEditor .ArticleDetail .ArticleDetail-panel .ArticleEditor
.MarkdownPreview .MarkdownPreview
color themeDarkText color themeDarkText

View File

@@ -19,7 +19,7 @@ module.exports = function (grunt) {
var initConfig = { var initConfig = {
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
'create-windows-installer': { 'create-windows-installer': {
x64: { ia32: {
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'), appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
outputDirectory: path.join(__dirname, 'dist'), outputDirectory: path.join(__dirname, 'dist'),
authors: 'MAISIN&CO., Inc.', authors: 'MAISIN&CO., Inc.',
@@ -109,7 +109,7 @@ module.exports = function (grunt) {
var done = this.async() var done = this.async()
var opts = { var opts = {
name: 'Boostnote', name: 'Boostnote',
arch: 'x64', arch: 'ia32',
dir: __dirname, dir: __dirname,
version: grunt.config.get('pkg.config.electron-version'), version: grunt.config.get('pkg.config.electron-version'),
'app-version': grunt.config.get('pkg.version'), 'app-version': grunt.config.get('pkg.version'),

View File

@@ -4,11 +4,6 @@ const path = require('path')
var error = null var error = null
function isFinderCalled () {
var argv = process.argv.slice(1)
return argv.some((arg) => arg.match(/--finder/))
}
function execMainApp () { function execMainApp () {
const appRootPath = path.join(process.execPath, '../..') const appRootPath = path.join(process.execPath, '../..')
const updateDotExePath = path.join(appRootPath, 'Update.exe') const updateDotExePath = path.join(appRootPath, 'Update.exe')
@@ -78,8 +73,4 @@ function execMainApp () {
require('./lib/main-app') require('./lib/main-app')
} }
if (isFinderCalled()) { execMainApp()
require('./lib/finder-app')
} else {
execMainApp()
}

View File

@@ -1,14 +0,0 @@
const electron = require('electron')
const app = electron.app
app.on('ready', function () {
if (process.platform === 'darwin') {
app.dock.hide()
}
/* eslint-disable */
finderWindow = require('./finder-window')
/* eslint-enable */
})
module.exports = app

View File

@@ -1,101 +0,0 @@
const electron = require('electron')
const { app } = electron
const BrowserWindow = electron.BrowserWindow
const Menu = electron.Menu
const MenuItem = electron.MenuItem
const Tray = electron.Tray
const path = require('path')
var config = {
width: 840,
height: 540,
show: false,
frame: false,
resizable: false,
zoomFactor: 1.0,
webPreferences: {
blinkFeatures: 'OverlayScrollbars'
},
skipTaskbar: true,
standardWindow: false
}
if (process.platform === 'darwin') {
config['always-on-top'] = true
}
var finderWindow = new BrowserWindow(config)
var url = path.resolve(__dirname, './finder.html')
finderWindow.loadURL('file://' + url)
finderWindow.setSkipTaskbar(true)
if (process.platform === 'darwin') {
finderWindow.setVisibleOnAllWorkspaces(true)
}
finderWindow.on('blur', function () {
hideFinder()
})
finderWindow.on('close', function (e) {
e.preventDefault()
finderWindow.hide()
})
var trayIcon = process.platform === 'darwin' || process.platform === 'win32'
? path.join(__dirname, '../resources/tray-icon-default.png')
: path.join(__dirname, '../resources/tray-icon.png')
var appIcon = new Tray(trayIcon)
appIcon.setToolTip('Boostnote')
if (process.platform === 'darwin') {
appIcon.setPressedImage(path.join(__dirname, '../resources/tray-icon-dark.png'))
}
var trayMenu = new Menu()
trayMenu.append(new MenuItem({
label: 'Open Main window',
click: function () {
finderWindow.webContents.send('open-main-from-tray')
}
}))
if (process.env.platform !== 'linux' || process.env.DESKTOP_SESSION === 'cinnamon') {
trayMenu.append(new MenuItem({
label: 'Open Finder window',
click: function () {
finderWindow.webContents.send('open-finder-from-tray')
}
}))
}
trayMenu.append(new MenuItem({
label: 'Quit',
click: function () {
finderWindow.webContents.send('quit-from-tray')
}
}))
appIcon.setContextMenu(trayMenu)
appIcon.on('click', function (e) {
e.preventDefault()
appIcon.popUpContextMenu(trayMenu)
})
function hideFinder () {
if (process.platform === 'win32') {
finderWindow.minimize()
return
}
if (process.platform === 'darwin') {
Menu.sendActionToFirstResponder('hide:')
}
finderWindow.hide()
}
app.on('before-quit', function (e) {
finderWindow.removeAllListeners()
})
module.exports = finderWindow

View File

@@ -1,63 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Boostnote Finder</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
<style>
@font-face {
font-family: 'Lato';
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
font-style: normal;
font-weight: normal;
text-rendering: optimizeLegibility;
}
</style>
</head>
<body>
<div id="content"></div>
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
<script src="../node_modules/codemirror/mode/meta.js"></script>
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
<script src="../node_modules/codemirror/keymap/vim.js"></script>
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
<script src="../node_modules/raphael/raphael.min.js"></script>
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
<script src="../node_modules/katex/dist/katex.min.js"></script>
<script src="../node_modules/react/dist/react.min.js"></script>
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script>
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
<script>
window._ = require('lodash');
</script>
<script src="../node_modules/js-sequence-diagrams/fucknpm/sequence-diagram-min.js"></script>
<script>
const electron = require('electron')
electron.webFrame.setZoomLevelLimits(1, 1)
const _ = require('lodash')
var scriptUrl = _.find(electron.remote.process.argv, (a) => a === '--hot')
? 'http://localhost:8080/assets/finder.js'
: '../compiled/finder.js'
var scriptEl = document.createElement('script')
scriptEl.setAttribute('type', 'text/javascript')
scriptEl.setAttribute('src', scriptUrl)
document.body.appendChild(scriptEl)
</script>
</body>
</html>

View File

@@ -26,10 +26,6 @@ function toggleMainWindow () {
} }
} }
function toggleFinder () {
nodeIpc.server.broadcast('open-finder')
}
ipcMain.on('config-renew', (e, payload) => { ipcMain.on('config-renew', (e, payload) => {
nodeIpc.server.broadcast('config-renew', payload) nodeIpc.server.broadcast('config-renew', payload)
@@ -37,11 +33,6 @@ ipcMain.on('config-renew', (e, payload) => {
var { config } = payload var { config } = payload
var errors = [] var errors = []
try {
globalShortcut.register(config.hotkey.toggleFinder, toggleFinder)
} catch (err) {
errors.push('toggleFinder')
}
try { try {
globalShortcut.register(config.hotkey.toggleMain, toggleMainWindow) globalShortcut.register(config.hotkey.toggleMain, toggleMainWindow)
} catch (err) { } catch (err) {
@@ -61,12 +52,6 @@ ipcMain.on('config-renew', (e, payload) => {
nodeIpc.serve( nodeIpc.serve(
path.join(app.getPath('userData'), 'boostnote.service'), path.join(app.getPath('userData'), 'boostnote.service'),
function () { function () {
nodeIpc.server.on('open-main-from-finder', toggleMainWindow)
nodeIpc.server.on('quit-from-finder', function () {
app.quit()
})
nodeIpc.server.on('connect', function (socket) { nodeIpc.server.on('connect', function (socket) {
nodeIpc.log('ipc server >> socket joinned'.rainbow) nodeIpc.log('ipc server >> socket joinned'.rainbow)
socket.on('close', function () { socket.on('close', function () {
@@ -76,14 +61,6 @@ nodeIpc.serve(
nodeIpc.server.on('error', function (err) { nodeIpc.server.on('error', function (err) {
nodeIpc.log('Node IPC error'.rainbow, err) nodeIpc.log('Node IPC error'.rainbow, err)
}) })
// Todo: Direct connection between Main window renderer & Finder window renderer
nodeIpc.server.on('request-data-from-finder', function () {
nodeIpc.server.broadcast('request-data-from-finder')
})
nodeIpc.server.on('throttle-data', function (payload) {
nodeIpc.server.broadcast('throttle-data', payload)
})
} }
) )

View File

@@ -2,9 +2,6 @@ const electron = require('electron')
const app = electron.app const app = electron.app
const Menu = electron.Menu const Menu = electron.Menu
const ipc = electron.ipcMain const ipc = electron.ipcMain
const path = require('path')
const ChildProcess = require('child_process')
const _ = require('lodash')
const GhReleases = require('electron-gh-releases') const GhReleases = require('electron-gh-releases')
// electron.crashReporter.start() // electron.crashReporter.start()
var ipcServer = null var ipcServer = null
@@ -65,14 +62,6 @@ updater.autoUpdater.on('error', (err) => {
console.log(err) console.log(err)
}) })
ipc.on('update-check', function (event, msg) {
if (isUpdateReady) {
mainWindow.webContents.send('update-ready', 'Update available!')
} else {
checkUpdate()
}
})
ipc.on('update-app-confirm', function (event, msg) { ipc.on('update-app-confirm', function (event, msg) {
if (isUpdateReady) { if (isUpdateReady) {
mainWindow.removeAllListeners() mainWindow.removeAllListeners()
@@ -80,17 +69,6 @@ ipc.on('update-app-confirm', function (event, msg) {
} }
}) })
function spawnFinder () {
var finderArgv = [path.join(__dirname, 'finder-app.js'), '--finder']
if (_.find(process.argv, a => a === '--hot')) finderArgv.push('--hot')
var finderProcess = ChildProcess
.execFile(process.execPath, finderArgv)
app.on('before-quit', function () {
finderProcess.kill()
})
}
app.on('ready', function () { app.on('ready', function () {
mainWindow = require('./main-window') mainWindow = require('./main-window')
@@ -98,13 +76,9 @@ app.on('ready', function () {
var menu = Menu.buildFromTemplate(template) var menu = Menu.buildFromTemplate(template)
switch (process.platform) { switch (process.platform) {
case 'darwin': case 'darwin':
spawnFinder()
Menu.setApplicationMenu(menu) Menu.setApplicationMenu(menu)
break break
case 'win32': case 'win32':
/* eslint-disable */
finderWindow = require('./finder-window')
/* eslint-disable */
mainWindow.setMenu(menu) mainWindow.setMenu(menu)
break break
case 'linux': case 'linux':
@@ -115,12 +89,22 @@ app.on('ready', function () {
// Check update every hour // Check update every hour
setInterval(function () { setInterval(function () {
checkUpdate() checkUpdate()
}, 1000 * 60 * 60) }, 1000 * 60 * 60 * 24)
checkUpdate() // Check update after 10 secs to prevent file locking of Windows
setTimeout(() => {
checkUpdate()
ipc.on('update-check', function (event, msg) {
if (isUpdateReady) {
mainWindow.webContents.send('update-ready', 'Update available!')
} else {
checkUpdate()
}
})
}, 10000)
ipcServer = require('./ipcServer') ipcServer = require('./ipcServer')
ipcServer.server.start() ipcServer.server.start()
}) })
module.exports = app module.exports = app

View File

@@ -108,6 +108,13 @@ const file = {
mainWindow.webContents.send('list:isMarkdownNote') mainWindow.webContents.send('list:isMarkdownNote')
mainWindow.webContents.send('export:save-md') mainWindow.webContents.send('export:save-md')
} }
},
{
label: 'HTML (.html)',
click () {
mainWindow.webContents.send('list:isMarkdownNote')
mainWindow.webContents.send('export:save-html')
}
} }
] ]
}, },

View File

@@ -4,6 +4,7 @@ const BrowserWindow = electron.BrowserWindow
const path = require('path') const path = require('path')
const Config = require('electron-config') const Config = require('electron-config')
const config = new Config() const config = new Config()
const _ = require('lodash')
var showMenu = process.platform !== 'win32' var showMenu = process.platform !== 'win32'
const windowSize = config.get('windowsize') || { width: 1080, height: 720 } const windowSize = config.get('windowsize') || { width: 1080, height: 720 }
@@ -39,41 +40,28 @@ mainWindow.webContents.sendInputEvent({
keyCode: '\u0008' keyCode: '\u0008'
}) })
if (process.platform !== 'linux' || process.env.DESKTOP_SESSION === 'cinnamon') { if (process.platform === 'darwin' || process.env.DESKTOP_SESSION === 'cinnamon') {
mainWindow.on('close', function (e) { mainWindow.on('close', function (e) {
e.preventDefault() e.preventDefault()
if (process.platform === 'win32') { if (mainWindow.isFullScreen()) {
quitApp() mainWindow.once('leave-full-screen', function () {
} else {
if (mainWindow.isFullScreen()) {
mainWindow.once('leave-full-screen', function () {
mainWindow.hide()
})
mainWindow.setFullScreen(false)
} else {
mainWindow.hide() mainWindow.hide()
} })
mainWindow.setFullScreen(false)
} else {
mainWindow.hide()
} }
}) })
app.on('before-quit', function (e) { app.on('before-quit', function (e) {
storeWindowSize()
mainWindow.removeAllListeners() mainWindow.removeAllListeners()
}) })
} else { } else {
mainWindow.on('close', function () {
storeWindowSize()
})
app.on('window-all-closed', function () { app.on('window-all-closed', function () {
app.quit() app.quit()
}) })
} }
mainWindow.on('resize', _.throttle(storeWindowSize, 500))
function quitApp () {
storeWindowSize()
app.quit()
}
function storeWindowSize () { function storeWindowSize () {
try { try {

View File

@@ -69,6 +69,7 @@
<script src="../node_modules/codemirror/lib/codemirror.js"></script> <script src="../node_modules/codemirror/lib/codemirror.js"></script>
<script src="../node_modules/codemirror/mode/meta.js"></script> <script src="../node_modules/codemirror/mode/meta.js"></script>
<script src="../node_modules/codemirror-mode-elixir/dist/elixir.js"></script>
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script> <script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script> <script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
<script src="../node_modules/codemirror/addon/mode/simple.js"></script> <script src="../node_modules/codemirror/addon/mode/simple.js"></script>
@@ -79,6 +80,8 @@
<script src="../node_modules/codemirror/addon/edit/continuelist.js"></script> <script src="../node_modules/codemirror/addon/edit/continuelist.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/search/search.js"></script> <script src="../node_modules/codemirror/addon/search/search.js"></script>
<script src="../node_modules/codemirror/addon/search/searchcursor.js"></script> <script src="../node_modules/codemirror/addon/search/searchcursor.js"></script>
<script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script> <script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script>

View File

@@ -1,7 +1,7 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.8.19", "version": "0.9.0",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
@@ -10,13 +10,13 @@
"hot": "electron ./index.js --hot", "hot": "electron ./index.js --hot",
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js", "webpack": "webpack-dev-server --hot --inline --config webpack.config.js",
"compile": "grunt compile", "compile": "grunt compile",
"test": "PWD=$(pwd) NODE_ENV=test ava", "test": "PWD=$(pwd) NODE_ENV=test ava --serial",
"fix": "npm run lint --fix", "fix": "npm run lint --fix",
"lint": "eslint .", "lint": "eslint .",
"dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\"" "dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\""
}, },
"config": { "config": {
"electron-version": "1.6.15" "electron-version": "1.7.11"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -53,13 +53,15 @@
"aws-sdk": "^2.48.0", "aws-sdk": "^2.48.0",
"aws-sdk-mobile-analytics": "^0.9.2", "aws-sdk-mobile-analytics": "^0.9.2",
"codemirror": "^5.19.0", "codemirror": "^5.19.0",
"codemirror-mode-elixir": "^1.1.1",
"electron-config": "^0.2.1", "electron-config": "^0.2.1",
"electron-gh-releases": "^2.0.2", "electron-gh-releases": "^2.0.2",
"electron-windows-installer": "^1.7.8",
"flowchart.js": "^1.6.5", "flowchart.js": "^1.6.5",
"font-awesome": "^4.3.0", "font-awesome": "^4.3.0",
"immutable": "^3.8.1", "immutable": "^3.8.1",
"js-sequence-diagrams": "^1000000.0.6", "js-sequence-diagrams": "^1000000.0.6",
"katex": "^0.7.1", "katex": "^0.8.3",
"lodash": "^4.11.1", "lodash": "^4.11.1",
"lodash-move": "^1.1.1", "lodash-move": "^1.1.1",
"markdown-it": "^6.0.1", "markdown-it": "^6.0.1",
@@ -102,7 +104,7 @@
"css-loader": "^0.19.0", "css-loader": "^0.19.0",
"devtron": "^1.1.0", "devtron": "^1.1.0",
"dom-storage": "^2.0.2", "dom-storage": "^2.0.2",
"electron": "^1.6.15", "electron": "1.7.11",
"electron-packager": "^6.0.0", "electron-packager": "^6.0.0",
"eslint": "^3.13.1", "eslint": "^3.13.1",
"eslint-config-standard": "^6.2.1", "eslint-config-standard": "^6.2.1",
@@ -111,7 +113,8 @@
"eslint-plugin-standard": "^3.0.1", "eslint-plugin-standard": "^3.0.1",
"faker": "^3.1.0", "faker": "^3.1.0",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-electron-installer": "^1.2.0", "grunt-electron-installer": "2.1.0",
"gulp": "^3.9.1",
"history": "^1.17.0", "history": "^1.17.0",
"jsdom": "^9.4.2", "jsdom": "^9.4.2",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",

View File

@@ -1,6 +1,4 @@
New:zap: :mega: Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!
Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!
![Boostnote app screenshot](./resources/repository/top.png) ![Boostnote app screenshot](./resources/repository/top.png)
@@ -12,8 +10,6 @@ Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mo
## Authors & Maintainers ## Authors & Maintainers
- [Rokt33r](https://github.com/rokt33r) - [Rokt33r](https://github.com/rokt33r)
- [sota1235](https://github.com/sota1235)
- [Kohei TAKATA](https://github.com/kohei-takata)
- [Sosuke](https://github.com/sosukesuzuki) - [Sosuke](https://github.com/sosukesuzuki)
- [Kazz](https://github.com/kazup01) - [Kazz](https://github.com/kazup01)
@@ -29,16 +25,17 @@ Boostnote is an open source project. It's an independent project with its ongoin
## Community ## Community
- [Facebook Group](https://www.facebook.com/groups/boostnote/) - [Facebook Group](https://www.facebook.com/groups/boostnote/)
- [Twitter](https://twitter.com/boostnoteapp) - [Twitter](https://twitter.com/boostnoteapp)
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjc2MDM0MDEyODk2LThlZDlhYmYwMjdkMmJjMGM5MGFiMGJmNzk5ZTdhNzFhMmNmMDFlY2M2YTE1MTZkOThiOGZmNTI3YzJiOTBhMTQ) - [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzAzMjI1MTIyNTQ3LTc2MjNiYWU3NTc1YjZlMTk3NzFmOWE1ZWU1MGRhMzBkMGIwMWFjOWMxMDRiM2I2NzkzYzc4OGZhNmVhZjYzZTM)
- [Blog](https://medium.com/boostnote) - [Blog](https://medium.com/boostnote)
- [Reddit](https://www.reddit.com/r/Boostnote/) - [Reddit](https://www.reddit.com/r/Boostnote/)
#### More Information #### More Information
* [Website](https://boostnote.io) * Website: https://boostnote.io
* [Subscribe to the Newsletter](https://boostnote.io/#community): Get updates on Boostnote progress. No spam, ever :) * Newsletters: https://boostnote.io/#subscribe
* [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote. * [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
* Copyright (C) 2017 Maisin&Co. * Copyright (C) 2016 - 2018 BoostIO, Inc.
#### License #### License

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="13px" height="13px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch --> <!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
<title>icon-full</title> <title>icon-full</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>

Before

Width:  |  Height:  |  Size: 954 B

After

Width:  |  Height:  |  Size: 954 B

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="15px" height="15px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch --> <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
<title>icon-info</title> <title>icon-info</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="15px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch --> <!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
<title>icon-mode-markdown-off</title> <title>icon-mode-markdown-off</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="13px" height="13px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch --> <!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
<title>icon-mode-split-on</title> <title>icon-mode-split-on</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="14px" height="14px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch --> <!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
<title>icon-previewoff-off</title> <title>icon-previewoff-off</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="14px" height="14px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch --> <!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
<title>icon-previewoff-on</title> <title>icon-previewoff-on</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

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