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

Compare commits

...

637 Commits

Author SHA1 Message Date
Junyoung Choi
78c00b1722 v0.11.15 2019-02-08 20:33:20 +09:00
Junyoung Choi
2ff655d2dc Check update if the app is packaged 2019-02-08 19:30:46 +09:00
Junyoung Choi
e53717cd87 v0.11.14 2019-02-08 16:46:29 +09:00
Junyoung Choi
8b8d915ab7 Merge pull request #2642 from daiyam/drop-browser-image
handle all dropped images
2019-02-05 17:55:18 +09:00
Junyoung Choi
54de57ee7b Merge pull request #2863 from zzdjk6/master
FIX #2853 Allow "#" in title
2019-02-05 11:25:21 +09:00
Baptiste Augrain
e0d9cf7f5c fix dropping image from Firefox on Linux 2019-02-04 18:22:10 +01:00
Baptiste Augrain
10ea5d00eb avoid converting SVG 2019-02-04 14:09:11 +01:00
Baptiste Augrain
de76f55fe2 fix gif 2019-02-04 13:54:45 +01:00
Baptiste Augrain
cd301d514c Merge branch 'master' into drop-browser-image 2019-02-04 13:36:32 +01:00
Shenghan Chen
0f232b3d86 FIX #2853 Allow "#" in title
- Only strip the leading # in the title
- Make the finding title logic more straightforward
- Add unit test
2019-02-04 20:07:33 +13:00
Junyoung Choi
53ff693e95 Merge pull request #2852 from Aaron-Bird/bug-gif
[Fix] GIFs don't animate
2019-02-04 12:25:28 +09:00
Baptiste Augrain
215484c19a add tests 2019-02-01 16:27:47 +01:00
Junyoung Choi
885f656d34 Merge pull request #2856 from K-Sato1995/fix/grammer-error#2844
[Fix  #2844] Grammar Error In Notes
2019-02-01 16:44:12 +09:00
Baptiste Augrain
851d3ba159 fix image path when the dropped image's url contains a query 2019-01-31 23:26:31 +01:00
Katsuki
62ab444b29 Fix grammer error 2019-01-31 19:33:43 +09:00
Junyoung Choi
f1b929c13b Merge pull request #2848 from daiyam/fix-gallery
fix image path due to bad regex
2019-01-31 10:58:20 +09:00
Aaron-Bird
806139091c fix: GIFs don't animate 2019-01-31 01:08:07 +08:00
Junyoung Choi
6960c8b2d6 Merge pull request #2835 from ehhc/fix_broken_attachments
Strange url-handling reverted + tests modified so that they might fin…
2019-01-30 10:32:20 +09:00
Baptiste Augrain
b1c6c0442f fix guardrails errors 2019-01-29 16:49:51 +01:00
Baptiste Augrain
a85a27f225 - fix bad regex
- improve test
- fix missing 'e' in some functions name
2019-01-29 16:05:30 +01:00
Junyoung Choi
950d31ada8 Merge pull request #2809 from roottool/zoom-in-and-out-image#1448
Zoom in and out image #1448
2019-01-28 11:39:14 +09:00
Junyoung Choi
543c31cec6 Merge pull request #2831 from roottool/change_default_editor_font#1995
Change default editor font #1995
2019-01-28 10:02:19 +09:00
roottool
5e87ec2627 Specified MarkdownPreview class 2019-01-27 22:26:31 +09:00
ehhc
7165c4550b possibly fix for the broken test... 2019-01-26 18:16:39 +01:00
ehhc
472496d59c possibly fix for the broken test... 2019-01-26 17:53:31 +01:00
ehhc
127da40256 possibly fix for the broken test... 2019-01-26 17:37:04 +01:00
ehhc
a113b99de0 Strange url-handling reverted + tests modified so that they might find that issue in the future + test modified so that they both contain tests for posix and windows path separator -> fixes #2834 2019-01-26 17:21:53 +01:00
Junyoung Choi
74535c9cba Update yarn.lock 2019-01-26 12:30:22 +09:00
Junyoung Choi
79254a562f Update contributors 2019-01-26 12:29:20 +09:00
Junyoung Choi
4b1469748b v0.11.13 2019-01-26 12:25:05 +09:00
roottool
1683d63f33 Modified indent 2019-01-26 01:03:37 +09:00
roottool
4cce52f9ce changed default editor font #1995 2019-01-25 23:17:05 +09:00
roottool
33fb03066e Reduced the count of calculation 2019-01-25 20:06:43 +09:00
Junyoung Choi
a1deb15db8 Merge pull request #2651 from ZeroX-DG/fix-delete-note
Fixed delete note not navigating to the next note
2019-01-23 14:58:50 +09:00
Junyoung Choi
96ab8ec958 Merge pull request #2661 from miguelalexbt/bugfix-2121
Fixed lock button behavior and display
2019-01-23 14:50:08 +09:00
Junyoung Choi
c0f68dce25 Merge pull request #2798 from MiloTodt/disable-updates-in-dev
Disable updates in dev
2019-01-23 13:03:14 +09:00
Junyoung Choi
2b6c38083c Merge pull request #2774 from gurungrahul2/2194
#2194 - fixes Hotkey not working
2019-01-23 13:01:45 +09:00
Milo Todt
667ece7d3f Update main-app.js 2019-01-22 19:59:27 -08:00
Junyoung Choi
9270e59508 Merge pull request #2786 from Foo-x/2232
Fix bullet position of LaTeX equation in list
2019-01-22 18:09:09 +09:00
Nguyen Viet Hung
b32865488e fixed app body background (#2791) 2019-01-22 18:07:44 +09:00
Junyoung Choi
e43c7e9a6a Merge pull request #2598 from richardtks/fix-number-list
Fix the auto-updating numbered list to the middle of a list
2019-01-22 17:47:26 +09:00
Milo Todt
c3a980836a change to isDev 2019-01-20 21:04:56 -08:00
Junyoung Choi
304b83be89 Merge pull request #2801 from MiloTodt/issue-2799-add_storage
Removal of USB drive will no longer cause irreparable damage.
2019-01-21 09:53:14 +09:00
Milo Todt
eea01f10ac Added catch for exceptions, removed uneeded duplicate test. 2019-01-17 17:00:07 -08:00
Junyoung Choi
3d9b85dc6d Merge pull request #2811 from MiloTodt/Add_cheatsheets_to_menu
Added cheatsheets to menu
2019-01-17 17:40:05 +09:00
Junyoung Choi
743a9009de Merge pull request #2797 from MiloTodt/patch-1
Update copywrite year
2019-01-17 17:39:22 +09:00
Junyoung Choi
a0da4f9dd0 Merge pull request #2795 from MiloTodt/update-build.md
Updated build.md to include directions for checking out PRs
2019-01-17 17:38:57 +09:00
Milo Todt
3fe45e9cbb Added cheatsheets to menu 2019-01-16 16:09:07 -08:00
roottool
294bf742cd change variable name #1448 2019-01-16 23:14:21 +09:00
roottool
ea5970ab1c change a magnification by image size #1448 2019-01-16 22:47:42 +09:00
roottool
72df418953 DRY my code #1448 2019-01-16 18:10:28 +09:00
roottool
f566b567be add zoom-in and zoom-out action of images #1448 2019-01-16 01:54:54 +09:00
Milo Todt
8d817066e8 Added a few more checks in related methods to prevent simular errors 2019-01-13 18:35:24 -08:00
Milo Todt
99b53f4a55 added filter. 2019-01-13 18:21:12 -08:00
Milo Todt
038154c441 Removed uneeded "electron-is-dev" package 2019-01-13 17:21:30 -08:00
Milo Todt
951a126d63 Update LICENSE 2019-01-12 18:51:03 -08:00
Milo Todt
fa77cda0b4 whitespace 2019-01-12 17:59:04 -08:00
Milo Todt
a0bfd9e497 Disables updates in development mode 2019-01-12 17:58:19 -08:00
Milo Todt
a8e601e5e0 Update InfoTab.js 2019-01-12 16:09:25 -08:00
Milo Todt
47d7cef214 Update copywrite year
2018->2019
2019-01-12 15:50:33 -08:00
Milo Todt
e298739cb9 fixed typo 2019-01-12 12:34:25 -08:00
Milo Todt
6fb72bd44a minor typo 2019-01-12 09:46:51 -08:00
Milo Todt
7b8fb56440 fixed error in url 2019-01-12 08:38:18 -08:00
Milo Todt
5e9bd2fd2d escaped angle brackets 2019-01-12 08:34:14 -08:00
Milo Todt
aac13dcdca Updated build.md to include directions for checking out PRs 2019-01-12 08:26:31 -08:00
Foo-x
406e230ed3 Fix bullet position of LaTeX equation in list 2019-01-10 23:26:47 +09:00
Junyoung Choi
5a9de1a95d Merge pull request #2759 from richardtks/toggle-menu-bar-settings
allow menu bar visibility to be set in the settings
2019-01-08 08:27:41 +09:00
Junyoung Choi
0289caad67 Merge pull request #2712 from roottool/fix-issue#2644-and-#2662
fix issues #2644 and #2662
2019-01-07 14:15:48 +09:00
Junyoung Choi
99cb6fa9ed Merge pull request #2720 from richardtks/fix-tag-allow-overflow
show the scroll bar when the tag list is overflow
2019-01-07 14:15:21 +09:00
Junyoung Choi
fe011e87d1 Merge pull request #2758 from elfman/colorTag
add feature: colored tags
2019-01-07 14:14:40 +09:00
Junyoung Choi
5a5563f00a Merge pull request #2770 from vienai8d/update_snippet_note
Replace current fullscreen button of SnippetNote to FullscreenButton module
2019-01-07 14:12:15 +09:00
Junyoung Choi
5a8f076d85 Merge pull request #2762 from coliff/patch-1
Update mousetrap
2019-01-07 14:11:45 +09:00
Junyoung Choi
45deb5ba7f Merge pull request #2771 from caina/patch-1
typo on pt-BR.json
2019-01-07 14:11:24 +09:00
Junyoung Choi
bcea3eb7c1 Merge pull request #2764 from vienai8d/translate_ja
Add Japanese translation
2019-01-07 10:30:37 +09:00
Junyoung Choi
9ca5fa6144 Merge pull request #2761 from abnersajr/ptBr-translations
Pt br translations
2019-01-07 10:30:20 +09:00
Junyoung Choi
d9809318fc Merge pull request #2772 from vienai8d/localize_tooltips_ja
Localize tooltips for Japanese
2019-01-07 10:27:35 +09:00
Junyoung Choi
04ae8a81a5 Merge pull request #2768 from elfman/detect
new feature: auto detect snippet language
2019-01-06 09:58:13 +09:00
HarlanLuo
082a078b51 check config before auto detect language 2019-01-04 21:55:58 +08:00
Rahul Gurung
04fdb67fc9 #2194 - fixes Hotkey not working 2019-01-02 12:38:26 +05:30
vienai8d
e6a927e5af localize ToggleModeButton for Japanese 2019-01-02 11:10:07 +09:00
vienai8d
b05ba64db8 localize TrashButton for Japanese 2019-01-02 11:07:15 +09:00
vienai8d
d0f5ec8ada fix FullscreenButton 2019-01-02 11:03:40 +09:00
vienai8d
caa6c8d4b9 localize FullscreenButton for Japanese 2019-01-02 11:01:37 +09:00
vienai8d
5cf4a0e09d localize StarButton for Japanese 2019-01-02 10:59:06 +09:00
vienai8d
0a0c1c45a1 translate tooltip texts into Japanese 2019-01-02 10:56:27 +09:00
Douglas Caina
fce89fe8be Update pt-BR.json
## Description
There was a misspelling on the word "escluir", the correct one is "excluir", as you can see on [google translate](https://translate.google.com/?source=osdd#view=home&op=translate&sl=pt&tl=en&text=excluir)


## Type of changes

-  Bug fix (Change that fixed an issue)
-  Breaking change (Change that can cause existing functionality to change)
-  Improvement (Change that improves the code. Maybe performance or development improvement)
-  Feature (Change that adds new functionality)
-  Documentation change (Change that modifies documentation. Maybe typo fixes)

## Checklist:

-  My code follows [the project code style](docs/code_style.md)
-  I have written test for my code and it has been tested
-  All existing tests have been passed
-  I have attached a screenshot/video to visualize my change if possible
2019-01-01 22:20:23 +01:00
vienai8d
2bbe39120a use FullscreenButton instead of current button 2019-01-01 23:11:39 +09:00
vienai8d
2a5da746c7 add translation about content menu 2019-01-01 18:39:39 +09:00
HarlanLuo
deb2cd0156 new feature: auto detect snippet language
only try to detect after pasting and mode has not been set and default snippet language is "Auto Detect"
2019-01-01 17:01:49 +08:00
HarlanLuo
b4e4d7055f improve style of color-picker 2018-12-30 20:49:26 +08:00
HarlanLuo
a9feddf6f6 fix bug missing param colored tags in sorted list 2018-12-30 19:23:43 +08:00
tool root
071f7cb035 rewrote the function code to MarkdownPreview.js #2644 #2662 2018-12-30 16:20:19 +09:00
tool root
a11b0f1665 Merge branch 'master' into fix-issue#2644-and-#2662 2018-12-30 15:57:31 +09:00
HarlanLuo
39442bcafe invert icon-x color when tag text color is inverted 2018-12-29 22:23:48 +08:00
richardtks8@gmail.com
48a905bf6f decrease the height of tags scrollbar 2018-12-29 15:34:36 +08:00
vienai8d
440b50b4e8 add translation about interface in preferences 2018-12-29 15:08:03 +09:00
HarlanLuo
0cf6487cad invert text color in colored tags 2018-12-28 22:12:51 +08:00
Abner Soares Alves Junior
74825dddbf Minor tweaks on portuguese translation 2018-12-28 09:59:22 -02:00
HarlanLuo
699006a3e9 Improve ui of colored tags 2018-12-28 14:09:35 +08:00
HarlanLuo
6367be213f rename config.tag to config.coloredTags 2018-12-28 12:30:23 +08:00
Christian Oliff
4e97ac3b8c Update mousetrap 2018-12-28 10:11:44 +09:00
Abner Soares Alves Junior
57817fd90c Add pt_BR translation to debug documentation 2018-12-27 23:07:18 -02:00
Abner Soares Alves Junior
5b79d0439c Add pt_BR translation to build documentation 2018-12-27 22:43:17 -02:00
richardtks8@gmail.com
6d4cee0041 allow menu bar to be set in the settings 2018-12-28 02:15:24 +08:00
HarlanLuo
3e645db324 add feature: colored tags 2018-12-27 23:22:28 +08:00
Junyoung Choi
05da826c24 Merge pull request #2755 from empeje/patch-1
Create FAQ.md
2018-12-27 20:16:29 +09:00
Junyoung Choi
70e16d853e Replace heading 1 with heading 2 2018-12-27 15:36:20 +09:00
mpj
9ebf949890 Update FAQ.md 2018-12-27 13:20:44 +07:00
mpj
a06bdced8a Update FAQ.md 2018-12-27 13:19:54 +07:00
Junyoung Choi
3ab506ea94 Merge pull request #2756 from vienai8d/saveTagsAlphabetically
Fix feature 'saveTagsAlphabetically'
2018-12-27 13:45:28 +09:00
mpj
0340402dc1 Create FAQ.md 2018-12-27 11:12:36 +07:00
vienai8d
6e8fe7308c implement feature 'saveTagsAlphabeticall' 2018-12-27 12:09:56 +09:00
Junyoung Choi
13c2f471aa Merge pull request #2747 from daiyam/fix-paste-image
fix paste image
2018-12-25 01:07:37 +09:00
Junyoung Choi
604f17fbfd Merge pull request #2586 from GuilhermeJSilva/feature/autoBracketMatching
Feature/auto bracket matching
2018-12-25 00:24:37 +09:00
Baptiste Augrain
50669f65bb update preferences' labels 2018-12-24 11:04:17 +01:00
Guilherme Silva
21c61121b0 Fixing code style probllems 2018-12-24 09:46:25 +00:00
Baptiste Augrain
073a5d4d68 Ctrl+V can paste an image 2018-12-24 09:50:14 +01:00
Junyoung Choi
7a3cab8947 Merge pull request #2455 from daiyam/fix-notelist
fix scrolling in note list
2018-12-24 17:20:05 +09:00
Junyoung Choi
aec79c4eeb Merge pull request #2591 from daiyam/drop-image-preview
drag image into preview
2018-12-24 17:19:41 +09:00
Junyoung Choi
5f385e4c03 Merge pull request #2465 from daiyam/gallery
add image gallery
2018-12-24 17:19:03 +09:00
Junyoung Choi
b018502079 Merge pull request #2456 from daiyam/precommit-command
lint before commit
2018-12-24 17:17:53 +09:00
Junyoung Choi
47e0a82caf Merge pull request #2746 from BoostIO/fix-go-to-eol-on-mac
Fix Cmd+Left to go to end of line on Mac
2018-12-24 17:02:51 +09:00
Junyoung Choi
d848ee5d5f Merge pull request #2692 from Brunovsky/fix-2557
Fix issue 2557 katex alignment in display math
2018-12-24 17:00:11 +09:00
Junyoung Choi
2df0f1bcb8 Merge pull request #2682 from duartefrazao/master
Feature - Line highlighting within code block #2469
2018-12-24 16:56:59 +09:00
Junyoung Choi
1ae141492a Merge pull request #2743 from daiyam/fix-notelist-width
fix min width of note list
2018-12-24 16:54:37 +09:00
Junyoung Choi
7232d07b1c Merge pull request #2704 from jarvisuser90/Update-tabs-ui
Update tabs ui
2018-12-24 16:53:59 +09:00
Ryan Scott
64abd564b4 Fix Cmd+Left to go to end of line on Mac 2018-12-24 16:48:16 +09:00
Junyoung Choi
483ea77d14 Merge pull request #2710 from daiyam/fix-toc
fix toc
2018-12-24 16:41:41 +09:00
Junyoung Choi
cca5abdc8f Merge pull request #2741 from roottool/fix-issue#2729
fixed issue #2729
2018-12-24 16:38:35 +09:00
Junyoung Choi
17b3b02ac5 Merge pull request #2739 from coliff/patch-1
Https link to EditorConfig.org
2018-12-24 16:37:56 +09:00
Junyoung Choi
ce4e203c14 Merge pull request #2730 from joaocastro/vscode-debug-windows
Fixed windows debug path
2018-12-24 16:37:34 +09:00
Junyoung Choi
011defc1f7 Merge pull request #2745 from BoostIO/change-issuehunt-image
Change IssueHunt image
2018-12-24 15:03:01 +09:00
kazup01
5ccb9bde28 Change IssueHunt image 2018-12-24 14:10:52 +09:00
Baptiste Augrain
30e262d8ac fix min width of note list 2018-12-23 17:01:46 +01:00
roottool
692f6779d6 fixed the checkboxes are too far right #2729 2018-12-23 01:13:57 +09:00
duartefrazao
e93bf1cfe7 Removed bind in MarkdownEditor and MarkdownSplitEditor 2018-12-22 11:44:30 +00:00
Christian Oliff
8d769d4c4b Https link 2018-12-22 12:00:19 +09:00
MiguelPedrosa
b539ac6335 Fixed windows debug path 2018-12-19 20:57:27 +00:00
Duarte-Frazao
72906b3ee7 Corrections to make line highlighting robust, added tests
Lines now save correctly with different inputs, making sure that different inputs like enter, delete, paste and where it's deleted stay consistent when saving.
Included in the create/update  snippet/note tests the structure from lines highlighting saved to the files.
2018-12-19 16:34:16 +00:00
Miguel Teixeira
7f6d4acf90 Fixed lock button not appearing 2018-12-19 15:16:55 +00:00
richardtks
bb892f7e78 Updated the overflow-x to auto 2018-12-18 18:34:49 +08:00
Baptiste Augrain
ead6bb09dc Merge branch 'master' into fix-notelist 2018-12-18 11:07:49 +01:00
richardtks
0b1ec3f29f show the scroll bar when the tag list is overflow 2018-12-18 16:32:30 +08:00
Guilherme Silva
e77db372bd Merge branch 'master' into feature/autoBracketMatching 2018-12-17 15:39:03 +00:00
Junyoung Choi
64ca875cfd v0.11.12 2018-12-17 14:56:16 +09:00
Baptiste Augrain
256653677e fix lint errors and remove unused dependencies 2018-12-16 19:45:18 +01:00
Baptiste Augrain
b99980fda1 improve slug by replacing diacritics and removing unwanted characters 2018-12-16 19:38:04 +01:00
roottool
13857d4313 Modified position of escapeHtmlCharactersInCodeTag definition 2018-12-17 00:37:25 +09:00
roottool
fe1ab73818 fix #2644 and #2662 2018-12-16 22:08:55 +09:00
Baptiste Augrain
d5a2aa6d6d fix missing bullets 2018-12-15 10:41:47 +01:00
Baptiste Augrain
4f9b37433c fix toc by sharing slugify() 2018-12-15 10:11:04 +01:00
John Ciprian
ec506e71a4 Adding support for dracula interface theme 2018-12-13 20:56:47 -05:00
John Ciprian
cfcaa58b71 Optimizing css for dark and solarized-dark themes 2018-12-13 20:56:47 -05:00
John Ciprian
29cf4769f5 Adding support for monokai interface theme 2018-12-13 20:56:47 -05:00
John Ciprian
58fd2273ea Adding support for solarized-dark interface theme 2018-12-13 20:56:47 -05:00
John Ciprian
b52616c64d Changing tabs from material design to traditional
- Adding support for “default” interface theme.
- Adding support for “white” interface theme.
- Adding support for “dark” interface theme.
2018-12-13 20:56:47 -05:00
Duarte-Frazao
ac1ce6043b Fixed legacy default Markdown/Snippet notes bug
Fixed a bug where a note or snippet is created before the pull request and you ran Boostnote for the first time after the pr and you firstly created a note or markdown and only then returned to the old default notes and you couldn't highlight
2018-12-13 20:19:02 +00:00
Miguel Teixeira
6631f98c43 Removed debug log 2018-12-13 19:13:12 +00:00
Miguel Teixeira
37340d0445 Fixed issue with double and right click 2018-12-13 19:09:41 +00:00
Junyoung Choi
743c97940e Merge pull request #2641 from daiyam/fix-paste-code
fix pasting into fenced code block
2018-12-14 01:26:49 +09:00
Duarte-Frazao
f2a0f59b08 Fixed error on call to bind. 2018-12-13 13:27:20 +00:00
Duarte-Frazao
4fb11b68e4 Markdown functionality 2018-12-13 13:13:01 +00:00
Guilherme Silva
ed742c7e16 Merge branch 'master' into feature/autoBracketMatching 2018-12-13 11:58:45 +00:00
Miguel Teixeira
3b110bcd4b WIP 2018-12-13 11:55:04 +00:00
Baptiste Augrain
d4dd74e820 Merge branch 'master' into fix-paste-code 2018-12-13 08:20:36 +01:00
Junyoung Choi
3f77cb2214 Merge pull request #2684 from GuilhermeJSilva/fix/highlight-folder-on-hover
Dragged note highlighting
2018-12-13 13:59:10 +09:00
Junyoung Choi
7c839a1df9 Merge pull request #2685 from jhdcruz/patch-1
Fixed Build Badge Alignment w/ Fixed link
2018-12-13 13:58:29 +09:00
Miguel Teixeira
c5de940946 Fixed bug where icon was hidden 2018-12-12 18:19:41 +00:00
Miguel Teixeira
2943c5fafb Sync with upstream 2018-12-12 18:09:46 +00:00
Junyoung Choi
fddeaa966d Merge pull request #2690 from vienai8d/translate_ja
Add Japanese translation
2018-12-13 00:27:54 +09:00
Junyoung Choi
e706ec0ffe Merge pull request #2643 from daiyam/fix-contextmenu
fix editor's context menu
2018-12-13 00:26:05 +09:00
Brunovsky
e65c48be33 Fix issue 2557 katex alignment in display math 2018-12-11 00:41:51 +00:00
vienai8d
d44a76bae3 add translation about interface in preferences 2018-12-10 07:20:02 +09:00
vienai8d
4488de9add add translation about hotkeys in preferences 2018-12-09 10:58:06 +09:00
Duarte-Frazao
62609a2918 Fixed last nonfunctional changes made earlier
Now iterates in the SnippetNoteDetail constructor the snippets and if linesHighlighted is not defined assigns an empty array
2018-12-08 17:22:57 +00:00
Duarte-Frazao
492294e11d Reset of linesHighlighted 2018-12-08 12:19:42 +00:00
Duarte-Frazao
f483f8fdf0 Fix so that linesHighlighted defaults to [] when does't find it 2018-12-08 12:07:28 +00:00
Guilherme Silva
4061866042 Moving prevent default 2018-12-08 09:06:55 +00:00
Duarte-Frazao
191295b6de Added array of linesHighlighted to default snippet
This makes the default snippet also handle highlight on the lines, because this snippet is created in the code without the normal snippet constructor
2018-12-07 23:41:51 +00:00
Joshua Dela Cruz
6d4aa27e15 Fixed Build Badge Alignment w/ Fixed link
Related to PR #2584
2018-12-07 17:22:01 +08:00
Junyoung Choi
d70d3b439f Merge pull request #2672 from arkist/handle-encoded-uri-on-copyfile
Handle encoded uri path on `copyFile`
2018-12-07 14:30:35 +09:00
Junyoung Choi
56851eb91f Merge pull request #2666 from yougotwill/menu_items_overhaul
Menu items overhaul
2018-12-07 12:41:22 +09:00
Baptiste Augrain
2cfe8de030 Merge branch 'master' into precommit-command 2018-12-06 15:48:27 +01:00
Duarte-Frazao
b5604ba0a9 Changes to optimize initial highlighting
Now iterates over highlighted lines instead of all lines of the snippet
2018-12-06 13:23:23 +00:00
Duarte-Frazao
1a0e15e04c Fixed bug when switching to markdown notes 2018-12-05 14:12:29 +00:00
Junyoung Choi (Sai)
b224c72e98 Merge pull request #2637 from miguelalexbt/master
Warning when printing snippet
2018-12-03 17:16:50 +09:00
Junyoung Choi (Sai)
6c57ac8f01 Merge pull request #2383 from ehhc/exportMDexportsAttachments
export folder should also export the attachments -> fixes #2374
2018-12-03 17:15:02 +09:00
Jinwoo Oh
cf35dc5345 Handle encoded uri path on copyFile
fix #2578
2018-12-03 14:04:13 +09:00
William Grant
102d13bbae you can't have multiple accelerators on a single menu item. Bye bye delete key :( 2018-12-02 16:29:10 +02:00
William Grant
faf2aff959 added in the shift key for deleting a note based on https://github.com/BoostIO/Boostnote/pull/2452 2018-12-02 16:02:29 +02:00
William Grant
5d54ad3342 Cleaned up menu items ordering and separators, added clone note menu item and shortcut, updated focus note shortcut to be mac friendly, made delete note shortcut use either command+backspace or ctrl+backspace and the delete key on windows and linux machines 2018-12-02 15:59:47 +02:00
William Grant
0f5d753910 Removed the old ctrl+d shortcut for deleting a note since we now have the super+shift+backspace shortcut which can be changed in the hotkey settings 2018-12-02 15:21:01 +02:00
Duarte-Frazao
a9442a019f Changes to pass tests and lint code 2018-11-30 19:20:40 +00:00
Duarte-Frazao
1668ef6bb4 Small changes 2018-11-30 18:32:14 +00:00
Duarte-Frazao
45436f65af Issue #2469 almost done, missing refactor to reduce calls on code mirror 2018-11-30 18:03:23 +00:00
Guilherme Silva
b8a295713c Dragged note highlighting 2018-11-30 08:42:27 +00:00
Miguel Teixeira
aa20bc769c Fixed lock button behaviour and display 2018-11-29 17:06:28 +00:00
Guilherme Silva
9bc291e618 Merge branch 'master' into feature/autoBracketMatching 2018-11-29 11:47:07 +00:00
Baptiste Augrain
fdb54b5cdc fix expanding snippet 2018-11-28 16:47:19 +01:00
Baptiste Augrain
df20662005 remove console.log 2018-11-28 16:42:10 +01:00
Baptiste Augrain
900f20f164 handle all dropped images 2018-11-28 15:58:58 +01:00
Baptiste Augrain
df3b2cd8fe fix double paste when pasting attachement links 2018-11-28 15:12:18 +01:00
Baptiste Augrain
c2e4bae9dd fix regex to correctly match the src attribute when there is a data-src attribute 2018-11-28 15:00:29 +01:00
Miguel Teixeira
a39da481e0 Changed message. 2018-11-28 09:08:58 +00:00
Junyoung Choi (Sai)
830ade9596 Merge pull request #2399 from Pudge601/bug/2241
Clear search when a new note is created
2018-11-28 11:36:46 +09:00
Baptiste Augrain
2aa296ff33 fix lint error 2018-11-28 00:45:20 +01:00
Baptiste Augrain
9050035c74 - add option to enable/disable smart paste
- add shortcut to paste smartly
- use electron's clipboard
2018-11-28 00:44:15 +01:00
Miguel Teixeira
c245855bbf Improved messages. 2018-11-27 20:28:57 +00:00
Miguel Teixeira
ef66e71feb Solved some errors in identation 2018-11-27 18:02:59 +00:00
Miguel Teixeira
c33058ae2b Added custom warning messages. 2018-11-27 17:42:04 +00:00
Nguyễn Việt Hưng
629d4a82ae fixed delete note not navigate to next note 2018-11-27 13:58:06 +07:00
Baptiste Augrain
d95d282f39 disable editor's context menu when switch preview is using right click 2018-11-25 17:09:54 +01:00
Baptiste Augrain
64f7233bfc fix regex to match :storage reference, added comment to explain what it does. 2018-11-25 16:58:11 +01:00
Baptiste Augrain
9d81e4be2f undo change 2018-11-25 16:17:01 +01:00
Baptiste Augrain
0a1ee86baf Merge branch 'master' into gallery 2018-11-25 16:05:27 +01:00
Baptiste Augrain
2908884202 drag and drop image from browser 2018-11-25 15:56:11 +01:00
Baptiste Augrain
aac075be06 fix lint error 2018-11-25 14:57:03 +01:00
Baptiste Augrain
bf288fdaeb fix pasting into fenced code block 2018-11-25 14:55:29 +01:00
Junyoung Choi (Sai)
a6eddb5798 Merge pull request #2592 from enyaxu/bug-2581
Fixed duplicate TOC Title jump error
2018-11-25 15:49:06 +09:00
Junyoung Choi (Sai)
78ae7b847c Merge branch 'master' into bug-2581 2018-11-25 15:39:39 +09:00
Junyoung Choi (Sai)
c7bae93b48 Merge pull request #2601 from yougotwill/zoom_controls
Zoom Controls
2018-11-25 15:37:29 +09:00
Junyoung Choi (Sai)
938cf238ff Merge pull request #2604 from arcturus140/fixGUI
fix GUI style element for default theme
2018-11-25 15:36:17 +09:00
Junyoung Choi (Sai)
5188846e2e Merge pull request #2611 from gregueiras/issue2199
Ability to go from 'editor with preview' mode to 'preview' using mouse
2018-11-25 15:35:53 +09:00
Junyoung Choi (Sai)
d874a3a493 Merge pull request #2620 from arcturus140/fix2618
fix2618
2018-11-25 15:29:37 +09:00
Junyoung Choi (Sai)
f95284622e Merge pull request #2621 from mehr-licht/master
included shortcut for Info Panel (issue 2533)
2018-11-25 15:28:25 +09:00
Junyoung Choi (Sai)
0b6c0e6b94 Merge pull request #2622 from daiyam/fix-fence
fix code blocks
2018-11-25 15:26:48 +09:00
Junyoung Choi (Sai)
b021bb73ed Merge pull request #2498 from daiyam/create-note-with-tags
create note with selected tags
2018-11-25 15:23:04 +09:00
Junyoung Choi (Sai)
c76b653737 Merge pull request #2338 from ehhc/spellchecker
Spellchecker
2018-11-25 15:21:04 +09:00
Miguel Teixeira
3414e2daf0 Added warning when trying to print from InfoPanel 2018-11-22 12:22:49 +00:00
mehr-licht
92e2cd102e deleted ctr+l and ctrl+t 2018-11-19 19:00:51 +00:00
Junyoung Choi (Sai)
b703c42ee3 Merge pull request #2504 from ZeroX-DG/pr-template
Added PR Template and update docs
2018-11-19 15:14:02 +09:00
Baptiste Augrain
94f7533ee7 fix code blocks 2018-11-18 23:53:46 +01:00
mehr-licht
101e5d5035 included shortcut for Info Panel 2018-11-18 19:22:05 +00:00
Arcturus
ab78af0691 fix2618 2018-11-18 17:36:42 +00:00
Gonçalo Santos
af14b682b1 User can now go back to previous mode 2018-11-15 12:51:53 +00:00
Junyoung Choi
a26d4fb499 Update yarn.lock 2018-11-15 19:40:52 +09:00
Junyoung Choi
f644d21c61 v0.11.11 2018-11-15 19:24:56 +09:00
Junyoung Choi (Sai)
2af52c193c Merge pull request #2603 from jhit/master
Update electron framework to version 3.0.8
2018-11-13 08:33:40 +09:00
Arcturus
7717cda52a fix GUI style element for default theme 2018-11-12 20:50:09 +00:00
Baptiste Augrain
c13746f10e Merge branch 'master' into create-note-with-tags 2018-11-12 19:16:50 +01:00
ehhc
49abfac98e don't do spellcheck if disabled... 2018-11-12 17:47:45 +01:00
ehhc
aa26e5ac2a fix linter errors 2018-11-12 17:40:37 +01:00
ehhc
336f007fa1 Merge branch 'spellchecker' of https://github.com/ehhc/Boostnote into spellchecker 2018-11-12 17:36:36 +01:00
ehhc
a3e59d43a7 hiding spellcheck by default and adding a interface option to enable it 2018-11-12 17:35:33 +01:00
Junyoung Choi (Sai)
ae840ea689 Merge pull request #2492 from amireldor/fix-paste-with-emacs-keymap
Remove emacs keymap Ctrl-V binding to allow pasting
2018-11-13 00:40:48 +09:00
Junyoung Choi (Sai)
79ee674ad9 Merge pull request #2593 from daiyam/fix-snippet-cursor
fix cursor position after expending snippet
2018-11-13 00:35:46 +09:00
Junyoung Choi (Sai)
4d77053313 Merge pull request #2590 from arcturus140/improve2588
enhancement for #2588
2018-11-13 00:31:04 +09:00
Junyoung Choi (Sai)
235e88f115 Merge pull request #2571 from enyaxu/vscode-debug-support
Add vscode debug support
2018-11-13 00:30:32 +09:00
Junyoung Choi (Sai)
021bac5b68 Merge pull request #2589 from arcturus140/master
fix 2530
2018-11-13 00:29:27 +09:00
Junyoung Choi (Sai)
60c4cacfbc Merge pull request #2596 from richardtks/correct-shift-select-behavior
Fix the shift selection and ctrl selection in note list
2018-11-13 00:27:45 +09:00
Junyoung Choi (Sai)
30e6fc516b Merge pull request #2600 from daiyam/fix-taglist
fix route when selecting multiple tags
2018-11-13 00:26:10 +09:00
Jürgen Hörmann
1388e7d7f4 Missed to update config value for electron-version 2018-11-12 10:23:24 +01:00
Jürgen Hörmann
b79b123c65 Update electron framework to version 3.0.8
On Ubuntu 18.10 there is a issue with glibc versions below 2.28 see
[Prebuilt Electron binaries segfault at startup on Arch Linux with glibc 2.28 #13972](https://github.com/electron/electron/issues/13972)
2018-11-12 08:47:35 +01:00
William Grant
d3a6ff6b6a updated zoom out accelerator for consistency 2018-11-12 06:53:10 +02:00
Junyoung Choi (Sai)
10d3adbe40 Merge pull request #2595 from daiyam/fold-headings
fold headings
2018-11-12 11:14:43 +09:00
JianXu
e100e080da fixed debug.md format error 2018-11-11 21:19:38 +08:00
William Grant
e2c7a8c384 updated the zoom logic for the zoom in/out menu items so that if you zoom it reflects in the status bar and the config. Also added an actual size / zoom reset menu item for convenience. 2018-11-11 12:59:50 +02:00
Baptiste Augrain
d97bbe7918 fix URI when selecting multiple tags 2018-11-11 10:10:09 +01:00
JianXu
3864d73d1a Add vscode debug support doc 2018-11-11 14:49:48 +08:00
richardtks
01891d46b3 Added the auto-updating numbered list to the middle of a list 2018-11-11 00:15:22 +08:00
richardtks
82987cd53c Fix the variable name to more appropriate one 2018-11-10 21:23:56 +08:00
richardtks
aa56aad46e Improvement on the code
The improvement was suggested by github
2018-11-10 20:29:24 +08:00
richardtks
ad7b155a99 Updated the condition with more appropriate naming 2018-11-10 19:39:44 +08:00
Baptiste Augrain
f1ca06daf5 fold markdown's headings 2018-11-10 12:33:07 +01:00
richardtks
3df743f47b Change the selection behavior to the default behavior
There are two behaviors being changed:
1. When user click on the notes 
     a. Shift - It will select the notes among the first and the last note
     b. Ctrl - it will only select the selected note
2. When user uses the arrow keys
    a. Shift - it will extends the selection from the last selected note
    b. Ctrl - Nothing will happens
2018-11-10 19:17:11 +08:00
Baptiste Augrain
637225c2bc fix cursor position after expending snippet 2018-11-10 11:18:44 +01:00
JianXu
849104f530 Update Snapshots test 2018-11-10 16:19:42 +08:00
JianXu
3a4bc33d53 Fixed duplicate TOC Title jump error 2018-11-10 15:56:21 +08:00
Baptiste Augrain
3679fbe3ea fix lint errors 2018-11-10 00:18:06 +01:00
Baptiste Augrain
b1d2c25ce5 when dropping an image, switch to editor and add it at the end of the file 2018-11-10 00:05:45 +01:00
Arcturus
480c05650b fix linting issues 2018-11-09 13:58:54 +00:00
Arcturus
fa265d769c fix multiple spaces issue 2018-11-09 13:40:45 +00:00
Arcturus
4a197a5c90 add confirmation dialog 2018-11-09 13:37:17 +00:00
Arcturus
23702056fd Merge branch 'hotfix/2530' 2018-11-09 12:21:31 +00:00
Arcturus
2a774d20f4 fix issue 2530 2018-11-09 12:21:00 +00:00
Baptiste Augrain
b1a7f0fd64 Merge branch 'master' into gallery 2018-11-08 23:52:31 +01:00
Baptiste Augrain
70b86907f3 Merge branch 'master' into create-note-with-tags 2018-11-08 18:31:32 +01:00
Baptiste Augrain
d0d813552c add option to tag the new notes with the filtering tags, or not... 2018-11-08 18:22:52 +01:00
Guilherme Silva
707dace3d0 removing spaces after = 2018-11-08 14:11:43 +00:00
Guilherme Silva
8361106660 Removing trailing spaces and added spaces in config 2018-11-08 14:07:35 +00:00
Guilherme Silva
a46c519459 Fixing indentations 2018-11-08 14:03:21 +00:00
Guilherme Silva
441c70b388 Removing checkmark and fixed Code Editor indentation 2018-11-08 13:25:22 +00:00
Guilherme Silva
ab65fb7a5c Choosing which characters to match and explode 2018-11-08 13:18:13 +00:00
Guilherme Silva
59d31c9a18 Deleted useless console log 2018-11-08 12:01:12 +00:00
Guilherme Silva
db97ab51ac Bracket matching option added to config 2018-11-08 11:44:03 +00:00
ehhc
c6f1f97a57 Merge branch 'master' into spellchecker 2018-11-08 11:19:46 +01:00
Junyoung Choi (Sai)
fdb1ef540d Merge pull request #2515 from FabioRosado/translation-pt
Add translation to Portuguese from Portugal
2018-11-08 19:14:47 +09:00
Junyoung Choi (Sai)
62a8ffb4ae Merge pull request #2579 from daiyam/fix-turndown
fix paste
2018-11-08 19:12:36 +09:00
Baptiste Augrain
1e3cf6f374 remove console.log 2018-11-08 10:10:45 +01:00
Baptiste Augrain
48c29dd7d9 fix turndown 2018-11-08 10:01:16 +01:00
JianXu
e30727ab27 Add detail mode support for vscode debug 2018-11-08 13:47:34 +08:00
Junyoung Choi (Sai)
157eb5f87b Merge pull request #2346 from daiyam/text-deflist
add support to abbreviations, definition lists and subscript/superscript text
2018-11-08 14:35:56 +09:00
Baptiste Augrain
8f290c2a6d rounding paddings 2018-11-08 06:22:12 +01:00
Baptiste Augrain
47f6a217e8 Merge branch 'master' into text-deflist 2018-11-08 05:24:57 +01:00
Junyoung Choi (Sai)
69691bdf2a Merge pull request #2448 from jacobherrington/alt-hotkey-bug
Fix bug caused by poor regex
2018-11-08 11:57:10 +09:00
Junyoung Choi (Sai)
522a37b8e3 Merge pull request #2451 from jacobherrington/improve-french-translations
Add French translations
2018-11-08 11:52:49 +09:00
Junyoung Choi (Sai)
53aa142d94 Merge pull request #2395 from daiyam/chart-yaml
add YAML for chart
2018-11-08 11:41:45 +09:00
Fábio Rosado
61bd591ee8 Merge pull request #1 from arcturus140/europeanPortuguese
add language support for european portuguese
2018-11-07 15:12:52 +00:00
Arcturus
fd2b438c67 add language support for european portuguese 2018-11-07 13:58:47 +00:00
jacob
5855cdfb26 Merge branch 'master' into improve-french-translations 2018-11-07 07:54:44 -06:00
Baptiste Augrain
a63266b0e9 Merge branch 'master' into chart-yaml 2018-11-06 11:33:11 +01:00
Fábio Rosado
fe19d96088 Merge branch 'master' into translation-pt 2018-11-06 09:06:01 +00:00
Junyoung Choi (Sai)
37933782d2 Merge pull request #2394 from daiyam/fence-attrs
add attributes to fence blocks
2018-11-06 17:48:29 +09:00
Junyoung Choi (Sai)
1dcc51e5a4 Merge pull request #2499 from daiyam/store-editor-status
store editor view into config
2018-11-06 17:47:42 +09:00
Baptiste Augrain
eb61ce2cf2 use config's local reference 2018-11-06 09:40:48 +01:00
Baptiste Augrain
c5bcfe6ab3 Merge branch 'master' into fence-attrs 2018-11-06 09:35:59 +01:00
ehhc
6c7ed82fa9 Merge branch 'master' into spellchecker 2018-11-06 08:36:59 +01:00
Junyoung Choi (Sai)
00ed0d79ec Merge pull request #2314 from daiyam/tags
tag enhancements
2018-11-06 16:09:26 +09:00
Junyoung Choi (Sai)
cfc84f3e78 Merge pull request #2434 from jacobherrington/chore-remove-console-logs
Remove console.logs
2018-11-06 15:49:07 +09:00
Junyoung Choi (Sai)
27e5010d8e Merge pull request #2430 from jacobherrington/hotkey-error-message-fix
Fix errors when saving a blank hotkey
2018-11-06 15:48:33 +09:00
Junyoung Choi (Sai)
efa00728d6 Merge pull request #2342 from daiyam/bug-1992
fixing inline html
2018-11-06 15:45:41 +09:00
Junyoung Choi (Sai)
106caf2b3e Merge pull request #2484 from crakoucas/fix-electron-glibc-segmentation-fault
fix segmentation fault with glibc 2.28
2018-11-06 15:39:28 +09:00
Junyoung Choi (Sai)
3dca6c1fd6 Merge pull request #2485 from ninearif/thai-local
add Thai locale
2018-11-06 15:22:25 +09:00
Junyoung Choi (Sai)
bd56055593 Merge pull request #2511 from nagledb/fix-blockquote-in-html-export
Fixed "Save as HTML" to respect blockquotes.
2018-11-06 15:18:59 +09:00
Junyoung Choi (Sai)
dbd2c7049e Merge pull request #2547 from mattkoskela/fix-storagelist-typo
fix: Fixed storageList typo (storgaeList).
2018-11-06 15:08:36 +09:00
JianXu
07aa775de1 Add vscode debug support 2018-11-05 00:39:27 +08:00
ehhc
88ac2a98e8 Merge branch 'master' into spellchecker 2018-11-02 10:41:30 +01:00
Junyoung Choi (Sai)
d0c5592855 Merge pull request #2278 from kiryanenko/master
Add support for converting to MD when paste HTML
2018-11-02 18:29:22 +09:00
Junyoung Choi (Sai)
b03ad0cfe0 Merge pull request #2452 from jacobherrington/add-delete-hotkey
Add delete hotkey
2018-11-02 18:28:09 +09:00
Junyoung Choi (Sai)
a43b0e6a57 Merge pull request #2542 from nagledb/optional-scroll-sync
Added option to disable scroll sync.
2018-11-02 18:27:30 +09:00
Junyoung Choi (Sai)
12176473ff Merge pull request #2549 from timarcosdias/localization
Fixed pt-BR locale
2018-11-02 18:24:59 +09:00
Kiryanenko Alexander
1614c1452f Merge branch 'master' into master 2018-10-29 21:25:11 +03:00
Marcos Dias da Conceição
d38f16d732 Fixed pt-BR locale
Added 'BR' to 'pt-BR' locale so the Brazilian Portuguese language can be shown
2018-10-28 01:55:45 -03:00
Matt Koskela
d7a2296909 fix: Fixed storageList typo (storgaeList). 2018-10-27 13:48:11 -07:00
Nguyễn Việt Hưng
4550d888bb updated code style with class property style 2018-10-27 09:47:15 +07:00
David Nagle
d62d1d670b Added option to disable scroll sync. 2018-10-26 11:17:06 -07:00
FabioRosado
f18e8c5a38 Fix typo, change for suggested term 2018-10-26 17:25:38 +01:00
Nguyễn Việt Hưng
6cb6cd3f26 updated docs and pull request template 2018-10-26 00:06:06 +07:00
Nguyễn Việt Hưng
c5554e8f1e update pull request template 2018-10-25 23:36:31 +07:00
David Nagle
5144c0ecdc Fixed "Save as HTML" to respect blockquotes.
When saving as HTML, the raw text was being HTML escaped prior to
running it through markdown. This was preventing blockquotes from
rendering because greater-than signs were escaped. The HTML escaping
seems unneeded since the markdown renderer already takes care of it,
and it also wasn't consistent with how markdown was being rendered
within the application.
2018-10-23 13:41:23 -07:00
jacob
642c62d8ce Merge branch 'master' into add-delete-hotkey 2018-10-23 09:16:29 -05:00
Junyoung Choi (Sai)
f92da16544 Merge pull request #2487 from daiyam/fix-task-config
fix error generated by toggling split editor
2018-10-23 18:30:41 +09:00
Junyoung Choi (Sai)
6e8b370e54 Merge pull request #2497 from daiyam/delete-tag
delete tag through context menu
2018-10-23 18:30:20 +09:00
Junyoung Choi (Sai)
1ecf1e0413 Merge pull request #2494 from intercloud/feat/link-to-a-line
feat(editor): add ability to jump to line
2018-10-23 18:25:36 +09:00
Junyoung Choi (Sai)
1ea1482aad Merge pull request #2474 from daiyam/fix-attachment-hyperlink
disable hyperlink for attachments
2018-10-23 18:00:03 +09:00
Junyoung Choi (Sai)
aeded9ac0e Merge pull request #2510 from nagledb/nagledb-front-matter-title
Title will now be extracted from front matter if title: is present.
2018-10-23 17:50:28 +09:00
Junyoung Choi (Sai)
1cec872273 Merge pull request #2503 from makibishi0212/uncheck-all-checkboxes
Add button clear all checkboxes
2018-10-23 17:48:33 +09:00
Junyoung Choi (Sai)
e550295644 Merge pull request #2506 from daiyam/fix-alt-color-table
fix color for even rows in table
2018-10-23 17:47:54 +09:00
Junyoung Choi (Sai)
70da43eeb7 Merge pull request #2512 from nagledb/fix-print-scroll-past-end
Prevent empty printed page with scroll-past-end
2018-10-23 17:20:35 +09:00
Junyoung Choi (Sai)
7e9fb6be32 Merge pull request #2518 from BoostIO/change-blog-url
Change blog url
2018-10-23 17:13:10 +09:00
Junyoung Choi (Sai)
258ed4e866 Merge pull request #2519 from BoostIO/fix-crowdfunding
Remove OpenCollective and add IssueHunt description
2018-10-23 17:12:52 +09:00
Junyoung Choi (Sai)
b8992362c2 Merge pull request #2524 from nagledb/custom-css
Moved custom CSS to bottom of style definitions so that it has higher precedence.
2018-10-23 17:11:45 +09:00
Baptiste Augrain
60fcf2734a Merge branch 'master' into text-deflist 2018-10-23 09:42:54 +02:00
Baptiste Augrain
ae9e83cf66 Merge branch 'master' into bug-1992 2018-10-23 09:34:26 +02:00
David Nagle
b8d1e37cce Moved custom CSS to bottom of style definitions so that it has higher precedence. 2018-10-22 10:47:47 -07:00
kazup01
6ecd1e5ea5 Remove OpenCollective and add IssueHunt description 2018-10-21 15:47:11 +09:00
kazup01
52956503f1 Change blog url 2018-10-21 15:18:28 +09:00
Nguyễn Việt Hưng
3b7bedbbe8 update pull request message 2018-10-21 10:25:28 +07:00
FabioRosado
148feac43e Add translation to Portuguese from Portugal 2018-10-20 15:03:07 +01:00
David Nagle
3272033c63 Added preferences to support toggling front matter title extraction on/off and to customize what field is used for the title. 2018-10-19 23:29:30 -07:00
David Nagle
73d1bdf84b Prevent empty printed page with scroll-past-end 2018-10-19 14:29:48 -07:00
David Nagle
0e38f61c85 Title will now be extracted from front matter if title: is present. 2018-10-19 10:59:37 -07:00
Baptiste Augrain
9d84fe7719 fix lint errors 2018-10-19 14:09:13 +02:00
Baptiste Augrain
5aaecfc0fe fix color for even rows in table 2018-10-19 14:06:21 +02:00
Junyoung Choi
ff3026686f v0.11.10 2018-10-19 19:04:53 +09:00
Nguyễn Việt Hưng
83243b61a6 smaller header font size 2018-10-18 12:39:57 +07:00
Nguyễn Việt Hưng
80c4601fdc fixed grammar in PR template 2018-10-18 12:36:21 +07:00
Nguyễn Việt Hưng
fa3700df7c updated pr template 2018-10-18 12:33:54 +07:00
Nguyễn Việt Hưng
6244e44033 added pr template 2018-10-18 12:29:47 +07:00
makibishi
d64dafc715 change prop name in TodoListPercentage 2018-10-18 03:11:44 +09:00
makibishi
9e67880456 Add button clear all checkboxes 2018-10-17 17:43:00 +09:00
Baptiste Augrain
7484f6e6a6 increase readability of the condition 2018-10-16 20:30:15 +02:00
Baptiste Augrain
d8d5810d7c avoid rerendering TagSelect when adding a tag 2018-10-15 15:29:24 +02:00
Baptiste Augrain
4a167aa3d7 update only if tags' length has been changed 2018-10-15 12:11:52 +02:00
Baptiste Augrain
ced3460673 store editor's status in config 2018-10-15 09:04:45 +02:00
Baptiste Augrain
d75dd874ca create note with selected tags 2018-10-15 08:24:21 +02:00
Baptiste Augrain
1da477d1d1 add context menu to delete tag 2018-10-15 08:09:50 +02:00
Baptiste Augrain
a4ad3896f0 Merge branch 'master' into fence-attrs 2018-10-14 15:21:27 +02:00
antogyn
e536d203d2 feat(editor): add ability to jump to line 2018-10-13 14:21:46 +02:00
jacob
ca038937e9 Update ConfigManager.js 2018-10-12 22:10:35 -05:00
Amir Eldor
342d0862c6 Change to single-quotes 2018-10-12 20:31:48 +03:00
Amir Eldor
3b11285bd5 Remove emacs keymap Ctrl-V binding to allow pasting
Fixes #1406
2018-10-12 20:15:07 +03:00
Baptiste Augrain
3c404f3678 remove unused method 2018-10-11 15:09:26 +02:00
Baptiste Augrain
eb37be1381 fix error generated by toggling split editor 2018-10-11 15:03:04 +02:00
Baptiste Augrain
c5f6ace332 Merge branch 'chart-yaml' into gallery 2018-10-11 12:59:53 +02:00
Baptiste Augrain
0e29e8ac76 Merge branch 'master' into chart-yaml 2018-10-11 12:48:40 +02:00
Junyoung Choi (Sai)
71ae42056f Merge pull request #2414 from yougotwill/dracula_theme
Dracula theme
2018-10-11 18:33:55 +09:00
Junyoung Choi (Sai)
a9bad53209 Merge pull request #2438 from jacobherrington/fix-type-error-on-snippets
Fix TypeError thrown on the Snippets page
2018-10-11 17:33:37 +09:00
Junyoung Choi (Sai)
eee340366e Merge pull request #2460 from ZeroX-DG/linethrough-checkbox
Added linethrough checkbox & make it optional
2018-10-11 17:32:57 +09:00
Junyoung Choi (Sai)
a2bc1a5d2d Merge pull request #2443 from daiyam/table-emoji
update codemirror to fix centered column as not emoji
2018-10-11 17:24:47 +09:00
Junyoung Choi (Sai)
3610d5ea93 Merge pull request #2462 from daiyam/fix-tag-autocomplete
fix small visual bug in tag autocomplete
2018-10-11 17:21:23 +09:00
Junyoung Choi (Sai)
a75f8e5fdf Merge pull request #2444 from daiyam/snippet-list
fix graphical bugs in snippet list
2018-10-11 17:02:46 +09:00
Junyoung Choi (Sai)
1821a5c678 Merge pull request #2445 from jacobherrington/snippet-copy-button
Add a Copy button the Snippets tab
2018-10-11 17:01:12 +09:00
Junyoung Choi (Sai)
5c3a62b9c5 Merge pull request #2449 from jacobherrington/patch-3
Update ISSUE_TEMPLATE.md
2018-10-11 16:49:22 +09:00
Junyoung Choi (Sai)
851f57c1f5 Merge pull request #2476 from daiyam/table-even-odd
alternate background color for table rows
2018-10-11 16:49:00 +09:00
Junyoung Choi (Sai)
2cd3f8c6ee Merge pull request #2477 from daiyam/fix-preview
fix visual bug with preview
2018-10-11 16:41:53 +09:00
Renaud R
a331d82cb5 Update Electon & Resize Bug
Electron to V 3.0.3
Electron-gh-release to V2.0.4
Fix Bug restore windows size
2018-10-11 09:40:05 +02:00
ninearif
6ef2ec4ed2 add Thai locale 2018-10-11 10:37:35 +07:00
renaud
29ed26a503 fix semgmentation fault with glibc 2.28 2018-10-10 11:19:00 +02:00
Sam Herrington
7b3d5ab1ae Update ConfigManager.js
Added console.log back per request.
2018-10-09 14:07:54 -05:00
Sam Herrington
6f2f6e9567 Update dev.js
Fixed console.log that is there by design
2018-10-09 11:00:18 -05:00
Baptiste Augrain
9a0bc984d4 fix preview 2018-10-09 10:38:19 +02:00
Baptiste Augrain
1922c8dbf8 add comment to the STORAGE_FOLDER_PLACEHOLDER variable 2018-10-09 10:15:30 +02:00
Baptiste Augrain
a841449771 fix lint error 2018-10-09 10:10:20 +02:00
Baptiste Augrain
ff4ef16375 update how to calculate alternative color 2018-10-09 10:07:00 +02:00
Baptiste Augrain
33f6926916 fix regex for windows' url 2018-10-09 09:35:10 +02:00
Baptiste Augrain
5d9b1abe82 allow markdown image syntax 2018-10-09 01:18:19 +02:00
Baptiste Augrain
7a5a821f8a fix failing test 2018-10-09 00:47:37 +02:00
Baptiste Augrain
0b9635c160 remove unused variable 2018-10-09 00:28:51 +02:00
Baptiste Augrain
555ae327b6 alternate background color for table rows 2018-10-09 00:25:04 +02:00
Baptiste Augrain
944c79ec61 disable hyperlink for attachments 2018-10-08 16:04:16 +02:00
Baptiste Augrain
39eaed260a display correctly attached image 2018-10-08 15:57:42 +02:00
Baptiste Augrain
f308836264 add new fenced block language: gallery 2018-10-02 23:48:07 +02:00
Baptiste Augrain
623688c28e move down the tag list of the autocomplete 2018-10-02 18:29:42 +02:00
Nguyễn Việt Hưng
8936d8b517 updated to code to pass test 2018-10-02 21:43:45 +07:00
Nguyễn Việt Hưng
14fec7473a added linethrough checkbox and make it optional 2018-10-02 20:52:51 +07:00
jacobherrington
33b40bf35f Add notification for copying snippets 2018-10-02 05:26:30 -05:00
Baptiste Augrain
e52bcf33c5 add precommit command 2018-10-01 18:54:50 +02:00
Baptiste Augrain
fa6c504b34 fix lint error 2018-10-01 18:42:18 +02:00
Baptiste Augrain
e9dac8c8f3 fix scrolling in note list 2018-10-01 18:16:26 +02:00
jacob
52bea8f808 Change French translations
Follow up on more PR feedback
2018-10-01 10:30:50 -05:00
jacob
39ce706f3e Change French translations
Following up on PR feedback
2018-10-01 09:48:16 -05:00
jacobherrington
d0171a8933 Remove package-lock.json 2018-10-01 09:27:22 -05:00
samherrington
9f75d2fe4b Resolve merge conflicts 2018-10-01 09:24:56 -05:00
Sam Herrington
9dfc6c2bc1 Update HotkeyTab.js to satisfy CI 2018-10-01 09:24:56 -05:00
Sam Herrington
025e778252 Update HotkeyTab.js
Saving a blank hotkey will now display a success message when the user saves the setting with the empty Show/Hide Boostnote hotkey.
2018-10-01 09:24:05 -05:00
Sam Herrington
c761f631a1 Update HotkeyTab.js to satisfy CI 2018-10-01 09:24:05 -05:00
samherrington
e173117a44 Fix errors when saving a blank hotkey
Saving a blank hotkey showed false error even though hotkey was
saved. This solution solves the issue as it stands, but I
would recommend looking into cleaning up the error handling
surrounding this functionality before adding any new hotkey
fields.
2018-10-01 09:24:05 -05:00
jacob
1ff4206bed Capitalize Boostnote 2018-10-01 00:14:44 -05:00
jacob
babc5626a9 Update ISSUE_TEMPLATE.md 2018-10-01 00:07:15 -05:00
jacobherrington
b624c9a4d2 Add French translations 2018-09-30 22:17:11 -05:00
jacobherrington
d83feafcb2 Add French translation of Delete Note 2018-09-30 21:10:47 -05:00
jacobherrington
cc1cb5fbc7 Add English translation for Delete Note 2018-09-30 21:02:41 -05:00
jacobherrington
80666fed1a Add UI to change deleteNote hotkey 2018-09-30 20:58:56 -05:00
jacobherrington
db2c6c99f7 Change default hotkeys
Setting the default hotkeys to be more consistent on OS X (using
the Alt notation as opposed to the Option notation).
2018-09-30 20:44:23 -05:00
jacobherrington
3f1fa44ee7 Add a default hotkey for deleteNote 2018-09-30 20:19:22 -05:00
jacobherrington
4717e4fe3f Add an event for deletion hotkey 2018-09-30 20:17:10 -05:00
jacobherrington
ffc390d49d Reset default hotkey on OS X
This shouldn't have been committed it was for testing. This commit
resets it to the original hotkey.
2018-09-30 18:30:34 -05:00
jacobherrington
002e7ab9dd Fix bug caused by poor regex 2018-09-30 18:26:36 -05:00
Sam Herrington
ca69bd69f2 Remove unnecessary conditional logic
In reference to PR feedback.
2018-09-30 17:46:38 -05:00
jacobherrington
3e2a366dc6 Add a Copy button the Snippets tab 2018-09-30 16:57:58 -05:00
Baptiste Augrain
d365aaf270 fix graphical bugs 2018-09-30 23:21:48 +02:00
Baptiste Augrain
818ee16e39 update codemirror 2018-09-30 22:41:49 +02:00
Baptiste Augrain
533caba717 Merge branch 'master' into text-deflist 2018-09-30 22:29:18 +02:00
Baptiste Augrain
cae6fd45b3 Merge branch 'master' into tags 2018-09-30 22:26:49 +02:00
William Grant
8c268be823 Merge branch 'master' into dracula_theme
# Conflicts:
#	browser/components/TodoListPercentage.styl
#	browser/main/Detail/TagSelect.styl
2018-09-30 22:16:25 +02:00
Baptiste Augrain
17845428bd Merge branch 'fence-attrs' into chart-yaml 2018-09-30 21:46:09 +02:00
Baptiste Augrain
efd1b3cd3c Merge branch 'master' into fence-attrs 2018-09-30 21:33:02 +02:00
Junyoung Choi (Sai)
2ccd00a378 Merge pull request #2322 from youngyou/fix-drop-image-rotate
Fix drop image rotate wrong
2018-10-01 03:48:54 +09:00
Junyoung Choi (Sai)
0123a99b5d Merge pull request #2428 from yougotwill/monokai_theme_fixes
Monokai theme fixes
2018-10-01 03:28:31 +09:00
Junyoung Choi (Sai)
ac744fbd90 Merge pull request #2435 from jacobherrington/handle-opt-in-hotkeys
Allow abbreviation for Option in hotkeys
2018-10-01 03:21:25 +09:00
Junyoung Choi (Sai)
10a1104073 Merge pull request #2436 from jacobherrington/bug-missing-relative-timestamp-inside-storages
Display timestamp on all notes
2018-10-01 03:04:13 +09:00
Junyoung Choi (Sai)
a39a856f69 Merge pull request #2439 from jacobherrington/patch-2
Bring readme up to date and fixup for grammar
2018-10-01 02:39:28 +09:00
Junyoung Choi (Sai)
e6e69b4fd2 Merge pull request #2442 from jacobherrington/unsaved-changes-copy
Change You Have to Save! copy to Unsaved Changes!
2018-10-01 02:35:54 +09:00
Junyoung Choi (Sai)
b4de1b49f2 Merge pull request #2431 from jacobherrington/tweak-the-crowdfunding-copy
Rewrite the crowdfunding copy
2018-10-01 02:23:06 +09:00
Junyoung Choi (Sai)
c47428b27f Merge pull request #2437 from jacobherrington/change-default-mac-hotkeys
Change Toggle Editor Mode hotkey on OS X
2018-10-01 02:14:43 +09:00
Junyoung Choi (Sai)
d267a78416 Merge pull request #2429 from jacobherrington/bug/capitalize-toggle-editor-mode
Capitalize settings labels
2018-10-01 02:13:49 +09:00
Junyoung Choi (Sai)
04bb04a6a9 Merge pull request #2418 from ehhc/right_click_preview
Implementing a context menu handler to show an attachment in the expl…
2018-10-01 02:13:20 +09:00
Junyoung Choi (Sai)
8eb535169f Merge pull request #2412 from brapifra/brais/folderitem-text-overflow
Add text-overflow: ellipsis to FolderItem
2018-10-01 01:59:15 +09:00
Junyoung Choi (Sai)
e0e0fbf739 Merge pull request #2410 from jacobherrington/chore/change-storage-header
change storage header from Storages to Storage Locations
2018-10-01 01:58:05 +09:00
Junyoung Choi (Sai)
4f8e8ae7b9 Merge pull request #2389 from ehhc/fix_admonitions
fix admonitions -> use the "legacy" types and pass them as options to…
2018-10-01 01:56:29 +09:00
Junyoung Choi (Sai)
15b77482ac Merge pull request #2406 from daiyam/fix-input-position
fix input position when going fullscreen or resizing splitEditor
2018-10-01 01:55:51 +09:00
Junyoung Choi (Sai)
7420363adf Merge pull request #2404 from jacobherrington/chore/improve-error-message-grammar
improve error message grammar
2018-10-01 01:51:42 +09:00
Junyoung Choi (Sai)
1666e3a58a Merge pull request #2403 from jacobherrington/patch-1
Clarify contributing.md
2018-10-01 01:51:26 +09:00
Junyoung Choi (Sai)
adbe85cc33 Merge pull request #2402 from jacobherrington/bug/right-click-should-not-copy-note
fix a bug causing right click to copy a note -- fixes #1833
2018-10-01 01:50:40 +09:00
Junyoung Choi (Sai)
89d8d36ec3 Merge pull request #2400 from daiyam/tag-autocomplete-snippet
fix tag autocomplete in snippet note
2018-10-01 01:50:00 +09:00
Junyoung Choi (Sai)
a631adacb5 Merge pull request #2386 from Rxbsxn/fix-notifaction-in-interface
Make notification sticky to the Save button
2018-10-01 01:07:13 +09:00
Junyoung Choi (Sai)
3384d1b7c3 Merge pull request #2371 from daiyam/tag-clickable
Tags over the editor are clickable
2018-10-01 01:06:27 +09:00
jacobherrington
f70de60672 Change You Have to Save! copy to Unsaved Changes! 2018-09-30 08:45:50 -05:00
jacob
cfd54c3f0e Bring readme up to date and fixup for grammar 2018-09-29 23:33:30 -05:00
jacobherrington
b29c0fe8cb Fix TypeError thrown on the Snippets page
This commit protects from a TypeError that is consistently
thrown on the Snippets page.
2018-09-29 23:17:19 -05:00
jacobherrington
5c1e5e0fcc Correct a typo in the crowdfunding copy 2018-09-29 22:44:55 -05:00
jacobherrington
8ec56390c4 Change Toggle Editor Mode hotkey on OS X
By default the Toggle Editor Mode hotkey was set to Command + M,
which is a great and mnemonic hotkey. Unfortunately that hotkey
is also the hotkey to minimize an application.

This commit changes the default Toggle Editor Mode hotkey to
Command + Option + M on OS X, which will not minimize the window
and maintains the mnemonic pattern.
2018-09-29 22:37:04 -05:00
samherrington
2ad27e175c Display timestamp on all notes
Notes inside My Storage did not display timestamp of last edit,
this commit makes the timestamp consistent across all notes in
all locations.
2018-09-29 18:25:00 -05:00
jacobherrington
da1bd3f1fd Allow abbreviation for Option in hotkeys
This commit allows a user to use 'Opt' as an abbreviation
for Option when setting a hotkey.
2018-09-29 16:54:22 -05:00
samherrington
dd913279d7 Fixup for errors 2018-09-29 15:57:02 -05:00
samherrington
1246a677d1 Remove console.logs
Using console in production is generally undesirable due to
performance loss and security concerns. Errors were changed
to console.error and console.logs were removed.
2018-09-29 15:50:32 -05:00
jacobherrington
43f2fc0740 Rewrite the crowdfunding copy
Some of the grammar needed to be revisted, and there were a
few opportunities to communicate a more genuine message.
2018-09-29 00:16:39 -05:00
jacobherrington
b821209807 Capitalize Code block Theme copy 2018-09-28 22:54:54 -05:00
jacobherrington
39fc5da98f Capitalize Toggle editor mode in preferences
All of the other options are in Title Case so it makes sense to
change this option's label to Title Case as well.
2018-09-28 21:32:59 -05:00
William Grant
504b6af3f6 fixed TodoListPercentage styling 2018-09-28 17:58:30 +02:00
William Grant
b3ede3230c Fixed the TodoListPercentage styling, reverted tag color back to the default button color 2018-09-28 17:53:13 +02:00
ehhc
7035503fa7 using file-to-uri to remove the 'file://'-prefix.. might solve the linux issues 2018-09-28 16:56:23 +02:00
ehhc
7d147fd040 Implementing a context menu handler to show an attachment in the explorer 2018-09-26 13:30:15 +02:00
William Grant
2a44e0b7eb updated the toggleModeButton styling 2018-09-24 17:52:26 +02:00
William Grant
686b9bc82c cleanup duplicated code 2018-09-24 17:46:31 +02:00
William Grant
0c1497a255 Updated NoteItem Styling, fixed NoteItemSimple styling, Changed the tag color based on the monokai color palette, 2018-09-24 17:45:28 +02:00
William Grant
496090610f updated tag color to cyan, fixed togglemodebutton styling 2018-09-24 17:20:01 +02:00
William Grant
16177754d5 fixed togglemodebutton styling 2018-09-24 17:16:56 +02:00
William Grant
f41f4939bc fix for the new folder modal 2018-09-23 17:10:39 +02:00
William Grant
610503472a finished styling 2018-09-23 16:35:26 +02:00
Brais Piñeiro
5dcd74b3b0 FolderItem/StorageItem: Use flex layout 2018-09-23 11:13:45 +01:00
samherrington
305825da78 Change My Storages to My Storage Location to mirror Add Storage Location option 2018-09-22 18:01:48 -05:00
William Grant
205451a31d initial work for dracula theme done 2018-09-22 17:17:22 +02:00
Brais Piñeiro
f1ae04fd07 Add text-overflow: ellipsi to FolderItem. Fixes #2409 2018-09-20 00:12:38 +01:00
jacobherrington
4ba82275b9 change storage header from Storages to Storage Locations 2018-09-18 10:17:14 -05:00
ehhc
093920173e Update markdown.js 2018-09-18 09:33:17 +02:00
ehhc
2c3d95a4db Merge branch 'master' into fix_admonitions 2018-09-18 09:29:53 +02:00
Baptiste Augrain
76928e43a3 - add data-line attribute to definition lists and lists
- add tests
2018-09-17 22:46:20 +02:00
Baptiste Augrain
a816c5dc23 fix test 2018-09-17 21:45:59 +02:00
Baptiste Augrain
5ef84e4bfc fix input position when going fullscreen or resizing splitEditor 2018-09-17 21:39:57 +02:00
Baptiste Augrain
fbdc9c9f8d add data-line attribute to fenced blocks 2018-09-17 17:25:37 +02:00
Martin Price
6510152138 Always redirect to /home when jumping to a note by hash
The note which we are jumping to may not be available in the note list for a number of reasons (e.g. if there is an active search, or if another storage folder is selected, or if the note list is showing starred notes).

This affects both when we are creating a new note (which may not match the current search criteria), and when jumping to a note via a link in another note (and the linked note may not be available for any of the above reasons).

2241
2018-09-17 11:55:01 +01:00
jacobherrington
df0c6a3b94 improve error message grammar 2018-09-16 11:58:20 -05:00
jacobherrington
dfe0d74845 remove code finding the selected note 2018-09-16 11:22:35 -05:00
jacob
b9edd0238d Clarify contributing.md
Improved some of the grammar mistakes and wording of the English portion. There are still a few things that are hard to understand.
2018-09-15 17:38:21 -05:00
jacobherrington
bf72237b38 fix a bug causing right click to copy a note 2018-09-15 17:12:54 -05:00
Baptiste Augrain
a24f6e80c7 fix styling of lineNumber and filename with big sized font 2018-09-15 18:57:31 +02:00
Baptiste Augrain
297c764fe1 - avoid logging to console, the error when the diagram syntax is bad
- fix line numbering of code blocks
2018-09-15 18:33:02 +02:00
Baptiste Augrain
b03c2a1f80 fix XSS bug 2018-09-15 15:24:59 +02:00
Baptiste Augrain
7af77384e7 Merge branch 'master' into fence-attrs 2018-09-15 15:04:55 +02:00
Baptiste Augrain
bacbfc8615 add test 2018-09-15 10:43:41 +02:00
Baptiste Augrain
c8466e9fa6 Merge branch 'master' into bug-1992 2018-09-15 10:27:21 +02:00
Baptiste Augrain
4a231d6fdb fix tag autocomplete in snippet 2018-09-15 10:24:31 +02:00
Baptiste Augrain
4e80e1dd03 Merge branch 'master' into tags 2018-09-15 10:05:38 +02:00
Baptiste Augrain
e0b18c6868 Merge branch 'master' into tag-clickable 2018-09-15 09:58:49 +02:00
Baptiste Augrain
15b9f8e13f Merge branch 'master' into text-deflist 2018-09-15 09:47:42 +02:00
Junyoung Choi (Sai)
03b8dbbc44 Merge pull request #2381 from Antogin/dev
Escape Pipe character when inserting a URL into a table
2018-09-15 13:26:02 +09:00
Junyoung Choi (Sai)
80af8dcf80 Merge pull request #2382 from daiyam/link-clickable
Links are clickable
2018-09-15 13:24:37 +09:00
Junyoung Choi (Sai)
62b2856d29 Merge pull request #2369 from daiyam/default-new-note
Implementing option `ui.defaultNote`
2018-09-15 13:04:02 +09:00
Junyoung Choi (Sai)
7ab3ce91a1 Merge pull request #2281 from mbarczak/features/toc_generator
Automatic table of contents generation for Markdown
2018-09-15 12:36:54 +09:00
Junyoung Choi (Sai)
e040aeef55 Merge pull request #2337 from daiyam/bug-2321
fixing bug #2321
2018-09-15 12:35:57 +09:00
Junyoung Choi (Sai)
46df5a8fa7 Merge pull request #2292 from zhoufeng1989/master
Export  all markdown files in a storage
2018-09-15 12:34:47 +09:00
Junyoung Choi (Sai)
f61fbbaead Merge pull request #2347 from daiyam/front-matter
add support to front-matter
2018-09-15 12:31:12 +09:00
Junyoung Choi (Sai)
480f515114 Merge pull request #2363 from Rxbsxn/add-polish-language
Add Polish translations
2018-09-15 12:30:38 +09:00
Junyoung Choi (Sai)
e206e6babf Merge pull request #2329 from owazae/patch-2
Update contributing.md
2018-09-15 12:26:44 +09:00
Junyoung Choi (Sai)
eae4b52aa1 Merge pull request #2364 from djdany01/master
Improved ES translations
2018-09-15 12:26:12 +09:00
Junyoung Choi (Sai)
933f75f1ee Merge pull request #2327 from mikaoelitiana/fix-1155
Add osx touchbar support
2018-09-15 12:22:42 +09:00
Junyoung Choi (Sai)
f3c72e561a Merge pull request #2320 from daiyam/tag-autocomplete
add tag autocomplete
2018-09-15 12:00:22 +09:00
Baptiste Augrain
44d6374cfe remove use of overlayMode to be able to detect YAML mode in chart code block 2018-09-14 16:25:52 +02:00
Baptiste Augrain
eba13800ff Merge branch 'master' into chart-yaml 2018-09-14 16:17:50 +02:00
Junyoung Choi (Sai)
3942492f32 Merge pull request #2398 from BoostIO/update-readme
Add IssueHunt badge on Readme
2018-09-14 17:29:23 +09:00
Junyoung Choi (Sai)
fe323d5764 Merge branch 'master' into tags 2018-09-14 17:16:55 +09:00
Kazz Yokomizo
4740edfb1f Add IssueHunt badge on Readme 2018-09-14 14:35:23 +09:00
Junyoung Choi (Sai)
671dff060d Merge pull request #2312 from daiyam/table-editor
add smart table editor
2018-09-14 12:25:15 +09:00
Baptiste Augrain
4b0dc08426 highlight code block for chart.js 2018-09-14 00:03:33 +02:00
Baptiste Augrain
10ffa35b29 Merge branch 'fence-attrs' into chart-yaml 2018-09-13 12:29:41 +02:00
Baptiste Augrain
57fadacda0 update ava snapshots 2018-09-13 12:17:59 +02:00
Baptiste Augrain
5c186f30a8 add yaml format for chart 2018-09-13 12:00:08 +02:00
Baptiste Augrain
0a205f77b0 fix diagram's alignment 2018-09-13 11:46:09 +02:00
Baptiste Augrain
75a0f4373c fix fileName styling 2018-09-13 11:34:29 +02:00
Baptiste Augrain
189b245b1d add attributes to fence blocks 2018-09-13 11:07:19 +02:00
ehhc
0eae47c8be fix admonitions -> use the "legacy" types and pass them as options to the plugin. should fix #2379 2018-09-11 16:59:10 +02:00
Robert Harężlak
00607cb704 Make notifaction sticky to the button 2018-09-10 19:00:03 +02:00
ehhc
d79b6e094a export folder should also export the attachments -> fixes #2374 2018-09-09 17:41:51 +02:00
Baptiste Augrain
f9d5c86245 display invalid front-matter 2018-09-09 11:24:54 +02:00
Baptiste Augrain
a6a1291d0e use Cmd key for only macOS 2018-09-09 11:12:47 +02:00
Baptiste Augrain
49db1c8244 fix style to match table padding 2018-09-08 19:28:06 +02:00
Baptiste Augrain
dbe1721d50 use mousedown instead of click event to fix double caret 2018-09-08 19:12:53 +02:00
Baptiste Augrain
b55420e935 remove xOffset 2018-09-08 18:47:48 +02:00
Baptiste Augrain
2706df2b24 fix styling 2018-09-08 18:14:41 +02:00
Baptiste Augrain
90f791de1b front-matter must be delimited by --- 2018-09-08 10:43:02 +02:00
antogyn
c9db3f98d1 Escape Pipe symbols to prevent tables from braking 2018-09-07 13:44:32 +02:00
Antogin
bdfe233472 Merge pull request #1 from BoostIO/master
Merge updated
2018-09-07 11:17:03 +02:00
Daniel J. Pérez Nieto
d340aeb77d Update es-ES.json 2018-09-06 15:10:29 +02:00
Baptiste Augrain
2da1105ff8 make markdown links clickable 2018-09-06 14:57:44 +02:00
Daniel J. Pérez Nieto
e6e5036474 Update es-ES.json 2018-09-06 08:12:14 +02:00
igorjuraszek
609a1709c5 apply feedback 2018-09-06 00:47:39 +02:00
Robert Harężlak
2bb9607eea Merge pull request #1 from Rxbsxn/add-polish-language-by-wucian
Add polish language by wucian
2018-09-05 23:44:57 +02:00
igorjuraszek
ca32d05bb2 Merge branch 'add-polish-language' into add-polish-language-by-wucian 2018-09-05 23:44:16 +02:00
igorjuraszek
67a016add0 further corrections 2018-09-05 23:37:20 +02:00
Robert Harężlak
ea41dbb3bc Add missing keys 2018-09-05 23:30:34 +02:00
igorjuraszek
637090d259 minor polish translation fixes 2018-09-05 22:44:29 +02:00
igorjuraszek
911d65131a fix conflicts 2018-09-05 22:42:44 +02:00
Robert Harężlak
f4203263bb update translations 2018-09-05 22:36:48 +02:00
Robert Harężlak
79df5249ef update translations 2018-09-05 21:34:04 +02:00
Baptiste Augrain
ac71093888 tags are clickable 2018-09-05 18:08:47 +02:00
Baptiste Augrain
d15e0f9fe5 remove unnecessary console.log 2018-09-05 17:31:27 +02:00
Baptiste Augrain
d3861caf28 add label style for Theme and Language 2018-09-05 17:13:41 +02:00
Baptiste Augrain
1c8e379fdd add option for default new note 2018-09-05 17:08:39 +02:00
Daniel J. Pérez Nieto
d9783490ec New phrase from last commit
Added the new phrase from last commit.
2018-09-05 08:48:43 +02:00
Daniel J. Pérez Nieto
bb32c3a8d3 Improved es-ES translation. 2018-09-05 08:41:06 +02:00
Baptiste Augrain
bab7ec388c Merge branch 'master' into bug-2321 2018-09-04 23:48:37 +02:00
Baptiste Augrain
e2957192d0 Merge branch 'master' into bug-2335 2018-09-04 23:46:34 +02:00
Baptiste Augrain
3994c78365 Merge branch 'master' into bug-1992 2018-09-04 23:41:11 +02:00
Baptiste Augrain
bfd4d7ffe1 Merge branch 'master' into front-matter 2018-09-04 23:39:27 +02:00
Baptiste Augrain
cb2f18c078 Merge branch 'master' into tag-autocomplete 2018-09-04 23:37:15 +02:00
Baptiste Augrain
69a032c1cf Merge branch 'master' into tags 2018-09-04 23:35:34 +02:00
Baptiste Augrain
a287afb3e9 Merge branch 'master' into text-deflist 2018-09-04 23:33:01 +02:00
Robert Harężlak
da81f10e04 Add Polish translations 2018-09-04 21:39:14 +02:00
ehhc
786675a99b Merge branch 'master' into spellchecker 2018-09-04 11:36:27 +02:00
Baptiste Augrain
6603f46678 Merge branch 'master' into table-editor 2018-09-04 10:54:49 +02:00
Junyoung Choi (Sai)
fdfa3bb8f5 Merge pull request #2355 from yougotwill/statusbar_fix
Statusbar height fix
2018-09-04 17:42:12 +09:00
Junyoung Choi (Sai)
da204a27c5 Merge pull request #2345 from hsiehjack/bug-2007
Fix scroll for long table
2018-09-04 17:41:28 +09:00
Junyoung Choi (Sai)
e11a68afba Merge pull request #2352 from daiyam/issue-2349
add default language for snippet note
2018-09-04 17:41:11 +09:00
Junyoung Choi (Sai)
168b0f82dd Merge pull request #2344 from hsiehjack/bug-2289
Fix text not readable for Monokai theme while creating new folder
2018-09-04 17:40:37 +09:00
Junyoung Choi (Sai)
6715a54da2 Merge pull request #2304 from amedora/feature/2069-monokai
add Monokai style for SnippetTab
2018-09-04 17:38:51 +09:00
Junyoung Choi (Sai)
6b32b3ae80 Merge pull request #2313 from daiyam/editorconfig
add EditorConfig
2018-09-04 17:37:41 +09:00
Junyoung Choi
6a2242725d v0.11.9 2018-09-04 16:11:27 +09:00
Maciek
e9070fadab Refactoring : use object destructuring to retain file code style 2018-09-02 19:38:15 +02:00
Junyoung Choi (Sai)
1117e1b724 Merge pull request #2357 from BoostIO/revert-flickering-fix
Revert flickering fix
2018-09-01 16:56:11 +09:00
William Grant
01641b5af4 fixed 2018-09-01 12:54:48 +10:00
Baptiste Augrain
7c0c81207b add test 2018-08-31 10:27:02 +02:00
Baptiste Augrain
c844b60941 fix lint errors 2018-08-30 18:00:19 +02:00
Baptiste Augrain
9f8246a26a add default language for snippet note 2018-08-30 17:52:50 +02:00
Baptiste Augrain
6d5141b60f fix lint errors 2018-08-29 19:29:57 +02:00
Baptiste Augrain
8b4a9dd325 add saveTagsAlphabetically option 2018-08-29 19:28:09 +02:00
Baptiste Augrain
5006aaae38 fix live note counts when multiple tags are selected 2018-08-28 01:44:33 +02:00
Maciek
1bb841d5c5 Create Markdown TOC at current cursor position
If there is no TOC in the current document, it's created at current
cursor position. Subsequent generation calls update TOC at existing
position.

Add additional tests with CodeMirror editor mock.
2018-08-28 00:51:49 +02:00
Baptiste Augrain
f57c4f390d fix lint errors 2018-08-27 23:16:21 +02:00
Baptiste Augrain
646151e020 allows compact definition lists 2018-08-27 19:12:28 +02:00
Baptiste Augrain
20f573c477 - hide front-matter in preview
- skip front-matter when looking for note's title
2018-08-27 10:06:20 +02:00
Baptiste Augrain
094e4c5da8 add support to abbreviations, subscript text, superscript text and definition lists 2018-08-27 02:41:56 +02:00
Jack Hsieh
7716880a6c Fix scroll for long table 2018-08-26 16:47:22 -07:00
Jack Hsieh
71605fb8fe Fix text not readable for Monokai theme while creating new folder 2018-08-26 09:36:00 -07:00
Baptiste Augrain
fa9d8b8881 replace awesomplete with React component react-autosuggest 2018-08-26 15:48:41 +02:00
zhoufeng1989
aa0566b8ca Update test cases for export storage, check content of exported notes. 2018-08-26 21:15:20 +12:00
Baptiste Augrain
2a838ebb0b fixing single quoted attributes 2018-08-26 00:14:29 +02:00
Baptiste Augrain
fabc975b20 - fix lint errors
- correctly parse self-closed tag
- fix naughty functions
2018-08-25 23:36:43 +02:00
Baptiste Augrain
3bdc88cecb fixing sanitization of inline html like (<kbd>) #1992 2018-08-25 23:14:05 +02:00
Baptiste Augrain
a591001761 add parameter to specify the height of the diagram, fixing #2335 2018-08-25 20:18:59 +02:00
ehhc
54717ea6f2 linter errors fixed 2018-08-25 18:45:13 +02:00
ehhc
ceca4c98a3 linter errors fixed 2018-08-25 18:39:29 +02:00
ehhc
39b4287c5e linter errors fixed 2018-08-25 18:34:21 +02:00
Baptiste Augrain
53923c9c87 filtering out the note's tags 2018-08-25 18:22:40 +02:00
Maciek
ede733888d Code style: remove redundant brackets from lambda expression 2018-08-25 17:59:04 +02:00
Baptiste Augrain
a79db03093 enable/disable the table editor without restarting the app 2018-08-25 17:51:40 +02:00
Baptiste Augrain
5c8254a9c4 fixing bug #2321 due to the unescaped characters # or ? in the route 2018-08-24 23:48:26 +02:00
owazae
2f7b62f710 Update contributing.md
Adding french translation
2018-08-23 14:48:33 +02:00
Mika Andrianarijaona
a19c13eb3c remove unused spacing 2018-08-22 18:14:31 +02:00
Mika Andrianarijaona
64d4cd84af send events when user click touchbar buttons 2018-08-22 18:09:55 +02:00
ehhc
734db58d85 Spellcheck - first try to fix #2176 2018-08-22 16:48:10 +02:00
Keyon U
2bbcb8ca89 Add comments for the “rotate fix” 2018-08-22 18:03:02 +08:00
Mika Andrianarijaona
07e810a231 init touchbar menu 2018-08-22 10:58:53 +02:00
Keyon U
48beb184df Fix drop image rotate wrong
Fix drag-drop image rotate wrong, as Jordan Thornquest mentioned in slack
2018-08-22 12:23:45 +08:00
Baptiste Augrain
f195e87568 update background color of markers 2018-08-21 11:52:46 +02:00
Baptiste Augrain
13d44ae56a fix lint errors 2018-08-21 11:19:46 +02:00
Baptiste Augrain
00b4874d09 add tag autocomplete 2018-08-21 10:57:25 +02:00
Maciek
5bb90babbc Add tests for Markdown TOC generator 2018-08-21 01:03:53 +02:00
Baptiste Augrain
7cde30d352 fix lint errors 2018-08-21 00:24:03 +02:00
Baptiste Augrain
73fbf49ba4 - show tags of note in alphabetical order
- enable live count of notes
2018-08-21 00:19:26 +02:00
Baptiste Augrain
b39ef5948b add EditorConfig 2018-08-20 20:30:49 +02:00
Baptiste Augrain
7cf9dda821 fix lint errors 2018-08-20 20:29:10 +02:00
Baptiste Augrain
657806c8cf add smart table editor 2018-08-20 20:14:45 +02:00
Kiryanenko Alexander
d61a218808 Merge branch 'master' into master 2018-08-20 14:14:02 +03:00
amedora
ab35c3557f add Monokai style for SnippetTab 2018-08-16 16:36:00 +09:00
Maciek
ce3b29085f Change menu position and accelerator for TOC gen.
Due to the fact, that submenu "Edit" is visible only in macOS,
let's move TOC generator to "File" menu. Also, change
accelerator to SHIFT+CTRL+T which is working without conflicts
and problems on all platforms.
2018-08-14 23:24:41 +02:00
zhoufeng1989
b93d7a204f Fix 2207 and 2273, add export for storage. 2018-08-14 12:38:31 +12:00
Maciek
3c14cc219e ESLint: fix let -> const warnings 2018-08-11 10:09:22 +02:00
Maciek
7804a22984 Automatic table of contents generation for Markdown
Adds table of contents for any Markdown note or
Markdown snippet.
Consequent generations update existing TOC.
Generated TOC is case sensitive to handle #2067

Shortcut : CommandOrControl+Alt+T
Menu : Edit/Generate/Update Markdown TOC
2018-08-10 23:20:56 +02:00
Alexander
5bf3824f28 Fix 2018-08-10 15:15:19 +03:00
Alexander
dac23e38d9 Add support for converting to MD when paste HTML 2018-08-10 13:27:17 +03:00
ehhc
4307db11c5 Merge branch 'master' of https://github.com/BoostIO/Boostnote into spell_check
# Conflicts:
#	browser/components/CodeEditor.js
#	locales/fr.json
#	package.json
2018-07-03 09:05:47 +02:00
ehhc
83f8151ca4 spellcheck -> context menu with spelling suggestions 2018-07-02 17:27:47 +02:00
ehhc
342575a576 Spellcheck - Dropdown & localisation 2018-06-23 19:29:22 +02:00
ehhc
785272540e Spellcheck - liveSpellcheck 2018-06-23 18:16:39 +02:00
ehhc
82178055af Spellcheck - initialisation and first draft onChange 2018-06-17 17:13:44 +02:00
196 changed files with 228271 additions and 1759 deletions

16
.editorconfig Normal file
View File

@@ -0,0 +1,16 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Space indentation
[*]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# The indent size used in the `package.json` file cannot be changed
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
[{*.yml,*.yaml,package.json}]
indent_style = space
indent_size = 2

41
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "BoostNote Main",
"protocol": "inspector",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"runtimeArgs": [
"--remote-debugging-port=9223",
"--hot",
"${workspaceFolder}/index.js"
],
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
}
},
{
"type": "chrome",
"request": "attach",
"name": "BoostNote Renderer",
"port": 9223,
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///./~/*": "${webRoot}/node_modules/*",
"webpack:///*": "${webRoot}/*"
}
}
],
"compounds": [
{
"name": "BostNote All",
"configurations": ["BoostNote Main", "BoostNote Renderer"]
}
]
}

27
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,27 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build Boostnote",
"group": "build",
"type": "npm",
"script": "watch",
"isBackground": true,
"presentation": {
"reveal": "always",
},
"problemMatcher": {
"pattern":[
{
"regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$",
"file": 1,
"location": 2,
"message": 3
}
]
}
}
]
}

View File

@@ -1,72 +0,0 @@
<h1 align="center">Sponsors &amp; Backers</h1>
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. If you'd like to join them, please consider:
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
---
## Backers via OpenCollective
### [Gold Sponsors / $1,000 per month](https://opencollective.com/boostnoteio/order/2259)
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
### [Silver Sponsors / $250 per month](https://opencollective.com/boostnoteio/order/2257)
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
### [Bronze Sponsors / $50 per month](https://opencollective.com/boostnoteio/order/2258)
- Get your name and Url (or E-mail) on Readme.md on GitHub.
### [Backers3 / $10 per month](https://opencollective.com/boostnoteio/order/2176)
- [Ralph03](https://opencollective.com/ralph03)
- [Nikolas Dan](https://opencollective.com/nikolas-dan)
### [Backers2 / $5 per month](https://opencollective.com/boostnoteio/order/2175)
- [Yeojong Kim](https://twitter.com/yeojoy)
- [Scotia Draven](https://opencollective.com/scotia-draven)
- [A. J. Vargas](https://opencollective.com/aj-vargas)
### [Backers1](https://opencollective.com/boostnoteio/order/2563) and One-time sponsors
- Ryosuke Tamura - $30
- tatoosh11 - $10
- Alexander Borovkov - $10
- spoonhoop - $5
- Drew Williams - $2
- Andy Shaw - $2
- mysafesky -$2
---
## Backers via Bountysource
https://salt.bountysource.com/teams/boostnote
- Kuzz - $65
- Intense Raiden - $45
- ravy22 - $25
- trentpolack - $20
- hikariru - $10
- kolchan11 - $10
- RonWalker22 - $10
- hocchuc - $5
- Adam - $5
- Steve - $5
- evmin - $5

29
FAQ.md Normal file
View File

@@ -0,0 +1,29 @@
# Frequently Asked Questions
<details><summary>Allowing dangerous HTML tags</summary>
Sometimes it is useful to allow dangerous HTML tags to add interactivity to your notebook. One of the example is to use details/summary as a way to expand/collaps your todo-list.
* How to enable:
* Go to **Preferences****Interface****Sanitization****Allow dangerous html tags**
* Example note: Multiple todo-list
* Create new notes
* Paste the below code, and you'll see that you can expand/collaps the todo-list, and you can have multiple todo-list in your note.
```html
<details><summary>What I want to do</summary>
- [x] Create an awesome feature X
- [ ] Do my homework
</details>
```
</details>
## Other questions
You can ask [here][ISSUES]
[ISSUES]: https://github.com/BoostIO/Boostnote/issues

View File

@@ -1,15 +1,25 @@
# Current behavior # Current behavior
<!-- <!--
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug. Let us know what is currently happening.
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile. Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.
If your issue is regarding Boostnote mobile, please open an issue in the Boostnote Mobile repo 👉 https://github.com/BoostIO/boostnote-mobile.
--> -->
# Expected behavior # Expected behavior
<!--
Let us know what you think should happen!
-->
# Steps to reproduce # Steps to reproduce
<!--
Please be thorough, issues we can reproduce are easier to fix!
-->
1. 1.
2. 2.
3. 3.

View File

@@ -2,7 +2,7 @@ GPL-3.0
Boostnote - an open source note-taking app made for programmers just like you. Boostnote - an open source note-taking app made for programmers just like you.
Copyright (C) 2017 - 2018 BoostIO Copyright (C) 2017 - 2019 BoostIO
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

36
PULL_REQUEST_TEMPLATE.md Normal file
View File

@@ -0,0 +1,36 @@
<!--
Before submitting this PR, please make sure that:
- You have read and understand the contributing.md
- You have checked docs/code_style.md for information on code style
-->
## Description
<!--
Tell us what your PR does.
Please attach a screenshot/ video/gif image describing your PR if possible.
-->
## Issue fixed
<!--
Please list out all issue fixed with this PR here.
-->
<!--
Please make sure you fill in these checkboxes,
your PR will be reviewed faster if we know exactly what it does.
Change :white_circle: to :radio_button: in all the options that apply
-->
## Type of changes
- :white_circle: Bug fix (Change that fixed an issue)
- :white_circle: Breaking change (Change that can cause existing functionality to change)
- :white_circle: Improvement (Change that improves the code. Maybe performance or development improvement)
- :white_circle: Feature (Change that adds new functionality)
- :white_circle: Documentation change (Change that modifies documentation. Maybe typo fixes)
## Checklist:
- :white_circle: My code follows [the project code style](docs/code_style.md)
- :white_circle: I have written test for my code and it has been tested
- :white_circle: All existing tests have been passed
- :white_circle: I have attached a screenshot/video to visualize my change if possible

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
.codeEditor-typo
text-decoration underline wavy red
.spellcheck-select
border: none
text-decoration underline wavy red

View File

@@ -0,0 +1,68 @@
import React from 'react'
import PropTypes from 'prop-types'
import { SketchPicker } from 'react-color'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ColorPicker.styl'
const componentHeight = 330
class ColorPicker extends React.Component {
constructor (props) {
super(props)
this.state = {
color: this.props.color || '#939395'
}
this.onColorChange = this.onColorChange.bind(this)
this.handleConfirm = this.handleConfirm.bind(this)
}
componentWillReceiveProps (nextProps) {
this.onColorChange(nextProps.color)
}
onColorChange (color) {
this.setState({
color
})
}
handleConfirm () {
this.props.onConfirm(this.state.color)
}
render () {
const { onReset, onCancel, targetRect } = this.props
const { color } = this.state
const clientHeight = document.body.clientHeight
const alignX = targetRect.right + 4
let alignY = targetRect.top
if (targetRect.top + componentHeight > clientHeight) {
alignY = targetRect.bottom - componentHeight
}
return (
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
<div styleName='cover' onClick={onCancel} />
<SketchPicker color={color} onChange={this.onColorChange} />
<div styleName='footer'>
<button styleName='btn-reset' onClick={onReset}>Reset</button>
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
</div>
</div>
)
}
}
ColorPicker.propTypes = {
color: PropTypes.string,
targetRect: PropTypes.object,
onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired
}
export default CSSModules(ColorPicker, styles)

View File

@@ -0,0 +1,39 @@
.colorPicker
position fixed
z-index 2
display flex
flex-direction column
.cover
position fixed
top 0
right 0
bottom 0
left 0
.footer
display flex
justify-content center
z-index 2
align-items center
& > button + button
margin-left 10px
.btn-cancel,
.btn-confirm,
.btn-reset
vertical-align middle
height 25px
margin-top 2.5px
border-radius 2px
border none
padding 0 5px
background-color $default-button-background
&:hover
background-color $default-button-background--hover
.btn-confirm
background-color #1EC38B
&:hover
background-color darken(#1EC38B, 25%)

View File

@@ -6,6 +6,8 @@ import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview' import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import ConfigManager from 'browser/main/lib/ConfigManager'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
class MarkdownEditor extends React.Component { class MarkdownEditor extends React.Component {
constructor (props) { constructor (props) {
@@ -18,10 +20,10 @@ class MarkdownEditor extends React.Component {
this.supportMdSelectionBold = [16, 17, 186] this.supportMdSelectionBold = [16, 17, 186]
this.state = { this.state = {
status: 'PREVIEW', status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE',
renderValue: props.value, renderValue: props.value,
keyPressed: new Set(), keyPressed: new Set(),
isLocked: false isLocked: props.isLocked
} }
this.lockEditorCode = () => this.handleLockEditor() this.lockEditorCode = () => this.handleLockEditor()
@@ -64,17 +66,20 @@ class MarkdownEditor extends React.Component {
}) })
} }
setValue (value) {
this.refs.code.setValue(value)
}
handleChange (e) { handleChange (e) {
this.value = this.refs.code.value this.value = this.refs.code.value
this.props.onChange(e) this.props.onChange(e)
} }
handleContextMenu (e) { handleContextMenu (e) {
if (this.state.isLocked) return
const { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') { if (config.editor.switchPreview === 'RIGHTCLICK') {
const newStatus = this.state.status === 'PREVIEW' const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
? 'CODE'
: 'PREVIEW'
this.setState({ this.setState({
status: newStatus status: newStatus
}, () => { }, () => {
@@ -84,6 +89,10 @@ class MarkdownEditor extends React.Component {
this.refs.preview.focus() this.refs.preview.focus()
} }
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
const newConfig = Object.assign({}, config)
newConfig.editor.delfaultStatus = newStatus
ConfigManager.set(newConfig)
}) })
} }
} }
@@ -140,8 +149,10 @@ class MarkdownEditor extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/ const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /\[x\]/i const checkedMatch = /^\s*[\+\-\*] \[x\]/i
const uncheckedMatch = /\[ \]/ const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
const checkReplace = /\[x\]/i
const uncheckReplace = /\[ \]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value const lines = this.refs.code.value
@@ -150,10 +161,10 @@ class MarkdownEditor extends React.Component {
const targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
if (targetLine.match(checkedMatch)) { if (targetLine.match(checkedMatch)) {
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]') lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
} }
if (targetLine.match(uncheckedMatch)) { if (targetLine.match(uncheckedMatch)) {
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]') lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
} }
this.refs.code.setValue(lines.join('\n')) this.refs.code.setValue(lines.join('\n'))
} }
@@ -212,6 +223,28 @@ class MarkdownEditor extends React.Component {
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`) this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
} }
handleDropImage (dropEvent) {
dropEvent.preventDefault()
const { storageKey, noteKey } = this.props
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
this.refs.code.editor.execCommand('goDocEnd')
this.refs.code.editor.execCommand('goLineEnd')
this.refs.code.editor.execCommand('newlineAndIndent')
attachmentManagement.handleAttachmentDrop(
this.refs.code,
storageKey,
noteKey,
dropEvent
)
})
}
handleKeyUp (e) { handleKeyUp (e) {
const keyPressed = this.state.keyPressed const keyPressed = this.state.keyPressed
keyPressed.delete(e.keyCode) keyPressed.delete(e.keyCode)
@@ -223,7 +256,7 @@ class MarkdownEditor extends React.Component {
} }
render () { render () {
const {className, value, config, storageKey, noteKey} = this.props const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -250,7 +283,7 @@ class MarkdownEditor extends React.Component {
: 'codeEditor--hide' : 'codeEditor--hide'
} }
ref='code' ref='code'
mode='GitHub Flavored Markdown' mode='Boost Flavored Markdown'
value={value} value={value}
theme={config.editor.theme} theme={config.editor.theme}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
@@ -261,12 +294,21 @@ class MarkdownEditor extends React.Component {
enableRulers={config.editor.enableRulers} enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers} rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey} noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)} onBlur={(e) => this.handleBlur(e)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
/> />
<MarkdownPreview styleName={this.state.status === 'PREVIEW' <MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview' ? 'preview'
@@ -299,6 +341,8 @@ class MarkdownEditor extends React.Component {
noteKey={noteKey} noteKey={noteKey}
customCSS={config.preview.customCSS} customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS} allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
onDrop={(e) => this.handleDropImage(e)}
/> />
</div> </div>
) )

View File

@@ -16,7 +16,6 @@
.preview .preview
display block display block
absolute top bottom left right absolute top bottom left right
z-index 100
background-color white background-color white
height 100% height 100%
width 100% width 100%

View File

@@ -17,8 +17,15 @@ import copy from 'copy-to-clipboard'
import mdurl from 'mdurl' import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote' import exportNote from 'browser/main/lib/dataApi/exportNote'
import { escapeHtmlCharacters } from 'browser/lib/utils' import { escapeHtmlCharacters } from 'browser/lib/utils'
import yaml from 'js-yaml'
import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
import fs from 'fs'
import { render } from 'react-dom'
import Carousel from 'react-image-carousel'
import ConfigManager from '../main/lib/ConfigManager'
const { remote } = require('electron') const { remote, shell } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const { app } = remote const { app } = remote
@@ -27,13 +34,16 @@ const fileUrl = require('file-url')
const dialog = remote.dialog const dialog = remote.dialog
const uri2path = require('file-uri-to-path')
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1] const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = fileUrl( const appPath = fileUrl(
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve() process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
) )
const CSS_FILES = [ const CSS_FILES = [
`${appPath}/node_modules/katex/dist/katex.min.css`, `${appPath}/node_modules/katex/dist/katex.min.css`,
`${appPath}/node_modules/codemirror/lib/codemirror.css` `${appPath}/node_modules/codemirror/lib/codemirror.css`,
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
] ]
function buildStyle ( function buildStyle (
@@ -75,7 +85,6 @@ function buildStyle (
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'), url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype'); url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
} }
${allowCustomCSS ? customCSS : ''}
${markdownStyle} ${markdownStyle}
body { body {
@@ -83,6 +92,11 @@ body {
font-size: ${fontSize}px; font-size: ${fontSize}px;
${scrollPastEnd && 'padding-bottom: 90vh;'} ${scrollPastEnd && 'padding-bottom: 90vh;'}
} }
@media print {
body {
padding-bottom: initial;
}
}
code { code {
font-family: '${codeBlockFontFamily.join("','")}'; font-family: '${codeBlockFontFamily.join("','")}';
background-color: rgba(0,0,0,0.04); background-color: rgba(0,0,0,0.04);
@@ -139,6 +153,8 @@ body p {
display: none display: none
} }
} }
${allowCustomCSS ? customCSS : ''}
` `
} }
@@ -161,7 +177,6 @@ const scrollBarDarkStyle = `
} }
` `
const { shell } = require('electron')
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif'] const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
@@ -195,7 +210,7 @@ export default class MarkdownPreview extends React.Component {
this.saveAsHtmlHandler = () => this.handleSaveAsHtml() this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.printHandler = () => this.handlePrint() this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handlelinkClick.bind(this) this.linkClickHandler = this.handleLinkClick.bind(this)
this.initMarkdown = this.initMarkdown.bind(this) this.initMarkdown = this.initMarkdown.bind(this)
this.initMarkdown() this.initMarkdown()
} }
@@ -219,8 +234,32 @@ export default class MarkdownPreview extends React.Component {
} }
} }
handleContextMenu (e) { handleContextMenu (event) {
this.props.onContextMenu(e) // If a contextMenu handler was passed to us, use it instead of the self-defined one -> return
if (_.isFunction(this.props.onContextMenu)) {
this.props.onContextMenu(event)
return
}
// No contextMenu was passed to us -> execute our own link-opener
if (event.target.tagName.toLowerCase() === 'a') {
const href = event.target.href
const isLocalFile = href.startsWith('file:')
if (isLocalFile) {
const absPath = uri2path(href)
try {
if (fs.lstatSync(absPath).isFile()) {
context.popup([
{
label: i18n.__('Show in explorer'),
click: (e) => shell.showItemInFolder(absPath)
}
])
}
} catch (e) {
console.log('Error while evaluating if the file is locally available', e)
}
}
}
} }
handleDoubleClick (e) { handleDoubleClick (e) {
@@ -228,6 +267,10 @@ export default class MarkdownPreview extends React.Component {
} }
handleMouseDown (e) { handleMouseDown (e) {
const config = ConfigManager.get()
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
}
if (e.target != null) { if (e.target != null) {
switch (e.target.tagName) { switch (e.target.tagName) {
case 'A': case 'A':
@@ -251,26 +294,7 @@ export default class MarkdownPreview extends React.Component {
} }
handleSaveAsMd () { handleSaveAsMd () {
this.exportAsDocument('md', (noteContent, exportTasks) => { this.exportAsDocument('md')
let result = noteContent
if (this.props && this.props.storagePath && this.props.noteKey) {
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent,
this.props.storagePath
)
attachmentsAbsolutePaths.forEach(attachment => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
result = attachmentManagement.removeStorageAndNoteReferences(
noteContent,
this.props.noteKey
)
}
return result
})
} }
handleSaveAsHtml () { handleSaveAsHtml () {
@@ -297,15 +321,8 @@ export default class MarkdownPreview extends React.Component {
allowCustomCSS, allowCustomCSS,
customCSS customCSS
) )
let body = this.markdown.render( let body = this.markdown.render(noteContent)
escapeHtmlCharacters(noteContent, { detectCodeBlock: true })
)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent,
this.props.storagePath
)
files.forEach(file => { files.forEach(file => {
if (global.process.platform === 'win32') { if (global.process.platform === 'win32') {
file = file.replace('file:///', '') file = file.replace('file:///', '')
@@ -317,16 +334,6 @@ export default class MarkdownPreview extends React.Component {
dst: 'css' dst: 'css'
}) })
}) })
attachmentsAbsolutePaths.forEach(attachment => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
body = attachmentManagement.removeStorageAndNoteReferences(
body,
this.props.noteKey
)
let styles = '' let styles = ''
files.forEach(file => { files.forEach(file => {
@@ -359,8 +366,9 @@ export default class MarkdownPreview extends React.Component {
if (filename) { if (filename) {
const content = this.props.value const content = this.props.value
const storage = this.props.storagePath const storage = this.props.storagePath
const nodeKey = this.props.noteKey
exportNote(storage, content, filename, contentFormatter) exportNote(nodeKey, storage, content, filename, contentFormatter)
.then(res => { .then(res => {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info', type: 'info',
@@ -390,6 +398,31 @@ export default class MarkdownPreview extends React.Component {
} }
} }
/**
* @description Convert special characters between three ```
* @param {string[]} splitWithCodeTag Array of HTML strings separated by three ```
* @returns {string} HTML in which special characters between three ``` have been converted
*/
escapeHtmlCharactersInCodeTag (splitWithCodeTag) {
for (let index = 0; index < splitWithCodeTag.length; index++) {
const codeTagRequired = (splitWithCodeTag[index] !== '\`\`\`' && index < splitWithCodeTag.length - 1)
if (codeTagRequired) {
splitWithCodeTag.splice((index + 1), 0, '\`\`\`')
}
}
let inCodeTag = false
let result = ''
for (let content of splitWithCodeTag) {
if (content === '\`\`\`') {
inCodeTag = !inCodeTag
} else if (inCodeTag) {
content = escapeHtmlCharacters(content)
}
result += content
}
return result
}
getScrollBarStyle () { getScrollBarStyle () {
const { theme } = this.props const { theme } = this.props
@@ -397,6 +430,7 @@ export default class MarkdownPreview extends React.Component {
case 'dark': case 'dark':
case 'solarized-dark': case 'solarized-dark':
case 'monokai': case 'monokai':
case 'dracula':
return scrollBarDarkStyle return scrollBarDarkStyle
default: default:
return scrollBarStyle return scrollBarStyle
@@ -404,6 +438,8 @@ export default class MarkdownPreview extends React.Component {
} }
componentDidMount () { componentDidMount () {
const { onDrop } = this.props
this.refs.root.setAttribute('sandbox', 'allow-scripts') this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener( this.refs.root.contentWindow.document.body.addEventListener(
'contextmenu', 'contextmenu',
@@ -441,7 +477,7 @@ export default class MarkdownPreview extends React.Component {
) )
this.refs.root.contentWindow.document.addEventListener( this.refs.root.contentWindow.document.addEventListener(
'drop', 'drop',
this.preventImageDroppedHandler onDrop || this.preventImageDroppedHandler
) )
this.refs.root.contentWindow.document.addEventListener( this.refs.root.contentWindow.document.addEventListener(
'dragover', 'dragover',
@@ -458,6 +494,8 @@ export default class MarkdownPreview extends React.Component {
} }
componentWillUnmount () { componentWillUnmount () {
const { onDrop } = this.props
this.refs.root.contentWindow.document.body.removeEventListener( this.refs.root.contentWindow.document.body.removeEventListener(
'contextmenu', 'contextmenu',
this.contextMenuHandler this.contextMenuHandler
@@ -476,7 +514,7 @@ export default class MarkdownPreview extends React.Component {
) )
this.refs.root.contentWindow.document.removeEventListener( this.refs.root.contentWindow.document.removeEventListener(
'drop', 'drop',
this.preventImageDroppedHandler onDrop || this.preventImageDroppedHandler
) )
this.refs.root.contentWindow.document.removeEventListener( this.refs.root.contentWindow.document.removeEventListener(
'dragover', 'dragover',
@@ -498,7 +536,8 @@ export default class MarkdownPreview extends React.Component {
prevProps.smartQuotes !== this.props.smartQuotes || prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize || prevProps.sanitize !== this.props.sanitize ||
prevProps.smartArrows !== this.props.smartArrows || prevProps.smartArrows !== this.props.smartArrows ||
prevProps.breaks !== this.props.breaks prevProps.breaks !== this.props.breaks ||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
) { ) {
this.initMarkdown() this.initMarkdown()
this.rewriteIframe() this.rewriteIframe()
@@ -618,11 +657,16 @@ export default class MarkdownPreview extends React.Component {
indentSize, indentSize,
showCopyNotification, showCopyNotification,
storagePath, storagePath,
noteKey noteKey,
sanitize
} = this.props } = this.props
let { value, codeBlockTheme } = this.props let { value, codeBlockTheme } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme) this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
if (sanitize === 'NONE') {
const splitWithCodeTag = value.split('```')
value = this.escapeHtmlCharactersInCodeTag(splitWithCodeTag)
}
const renderedHTML = this.markdown.render(value) const renderedHTML = this.markdown.render(value)
attachmentManagement.migrateAttachments(value, storagePath, noteKey) attachmentManagement.migrateAttachments(value, storagePath, noteKey)
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS( this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
@@ -704,7 +748,6 @@ export default class MarkdownPreview extends React.Component {
el.addEventListener('click', this.linkClickHandler) el.addEventListener('click', this.linkClickHandler)
}) })
} catch (e) { } catch (e) {
console.error(e)
el.className = 'flowchart-error' el.className = 'flowchart-error'
el.innerHTML = 'Flowchart parse error: ' + e.message el.innerHTML = 'Flowchart parse error: ' + e.message
} }
@@ -725,7 +768,6 @@ export default class MarkdownPreview extends React.Component {
el.addEventListener('click', this.linkClickHandler) el.addEventListener('click', this.linkClickHandler)
}) })
} catch (e) { } catch (e) {
console.error(e)
el.className = 'sequence-error' el.className = 'sequence-error'
el.innerHTML = 'Sequence diagram parse error: ' + e.message el.innerHTML = 'Sequence diagram parse error: ' + e.message
} }
@@ -736,14 +778,21 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.querySelectorAll('.chart'), this.refs.root.contentWindow.document.querySelectorAll('.chart'),
el => { el => {
try { try {
const chartConfig = JSON.parse(el.innerHTML) const format = el.attributes.getNamedItem('data-format').value
const chartConfig = format === 'yaml' ? yaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
el.innerHTML = '' el.innerHTML = ''
var canvas = document.createElement('canvas')
const canvas = document.createElement('canvas')
el.appendChild(canvas) el.appendChild(canvas)
/* eslint-disable no-new */
new Chart(canvas, chartConfig) const height = el.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') {
el.style.height = height.value + 'vh'
canvas.height = height.value + 'vh'
}
const chart = new Chart(canvas, chartConfig)
} catch (e) { } catch (e) {
console.error(e)
el.className = 'chart-error' el.className = 'chart-error'
el.innerHTML = 'chartjs diagram parse error: ' + e.message el.innerHTML = 'chartjs diagram parse error: ' + e.message
} }
@@ -755,6 +804,109 @@ export default class MarkdownPreview extends React.Component {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme) mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme)
} }
) )
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.gallery'),
el => {
const images = el.innerHTML.split(/\n/g).filter(i => i.length > 0)
el.innerHTML = ''
const height = el.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') {
el.style.height = height.value + 'vh'
}
let autoplay = el.attributes.getNamedItem('data-autoplay')
if (autoplay && autoplay.value !== 'undefined') {
autoplay = parseInt(autoplay.value, 10) || 0
} else {
autoplay = 0
}
render(
<Carousel
images={images}
autoplay={autoplay}
/>,
el
)
}
)
const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect()
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
for (const img of imgList) {
img.onclick = () => {
const widthMagnification = document.body.clientWidth / img.width
const heightMagnification = document.body.clientHeight / img.height
const baseOnWidth = widthMagnification < heightMagnification
const magnification = baseOnWidth ? widthMagnification : heightMagnification
const zoomImgWidth = img.width * magnification
const zoomImgHeight = img.height * magnification
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
const originalImgTop = img.y + rect.top
const originalImgLeft = img.x + rect.left
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const zoomImg = document.createElement('img')
zoomImg.src = img.src
zoomImg.style = `
position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px;
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = `
position: absolute;
top: ${originalImgTop}px;
left: ${originalImgLeft}px;
width: ${img.width}px;
height: ${img.height}px;
`
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
}
}
} }
focus () { focus () {
@@ -797,7 +949,7 @@ export default class MarkdownPreview extends React.Component {
return new window.Notification(title, options) return new window.Notification(title, options)
} }
handlelinkClick (e) { handleLinkClick (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -827,6 +979,15 @@ export default class MarkdownPreview extends React.Component {
return return
} }
const regexIsLine = /^:line:[0-9]/
if (regexIsLine.test(linkHash)) {
const numberPattern = /\d+/g
const lineNumber = parseInt(linkHash.match(numberPattern)[0])
eventEmitter.emit('line:jump', lineNumber)
return
}
// this will match the old link format storage.key-note.key // this will match the old link format storage.key-note.key
// e.g. // e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490 // 877f99c3268608328037-1c211eb7dcb463de6490

View File

@@ -20,12 +20,18 @@ class MarkdownSplitEditor extends React.Component {
} }
} }
handleOnChange () { setValue (value) {
this.refs.code.setValue(value)
}
handleOnChange (e) {
this.value = this.refs.code.value this.value = this.refs.code.value
this.props.onChange() this.props.onChange(e)
} }
handleScroll (e) { handleScroll (e) {
if (!this.props.config.preview.scrollSync) return
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document') const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const codeDoc = _.get(this, 'refs.code.editor.doc') const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight let srcTop, srcHeight, targetTop, targetHeight
@@ -72,8 +78,10 @@ class MarkdownSplitEditor extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/ const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /\[x\]/i const checkedMatch = /^\s*[\+\-\*] \[x\]/i
const uncheckedMatch = /\[ \]/ const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
const checkReplace = /\[x\]/i
const uncheckReplace = /\[ \]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value const lines = this.refs.code.value
@@ -82,10 +90,10 @@ class MarkdownSplitEditor extends React.Component {
const targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
if (targetLine.match(checkedMatch)) { if (targetLine.match(checkedMatch)) {
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]') lines[lineIndex] = targetLine.replace(checkReplace, '[ ]')
} }
if (targetLine.match(uncheckedMatch)) { if (targetLine.match(uncheckedMatch)) {
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]') lines[lineIndex] = targetLine.replace(uncheckReplace, '[x]')
} }
this.refs.code.setValue(lines.join('\n')) this.refs.code.setValue(lines.join('\n'))
} }
@@ -128,7 +136,7 @@ class MarkdownSplitEditor extends React.Component {
} }
render () { render () {
const {config, value, storageKey, noteKey} = this.props const {config, value, storageKey, noteKey, linesHighlighted} = this.props
const storage = findStorage(storageKey) const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -145,23 +153,32 @@ class MarkdownSplitEditor extends React.Component {
styleName='codeEditor' styleName='codeEditor'
ref='code' ref='code'
width={this.state.codeEditorWidthInPercent + '%'} width={this.state.codeEditorWidthInPercent + '%'}
mode='GitHub Flavored Markdown' mode='Boost Flavored Markdown'
value={value} value={value}
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} fontSize={editorFontSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers} enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers} rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey} noteKey={noteKey}
onChange={this.handleOnChange.bind(this)} linesHighlighted={linesHighlighted}
onChange={(e) => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)} onScroll={this.handleScroll.bind(this)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
/> />
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} > <div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
<div styleName='slider-hitbox' /> <div styleName='slider-hitbox' />
@@ -191,6 +208,7 @@ class MarkdownSplitEditor extends React.Component {
noteKey={noteKey} noteKey={noteKey}
customCSS={config.preview.customCSS} customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS} allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
/> />
</div> </div>
) )

View File

@@ -4,6 +4,7 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import { isArray } from 'lodash' import { isArray } from 'lodash'
import invertColor from 'invert-color'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus' import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl' import styles from './NoteItem.styl'
@@ -13,27 +14,39 @@ import i18n from 'browser/lib/i18n'
/** /**
* @description Tag element component. * @description Tag element component.
* @param {string} tagName * @param {string} tagName
* @param {string} color
* @return {React.Component} * @return {React.Component}
*/ */
const TagElement = ({ tagName }) => ( const TagElement = ({ tagName, color }) => {
<span styleName='item-bottom-tagList-item' key={tagName}> const style = {}
#{tagName} if (color) {
</span> style.backgroundColor = color
) style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 })
}
return (
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
#{tagName}
</span>
)
}
/** /**
* @description Tag element list component. * @description Tag element list component.
* @param {Array|null} tags * @param {Array|null} tags
* @param {boolean} showTagsAlphabetically
* @param {Object} coloredTags
* @return {React.Component} * @return {React.Component}
*/ */
const TagElementList = tags => { const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
if (!isArray(tags)) { if (!isArray(tags)) {
return [] return []
} }
const tagElements = tags.map(tag => TagElement({ tagName: tag })) if (showTagsAlphabetically) {
return _.sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
return tagElements } else {
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
}
} }
/** /**
@@ -43,6 +56,7 @@ const TagElementList = tags => {
* @param {Function} handleNoteClick * @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu * @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart * @param {Function} handleDragStart
* @param {Object} coloredTags
* @param {string} dateDisplay * @param {string} dateDisplay
*/ */
const NoteItem = ({ const NoteItem = ({
@@ -55,7 +69,9 @@ const NoteItem = ({
pathname, pathname,
storageName, storageName,
folderName, folderName,
viewType viewType,
showTagsAlphabetically,
coloredTags
}) => ( }) => (
<div <div
styleName={isActive ? 'item--active' : 'item'} styleName={isActive ? 'item--active' : 'item'}
@@ -74,28 +90,26 @@ const NoteItem = ({
? note.title ? note.title
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>} : <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
</div> </div>
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
<div styleName='item-middle'> <div styleName='item-middle-time'>{dateDisplay}</div>
<div styleName='item-middle-time'>{dateDisplay}</div> <div styleName='item-middle-app-meta'>
<div styleName='item-middle-app-meta'> <div
<div title={
title={ viewType === 'ALL'
viewType === 'ALL' ? storageName
? storageName : viewType === 'STORAGE' ? folderName : null
: viewType === 'STORAGE' ? folderName : null }
} styleName='item-middle-app-meta-label'
styleName='item-middle-app-meta-label' >
> {viewType === 'ALL' && storageName}
{viewType === 'ALL' && storageName} {viewType === 'STORAGE' && folderName}
{viewType === 'STORAGE' && folderName}
</div>
</div> </div>
</div>} </div>
</div>
<div styleName='item-bottom'> <div styleName='item-bottom'>
<div styleName='item-bottom-tagList'> <div styleName='item-bottom-tagList'>
{note.tags.length > 0 {note.tags.length > 0
? TagElementList(note.tags) ? TagElementList(note.tags, showTagsAlphabetically, coloredTags)
: <span : <span
style={{ fontStyle: 'italic', opacity: 0.5 }} style={{ fontStyle: 'italic', opacity: 0.5 }}
styleName='item-bottom-tagList-empty' styleName='item-bottom-tagList-empty'
@@ -125,6 +139,7 @@ const NoteItem = ({
NoteItem.propTypes = { NoteItem.propTypes = {
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
dateDisplay: PropTypes.string.isRequired, dateDisplay: PropTypes.string.isRequired,
coloredTags: PropTypes.object,
note: PropTypes.shape({ note: PropTypes.shape({
storage: PropTypes.string.isRequired, storage: PropTypes.string.isRequired,
key: PropTypes.string.isRequired, key: PropTypes.string.isRequired,

View File

@@ -368,13 +368,13 @@ body[data-theme="monokai"]
.item-title .item-title
.item-title-icon .item-title-icon
.item-bottom-time .item-bottom-time
color $ui-monokai-text-color color $ui-monokai-active-color
.item-bottom-tagList-item .item-bottom-tagList-item
background-color alpha(white, 10%) background-color alpha(white, 10%)
color $ui-monokai-text-color color $ui-monokai-text-color
&:hover &:hover
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%) // background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
color #c0392b color #f92672
.item-bottom-tagList-item .item-bottom-tagList-item
background-color alpha(#fff, 20%) background-color alpha(#fff, 20%)
@@ -394,3 +394,76 @@ body[data-theme="monokai"]
.item-bottom-tagList-empty .item-bottom-tagList-empty
color $ui-inactive-text-color color $ui-inactive-text-color
vertical-align middle vertical-align middle
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.item
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 10%)
color $ui-dracula-text-color
.item-wrapper
border-color alpha($ui-dracula-button-backgroundColor, 60%)
.item--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-dracula-active-color
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dracula-button--active-backgroundColor, 60%)
color #ff79c6
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 20%)
.item-title
color $ui-inactive-text-color
.item-title-icon
color $ui-inactive-text-color
.item-title-empty
color $ui-inactive-text-color
.item-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle

View File

@@ -240,7 +240,7 @@ body[data-theme="monokai"]
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
transition 0.15s transition 0.15s
color $ui-solarized-dark-text-color color $ui-monokai-text-color
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
transition 0.15s transition 0.15s
background-color alpha(#fff, 20%) background-color alpha(#fff, 20%)
@@ -286,3 +286,67 @@ body[data-theme="monokai"]
.item-simple-right-storageName .item-simple-right-storageName
padding-left 4px padding-left 4px
opacity 0.4 opacity 0.4
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.item-simple
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
background-color alpha($ui-dracula-button-backgroundColor, 60%)
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
.item-simple--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button--active-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 20%)
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4

View File

@@ -51,4 +51,15 @@ body[data-theme="monokai"]
border none border none
background-color $ui-monokai-button-backgroundColor background-color $ui-monokai-button-backgroundColor
&:hover &:hover
color #5CB85C color #5CB85C
body[data-theme="dracula"]
.notification-area
background-color none
.notification-link
color $ui-dracula-text-color
border none
background-color $ui-dracula-button-backgroundColor
&:hover
color #ff79c6

View File

@@ -263,4 +263,46 @@ body[data-theme="monokai"]
background-color $ui-monokai-button-backgroundColor background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color color $ui-monokai-text-color
.menu-button-label .menu-button-label
color $ui-monokai-text-color color $ui-monokai-text-color
body[data-theme="dracula"]
.menu-button
&:active
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-star--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-trash--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color

View File

@@ -3,19 +3,30 @@
flex 1 flex 1
min-width 70px min-width 70px
overflow hidden overflow hidden
border-left 1px solid $ui-borderColor
border-top 1px solid $ui-borderColor
&:hover &:hover
background-color alpha($ui-button--active-backgroundColor, 20%)
.deleteButton .deleteButton
color $ui-inactive-text-color color: $ui-text-color
&:hover visibility visible
background-color darken($ui-backgroundColor, 15%) transition 0.15s
&:active .button
color white color: $ui-text-color
background-color $ui-active-color transition 0.15s
.root--active .root--active
@extend .root @extend .root
min-width 100px min-width 100px
border-bottom $ui-border background-color alpha($ui-button--active-backgroundColor, 60%)
.deleteButton
visibility visible
color: $ui-text-color
transition 0.15s
.button
font-weight bold
color: $ui-text-color
transition 0.15s
.button .button
width 100% width 100%
@@ -27,8 +38,7 @@
background-color transparent background-color transparent
transition 0.15s transition 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover color $ui-inactive-text-color
background-color $ui-button--hover-backgroundColor
.deleteButton .deleteButton
position absolute position absolute
@@ -42,6 +52,7 @@
color $ui-inactive-text-color color $ui-inactive-text-color
background-color transparent background-color transparent
border-radius 2px border-radius 2px
visibility hidden
.input .input
height 29px height 29px
@@ -50,76 +61,66 @@
width 100% width 100%
outline none outline none
body[data-theme="default"], body[data-theme="white"]
.root--active
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
body[data-theme="dark"] body[data-theme="dark"]
.root .root
color $ui-dark-text-color
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
border-top 1px solid $ui-dark-borderColor
&:hover &:hover
background-color $ui-dark-button--hover-backgroundColor background-color alpha($ui-dark-button--active-backgroundColor, 20%)
transition 0.15s
.button
color $ui-dark-text-color
transition 0.15s
.deleteButton .deleteButton
color $ui-dark-inactive-text-color color $ui-dark-text-color
&:hover transition 0.15s
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active .root--active
color $ui-dark-text-color background-color $ui-dark-button--active-backgroundColor
border-color $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
&:hover border-top 1px solid $ui-dark-borderColor
background-color $ui-dark-button--hover-backgroundColor .button
.deleteButton color $ui-dark-text-color
color $ui-dark-inactive-text-color .deleteButton
&:hover color $ui-dark-text-color
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.button .button
border none border none
color $ui-dark-text-color
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-dark-text-color
background-color $ui-dark-button--hover-backgroundColor
.input .input
background-color $ui-dark-button--hover-backgroundColor background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
transition 0.15s
.deleteButton
color alpha($ui-dark-text-color, 30%)
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
color $ui-solarized-dark-text-color
border-color $ui-dark-borderColor
&:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor
.deleteButton
color $ui-solarized-dark-text-color
&:hover
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
&:active
color $ui-solarized-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active
color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
&:hover &:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
transition 0.15s
.deleteButton .deleteButton
color $ui-solarized-dark-text-color color $ui-solarized-dark-button--active-color
&:hover transition 0.15s
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%) .button
&:active color $ui-solarized-dark-button--active-color
color $ui-solarized-dark-text-color transition 0.15s
background-color $ui-dark-button--active-backgroundColor
.root--active
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-button-backgroundColor
border-color $ui-solarized-dark-borderColor
.deleteButton
color $ui-solarized-dark-button--active-color
.button
color $ui-solarized-dark-button--active-color
.button .button
border none border none
@@ -127,13 +128,75 @@ body[data-theme="solarized-dark"]
background-color transparent background-color transparent
transition color background-color 0.15s transition color background-color 0.15s
border-left 4px solid transparent border-left 4px solid transparent
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-noteDetail-backgroundColor
.input .input
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color color $ui-solarized-dark-button--active-color
transition 0.15s
.deleteButton body[data-theme="monokai"]
color alpha($ui-solarized-dark-text-color, 30%) .root
border-color $ui-monokai-borderColor
&:hover
background-color $ui-monokai-noteDetail-backgroundColor
transition 0.15s
.deleteButton
color $ui-monokai-text-color
transition 0.15s
.button
color $ui-monokai-text-color
transition 0.15s
.root--active
color $ui-monokai-active-color
background-color $ui-monokai-button-backgroundColor
border-color $ui-monokai-borderColor
.deleteButton
color $ui-monokai-text-color
.button
color $ui-monokai-active-color
.button
border none
color $ui-inactive-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
.input
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
transition 0.15s
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
&:hover
background-color $ui-dracula-noteDetail-backgroundColor
transition 0.15s
.deleteButton
color $ui-dracula-text-color
transition 0.15s
.button
color $ui-dracula-text-color
transition 0.15s
.root--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
border-color $ui-dracula-borderColor
.deleteButton
color $ui-dracula-text-color
.button
color $ui-dracula-active-color
.button
border none
color $ui-inactive-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
.input
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color

View File

@@ -156,4 +156,23 @@ body[data-theme="monokai"]
background-color $ui-monokai-button-backgroundColor background-color $ui-monokai-button-backgroundColor
&:hover &:hover
color $ui-monokai-text-color color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor background-color $ui-monokai-button-backgroundColor
body[data-theme="dracula"]
.folderList-item
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
&:active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
&:active
background-color $ui-dracula-button-backgroundColor
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor

View File

@@ -7,18 +7,18 @@ import styles from './StorageList.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
/** /**
* @param {Array} storgaeList * @param {Array} storageList
*/ */
const StorageList = ({storageList, isFolded}) => ( const StorageList = ({storageList, isFolded}) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}> <div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
{storageList.length > 0 ? storageList : ( {storageList.length > 0 ? storageList : (
<div styleName='storgaeList-empty'>No storage mount.</div> <div styleName='storageList-empty'>No storage mount.</div>
)} )}
</div> </div>
) )
StorageList.propTypes = { StorageList.propTypes = {
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired storageList: PropTypes.arrayOf(PropTypes.element).isRequired
} }
export default CSSModules(StorageList, styles) export default CSSModules(StorageList, styles)

View File

@@ -10,12 +10,13 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {string} name * @param {string} name
* @param {Function} handleClickTagListItem * @param {Function} handleClickTagListItem
* @param {Function} handleClickNarrowToTag * @param {Function} handleClickNarrowToTag
* @param {bool} isActive * @param {boolean} isActive
* @param {bool} isRelated * @param {boolean} isRelated
* @param {string} bgColor tab backgroundColor
*/ */
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => ( const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => (
<div styleName='tagList-itemContainer'> <div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
{isRelated {isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}> ? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} /> <i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
@@ -23,9 +24,10 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isAc
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} /> : <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
} }
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}> <button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-color' style={{backgroundColor: color || 'transparent'}} />
<span styleName='tagList-item-name'> <span styleName='tagList-item-name'>
{`# ${name}`} {`# ${name}`}
<span styleName='tagList-item-count'>{count}</span> <span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
</span> </span>
</button> </button>
</div> </div>
@@ -33,7 +35,8 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isAc
TagListItem.propTypes = { TagListItem.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
handleClickTagListItem: PropTypes.func.isRequired handleClickTagListItem: PropTypes.func.isRequired,
color: PropTypes.string
} }
export default CSSModules(TagListItem, styles) export default CSSModules(TagListItem, styles)

View File

@@ -71,6 +71,11 @@
padding-right 15px padding-right 15px
font-size 13px font-size 13px
.tagList-item-color
height 26px
width 3px
display inline-block
body[data-theme="white"] body[data-theme="white"]
.tagList-item .tagList-item
color $ui-inactive-text-color color $ui-inactive-text-color

View File

@@ -12,7 +12,7 @@ import styles from './TodoListPercentage.styl'
*/ */
const TodoListPercentage = ({ const TodoListPercentage = ({
percentageOfTodo percentageOfTodo, onClearCheckboxClick
}) => ( }) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}> <div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}> <div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
@@ -20,11 +20,15 @@ const TodoListPercentage = ({
<p styleName='percentageText'>{percentageOfTodo}%</p> <p styleName='percentageText'>{percentageOfTodo}%</p>
</div> </div>
</div> </div>
<div styleName='todoClear'>
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
</div>
</div> </div>
) )
TodoListPercentage.propTypes = { TodoListPercentage.propTypes = {
percentageOfTodo: PropTypes.number.isRequired percentageOfTodo: PropTypes.number.isRequired,
onClearCheckboxClick: PropTypes.func.isRequired
} }
export default CSSModules(TodoListPercentage, styles) export default CSSModules(TodoListPercentage, styles)

View File

@@ -1,4 +1,5 @@
.percentageBar .percentageBar
display: flex
position absolute position absolute
top 72px top 72px
right 0px right 0px
@@ -30,6 +31,20 @@
color #f4f4f4 color #f4f4f4
font-weight 600 font-weight 600
.todoClear
display flex
justify-content: flex-end
position absolute
z-index 120
width 100%
height 100%
padding 2px 10px
.todoClearText
color #f4f4f4
cursor pointer
font-weight 500
body[data-theme="dark"] body[data-theme="dark"]
.percentageBar .percentageBar
background-color #444444 background-color #444444
@@ -39,7 +54,10 @@ body[data-theme="dark"]
.percentageText .percentageText
color $ui-dark-text-color color $ui-dark-text-color
.todoClearText
color $ui-dark-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.percentageBar .percentageBar
background-color #002b36 background-color #002b36
@@ -50,12 +68,28 @@ body[data-theme="solarized-dark"]
.percentageText .percentageText
color #fdf6e3 color #fdf6e3
.todoClearText
color #fdf6e3
body[data-theme="monokai"] body[data-theme="monokai"]
.percentageBar .percentageBar
background-color #f92672 background-color: $ui-monokai-borderColor
.progressBar .progressBar
background-color: #373831 background-color $ui-monokai-active-color
.percentageText .percentageText
color #fdf6e3 color $ui-monokai-text-color
body[data-theme="dracula"]
.percentageBar
background-color $ui-dracula-borderColor
.progressBar
background-color: $ui-dracula-active-color
.percentageText
color $ui-dracula-text-color
.percentageText
color $ui-dracula-text-color

View File

@@ -55,11 +55,14 @@ body
line-height 1.6 line-height 1.6
overflow-x hidden overflow-x hidden
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
// do not allow display line breaks
.katex-display > .katex
white-space nowrap
// allow inline line breaks
.katex .katex
font 400 1.2em 'KaTeX_Main'
line-height 1.2em
white-space initial white-space initial
text-indent 0 .katex .katex-html
display inline-flex
.katex .mfrac>.vlist>span:nth-child(2) .katex .mfrac>.vlist>span:nth-child(2)
top 0 !important top 0 !important
.katex-error .katex-error
@@ -80,6 +83,9 @@ li
&.checked &.checked
text-decoration line-through text-decoration line-through
opacity 0.5 opacity 0.5
&.taskListItem.checked
text-decoration line-through
opacity 0.5
div.math-rendered div.math-rendered
text-align center text-align center
.math-failed .math-failed
@@ -159,6 +165,7 @@ p
white-space pre-line white-space pre-line
word-wrap break-word word-wrap break-word
img img
cursor zoom-in
max-width 100% max-width 100%
strong, b strong, b
font-weight bold font-weight bold
@@ -180,6 +187,10 @@ ul
display list-item display list-item
&.taskListItem &.taskListItem
list-style none list-style none
&>input
margin-left -1.6em
&>p
margin-left -1.8em
p p
margin 0 margin 0
&>li>ul, &>li>ol &>li>ul, &>li>ol
@@ -206,41 +217,39 @@ code
text-decoration none text-decoration none
margin-right 2px margin-right 2px
pre pre
padding 0.5em !important padding 0.5rem !important
border solid 1px #D1D1D1 border solid 1px #D1D1D1
border-radius 5px border-radius 5px
overflow-x auto overflow-x auto
margin 0 0 1em margin 0 0 1rem
display flex display flex
line-height 1.4em line-height 1.4em
&.flowchart, &.sequence, &.chart
display flex
justify-content center
background-color white
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
code code
background-color inherit background-color inherit
margin 0 margin 0
padding 0 padding 0
border none border none
border-radius 0 border-radius 0
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
&.mermaid svg
max-width 100% !important
&>span.filename &>span.filename
width 100% margin -0.5rem 100% 0.5rem -0.5rem
border-radius: 5px 0px 0px 0px padding 0.125rem 0.375rem
margin -8px 100% 8px -8px
padding 0px 6px
background-color #777; background-color #777;
color white color white
&:empty
display none
&>span.lineNumber &>span.lineNumber
display none display none
font-size 1em font-size 1em
padding 0.5em 0 padding 0.5rem 0
margin -0.5em 0.5em -0.5em -0.5em margin -0.5rem 0.5rem -0.5rem -0.5rem
border-right 1px solid border-right 1px solid
text-align right text-align right
border-top-left-radius 4px border-top-left-radius 4px
@@ -257,6 +266,7 @@ table
display block display block
width 100% width 100%
margin 0 0 1em margin 0 0 1em
overflow-x auto
thead thead
tr tr
background-color tableHeadBgColor background-color tableHeadBgColor
@@ -360,7 +370,7 @@ for name, val in admonition_types
.admonition.{name} .admonition.{name}
@extend $admonition @extend $admonition
border-left-color: val[color] border-left-color: val[color]
.admonition.{name}>.admonition-title .admonition.{name}>.admonition-title
@extend $admonition-title @extend $admonition-title
border-bottom-color: .1rem solid rgba(val[color], 0.2) border-bottom-color: .1rem solid rgba(val[color], 0.2)
@@ -371,6 +381,69 @@ for name, val in admonition_types
color: val[color] color: val[color]
content: val[icon] content: val[icon]
dl
margin 2rem 0
padding 0
display flex
width 100%
flex-wrap wrap
align-items flex-start
border-bottom 1px solid borderColor
background-color tableHeadBgColor
dt
border-top 1px solid borderColor
font-weight bold
text-align right
overflow hidden
flex-basis 20%
padding 0.4rem 0.9rem
box-sizing border-box
dd
border-top 1px solid borderColor
flex-basis 80%
padding 0.4rem 0.9rem
min-height 2.5rem
background-color $ui-noteDetail-backgroundColor
box-sizing border-box
dd + dd
margin-left 20%
pre.fence
flex-wrap wrap
.chart, .flowchart, .mermaid, .sequence
display flex
justify-content center
background-color white
max-width 100%
flex-grow 1
canvas, svg
max-width 100% !important
.gallery
width 100%
height 50vh
.carousel
.carousel-main img
min-width auto
max-width 100%
min-height auto
max-height 100%
.carousel-footer::-webkit-scrollbar-corner
background-color transparent
.carousel-main, .carousel-footer
background-color $ui-noteDetail-backgroundColor
.prev, .next
color $ui-text-color
background-color $ui-tag-backgroundColor
themeDarkBackground = darken(#21252B, 10%) themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9 themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%) themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -421,6 +494,22 @@ body[data-theme="dark"]
kbd kbd
background-color themeDarkBorder background-color themeDarkBorder
color themeDarkText color themeDarkText
dl
border-color themeDarkBorder
background-color themeDarkTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color themeDarkPreview
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-dark-noteDetail-backgroundColor
.prev, .next
color $ui-dark-text-color
background-color $ui-dark-tag-backgroundColor
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%) themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
@@ -448,6 +537,22 @@ body[data-theme="solarized-dark"]
border-color themeSolarizedDarkTableBorder border-color themeSolarizedDarkTableBorder
&:last-child &:last-child
border-right solid 1px themeSolarizedDarkTableBorder border-right solid 1px themeSolarizedDarkTableBorder
dl
border-color themeDarkBorder
background-color themeSolarizedDarkTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-solarized-dark-noteDetail-backgroundColor
.prev, .next
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-button-backgroundColor
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%) themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
@@ -476,4 +581,67 @@ body[data-theme="monokai"]
&:last-child &:last-child
border-right solid 1px themeMonokaiTableBorder border-right solid 1px themeMonokaiTableBorder
kbd kbd
background-color themeDarkBackground background-color themeDarkBackground
dl
border-color themeDarkBorder
background-color themeMonokaiTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-monokai-noteDetail-backgroundColor
.prev, .next
color $ui-monokai-button--active-color
background-color $ui-monokai-button-backgroundColor
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
themeDraculaTableHead = themeDraculaTableEven
themeDraculaTableBorder = themeDarkBorder
body[data-theme="dracula"]
color $ui-dracula-text-color
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
table
thead
tr
background-color themeDraculaTableHead
th
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeDraculaTableOdd
tr:nth-child(2n)
background-color themeDraculaTableEven
td
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
kbd
background-color themeDarkBackground
dl
border-color themeDarkBorder
background-color themeDraculaTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-dracula-noteDetail-backgroundColor
.prev, .next
color $ui-dracula-button--active-color
background-color $ui-dracula-button-backgroundColor

View File

@@ -2,8 +2,8 @@ import mermaidAPI from 'mermaid'
// fixes bad styling in the mermaid dark theme // fixes bad styling in the mermaid dark theme
const darkThemeStyling = ` const darkThemeStyling = `
.loopText tspan { .loopText tspan {
fill: white; fill: white;
}` }`
function getRandomInt (min, max) { function getRandomInt (min, max) {
@@ -11,9 +11,9 @@ function getRandomInt (min, max) {
} }
function getId () { function getId () {
var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var id = 'm-' let id = 'm-'
for (var i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
id += pool[getRandomInt(0, 16)] id += pool[getRandomInt(0, 16)]
} }
return id return id
@@ -21,16 +21,20 @@ function getId () {
function render (element, content, theme) { function render (element, content, theme) {
try { try {
let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' const height = element.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') {
element.style.height = height.value + 'vh'
}
const isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula'
mermaidAPI.initialize({ mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default', theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : '' themeCSS: isDarkTheme ? darkThemeStyling : '',
useMaxWidth: false
}) })
mermaidAPI.render(getId(), content, (svgGraph) => { mermaidAPI.render(getId(), content, (svgGraph) => {
element.innerHTML = svgGraph element.innerHTML = svgGraph
}) })
} catch (e) { } catch (e) {
console.error(e)
element.className = 'mermaid-error' element.className = 'mermaid-error'
element.innerHTML = 'mermaid diagram parse error: ' + e.message element.innerHTML = 'mermaid diagram parse error: ' + e.message
} }

View File

@@ -48,8 +48,12 @@ const languages = [
locale: 'pl' locale: 'pl'
}, },
{ {
name: 'Portuguese', name: 'Portuguese (PT-BR)',
locale: 'pt' locale: 'pt-BR'
},
{
name: 'Portuguese (PT-PT)',
locale: 'pt-PT'
}, },
{ {
name: 'Russian', name: 'Russian',
@@ -61,6 +65,9 @@ const languages = [
}, { }, {
name: 'Turkish', name: 'Turkish',
locale: 'tr' locale: 'tr'
}, {
name: 'Thai',
locale: 'th'
} }
] ]

View File

@@ -1,53 +1,113 @@
import { Point } from '@susisu/mte-kernel' import { Point } from '@susisu/mte-kernel'
export default class TextEditorInterface { export default class TextEditorInterface {
constructor (editor) { constructor (editor) {
this.editor = editor this.editor = editor
} this.doc = editor.getDoc()
this.transaction = false
getCursorPosition () { }
const pos = this.editor.getCursor()
return new Point(pos.line, pos.ch) getCursorPosition () {
} const { line, ch } = this.doc.getCursor()
return new Point(line, ch)
setCursorPosition (pos) { }
this.editor.setCursor({line: pos.row, ch: pos.column})
} setCursorPosition (pos) {
this.doc.setCursor({
setSelectionRange (range) { line: pos.row,
this.editor.setSelection({ ch: pos.column
anchor: {line: range.start.row, ch: range.start.column}, })
head: {line: range.end.row, ch: range.end.column} }
})
} setSelectionRange (range) {
this.doc.setSelection(
getLastRow () { { line: range.start.row, ch: range.start.column },
return this.editor.lastLine() { line: range.end.row, ch: range.end.column }
} )
}
acceptsTableEdit (row) {
return true getLastRow () {
} return this.doc.lineCount() - 1
}
getLine (row) {
return this.editor.getLine(row) acceptsTableEdit () {
} return true
}
insertLine (row, line) {
this.editor.replaceRange(line, {line: row, ch: 0}) getLine (row) {
} return this.doc.getLine(row)
}
deleteLine (row) {
this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length}) insertLine (row, line) {
} const lastRow = this.getLastRow()
if (row > lastRow) {
replaceLines (startRow, endRow, lines) { const lastLine = this.getLine(lastRow)
endRow-- // because endRow is a first line after a table. this.doc.replaceRange(
const endRowCh = this.editor.getLine(endRow).length '\n' + line,
this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh}) { line: lastRow, ch: lastLine.length },
} { line: lastRow, ch: lastLine.length }
)
transact (func) { } else {
func() this.doc.replaceRange(
} line + '\n',
} { line: row, ch: 0 },
{ line: row, ch: 0 }
)
}
}
deleteLine (row) {
const lastRow = this.getLastRow()
if (row >= lastRow) {
if (lastRow > 0) {
const preLastLine = this.getLine(lastRow - 1)
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow - 1, ch: preLastLine.length },
{ line: lastRow, ch: lastLine.length }
)
} else {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
}
} else {
this.doc.replaceRange(
'',
{ line: row, ch: 0 },
{ line: row + 1, ch: 0 }
)
}
}
replaceLines (startRow, endRow, lines) {
const lastRow = this.getLastRow()
if (endRow > lastRow) {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
lines.join('\n'),
{ line: startRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
} else {
this.doc.replaceRange(
lines.join('\n') + '\n',
{ line: startRow, ch: 0 },
{ line: endRow, ch: 0 }
)
}
}
transact (func) {
this.transaction = true
func()
this.transaction = false
if (this.onDidFinishTransaction) {
this.onDidFinishTransaction.call(undefined)
}
}
}

View File

@@ -0,0 +1,65 @@
const {remote} = require('electron')
const {Menu} = remote.require('electron')
const spellcheck = require('./spellcheck')
/**
* Creates the context menu that is shown when there is a right click in the editor of a (not-snippet) note.
* If the word is does not contains a spelling error (determined by the 'error style'), no suggestions for corrections are requested
* => they are not visible in the context menu
* @param editor CodeMirror editor
* @param {MouseEvent} event that has triggered the creation of the context menu
* @returns {Electron.Menu} The created electron context menu
*/
const buildEditorContextMenu = function (editor, event) {
if (editor == null || event == null || event.pageX == null || event.pageY == null) {
return null
}
const cursor = editor.coordsChar({left: event.pageX, top: event.pageY})
const wordRange = editor.findWordAt(cursor)
const word = editor.getRange(wordRange.anchor, wordRange.head)
const existingMarks = editor.findMarks(wordRange.anchor, wordRange.head) || []
let isMisspelled = false
for (const mark of existingMarks) {
if (mark.className === spellcheck.getCSSClassName()) {
isMisspelled = true
break
}
}
let suggestion = []
if (isMisspelled) {
suggestion = spellcheck.getSpellingSuggestion(word)
}
const selection = {
isMisspelled: isMisspelled,
spellingSuggestions: suggestion
}
const template = [{
role: 'cut'
}, {
role: 'copy'
}, {
role: 'paste'
}, {
role: 'selectall'
}]
if (selection.isMisspelled) {
const suggestions = selection.spellingSuggestions
template.unshift.apply(template, suggestions.map(function (suggestion) {
return {
label: suggestion,
click: function (suggestion) {
if (editor != null) {
editor.replaceRange(suggestion.label, wordRange.anchor, wordRange.head)
}
}
}
}).concat({
type: 'separator'
}))
}
return Menu.buildFromTemplate(template)
}
module.exports = buildEditorContextMenu

View File

@@ -1,19 +1,37 @@
export function findNoteTitle (value) { export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
const splitted = value.split('\n') const splitted = value.split('\n')
let title = null let title = null
let isInsideCodeBlock = false let isInsideCodeBlock = false
splitted.some((line, index) => { if (splitted[0] === '---') {
const trimmedLine = line.trim() let line = 0
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim() while (++line < splitted.length) {
if (trimmedLine.match('```')) { if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
isInsideCodeBlock = !isInsideCodeBlock title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
break
}
if (splitted[line] === '---') {
splitted.splice(0, line + 1)
break
}
} }
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) { }
title = trimmedLine
return true if (title === null) {
} splitted.some((line, index) => {
}) const trimmedLine = line.trim()
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (trimmedLine.match('```')) {
isInsideCodeBlock = !isInsideCodeBlock
}
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
title = trimmedLine
return true
}
})
}
if (title === null) { if (title === null) {
title = '' title = ''

View File

@@ -0,0 +1,232 @@
'use strict'
module.exports = function definitionListPlugin (md) {
var isSpace = md.utils.isSpace
// Search `[:~][\n ]`, returns next pos after marker on success
// or -1 on fail.
function skipMarker (state, line) {
let start = state.bMarks[line] + state.tShift[line]
const max = state.eMarks[line]
if (start >= max) { return -1 }
// Check bullet
const marker = state.src.charCodeAt(start++)
if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 }
const pos = state.skipSpaces(start)
// require space after ":"
if (start === pos) { return -1 }
return start
}
function markTightParagraphs (state, idx) {
const level = state.level + 2
let i
let l
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
state.tokens[i + 2].hidden = true
state.tokens[i].hidden = true
i += 2
}
}
}
function deflist (state, startLine, endLine, silent) {
var ch,
contentStart,
ddLine,
dtLine,
itemLines,
listLines,
listTokIdx,
max,
newEndLine,
nextLine,
offset,
oldDDIndent,
oldIndent,
oldLineMax,
oldParentType,
oldSCount,
oldTShift,
oldTight,
pos,
prevEmptyEnd,
tight,
token
if (silent) {
// quirk: validation mode validates a dd block only, not a whole deflist
if (state.ddIndent < 0) { return false }
return skipMarker(state, startLine) >= 0
}
nextLine = startLine + 1
if (nextLine >= endLine) { return false }
if (state.isEmpty(nextLine)) {
nextLine++
if (nextLine >= endLine) { return false }
}
if (state.sCount[nextLine] < state.blkIndent) { return false }
contentStart = skipMarker(state, nextLine)
if (contentStart < 0) { return false }
// Start list
listTokIdx = state.tokens.length
tight = true
token = state.push('dl_open', 'dl', 1)
token.map = listLines = [ startLine, 0 ]
//
// Iterate list items
//
dtLine = startLine
ddLine = nextLine
// One definition list can contain multiple DTs,
// and one DT can be followed by multiple DDs.
//
// Thus, there is two loops here, and label is
// needed to break out of the second one
//
/* eslint no-labels:0,block-scoped-var:0 */
OUTER:
for (;;) {
prevEmptyEnd = false
token = state.push('dt_open', 'dt', 1)
token.map = [ dtLine, dtLine ]
token = state.push('inline', '', 0)
token.map = [ dtLine, dtLine ]
token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim()
token.children = []
token = state.push('dt_close', 'dt', -1)
for (;;) {
token = state.push('dd_open', 'dd', 1)
token.map = itemLines = [ ddLine, 0 ]
pos = contentStart
max = state.eMarks[ddLine]
offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine])
while (pos < max) {
ch = state.src.charCodeAt(pos)
if (isSpace(ch)) {
if (ch === 0x09) {
offset += 4 - offset % 4
} else {
offset++
}
} else {
break
}
pos++
}
contentStart = pos
oldTight = state.tight
oldDDIndent = state.ddIndent
oldIndent = state.blkIndent
oldTShift = state.tShift[ddLine]
oldSCount = state.sCount[ddLine]
oldParentType = state.parentType
state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
state.sCount[ddLine] = offset
state.tight = true
state.parentType = 'deflist'
newEndLine = ddLine
while (++newEndLine < endLine && (state.sCount[newEndLine] >= state.sCount[ddLine] || state.isEmpty(newEndLine))) {
}
oldLineMax = state.lineMax
state.lineMax = newEndLine
state.md.block.tokenize(state, ddLine, newEndLine, true)
state.lineMax = oldLineMax
// If any of list item is tight, mark list as tight
if (!state.tight || prevEmptyEnd) {
tight = false
}
// Item become loose if finish with empty line,
// but we should filter last element, because it means list finish
prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1)
state.tShift[ddLine] = oldTShift
state.sCount[ddLine] = oldSCount
state.tight = oldTight
state.parentType = oldParentType
state.blkIndent = oldIndent
state.ddIndent = oldDDIndent
token = state.push('dd_close', 'dd', -1)
itemLines[1] = nextLine = state.line
if (nextLine >= endLine) { break OUTER }
if (state.sCount[nextLine] < state.blkIndent) { break OUTER }
contentStart = skipMarker(state, nextLine)
if (contentStart < 0) { break }
ddLine = nextLine
// go to the next loop iteration:
// insert DD tag and repeat checking
}
if (nextLine >= endLine) { break }
dtLine = nextLine
if (state.isEmpty(dtLine)) { break }
if (state.sCount[dtLine] < state.blkIndent) { break }
ddLine = dtLine + 1
if (ddLine >= endLine) { break }
if (state.isEmpty(ddLine)) { ddLine++ }
if (ddLine >= endLine) { break }
if (state.sCount[ddLine] < state.blkIndent) { break }
contentStart = skipMarker(state, ddLine)
if (contentStart < 0) { break }
// go to the next loop iteration:
// insert DT and DD tags and repeat checking
}
// Finilize list
token = state.push('dl_close', 'dl', -1)
listLines[1] = nextLine
state.line = nextLine
// mark paragraphs tight if needed
if (tight) {
markTightParagraphs(state, listTokIdx)
}
return true
}
md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] })
}

View File

@@ -0,0 +1,136 @@
'use strict'
module.exports = function (md, renderers, defaultRenderer) {
const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
function fence (state, startLine, endLine, silent) {
let pos = state.bMarks[startLine] + state.tShift[startLine]
let max = state.eMarks[startLine]
if (state.sCount[startLine] - state.blkIndent >= 4 || pos + 3 > max) {
return false
}
const marker = state.src.charCodeAt(pos)
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
return false
}
let mem = pos
pos = state.skipChars(pos, marker)
let len = pos - mem
if (len < 3) {
return false
}
const markup = state.src.slice(mem, pos)
const params = state.src.slice(pos, max)
if (silent) {
return true
}
let nextLine = startLine
let haveEndMarker = false
while (true) {
nextLine++
if (nextLine >= endLine) {
break
}
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
max = state.eMarks[nextLine]
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
break
}
if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) {
continue
}
pos = state.skipChars(pos, marker)
if (pos - mem < len) {
continue
}
pos = state.skipSpaces(pos)
if (pos >= max) {
haveEndMarker = true
break
}
}
len = state.sCount[startLine]
state.line = nextLine + (haveEndMarker ? 1 : 0)
const parameters = {}
let langType = ''
let fileName = ''
let firstLineNumber = 1
let match = paramsRE.exec(params)
if (match) {
if (match[1]) {
langType = match[1]
}
if (match[3]) {
fileName = match[3]
}
if (match[4]) {
firstLineNumber = parseInt(match[4], 10)
}
if (match[2]) {
const params = match[2]
const regex = /(\w[-\w]*)(?:=(?:'(.*?[^\\])?'|"(.*?[^\\])?"|([^'"][^\s]*)))?/g
let name, value
while ((match = regex.exec(params))) {
name = match[1]
value = match[2] || match[3] || match[4] || null
const height = /^(\d+)h$/.exec(name)
if (height && !value) {
parameters.height = height[1]
} else {
parameters[name] = value
}
}
}
}
let token
if (renderers[langType]) {
token = state.push(`${langType}_fence`, 'div', 0)
} else {
token = state.push('_fence', 'code', 0)
}
token.langType = langType
token.fileName = fileName
token.firstLineNumber = firstLineNumber
token.parameters = parameters
token.content = state.getLines(startLine + 1, nextLine, len, true)
token.markup = markup
token.map = [startLine, state.line]
return true
}
md.block.ruler.before('fence', '_fence', fence, {
alt: ['paragraph', 'reference', 'blockquote', 'list']
})
for (const name in renderers) {
md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index])
}
if (defaultRenderer) {
md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
}
}

View File

@@ -0,0 +1,24 @@
'use strict'
module.exports = function frontMatterPlugin (md) {
function frontmatter (state, startLine, endLine, silent) {
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
return false
}
let line = 0
while (++line < state.lineMax) {
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
state.line = line + 1
return true
}
}
return false
}
md.block.ruler.before('table', 'frontmatter', frontmatter, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
}

View File

@@ -2,6 +2,7 @@
import sanitizeHtml from 'sanitize-html' import sanitizeHtml from 'sanitize-html'
import { escapeHtmlCharacters } from './utils' import { escapeHtmlCharacters } from './utils'
import url from 'url'
module.exports = function sanitizePlugin (md, options) { module.exports = function sanitizePlugin (md, options) {
options = options || {} options = options || {}
@@ -14,7 +15,7 @@ module.exports = function sanitizePlugin (md, options) {
options options
) )
} }
if (state.tokens[tokenIdx].type === 'fence') { if (state.tokens[tokenIdx].type === '_fence') {
// escapeHtmlCharacters has better performance // escapeHtmlCharacters has better performance
state.tokens[tokenIdx].content = escapeHtmlCharacters( state.tokens[tokenIdx].content = escapeHtmlCharacters(
state.tokens[tokenIdx].content, state.tokens[tokenIdx].content,
@@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) {
const inlineTokens = state.tokens[tokenIdx].children const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) { for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
if (inlineTokens[childIdx].type === 'html_inline') { if (inlineTokens[childIdx].type === 'html_inline') {
inlineTokens[childIdx].content = sanitizeHtml( inlineTokens[childIdx].content = sanitizeInline(
inlineTokens[childIdx].content, inlineTokens[childIdx].content,
options options
) )
@@ -35,3 +36,89 @@ module.exports = function sanitizePlugin (md, options) {
} }
}) })
} }
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
function sanitizeInline (html, options) {
let match = tagRegex.exec(html)
if (!match) {
return ''
}
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
if (match[1] !== undefined) {
// opening tag
const tag = match[1].toLowerCase()
if (allowedTags.indexOf(tag) === -1) {
return ''
}
const attributes = match[2]
let attrs = ''
let name
let value
while ((match = attributesRegex.exec(attributes))) {
name = match[1].toLowerCase()
value = match[3]
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
continue
}
}
attrs += ` ${name}`
if (match[2]) {
attrs += `="${value}"`
}
}
}
if (selfClosing.indexOf(tag) === -1) {
return '<' + tag + attrs + '>'
} else {
return '<' + tag + attrs + ' />'
}
} else {
// closing tag
if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) {
return html
} else {
return ''
}
}
}
function naughtyHRef (href, options) {
// href = href.replace(/[\x00-\x20]+/g, '')
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
const matches = href.match(/^([a-zA-Z]+)\:/)
if (!matches) {
if (href.match(/^[\/\\]{2}/)) {
return !options.allowProtocolRelative
}
// No scheme
return false
}
const scheme = matches[1].toLowerCase()
return options.allowedSchemes.indexOf(scheme) === -1
}
function naughtyIFrame (src, options) {
try {
const parsed = url.parse(src, false, true)
return options.allowedIframeHostnames.index(parsed.hostname) === -1
} catch (e) {
return true
}
}

View File

@@ -0,0 +1,98 @@
/**
* @fileoverview Markdown table of contents generator
*/
import { EOL } from 'os'
import toc from 'markdown-toc'
import mdlink from 'markdown-link'
import slugify from './slugify'
const hasProp = Object.prototype.hasOwnProperty
/**
* From @enyaxu/markdown-it-anchor
*/
function uniqueSlug (slug, slugs, opts) {
let uniq = slug
let i = opts.uniqueSlugStartIndex
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
slugs[uniq] = true
return uniq
}
function linkify (token) {
token.content = mdlink(token.content, '#' + token.slug)
return token
}
const TOC_MARKER_START = '<!-- toc -->'
const TOC_MARKER_END = '<!-- tocstop -->'
/**
* Takes care of proper updating given editor with TOC.
* If TOC doesn't exit in the editor, it's inserted at current caret position.
* Otherwise,TOC is updated in place.
* @param editor CodeMirror editor to be updated with TOC
*/
export function generateInEditor (editor) {
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
function tocExistsInEditor () {
return tocRegex.test(editor.getValue())
}
function updateExistingToc () {
const toc = generate(editor.getValue())
const search = editor.getSearchCursor(tocRegex)
while (search.findNext()) {
search.replace(toc)
}
}
function addTocAtCursorPosition () {
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
}
if (tocExistsInEditor()) {
updateExistingToc()
} else {
addTocAtCursorPosition()
}
}
/**
* Generates MD TOC based on MD document passed as string.
* @param markdownText MD document
* @returns generatedTOC String containing generated TOC
*/
export function generate (markdownText) {
const slugs = {}
const opts = {
uniqueSlugStartIndex: 1
}
const result = toc(markdownText, {
slugify: title => {
return uniqueSlug(slugify(title), slugs, opts)
},
linkify: false
})
const md = toc.bullets(result.json.map(linkify), {
highest: result.highest
})
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
}
function wrapTocWithEol (toc, editor) {
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
return leftWrap + toc + rightWrap
}
export default {
generate,
generateInEditor
}

View File

@@ -27,32 +27,6 @@ class Markdown {
html: true, html: true,
xhtmlOut: true, xhtmlOut: true,
breaks: config.preview.breaks, breaks: config.preview.breaks,
highlight: function (str, lang) {
const delimiter = ':'
const langInfo = lang.split(delimiter)
const langType = langInfo[0]
const fileName = langInfo[1] || ''
const firstLineNumber = parseInt(langInfo[2], 10)
if (langType === 'flowchart') {
return `<pre class="flowchart">${str}</pre>`
}
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
if (langType === 'chart') {
return `<pre class="chart">${str}</pre>`
}
if (langType === 'mermaid') {
return `<pre class="mermaid">${str}</pre>`
}
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
},
sanitize: 'STRICT' sanitize: 'STRICT'
} }
@@ -105,7 +79,11 @@ class Markdown {
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'], 'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
'input': ['type', 'id', 'checked'] 'input': ['type', 'id', 'checked']
}, },
allowedIframeHostnames: ['www.youtube.com'] allowedIframeHostnames: ['www.youtube.com'],
selfClosing: [ 'img', 'br', 'hr', 'input' ],
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
allowProtocolRelative: true
}) })
} }
@@ -139,16 +117,68 @@ class Markdown {
this.md.use(require('markdown-it-imsize')) this.md.use(require('markdown-it-imsize'))
this.md.use(require('markdown-it-footnote')) this.md.use(require('markdown-it-footnote'))
this.md.use(require('markdown-it-multimd-table')) this.md.use(require('markdown-it-multimd-table'))
this.md.use(require('markdown-it-named-headers'), { this.md.use(require('@enyaxu/markdown-it-anchor'), {
slugify: (header) => { slugify: require('./slugify')
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
}
}) })
this.md.use(require('markdown-it-kbd')) this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition')) this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
this.md.use(require('markdown-it-abbr'))
this.md.use(require('markdown-it-sub'))
this.md.use(require('markdown-it-sup'))
this.md.use(require('./markdown-it-deflist'))
this.md.use(require('./markdown-it-frontmatter'))
this.md.use(require('./markdown-it-fence'), {
chart: token => {
if (token.parameters.hasOwnProperty('yaml')) {
token.parameters.format = 'yaml'
}
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
</pre>`
},
flowchart: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
gallery: token => {
const content = token.content.split('\n').slice(0, -1).map(line => {
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
if (match) {
return match[1]
} else {
return line
}
}).join('\n')
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="gallery" data-autoplay="${token.parameters.autoplay}" data-height="${token.parameters.height}">${content}</div>
</pre>`
},
mermaid: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
},
sequence: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
</pre>`
}
}, token => {
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
${createGutter(token.content, token.firstLineNumber)}
<code class="${token.langType}">${token.content}</code>
</pre>`
})
const deflate = require('markdown-it-plantuml/lib/deflate') const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', { this.md.use(require('markdown-it-plantuml'), '', {
@@ -221,7 +251,11 @@ class Markdown {
if (!liToken.attrs) { if (!liToken.attrs) {
liToken.attrs = [] liToken.attrs = []
} }
liToken.attrs.push(['class', 'taskListItem']) if (config.preview.lineThroughCheckbox) {
liToken.attrs.push(['class', `taskListItem${match[1] !== ' ' ? ' checked' : ''}`])
} else {
liToken.attrs.push(['class', 'taskListItem'])
}
} }
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>` content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
} }
@@ -246,9 +280,12 @@ class Markdown {
this.md.renderer.render = (tokens, options, env) => { this.md.renderer.render = (tokens, options, env) => {
tokens.forEach((token) => { tokens.forEach((token) => {
switch (token.type) { switch (token.type) {
case 'heading_open':
case 'paragraph_open':
case 'blockquote_open': case 'blockquote_open':
case 'dd_open':
case 'dt_open':
case 'heading_open':
case 'list_item_open':
case 'paragraph_open':
case 'table_open': case 'table_open':
token.attrPush(['data-line', token.map[0]]) token.attrPush(['data-line', token.map[0]])
} }

View File

@@ -22,7 +22,7 @@ export function strip (input) {
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1') .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
.replace(/>/g, '') .replace(/>/g, '')
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '') .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1') .replace(/^#{1,6}\s*/gm, '')
.replace(/(`{3,})(.*?)\1/gm, '$2') .replace(/(`{3,})(.*?)\1/gm, '$2')
.replace(/^-{3,}\s*$/g, '') .replace(/^-{3,}\s*$/g, '')
.replace(/`(.+?)`/g, '$1') .replace(/`(.+?)`/g, '$1')

80
browser/lib/newNote.js Normal file
View File

@@ -0,0 +1,80 @@
import { hashHistory } from 'react-router'
import dataApi from 'browser/main/lib/dataApi'
import ee from 'browser/main/lib/eventEmitter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let tags = []
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
tags = params.tagname.split(' ')
}
return dataApi
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
tags,
content: '',
linesHighlighted: []
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}
export function createSnippetNote (storage, folder, dispatch, location, params, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let tags = []
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
tags = params.tagname.split(' ')
}
const defaultLanguage = config.editor.snippetDefaultLanguage === 'Auto Detect' ? null : config.editor.snippetDefaultLanguage
return dataApi
.createNote(storage, {
type: 'SNIPPET_NOTE',
folder: folder,
title: '',
tags,
description: '',
snippets: [
{
name: '',
mode: defaultLanguage,
content: '',
linesHighlighted: []
}
]
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}

17
browser/lib/slugify.js Normal file
View File

@@ -0,0 +1,17 @@
import diacritics from 'diacritics-map'
function replaceDiacritics (str) {
return str.replace(/[À-ž]/g, function (ch) {
return diacritics[ch] || ch
})
}
module.exports = function slugify (title) {
let slug = title.trim()
slug = replaceDiacritics(slug)
slug = slug.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
return encodeURI(slug).replace(/\-+$/, '')
}

232
browser/lib/spellcheck.js Normal file
View File

@@ -0,0 +1,232 @@
import styles from '../components/CodeEditor.styl'
import i18n from 'browser/lib/i18n'
const Typo = require('typo-js')
const _ = require('lodash')
const CSS_ERROR_CLASS = 'codeEditor-typo'
const SPELLCHECK_DISABLED = 'NONE'
const DICTIONARY_PATH = '../dictionaries'
const MILLISECONDS_TILL_LIVECHECK = 500
let dictionary = null
let self
function getAvailableDictionaries () {
return [
{label: i18n.__('Disabled'), value: SPELLCHECK_DISABLED},
{label: i18n.__('English'), value: 'en_GB'},
{label: i18n.__('German'), value: 'de_DE'},
{label: i18n.__('French'), value: 'fr_FR'}
]
}
/**
* Only to be used in the tests :)
*/
function setDictionaryForTestsOnly (newDictionary) {
dictionary = newDictionary
}
/**
* @description Initializes the spellcheck. It removes all existing marks of the current editor.
* If a language was given (i.e. lang !== this.SPELLCHECK_DISABLED) it will load the stated dictionary and use it to check the whole document.
* @param {Codemirror} editor CodeMirror-Editor
* @param {String} lang on of the values from getAvailableDictionaries()-Method
*/
function setLanguage (editor, lang) {
self = this
dictionary = null
if (editor == null) {
return
}
const existingMarks = editor.getAllMarks() || []
for (const mark of existingMarks) {
mark.clear()
}
if (lang !== SPELLCHECK_DISABLED) {
dictionary = new Typo(lang, false, false, {
dictionaryPath: DICTIONARY_PATH,
asyncLoad: true,
loadedCallback: () =>
checkWholeDocument(editor)
})
}
}
/**
* Checks the whole content of the editor for typos
* @param {Codemirror} editor CodeMirror-Editor
*/
function checkWholeDocument (editor) {
const lastLine = editor.lineCount() - 1
const textOfLastLine = editor.getLine(lastLine) || ''
const lastChar = textOfLastLine.length
const from = {line: 0, ch: 0}
const to = {line: lastLine, ch: lastChar}
checkMultiLineRange(editor, from, to)
}
/**
* Checks the given range for typos
* @param {Codemirror} editor CodeMirror-Editor
* @param {line, ch} from starting position of the spellcheck
* @param {line, ch} to end position of the spellcheck
*/
function checkMultiLineRange (editor, from, to) {
function sortRange (pos1, pos2) {
if (pos1.line > pos2.line || (pos1.line === pos2.line && pos1.ch > pos2.ch)) {
return {from: pos2, to: pos1}
}
return {from: pos1, to: pos2}
}
const {from: smallerPos, to: higherPos} = sortRange(from, to)
for (let l = smallerPos.line; l <= higherPos.line; l++) {
const line = editor.getLine(l) || ''
let w = 0
if (l === smallerPos.line) {
w = smallerPos.ch
}
let wEnd = line.length
if (l === higherPos.line) {
wEnd = higherPos.ch
}
while (w <= wEnd) {
const wordRange = editor.findWordAt({line: l, ch: w})
self.checkWord(editor, wordRange)
w += (wordRange.head.ch - wordRange.anchor.ch) + 1
}
}
}
/**
* @description Checks whether a certain range of characters in the editor (i.e. a word) contains a typo.
* If so the ranged will be marked with the class CSS_ERROR_CLASS.
* Note: Due to performance considerations, only words with more then 3 signs are checked.
* @param {Codemirror} editor CodeMirror-Editor
* @param wordRange Object specifying the range that should be checked.
* Having the following structure: <code>{anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}}</code>
*/
function checkWord (editor, wordRange) {
const word = editor.getRange(wordRange.anchor, wordRange.head)
if (word == null || word.length <= 3) {
return
}
if (!dictionary.check(word)) {
editor.markText(wordRange.anchor, wordRange.head, {className: styles[CSS_ERROR_CLASS]})
}
}
/**
* Checks the changes recently made (aka live check)
* @param {Codemirror} editor CodeMirror-Editor
* @param fromChangeObject codeMirror changeObject describing the start of the editing
* @param toChangeObject codeMirror changeObject describing the end of the editing
*/
function checkChangeRange (editor, fromChangeObject, toChangeObject) {
/**
* Calculate the smallest respectively largest position as a start, resp. end, position and return it
* @param start CodeMirror change object
* @param end CodeMirror change object
* @returns {{start: {line: *, ch: *}, end: {line: *, ch: *}}}
*/
function getStartAndEnd (start, end) {
const possiblePositions = [start.from, start.to, end.from, end.to]
let smallest = start.from
let biggest = end.to
for (const currentPos of possiblePositions) {
if (currentPos.line < smallest.line || (currentPos.line === smallest.line && currentPos.ch < smallest.ch)) {
smallest = currentPos
}
if (currentPos.line > biggest.line || (currentPos.line === biggest.line && currentPos.ch > biggest.ch)) {
biggest = currentPos
}
}
return {start: smallest, end: biggest}
}
if (dictionary === null || editor == null) { return }
try {
const {start, end} = getStartAndEnd(fromChangeObject, toChangeObject)
// Expand the range to include words after/before whitespaces
start.ch = Math.max(start.ch - 1, 0)
end.ch = end.ch + 1
// clean existing marks
const existingMarks = editor.findMarks(start, end) || []
for (const mark of existingMarks) {
mark.clear()
}
self.checkMultiLineRange(editor, start, end)
} catch (e) {
console.info('Error during the spell check. It might be due to problems figuring out the range of the new text..', e)
}
}
function saveLiveSpellCheckFrom (changeObject) {
liveSpellCheckFrom = changeObject
}
let liveSpellCheckFrom
const debouncedSpellCheckLeading = _.debounce(saveLiveSpellCheckFrom, MILLISECONDS_TILL_LIVECHECK, {
'leading': true,
'trailing': false
})
const debouncedSpellCheck = _.debounce(checkChangeRange, MILLISECONDS_TILL_LIVECHECK, {
'leading': false,
'trailing': true
})
/**
* Handles a keystroke. Buffers the input and performs a live spell check after a certain time. Uses _debounce from lodash to buffer the input
* @param {Codemirror} editor CodeMirror-Editor
* @param changeObject codeMirror changeObject
*/
function handleChange (editor, changeObject) {
if (dictionary === null) {
return
}
debouncedSpellCheckLeading(changeObject)
debouncedSpellCheck(editor, liveSpellCheckFrom, changeObject)
}
/**
* Returns an array of spelling suggestions for the given (wrong written) word.
* Returns an empty array if the dictionary is null (=> spellcheck is disabled) or the given word was null
* @param word word to be checked
* @returns {String[]} Array of suggestions
*/
function getSpellingSuggestion (word) {
if (dictionary == null || word == null) {
return []
}
return dictionary.suggest(word)
}
/**
* Returns the name of the CSS class used for errors
*/
function getCSSClassName () {
return styles[CSS_ERROR_CLASS]
}
module.exports = {
DICTIONARY_PATH,
CSS_ERROR_CLASS,
SPELLCHECK_DISABLED,
getAvailableDictionaries,
setLanguage,
checkChangeRange,
handleChange,
getSpellingSuggestion,
checkWord,
checkMultiLineRange,
checkWholeDocument,
setDictionaryForTestsOnly,
getCSSClassName
}

View File

@@ -23,7 +23,7 @@ body[data-theme="dark"]
border-left 1px solid $ui-dark-borderColor 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
@@ -37,3 +37,10 @@ body[data-theme="monokai"]
border-left 1px solid $ui-monokai-borderColor border-left 1px solid $ui-monokai-borderColor
.empty-message .empty-message
color $ui-monokai-text-color color $ui-monokai-text-color
body[data-theme="dracula"]
.root
background-color $ui-dracula-noteDetail-backgroundColor
border-left 1px solid $ui-dracula-borderColor
.empty-message
color $ui-dracula-text-color

View File

@@ -36,7 +36,7 @@
height 34px height 34px
width 20px width 20px
line-height 34px line-height 34px
.search-input .search-input
vertical-align middle vertical-align middle
position relative position relative
@@ -71,7 +71,7 @@
overflow ellipsis overflow ellipsis
cursor pointer cursor pointer
&:hover &:hover
background-color $ui-button--hover-backgroundColor background-color $ui-button--hover-backgroundColor
.search-optionList-item--active .search-optionList-item--active
@extend .search-optionList-item @extend .search-optionList-item
@@ -159,3 +159,29 @@ body[data-theme="monokai"]
color $ui-monokai-button--active-color color $ui-monokai-button--active-color
.search-optionList-item-name-surfix .search-optionList-item-name-surfix
color $ui-monokai-inactive-text-color color $ui-monokai-inactive-text-color
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
&:hover
color #f8f8f2
background-color $ui-dark-button--hover-backgroundColor
border-color $ui-dracula-borderColor
.search-optionList
color #f8f8f2
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.search-optionList-item
&:hover
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
.search-optionList-item--active
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-button--active-color
&:hover
background-color $ui-dark-button--hover-backgroundColor
color $ui-dracula-button--active-color
.search-optionList-item-name-surfix
color $ui-dracula-inactive-text-color

View File

@@ -5,15 +5,17 @@ import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
const FullscreenButton = ({ const FullscreenButton = ({
onClick onClick
}) => ( }) => {
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}> const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' /> return (
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span> <button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
</button> <img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
) <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
</button>
)
}
FullscreenButton.propTypes = { FullscreenButton.propTypes = {
onClick: PropTypes.func.isRequired onClick: PropTypes.func.isRequired

View File

@@ -17,6 +17,10 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 35px
body[data-theme="dark"] body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()

View File

@@ -70,22 +70,22 @@ class InfoPanel extends React.Component {
<hr /> <hr />
<div id='export-wrap'> <div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}> <button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
<i className='fa fa-file-code-o' /> <i className='fa fa-file-code-o' />
<p>{i18n.__('.md')}</p> <p>{i18n.__('.md')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}> <button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
<i className='fa fa-file-text-o' /> <i className='fa fa-file-text-o' />
<p>{i18n.__('.txt')}</p> <p>{i18n.__('.txt')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}> <button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
<i className='fa fa-html5' /> <i className='fa fa-html5' />
<p>{i18n.__('.html')}</p> <p>{i18n.__('.html')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => print(e)}> <button styleName='export--enable' onClick={(e) => print(e, 'print')}>
<i className='fa fa-print' /> <i className='fa fa-print' />
<p>{i18n.__('Print')}</p> <p>{i18n.__('Print')}</p>
</button> </button>

View File

@@ -257,3 +257,43 @@ body[data-theme="monokai"]
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
&:hover &:hover
color $ui-monokai-text-color color $ui-monokai-text-color
body[data-theme="dracula"]
.control-infoButton-panel
background-color $ui-dracula-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-dracula-noteList-backgroundColor
.modification-date
color $ui-dracula-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-dracula-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-dracula-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-dracula-borderColor, 20%)
color $ui-dracula-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-dracula-borderColor, 20%)
color $ui-dracula-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-dracula-text-color

View File

@@ -31,17 +31,17 @@ const InfoPanelTrashed = ({
</div> </div>
<div id='export-wrap'> <div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}> <button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
<i className='fa fa-file-code-o' /> <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, 'export-txt')}>
<i className='fa fa-file-text-o' /> <i className='fa fa-file-text-o' />
<p>.txt</p> <p>.txt</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}> <button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
<i className='fa fa-html5' /> <i className='fa fa-html5' />
<p>.html</p> <p>.html</p>
</button> </button>

View File

@@ -29,6 +29,7 @@ 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'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
class MarkdownNoteDetail extends React.Component { class MarkdownNoteDetail extends React.Component {
constructor (props) { constructor (props) {
@@ -38,15 +39,19 @@ class MarkdownNoteDetail extends React.Component {
isMovingNote: false, isMovingNote: false,
note: Object.assign({ note: Object.assign({
title: '', title: '',
content: '' content: '',
linesHighlighted: []
}, props.note), }, props.note),
isLockButtonShown: false, isLockButtonShown: props.config.editor.type !== 'SPLIT',
isLocked: false, isLocked: false,
editorType: props.config.editor.type editorType: props.config.editor.type,
switchPreview: props.config.editor.switchPreview
} }
this.dispatchTimer = null this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this) this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.generateToc = () => this.handleGenerateToc()
} }
focus () { focus () {
@@ -59,13 +64,20 @@ class MarkdownNoteDetail extends React.Component {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT' const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType) this.handleSwitchMode(reversedType)
}) })
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
ee.on('code:generate-toc', this.generateToc)
// Focus content if using blur or double click
if (this.state.switchPreview === 'BLUR' || this.state.switchPreview === 'DBL_CLICK') this.focus()
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) { const isNewNote = nextProps.note.key !== this.props.note.key
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
this.setState({ this.setState({
note: Object.assign({}, nextProps.note) note: Object.assign({linesHighlighted: []}, nextProps.note)
}, () => { }, () => {
this.refs.content.reload() this.refs.content.reload()
if (this.refs.tags) this.refs.tags.reset() if (this.refs.tags) this.refs.tags.reset()
@@ -75,6 +87,7 @@ class MarkdownNoteDetail extends React.Component {
componentWillUnmount () { componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton) ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.off('code:generate-toc', this.generateToc)
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
} }
@@ -87,7 +100,12 @@ class MarkdownNoteDetail extends React.Component {
handleUpdateContent () { handleUpdateContent () {
const { note } = this.state const { note } = this.state
note.content = this.refs.content.value note.content = this.refs.content.value
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)
title = striptags(title)
title = markdown.strip(title)
note.title = title
this.updateNote(note) this.updateNote(note)
} }
@@ -183,6 +201,36 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('export:save-html') ee.emit('export:save-html')
} }
handleKeyDown (e) {
switch (e.keyCode) {
// tab key
case 9:
if (e.ctrlKey && !e.shiftKey) {
e.preventDefault()
this.jumpNextTab()
} else if (e.ctrlKey && e.shiftKey) {
e.preventDefault()
this.jumpPrevTab()
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
e.preventDefault()
this.focusEditor()
}
break
// I key
case 73:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
if (isSuper) {
e.preventDefault()
this.handleInfoButtonClick(e)
}
}
break
}
}
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
const { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
@@ -255,13 +303,18 @@ class MarkdownNoteDetail extends React.Component {
handleToggleLockButton (event, noteStatus) { handleToggleLockButton (event, noteStatus) {
// first argument event is not used // first argument event is not used
if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') { if (noteStatus === 'CODE') {
this.setState({isLockButtonShown: true}) this.setState({isLockButtonShown: true})
} else { } else {
this.setState({isLockButtonShown: false}) this.setState({isLockButtonShown: false})
} }
} }
handleGenerateToc () {
const editor = this.refs.content.refs.code.editor
markdownToc.generateInEditor(editor)
}
handleFocus (e) { handleFocus (e) {
this.focus() this.focus()
} }
@@ -276,7 +329,8 @@ class MarkdownNoteDetail extends React.Component {
} }
handleSwitchMode (type) { handleSwitchMode (type) {
this.setState({ editorType: type }, () => { // If in split mode, hide the lock button
this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
this.focus() this.focus()
const newConfig = Object.assign({}, this.props.config) const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type newConfig.editor.type = type
@@ -284,9 +338,33 @@ class MarkdownNoteDetail extends React.Component {
}) })
} }
handleDeleteNote () {
this.handleTrashButtonClick()
}
handleClearTodo () {
const { note } = this.state
const splitted = note.content.split('\n')
const clearTodoContent = splitted.map((line) => {
const trimmedLine = line.trim()
if (trimmedLine.match(/\[x\]/i)) {
return line.replace(/\[x\]/i, '[ ]')
} else {
return line
}
}).join('\n')
note.content = clearTodoContent
this.refs.content.setValue(note.content)
this.updateNote(note)
}
renderEditor () { renderEditor () {
const { config, ignorePreviewPointerEvents } = this.props const { config, ignorePreviewPointerEvents } = this.props
const { note } = this.state const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') { if (this.state.editorType === 'EDITOR_PREVIEW') {
return <MarkdownEditor return <MarkdownEditor
ref='content' ref='content'
@@ -295,7 +373,9 @@ class MarkdownNoteDetail extends React.Component {
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key} noteKey={note.key}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
isLocked={this.state.isLocked}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
} else { } else {
@@ -305,6 +385,7 @@ class MarkdownNoteDetail extends React.Component {
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key} noteKey={note.key}
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
@@ -312,7 +393,7 @@ class MarkdownNoteDetail extends React.Component {
} }
render () { render () {
const { data, location } = this.props const { data, location, config } = this.props
const { note, editorType } = this.state const { note, editorType } = this.state
const storageKey = note.storage const storageKey = note.storage
const folderKey = note.folder const folderKey = note.folder
@@ -363,9 +444,13 @@ class MarkdownNoteDetail extends React.Component {
<TagSelect <TagSelect
ref='tags' ref='tags'
value={this.state.note.tags} value={this.state.note.tags}
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
onChange={this.handleUpdateTag.bind(this)} onChange={this.handleUpdateTag.bind(this)}
coloredTags={config.coloredTags}
/> />
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} /> <TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} /> <ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
@@ -419,6 +504,7 @@ class MarkdownNoteDetail extends React.Component {
<div className='NoteDetail' <div className='NoteDetail'
style={this.props.style} style={this.props.style}
styleName='root' styleName='root'
onKeyDown={(e) => this.handleKeyDown(e)}
> >
{location.pathname === '/trashed' ? trashTopBar : detailTopBar} {location.pathname === '/trashed' ? trashTopBar : detailTopBar}

View File

@@ -76,3 +76,8 @@ body[data-theme="monokai"]
.root .root
border-left 1px solid $ui-monokai-borderColor border-left 1px solid $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor background-color $ui-monokai-noteDetail-backgroundColor
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor

View File

@@ -13,6 +13,7 @@ $info-margin-under-border = 30px
display flex display flex
align-items center align-items center
padding 0 20px padding 0 20px
z-index 99
.info-left .info-left
padding 0 10px padding 0 10px
@@ -97,8 +98,13 @@ body[data-theme="solarized-dark"]
.info .info
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
body[data-theme="monokai"] body[data-theme="monokai"]
.info .info
border-color $ui-monokai-borderColor border-color $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor background-color $ui-monokai-noteDetail-backgroundColor
body[data-theme="dracula"]
.info
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor

View File

@@ -20,6 +20,7 @@ import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle' import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName' import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import FullscreenButton from './FullscreenButton'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton' import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton' import PermanentDeleteButton from './PermanentDeleteButton'
@@ -29,6 +30,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter' import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -47,11 +49,12 @@ class SnippetNoteDetail extends React.Component {
note: Object.assign({ note: Object.assign({
description: '' description: ''
}, props.note, { }, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet)) snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
}) })
} }
this.scrollToNextTabThreshold = 0.7 this.scrollToNextTabThreshold = 0.7
this.generateToc = () => this.handleGenerateToc()
} }
componentDidMount () { componentDidMount () {
@@ -65,6 +68,7 @@ class SnippetNoteDetail extends React.Component {
enableLeftArrow: allTabs.offsetLeft !== 0 enableLeftArrow: allTabs.offsetLeft !== 0
}) })
} }
ee.on('code:generate-toc', this.generateToc)
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
@@ -73,8 +77,9 @@ class SnippetNoteDetail extends React.Component {
const nextNote = Object.assign({ const nextNote = Object.assign({
description: '' description: ''
}, nextProps.note, { }, nextProps.note, {
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet)) snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
}) })
this.setState({ this.setState({
snippetIndex: 0, snippetIndex: 0,
note: nextNote note: nextNote
@@ -91,6 +96,16 @@ class SnippetNoteDetail extends React.Component {
componentWillUnmount () { componentWillUnmount () {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
ee.off('code:generate-toc', this.generateToc)
}
handleGenerateToc () {
const { note, snippetIndex } = this.state
const currentMode = note.snippets[snippetIndex].mode
if (currentMode.includes('Markdown')) {
const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor
markdownToc.generateInEditor(currentEditor)
}
} }
handleChange (e) { handleChange (e) {
@@ -99,7 +114,7 @@ class SnippetNoteDetail extends React.Component {
if (this.refs.tags) note.tags = this.refs.tags.value if (this.refs.tags) note.tags = this.refs.tags.value
note.description = this.refs.description.value note.description = this.refs.description.value
note.updatedAt = new Date() note.updatedAt = new Date()
note.title = findNoteTitle(note.description) note.title = findNoteTitle(note.description, false)
this.setState({ this.setState({
note note
@@ -341,12 +356,10 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + this.state.snippetIndex].reload() this.refs['code-' + this.state.snippetIndex].reload()
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) { if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
console.log('no need for arrows')
this.moveTabBarBy(0) this.moveTabBarBy(0)
} else { } else {
const lastTab = this.allTabs.lastChild const lastTab = this.allTabs.lastChild
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) { if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
console.log('need to scroll')
const width = this.visibleTabs.offsetWidth const width = this.visibleTabs.offsetWidth
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0) this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
@@ -399,6 +412,8 @@ class SnippetNoteDetail extends React.Component {
return (e) => { return (e) => {
const snippets = this.state.note.snippets.slice() const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value snippets[index].content = this.refs['code-' + index].value
snippets[index].linesHighlighted = e.options.linesHighlighted
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})})) this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({ this.setState(state => ({
note: state.note note: state.note
@@ -423,6 +438,18 @@ class SnippetNoteDetail extends React.Component {
this.focusEditor() this.focusEditor()
} }
break break
// I key
case 73:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
if (isSuper) {
e.preventDefault()
this.handleInfoButtonClick(e)
}
}
break
// L key // L key
case 76: case 76:
{ {
@@ -441,7 +468,7 @@ class SnippetNoteDetail extends React.Component {
const isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
? e.metaKey ? e.metaKey
: e.ctrlKey : e.ctrlKey
if (isSuper && !e.shiftKey) { if (isSuper && !e.shiftKey && !e.altKey) {
e.preventDefault() e.preventDefault()
this.addSnippet() this.addSnippet()
} }
@@ -573,12 +600,16 @@ class SnippetNoteDetail extends React.Component {
} }
addSnippet () { addSnippet () {
const { config: { editor: { snippetDefaultLanguage } } } = this.props
const { note } = this.state const { note } = this.state
const defaultLanguage = snippetDefaultLanguage === 'Auto Detect' ? null : snippetDefaultLanguage
note.snippets = note.snippets.concat([{ note.snippets = note.snippets.concat([{
name: '', name: '',
mode: 'Plain Text', mode: defaultLanguage,
content: '' content: '',
linesHighlighted: []
}]) }])
const snippetIndex = note.snippets.length - 1 const snippetIndex = note.snippets.length - 1
@@ -613,7 +644,6 @@ class SnippetNoteDetail extends React.Component {
} }
focusEditor () { focusEditor () {
console.log('code-' + this.state.snippetIndex)
this.refs['code-' + this.state.snippetIndex].focus() this.refs['code-' + this.state.snippetIndex].focus()
} }
@@ -622,11 +652,18 @@ class SnippetNoteDetail extends React.Component {
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none' if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
} }
showWarning () { showWarning (e, msg) {
const warningMessage = (msg) => ({
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'print': 'Print'
})[msg]
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Sorry!'), message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'), detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
buttons: [i18n.__('OK')] buttons: [i18n.__('OK')]
}) })
} }
@@ -638,6 +675,8 @@ class SnippetNoteDetail extends React.Component {
const storageKey = note.storage const storageKey = note.storage
const folderKey = note.folder const folderKey = note.folder
const autoDetect = config.editor.snippetDefaultLanguage === 'Auto Detect'
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
@@ -662,10 +701,6 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, index) => { const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index const isActive = this.state.snippetIndex === index
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return <div styleName='tabView' return <div styleName='tabView'
key={index} key={index}
style={{zIndex: isActive ? 5 : 4}} style={{zIndex: isActive ? 5 : 4}}
@@ -674,25 +709,34 @@ class SnippetNoteDetail extends React.Component {
? <MarkdownEditor styleName='tabView-content' ? <MarkdownEditor styleName='tabView-content'
value={snippet.content} value={snippet.content}
config={config} config={config}
linesHighlighted={snippet.linesHighlighted}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents} ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
storageKey={storageKey} storageKey={storageKey}
/> />
: <CodeEditor styleName='tabView-content' : <CodeEditor styleName='tabView-content'
mode={snippet.mode} mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)}
value={snippet.content} value={snippet.content}
linesHighlighted={snippet.linesHighlighted}
theme={config.editor.theme} theme={config.editor.theme}
fontFamily={config.editor.fontFamily} fontFamily={config.editor.fontFamily}
fontSize={editorFontSize} fontSize={editorFontSize}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
autoDetect={autoDetect}
/> />
} }
</div> </div>
@@ -744,7 +788,11 @@ class SnippetNoteDetail extends React.Component {
<TagSelect <TagSelect
ref='tags' ref='tags'
value={this.state.note.tags} value={this.state.note.tags}
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
coloredTags={config.coloredTags}
/> />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
@@ -753,11 +801,7 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred} isActive={note.isStarred}
/> />
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} <FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -773,7 +817,9 @@ 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}
type={note.type} type={note.type}
print={this.showWarning}
/> />
</div> </div>
</div> </div>

View File

@@ -31,7 +31,7 @@
.tabList .tabList
absolute left right absolute left right
top 55px top 70px
height 30px height 30px
display flex display flex
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
@@ -57,6 +57,9 @@
.tabList .tabButton .tabList .tabButton
navWhiteButtonColor() navWhiteButtonColor()
width 30px width 30px
border-left 1px solid $ui-borderColor
border-top 1px solid $ui-borderColor
border-right 1px solid $ui-borderColor
.tabView .tabView
absolute left right bottom absolute left right bottom
@@ -98,17 +101,34 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
body[data-theme="white"] body[data-theme="white"], body[data-theme="default"]
.root .root
box-shadow $note-detail-box-shadow box-shadow $note-detail-box-shadow
border none border none
.tabButton
&:hover
background-color alpha($ui-button--active-backgroundColor, 20%)
color $ui-text-color
transition 0.15s
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-left 1px solid $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
box-shadow none box-shadow none
.tabList .tabButton
border-color $ui-dark-borderColor
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
.tabButton
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color
transition 0.15s
.body .body
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
@@ -118,7 +138,6 @@ body[data-theme="dark"]
border 1px solid $ui-dark-borderColor border 1px solid $ui-dark-borderColor
.tabList .tabList
background-color $ui-button--active-backgroundColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
.tabList .list .tabList .list
@@ -150,6 +169,15 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
border 1px solid $ui-solarized-dark-borderColor border 1px solid $ui-solarized-dark-borderColor
.tabList .tabButton
border-color $ui-solarized-dark-borderColor
.tabButton
&:hover
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-noteDetail-backgroundColor
transition 0.15s
.tabList .tabList
background-color $ui-solarized-dark-noteDetail-backgroundColor background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
@@ -167,6 +195,39 @@ body[data-theme="monokai"]
color $ui-monokai-text-color color $ui-monokai-text-color
border 1px solid $ui-monokai-borderColor border 1px solid $ui-monokai-borderColor
.tabList .tabButton
border-color $ui-monokai-borderColor
.tabButton
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-noteDetail-backgroundColor
.tabList .tabList
background-color $ui-monokai-noteDetail-backgroundColor background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color color $ui-monokai-text-color
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
.body
background-color $ui-dracula-noteDetail-backgroundColor
.body .description textarea
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
border 1px solid $ui-dracula-borderColor
.tabList .tabButton
border-color $ui-dracula-borderColor
.tabButton
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-noteDetail-backgroundColor
.tabList
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color

View File

@@ -54,7 +54,7 @@ class StarButton extends React.Component {
: '../resources/icon/icon-star.svg' : '../resources/icon/icon-star.svg'
} }
/> />
<span styleName='tooltip'>{i18n.__('Star')}</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Star')}</span>
</button> </button>
) )
} }

View File

@@ -21,6 +21,11 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 103px
width 70px
.root--active .root--active
@extend .root @extend .root
transition 0.15s transition 0.15s

View File

@@ -1,76 +1,39 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import invertColor from 'invert-color'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl' import styles from './TagSelect.styl'
import _ from 'lodash' import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import Autosuggest from 'react-autosuggest'
class TagSelect extends React.Component { class TagSelect extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {
newTag: '' newTag: '',
suggestions: []
} }
this.addtagHandler = this.handleAddTag.bind(this)
this.handleAddTag = this.handleAddTag.bind(this)
this.onInputBlur = this.onInputBlur.bind(this)
this.onInputChange = this.onInputChange.bind(this)
this.onInputKeyDown = this.onInputKeyDown.bind(this)
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
} }
componentDidMount () { addNewTag (newTag) {
this.value = this.props.value
ee.on('editor:add-tag', this.addtagHandler)
}
componentDidUpdate () {
this.value = this.props.value
}
componentWillUnmount () {
ee.off('editor:add-tag', this.addtagHandler)
}
handleAddTag () {
this.refs.newTag.focus()
}
handleNewTagInputKeyDown (e) {
switch (e.keyCode) {
case 9:
e.preventDefault()
this.submitTag()
break
case 13:
this.submitTag()
break
case 8:
if (this.refs.newTag.value.length === 0) {
this.removeLastTag()
}
}
}
handleNewTagBlur (e) {
this.submitTag()
}
removeLastTag () {
this.removeTagByCallback((value) => {
value.pop()
})
}
reset () {
this.setState({
newTag: ''
})
}
submitTag () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG') AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_') newTag = newTag.trim().replace(/ +/g, '_')
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag if (newTag.charAt(0) === '#') {
newTag.substring(1)
}
if (newTag.length <= 0) { if (newTag.length <= 0) {
this.setState({ this.setState({
@@ -79,11 +42,18 @@ class TagSelect extends React.Component {
return return
} }
let { value } = this.props
value = _.isArray(value) value = _.isArray(value)
? value.slice() ? value.slice()
: [] : []
value.push(newTag)
value = _.uniq(value) if (!_.includes(value, newTag)) {
value.push(newTag)
}
if (this.props.saveTagsAlphabetically) {
value = _.sortBy(value)
}
this.setState({ this.setState({
newTag: '' newTag: ''
@@ -93,10 +63,41 @@ class TagSelect extends React.Component {
}) })
} }
handleNewTagInputChange (e) { buildSuggestions () {
this.setState({ this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
newTag: this.refs.newTag.value (tag, name) => ({
}) name,
nameLC: name.toLowerCase(),
size: tag.size
})
).filter(
tag => tag.size > 0
), ['name'])
}
componentDidMount () {
this.value = this.props.value
this.buildSuggestions()
ee.on('editor:add-tag', this.handleAddTag)
}
componentDidUpdate () {
this.value = this.props.value
}
componentWillUnmount () {
ee.off('editor:add-tag', this.handleAddTag)
}
handleAddTag () {
this.refs.newTag.input.focus()
}
handleTagLabelClick (tag) {
const { router } = this.context
router.push(`/tags/${tag}`)
} }
handleTagRemoveButtonClick (tag) { handleTagRemoveButtonClick (tag) {
@@ -105,6 +106,60 @@ class TagSelect extends React.Component {
}, tag) }, tag)
} }
onInputBlur (e) {
this.submitNewTag()
}
onInputChange (e, { newValue, method }) {
this.setState({
newTag: newValue
})
}
onInputKeyDown (e) {
switch (e.keyCode) {
case 9:
e.preventDefault()
this.submitNewTag()
break
case 13:
this.submitNewTag()
break
case 8:
if (this.state.newTag.length === 0) {
this.removeLastTag()
}
}
}
onSuggestionsClearRequested () {
this.setState({
suggestions: []
})
}
onSuggestionsFetchRequested ({ value }) {
const valueLC = value.toLowerCase()
const suggestions = _.filter(
this.suggestions,
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
)
this.setState({
suggestions
})
}
onSuggestionSelected (event, { suggestion, suggestionValue }) {
this.addNewTag(suggestionValue)
}
removeLastTag () {
this.removeTagByCallback((value) => {
value.pop()
})
}
removeTagByCallback (callback, tag = null) { removeTagByCallback (callback, tag = null) {
let { value } = this.props let { value } = this.props
@@ -118,26 +173,55 @@ class TagSelect extends React.Component {
this.props.onChange() this.props.onChange()
} }
reset () {
this.buildSuggestions()
this.setState({
newTag: ''
})
}
submitNewTag () {
this.addNewTag(this.refs.newTag.input.value)
}
render () { render () {
const { value, className } = this.props const { value, className, showTagsAlphabetically, coloredTags } = this.props
const tagList = _.isArray(value) const tagList = _.isArray(value)
? value.map((tag) => { ? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
const wrapperStyle = {}
const textStyle = {}
const BLACK = '#333333'
const WHITE = '#f1f1f1'
const color = coloredTags[tag]
const invertedColor = color && invertColor(color, { black: BLACK, white: WHITE })
let iconRemove = '../resources/icon/icon-x.svg'
if (color) {
wrapperStyle.backgroundColor = color
textStyle.color = invertedColor
}
if (invertedColor === WHITE) {
iconRemove = '../resources/icon/icon-x-light.svg'
}
return ( return (
<span styleName='tag' <span styleName='tag'
key={tag} key={tag}
style={wrapperStyle}
> >
<span styleName='tag-label'>#{tag}</span> <span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
<button styleName='tag-removeButton' <button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)} onClick={(e) => this.handleTagRemoveButtonClick(tag)}
> >
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' /> <img className='tag-removeButton-icon' src={iconRemove} width='8px' />
</button> </button>
</span> </span>
) )
}) })
: [] : []
const { newTag, suggestions } = this.state
return ( return (
<div className={_.isString(className) <div className={_.isString(className)
? 'TagSelect ' + className ? 'TagSelect ' + className
@@ -146,24 +230,40 @@ class TagSelect extends React.Component {
styleName='root' styleName='root'
> >
{tagList} {tagList}
<input styleName='newTag' <Autosuggest
ref='newTag' ref='newTag'
value={this.state.newTag} suggestions={suggestions}
placeholder={i18n.__('Add tag...')} onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onChange={(e) => this.handleNewTagInputChange(e)} onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)} onSuggestionSelected={this.onSuggestionSelected}
onBlur={(e) => this.handleNewTagBlur(e)} getSuggestionValue={suggestion => suggestion.name}
renderSuggestion={suggestion => (
<div>
{suggestion.name}
</div>
)}
inputProps={{
placeholder: i18n.__('Add tag...'),
value: newTag,
onChange: this.onInputChange,
onKeyDown: this.onInputKeyDown,
onBlur: this.onInputBlur
}}
/> />
</div> </div>
) )
} }
} }
TagSelect.contextTypes = {
router: PropTypes.shape({})
}
TagSelect.propTypes = { TagSelect.propTypes = {
className: PropTypes.string, className: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string), value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func onChange: PropTypes.func,
coloredTags: PropTypes.object
} }
export default CSSModules(TagSelect, styles) export default CSSModules(TagSelect, styles)

View File

@@ -3,19 +3,18 @@
align-items center align-items center
user-select none user-select none
vertical-align middle vertical-align middle
width 100% width 96%
overflow-x scroll overflow-x auto
white-space nowrap white-space nowrap
margin-top 31px top 50px
position absolute position absolute
&::-webkit-scrollbar
.root::-webkit-scrollbar height 8px
display none
.tag .tag
display flex display flex
align-items center align-items center
margin 0px 2px margin 0px 2px 2px
padding 2px 4px padding 2px 4px
background-color alpha($ui-tag-backgroundColor, 3%) background-color alpha($ui-tag-backgroundColor, 3%)
border-radius 4px border-radius 4px
@@ -39,16 +38,9 @@
.tag-label .tag-label
font-size 13px font-size 13px
color: $ui-text-color color $ui-text-color
padding 4px 16px 4px 8px padding 4px 16px 4px 8px
cursor pointer
.newTag
box-sizing border-box
border none
background-color transparent
outline none
padding 0 4px
font-size 13px
body[data-theme="dark"] body[data-theme="dark"]
.tag .tag
@@ -62,11 +54,6 @@ body[data-theme="dark"]
.tag-label .tag-label
color $ui-dark-text-color color $ui-dark-text-color
.newTag
border-color none
background-color transparent
color $ui-dark-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.tag .tag
background-color $ui-solarized-dark-tag-backgroundColor background-color $ui-solarized-dark-tag-backgroundColor
@@ -78,14 +65,9 @@ body[data-theme="solarized-dark"]
.tag-label .tag-label
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.newTag
border-color none
background-color transparent
color $ui-solarized-dark-text-color
body[data-theme="monokai"] body[data-theme="monokai"]
.tag .tag
background-color $ui-monokai-button-backgroundColor background-color $ui-monokai-tag-backgroundColor
.tag-removeButton .tag-removeButton
border-color $ui-button--focus-borderColor border-color $ui-button--focus-borderColor
@@ -93,8 +75,14 @@ body[data-theme="monokai"]
.tag-label .tag-label
color $ui-monokai-text-color color $ui-monokai-text-color
.newTag body[data-theme="dracula"]
border-color none .tag
background-color $ui-dracula-tag-backgroundColor
.tag-removeButton
border-color $ui-dracula-button--focus-borderColor
background-color transparent background-color transparent
color $ui-monokai-text-color
.tag-label
color $ui-dracula-borderColor

View File

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

View File

@@ -40,6 +40,11 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
left -8px
width 70px
body[data-theme="dark"] body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()
@@ -59,7 +64,14 @@ body[data-theme="solarized-dark"]
body[data-theme="monokai"] body[data-theme="monokai"]
.control-toggleModeButton .control-toggleModeButton
background-color #272822 background-color #373831
.active .active
background-color #1EC38B background-color #f92672
box-shadow 2px 0px 7px #222222
body[data-theme="dracula"]
.control-toggleModeButton
background-color #44475a
.active
background-color #bd93f9
box-shadow 2px 0px 7px #222222 box-shadow 2px 0px 7px #222222

View File

@@ -11,7 +11,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'>{i18n.__('Trash')}</span> <span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
</button> </button>
) )

View File

@@ -17,6 +17,10 @@
opacity 0 opacity 0
transition 0.1s transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
right 46px
.control-trashButton--in-trash .control-trashButton--in-trash
top 60px top 60px
topBarButtonRight() topBarButtonRight()

View File

@@ -56,7 +56,7 @@ class Main extends React.Component {
init () { init () {
dataApi dataApi
.addStorage({ .addStorage({
name: 'My Storage', name: 'My Storage Location',
path: path.join(remote.app.getPath('home'), 'Boostnote') path: path.join(remote.app.getPath('home'), 'Boostnote')
}) })
.then(data => { .then(data => {
@@ -80,7 +80,6 @@ class Main extends React.Component {
} }
}) })
.then(data => { .then(data => {
console.log(data)
store.dispatch({ store.dispatch({
type: 'ADD_STORAGE', type: 'ADD_STORAGE',
storage: data.storage, storage: data.storage,
@@ -97,12 +96,14 @@ class Main extends React.Component {
{ {
name: 'example.html', name: 'example.html',
mode: 'html', mode: 'html',
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>" content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
linesHighlighted: []
}, },
{ {
name: 'example.js', name: 'example.js',
mode: 'javascript', mode: 'javascript',
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)" content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)",
linesHighlighted: []
} }
] ]
}) })
@@ -141,7 +142,7 @@ class Main extends React.Component {
componentDidMount () { componentDidMount () {
const { dispatch, config } = this.props const { dispatch, config } = this.props
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai'] const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai', 'dracula']
if (supportedThemes.indexOf(config.ui.theme) !== -1) { if (supportedThemes.indexOf(config.ui.theme) !== -1) {
document.body.setAttribute('data-theme', config.ui.theme) document.body.setAttribute('data-theme', config.ui.theme)
@@ -168,11 +169,24 @@ class Main extends React.Component {
} }
}) })
delete CodeMirror.keyMap.emacs['Ctrl-V']
eventEmitter.on('editor:fullscreen', this.toggleFullScreen) eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
eventEmitter.on('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
} }
componentWillUnmount () { componentWillUnmount () {
eventEmitter.off('editor:fullscreen', this.toggleFullScreen) eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
eventEmitter.off('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
}
toggleMenuBarVisible () {
const { config } = this.props
const { ui } = config
const newUI = Object.assign(ui, {showMenuBar: !ui.showMenuBar})
const newConfig = Object.assign(config, newUI)
ConfigManager.set(newConfig)
} }
handleLeftSlideMouseDown (e) { handleLeftSlideMouseDown (e) {
@@ -233,8 +247,8 @@ class Main extends React.Component {
if (this.state.isRightSliderFocused) { if (this.state.isRightSliderFocused) {
const offset = this.refs.body.getBoundingClientRect().left const offset = this.refs.body.getBoundingClientRect().left
let newListWidth = e.pageX - offset let newListWidth = e.pageX - offset
if (newListWidth < 10) { if (newListWidth < 180) {
newListWidth = 10 newListWidth = 180
} else if (newListWidth > 600) { } else if (newListWidth > 600) {
newListWidth = 600 newListWidth = 600
} }
@@ -297,7 +311,7 @@ class Main extends React.Component {
onMouseUp={e => this.handleMouseUp(e)} onMouseUp={e => this.handleMouseUp(e)}
> >
<SideNav <SideNav
{..._.pick(this.props, ['dispatch', 'data', 'config', 'location'])} {..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
width={this.state.navWidth} width={this.state.navWidth}
/> />
{!config.isSideNavFolded && {!config.isSideNavFolded &&

View File

@@ -79,3 +79,7 @@ body[data-theme="solarized-dark"]
body[data-theme="monokai"] body[data-theme="monokai"]
.root, .root--expanded .root, .root--expanded
background-color $ui-monokai-noteList-backgroundColor background-color $ui-monokai-noteList-backgroundColor
body[data-theme="dracula"]
.root, .root--expanded
background-color $ui-dracula-noteList-backgroundColor

View File

@@ -7,6 +7,7 @@ import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal' import NewNoteModal from 'browser/main/modals/NewNoteModal'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
const { remote } = require('electron') const { remote } = require('electron')
const { dialog } = remote const { dialog } = remote
@@ -34,15 +35,23 @@ class NewNoteButton extends React.Component {
} }
handleNewNoteButtonClick (e) { handleNewNoteButtonClick (e) {
const { location, dispatch } = this.props const { location, params, dispatch, config } = this.props
const { storage, folder } = this.resolveTargetFolder() const { storage, folder } = this.resolveTargetFolder()
modal.open(NewNoteModal, { if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
storage: storage.key, createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
folder: folder.key, } else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
dispatch, createSnippetNote(storage.key, folder.key, dispatch, location, params, config)
location } else {
}) modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location,
params,
config
})
}
} }
resolveTargetFolder () { resolveTargetFolder () {

View File

@@ -84,7 +84,7 @@ 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
.control-button--active .control-button--active
color $ui-dark-text-color color $ui-dark-text-color
&:active &:active
@@ -109,7 +109,7 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-inactive-text-color color $ui-solarized-dark-inactive-text-color
&:hover &:hover
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.control-button--active .control-button--active
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
&:active &:active
@@ -138,3 +138,27 @@ body[data-theme="monokai"]
color $ui-monokai-text-color color $ui-monokai-text-color
&:active &:active
color $ui-monokai-text-color color $ui-monokai-text-color
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.control
background-color $ui-dracula-noteList-backgroundColor
border-color $ui-dracula-borderColor
.control-sortBy-select
&:hover
transition 0.2s
color $ui-dracula-text-color
.control-button
color $ui-dracula-inactive-text-color
&:hover
color $ui-dracula-text-color
.control-button--active
color $ui-dracula-text-color
&:active
color $ui-dracula-text-color

View File

@@ -2,7 +2,6 @@
import PropTypes from 'prop-types' 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 debounceRender from 'react-debounce-render'
import styles from './NoteList.styl' import styles from './NoteList.styl'
import moment from 'moment' import moment from 'moment'
import _ from 'lodash' import _ from 'lodash'
@@ -56,7 +55,6 @@ class NoteList extends React.Component {
super(props) super(props)
this.selectNextNoteHandler = () => { this.selectNextNoteHandler = () => {
console.log('fired next')
this.selectNextNote() this.selectNextNote()
} }
this.selectPriorNoteHandler = () => { this.selectPriorNoteHandler = () => {
@@ -65,13 +63,14 @@ class NoteList extends React.Component {
this.focusHandler = () => { this.focusHandler = () => {
this.refs.list.focus() this.refs.list.focus()
} }
this.alertIfSnippetHandler = () => { this.alertIfSnippetHandler = (event, msg) => {
this.alertIfSnippet() this.alertIfSnippet(msg)
} }
this.importFromFileHandler = this.importFromFile.bind(this) this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this) this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this) this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this) this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
this.cloneNote = this.cloneNote.bind(this)
this.deleteNote = this.deleteNote.bind(this) this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this) this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this) this.pinToTop = this.pinToTop.bind(this)
@@ -80,10 +79,13 @@ class NoteList extends React.Component {
this.getViewType = this.getViewType.bind(this) this.getViewType = this.getViewType.bind(this)
this.restoreNote = this.restoreNote.bind(this) this.restoreNote = this.restoreNote.bind(this)
this.copyNoteLink = this.copyNoteLink.bind(this) this.copyNoteLink = this.copyNoteLink.bind(this)
this.navigate = this.navigate.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing) // TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = { this.state = {
ctrlKeyDown: false,
shiftKeyDown: false, shiftKeyDown: false,
prevShiftNoteIndex: -1,
selectedNoteKeys: [] selectedNoteKeys: []
} }
@@ -94,10 +96,12 @@ class NoteList extends React.Component {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000) this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
ee.on('list:next', this.selectNextNoteHandler) ee.on('list:next', this.selectNextNoteHandler)
ee.on('list:prior', this.selectPriorNoteHandler) ee.on('list:prior', this.selectPriorNoteHandler)
ee.on('list:clone', this.cloneNote)
ee.on('list:focus', this.focusHandler) ee.on('list:focus', this.focusHandler)
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler) ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.on('import:file', this.importFromFileHandler) ee.on('import:file', this.importFromFileHandler)
ee.on('list:jump', this.jumpNoteByHash) ee.on('list:jump', this.jumpNoteByHash)
ee.on('list:navigate', this.navigate)
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
@@ -115,6 +119,7 @@ class NoteList extends React.Component {
ee.off('list:next', this.selectNextNoteHandler) ee.off('list:next', this.selectNextNoteHandler)
ee.off('list:prior', this.selectPriorNoteHandler) ee.off('list:prior', this.selectPriorNoteHandler)
ee.off('list:clone', this.cloneNote)
ee.off('list:focus', this.focusHandler) ee.off('list:focus', this.focusHandler)
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler) ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.off('import:file', this.importFromFileHandler) ee.off('import:file', this.importFromFileHandler)
@@ -170,16 +175,15 @@ class NoteList extends React.Component {
} }
} }
focusNote (selectedNoteKeys, noteKey) { focusNote (selectedNoteKeys, noteKey, pathname) {
const { router } = this.context const { router } = this.context
const { location } = this.props
this.setState({ this.setState({
selectedNoteKeys selectedNoteKeys
}) })
router.push({ router.push({
pathname: location.pathname, pathname,
query: { query: {
key: noteKey key: noteKey
} }
@@ -198,6 +202,7 @@ class NoteList extends React.Component {
} }
let { selectedNoteKeys } = this.state let { selectedNoteKeys } = this.state
const { shiftKeyDown } = this.state const { shiftKeyDown } = this.state
const { location } = this.props
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
@@ -214,7 +219,7 @@ class NoteList extends React.Component {
selectedNoteKeys.push(priorNoteKey) selectedNoteKeys.push(priorNoteKey)
} }
this.focusNote(selectedNoteKeys, priorNoteKey) this.focusNote(selectedNoteKeys, priorNoteKey, location.pathname)
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -225,6 +230,7 @@ class NoteList extends React.Component {
} }
let { selectedNoteKeys } = this.state let { selectedNoteKeys } = this.state
const { shiftKeyDown } = this.state const { shiftKeyDown } = this.state
const { location } = this.props
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
const isTargetLastNote = targetIndex === this.notes.length - 1 const isTargetLastNote = targetIndex === this.notes.length - 1
@@ -247,7 +253,7 @@ class NoteList extends React.Component {
selectedNoteKeys.push(nextNoteKey) selectedNoteKeys.push(nextNoteKey)
} }
this.focusNote(selectedNoteKeys, nextNoteKey) this.focusNote(selectedNoteKeys, nextNoteKey, location.pathname)
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -259,13 +265,13 @@ class NoteList extends React.Component {
} }
const selectedNoteKeys = [noteHash] const selectedNoteKeys = [noteHash]
this.focusNote(selectedNoteKeys, noteHash) this.focusNote(selectedNoteKeys, noteHash, '/home')
ee.emit('list:moved') ee.emit('list:moved')
} }
handleNoteListKeyDown (e) { handleNoteListKeyDown (e) {
if (e.metaKey || e.ctrlKey) return true if (e.metaKey) return true
// A key // A key
if (e.keyCode === 65 && !e.shiftKey) { if (e.keyCode === 65 && !e.shiftKey) {
@@ -273,12 +279,6 @@ class NoteList extends React.Component {
ee.emit('top:new-note') ee.emit('top:new-note')
} }
// D key
if (e.keyCode === 68) {
e.preventDefault()
this.deleteNote()
}
// E key // E key
if (e.keyCode === 69) { if (e.keyCode === 69) {
e.preventDefault() e.preventDefault()
@@ -305,6 +305,8 @@ class NoteList extends React.Component {
if (e.shiftKey) { if (e.shiftKey) {
this.setState({ shiftKeyDown: true }) this.setState({ shiftKeyDown: true })
} else if (e.ctrlKey) {
this.setState({ ctrlKeyDown: true })
} }
} }
@@ -312,6 +314,10 @@ class NoteList extends React.Component {
if (!e.shiftKey) { if (!e.shiftKey) {
this.setState({ shiftKeyDown: false }) this.setState({ shiftKeyDown: false })
} }
if (!e.ctrlKey) {
this.setState({ ctrlKeyDown: false })
}
} }
getNotes () { getNotes () {
@@ -388,25 +394,65 @@ class NoteList extends React.Component {
return pinnedNotes.concat(unpinnedNotes) return pinnedNotes.concat(unpinnedNotes)
} }
getNoteIndexByKey (noteKey) {
return this.notes.findIndex((note) => {
if (!note) return -1
return note.key === noteKey
})
}
handleNoteClick (e, uniqueKey) { handleNoteClick (e, uniqueKey) {
const { router } = this.context const { router } = this.context
const { location } = this.props const { location } = this.props
let { selectedNoteKeys } = this.state let { selectedNoteKeys, prevShiftNoteIndex } = this.state
const { shiftKeyDown } = this.state const { ctrlKeyDown, shiftKeyDown } = this.state
const hasSelectedNoteKey = selectedNoteKeys.length > 0
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) { if (ctrlKeyDown && selectedNoteKeys.includes(uniqueKey)) {
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey) const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
this.setState({ this.setState({
selectedNoteKeys: newSelectedNoteKeys selectedNoteKeys: newSelectedNoteKeys
}) })
return return
} }
if (!shiftKeyDown) { if (!ctrlKeyDown && !shiftKeyDown) {
selectedNoteKeys = [] selectedNoteKeys = []
} }
if (!shiftKeyDown) {
prevShiftNoteIndex = -1
}
selectedNoteKeys.push(uniqueKey) selectedNoteKeys.push(uniqueKey)
if (shiftKeyDown && hasSelectedNoteKey) {
let firstShiftNoteIndex = this.getNoteIndexByKey(selectedNoteKeys[0])
// Shift selection can either start from first note in the exisiting selectedNoteKeys
// or previous first shift note index
firstShiftNoteIndex = firstShiftNoteIndex > prevShiftNoteIndex
? firstShiftNoteIndex : prevShiftNoteIndex
const lastShiftNoteIndex = this.getNoteIndexByKey(uniqueKey)
const startIndex = firstShiftNoteIndex < lastShiftNoteIndex
? firstShiftNoteIndex : lastShiftNoteIndex
const endIndex = firstShiftNoteIndex > lastShiftNoteIndex
? firstShiftNoteIndex : lastShiftNoteIndex
selectedNoteKeys = []
for (let i = startIndex; i <= endIndex; i++) {
selectedNoteKeys.push(this.notes[i].key)
}
if (prevShiftNoteIndex < 0) {
prevShiftNoteIndex = firstShiftNoteIndex
}
}
this.setState({ this.setState({
selectedNoteKeys selectedNoteKeys,
prevShiftNoteIndex
}) })
router.push({ router.push({
@@ -445,14 +491,21 @@ class NoteList extends React.Component {
}) })
} }
alertIfSnippet () { alertIfSnippet (msg) {
const warningMessage = (msg) => ({
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'print': 'Print'
})[msg]
const targetIndex = this.getTargetIndex() const targetIndex = this.getTargetIndex()
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') { if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Sorry!'), message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'), detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
buttons: [i18n.__('OK'), i18n.__('Cancel')] buttons: [i18n.__('OK')]
}) })
} }
} }
@@ -517,7 +570,7 @@ class NoteList extends React.Component {
click: this.cloneNote.bind(this) click: this.cloneNote.bind(this)
}, { }, {
label: copyNoteLink, label: copyNoteLink,
click: this.copyNoteLink(note) click: this.copyNoteLink.bind(this, note)
}) })
if (note.type === 'MARKDOWN_NOTE') { if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) { if (note.blog && note.blog.blogLink && note.blog.blogId) {
@@ -603,18 +656,21 @@ class NoteList extends React.Component {
}) })
) )
.then((data) => { .then((data) => {
data.forEach((item) => { const dispatchHandler = () => {
dispatch({ data.forEach((item) => {
type: 'DELETE_NOTE', dispatch({
storageKey: item.storageKey, type: 'DELETE_NOTE',
noteKey: item.noteKey storageKey: item.storageKey,
noteKey: item.noteKey
})
}) })
}) }
ee.once('list:next', dispatchHandler)
}) })
.then(() => ee.emit('list:next'))
.catch((err) => { .catch((err) => {
console.error('Cannot Delete note: ' + err) console.error('Cannot Delete note: ' + err)
}) })
console.log('Notes were all deleted')
} else { } else {
if (!confirmDeleteNote(confirmDeletion, false)) return if (!confirmDeleteNote(confirmDeletion, false)) return
@@ -634,8 +690,8 @@ class NoteList extends React.Component {
}) })
}) })
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE') AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
console.log('Notes went to trash')
}) })
.then(() => ee.emit('list:next'))
.catch((err) => { .catch((err) => {
console.error('Notes could not go to trash: ' + err) console.error('Notes could not go to trash: ' + err)
}) })
@@ -659,7 +715,8 @@ class NoteList extends React.Component {
type: firstNote.type, type: firstNote.type,
folder: folder.key, folder: folder.key,
title: firstNote.title + ' ' + i18n.__('copy'), title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content content: firstNote.content,
linesHighlighted: firstNote.linesHighlighted
}) })
.then((note) => { .then((note) => {
attachmentManagement.cloneAttachments(firstNote, note) attachmentManagement.cloneAttachments(firstNote, note)
@@ -687,6 +744,16 @@ class NoteList extends React.Component {
return copy(noteLink) return copy(noteLink)
} }
navigate (sender, pathname) {
const { router } = this.context
router.push({
pathname,
query: {
// key: noteKey
}
})
}
save (note) { save (note) {
const { dispatch } = this.props const { dispatch } = this.props
dataApi dataApi
@@ -984,6 +1051,8 @@ class NoteList extends React.Component {
folderName={this.getNoteFolder(note).name} folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name} storageName={this.getNoteStorage(note).name}
viewType={viewType} viewType={viewType}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
coloredTags={config.coloredTags}
/> />
) )
} }
@@ -1066,4 +1135,4 @@ NoteList.propTypes = {
}) })
} }
export default debounceRender(CSSModules(NoteList, styles)) export default CSSModules(NoteList, styles)

View File

@@ -19,7 +19,7 @@
text-align center text-align center
.top-menu-label .top-menu-label
margin-left 5px margin-left 5px
overflow ellipsis overflow ellipsis
@@ -122,3 +122,8 @@ body[data-theme="monokai"]
.root, .root--folded .root, .root--folded
background-color $ui-monokai-backgroundColor background-color $ui-monokai-backgroundColor
border-right 1px solid $ui-monokai-borderColor border-right 1px solid $ui-monokai-borderColor
body[data-theme="dracula"]
.root, .root--folded
background-color $ui-dracula-backgroundColor
border-right 1px solid $ui-dracula-borderColor

View File

@@ -25,7 +25,8 @@ class StorageItem extends React.Component {
const { storage } = this.props const { storage } = this.props
this.state = { this.state = {
isOpen: !!storage.isOpen isOpen: !!storage.isOpen,
draggedOver: null
} }
} }
@@ -38,6 +39,22 @@ class StorageItem extends React.Component {
{ {
type: 'separator' type: 'separator'
}, },
{
label: i18n.__('Export Storage'),
submenu: [
{
label: i18n.__('Export as txt'),
click: (e) => this.handleExportStorageClick(e, 'txt')
},
{
label: i18n.__('Export as md'),
click: (e) => this.handleExportStorageClick(e, 'md')
}
]
},
{
type: 'separator'
},
{ {
label: i18n.__('Unlink Storage'), label: i18n.__('Unlink Storage'),
click: (e) => this.handleUnlinkStorageClick(e) click: (e) => this.handleUnlinkStorageClick(e)
@@ -68,6 +85,30 @@ class StorageItem extends React.Component {
} }
} }
handleExportStorageClick (e, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: i18n.__('Select directory'),
title: i18n.__('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
.exportStorage(storage.key, fileType, paths[0])
.then(data => {
dispatch({
type: 'EXPORT_STORAGE',
storage: data.storage,
fileType: data.fileType
})
})
}
})
}
handleToggleButtonClick (e) { handleToggleButtonClick (e) {
const { storage, dispatch } = this.props const { storage, dispatch } = this.props
const isOpen = !this.state.isOpen const isOpen = !this.state.isOpen
@@ -164,6 +205,20 @@ class StorageItem extends React.Component {
folderKey: data.folderKey, folderKey: data.folderKey,
fileType: data.fileType fileType: data.fileType
}) })
return data
})
.then(data => {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info',
message: 'Exported to "' + data.exportDir + '"'
})
})
.catch(err => {
dialog.showErrorBox(
'Export error',
err ? err.message || err : 'Unexpected error during export'
)
throw err
}) })
} }
}) })
@@ -191,14 +246,20 @@ class StorageItem extends React.Component {
} }
} }
handleDragEnter (e) { handleDragEnter (e, key) {
e.dataTransfer.setData('defaultColor', e.target.style.backgroundColor) e.preventDefault()
e.target.style.backgroundColor = 'rgba(129, 130, 131, 0.08)' if (this.state.draggedOver === key) { return }
this.setState({
draggedOver: key
})
} }
handleDragLeave (e) { handleDragLeave (e) {
e.target.style.opacity = '1' e.preventDefault()
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') if (this.state.draggedOver === null) { return }
this.setState({
draggedOver: null
})
} }
dropNote (storage, folder, dispatch, location, noteData) { dropNote (storage, folder, dispatch, location, noteData) {
@@ -223,8 +284,12 @@ class StorageItem extends React.Component {
} }
handleDrop (e, storage, folder, dispatch, location) { handleDrop (e, storage, folder, dispatch, location) {
e.target.style.opacity = '1' e.preventDefault()
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') if (this.state.draggedOver !== null) {
this.setState({
draggedOver: null
})
}
const noteData = JSON.parse(e.dataTransfer.getData('note')) const noteData = JSON.parse(e.dataTransfer.getData('note'))
this.dropNote(storage, folder, dispatch, location, noteData) this.dropNote(storage, folder, dispatch, location, noteData)
} }
@@ -234,7 +299,7 @@ class StorageItem extends React.Component {
const { folderNoteMap, trashedSet } = data const { folderNoteMap, trashedSet } = data
const SortableStorageItemChild = SortableElement(StorageItemChild) const SortableStorageItemChild = SortableElement(StorageItemChild)
const folderList = storage.folders.map((folder, index) => { const folderList = storage.folders.map((folder, index) => {
let folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key) const folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
const isActive = !!(location.pathname.match(folderRegex)) const isActive = !!(location.pathname.match(folderRegex))
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key) const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
@@ -251,16 +316,22 @@ class StorageItem extends React.Component {
<SortableStorageItemChild <SortableStorageItemChild
key={folder.key} key={folder.key}
index={index} index={index}
isActive={isActive} isActive={isActive || folder.key === this.state.draggedOver}
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)} handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)} handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
folderName={folder.name} folderName={folder.name}
folderColor={folder.color} folderColor={folder.color}
isFolded={isFolded} isFolded={isFolded}
noteCount={noteCount} noteCount={noteCount}
handleDrop={(e) => this.handleDrop(e, storage, folder, dispatch, location)} handleDrop={(e) => {
handleDragEnter={this.handleDragEnter} this.handleDrop(e, storage, folder, dispatch, location)
handleDragLeave={this.handleDragLeave} }}
handleDragEnter={(e) => {
this.handleDragEnter(e, folder.key)
}}
handleDragLeave={(e) => {
this.handleDragLeave(e, folder)
}}
/> />
) )
}) })

View File

@@ -18,9 +18,32 @@ import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc' import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import { remote } from 'electron'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import ColorPicker from 'browser/components/ColorPicker'
function matchActiveTags (tags, activeTags) {
return _.every(activeTags, v => tags.indexOf(v) >= 0)
}
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
constructor (props) {
super(props)
this.state = {
colorPicker: {
show: false,
color: null,
tagName: null,
targetRect: null
}
}
this.dismissColorPicker = this.dismissColorPicker.bind(this)
this.handleColorPickerConfirm = this.handleColorPickerConfirm.bind(this)
this.handleColorPickerReset = this.handleColorPickerReset.bind(this)
}
componentDidMount () { componentDidMount () {
EventEmitter.on('side:preferences', this.handleMenuButtonClick) EventEmitter.on('side:preferences', this.handleMenuButtonClick)
@@ -30,6 +53,52 @@ class SideNav extends React.Component {
EventEmitter.off('side:preferences', this.handleMenuButtonClick) EventEmitter.off('side:preferences', this.handleMenuButtonClick)
} }
deleteTag (tag) {
const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
ype: 'warning',
message: i18n.__('Confirm tag deletion'),
detail: i18n.__('This will permanently remove this tag.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (selectedButton === 0) {
const { data, dispatch, location, params } = this.props
const notes = data.noteMap
.map(note => note)
.filter(note => note.tags.indexOf(tag) !== -1)
.map(note => {
note = Object.assign({}, note)
note.tags = note.tags.slice()
note.tags.splice(note.tags.indexOf(tag), 1)
return note
})
Promise
.all(notes.map(note => dataApi.updateNote(note.storage, note.key, note)))
.then(updatedNotes => {
updatedNotes.forEach(note => {
dispatch({
type: 'UPDATE_NOTE',
note
})
})
if (location.pathname.match('/tags')) {
const tags = params.tagname.split(' ')
const index = tags.indexOf(tag)
if (index !== -1) {
tags.splice(index, 1)
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`)
}
}
})
}
}
handleMenuButtonClick (e) { handleMenuButtonClick (e) {
openModal(PreferencesModal) openModal(PreferencesModal)
} }
@@ -44,6 +113,72 @@ class SideNav extends React.Component {
router.push('/starred') router.push('/starred')
} }
handleTagContextMenu (e, tag) {
const menu = []
menu.push({
label: i18n.__('Delete Tag'),
click: this.deleteTag.bind(this, tag)
})
menu.push({
label: i18n.__('Customize Color'),
click: this.displayColorPicker.bind(this, tag, e.target.getBoundingClientRect())
})
context.popup(menu)
}
dismissColorPicker () {
this.setState({
colorPicker: {
show: false
}
})
}
displayColorPicker (tagName, rect) {
const { config } = this.props
this.setState({
colorPicker: {
show: true,
color: config.coloredTags[tagName],
tagName,
targetRect: rect
}
})
}
handleColorPickerConfirm (color) {
const { dispatch, config: {coloredTags} } = this.props
const { colorPicker: { tagName } } = this.state
const newColoredTags = Object.assign({}, coloredTags, {[tagName]: color.hex})
const config = { coloredTags: newColoredTags }
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
this.dismissColorPicker()
}
handleColorPickerReset () {
const { dispatch, config: {coloredTags} } = this.props
const { colorPicker: { tagName } } = this.state
const newColoredTags = Object.assign({}, coloredTags)
delete newColoredTags[tagName]
const config = { coloredTags: newColoredTags }
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
this.dismissColorPicker()
}
handleToggleButtonClick (e) { handleToggleButtonClick (e) {
const { dispatch, config } = this.props const { dispatch, config } = this.props
@@ -144,12 +279,21 @@ class SideNav extends React.Component {
tagListComponent () { tagListComponent () {
const { data, location, config } = this.props const { data, location, config } = this.props
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap) const { colorPicker } = this.state
const activeTags = this.getActiveTags(location.pathname)
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
let tagList = _.sortBy(data.tagNoteMap.map( let tagList = _.sortBy(data.tagNoteMap.map(
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) }) (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
), ['name']).filter( ).filter(
tag => tag.size > 0 tag => tag.size > 0
) ), ['name'])
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
const notesTags = data.noteMap.map(note => note.tags)
tagList = tagList.map(tag => {
tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length
return tag
})
}
if (config.sortTagsBy === 'COUNTER') { if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size)) tagList = _.sortBy(tagList, item => (0 - item.size))
} }
@@ -165,10 +309,12 @@ class SideNav extends React.Component {
name={tag.name} name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)} handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)} handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)} handleContextMenu={this.handleTagContextMenu.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name) || (colorPicker.tagName === tag.name)}
isRelated={tag.related} isRelated={tag.related}
key={tag.name} key={tag.name}
count={tag.size} count={tag.size}
color={config.coloredTags[tag.name]}
/> />
) )
}) })
@@ -198,12 +344,12 @@ class SideNav extends React.Component {
const tags = pathSegments[pathSegments.length - 1] const tags = pathSegments[pathSegments.length - 1]
return (tags === 'alltags') return (tags === 'alltags')
? [] ? []
: tags.split(' ') : decodeURIComponent(tags).split(' ')
} }
handleClickTagListItem (name) { handleClickTagListItem (name) {
const { router } = this.context const { router } = this.context
router.push(`/tags/${name}`) router.push(`/tags/${encodeURIComponent(name)}`)
} }
handleSortTagsByChange (e) { handleSortTagsByChange (e) {
@@ -230,7 +376,7 @@ class SideNav extends React.Component {
} else { } else {
listOfTags.push(tag) listOfTags.push(tag)
} }
router.push(`/tags/${listOfTags.join(' ')}`) router.push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`)
} }
emptyTrash (entries) { emptyTrash (entries) {
@@ -238,6 +384,8 @@ class SideNav extends React.Component {
const deletionPromises = entries.map((note) => { const deletionPromises = entries.map((note) => {
return dataApi.deleteNote(note.storage, note.key) return dataApi.deleteNote(note.storage, note.key)
}) })
const { confirmDeletion } = this.props.config.ui
if (!confirmDeleteNote(confirmDeletion, true)) return
Promise.all(deletionPromises) Promise.all(deletionPromises)
.then((arrayOfStorageAndNoteKeys) => { .then((arrayOfStorageAndNoteKeys) => {
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => { arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
@@ -247,7 +395,6 @@ class SideNav extends React.Component {
.catch((err) => { .catch((err) => {
console.error('Cannot Delete note: ' + err) console.error('Cannot Delete note: ' + err)
}) })
console.log('Trash emptied')
} }
handleFilterButtonContextMenu (event) { handleFilterButtonContextMenu (event) {
@@ -260,6 +407,7 @@ class SideNav extends React.Component {
render () { render () {
const { data, location, config, dispatch } = this.props const { data, location, config, dispatch } = this.props
const { colorPicker: colorPickerState } = this.state
const isFolded = config.isSideNavFolded const isFolded = config.isSideNavFolded
@@ -276,6 +424,20 @@ class SideNav extends React.Component {
useDragHandle useDragHandle
/> />
}) })
let colorPicker
if (colorPickerState.show) {
colorPicker = (
<ColorPicker
color={colorPickerState.color}
targetRect={colorPickerState.targetRect}
onConfirm={this.handleColorPickerConfirm}
onCancel={this.dismissColorPicker}
onReset={this.handleColorPickerReset}
/>
)
}
const style = {} const style = {}
if (!isFolded) style.width = this.props.width if (!isFolded) style.width = this.props.width
const isTagActive = location.pathname.match(/tag/) const isTagActive = location.pathname.match(/tag/)
@@ -295,6 +457,7 @@ class SideNav extends React.Component {
</div> </div>
</div> </div>
{this.SideNavComponent(isFolded, storageList)} {this.SideNavComponent(isFolded, storageList)}
{colorPicker}
</div> </div>
) )
} }

View File

@@ -34,7 +34,7 @@
color $ui-active-color color $ui-active-color
span span
margin-left 5px margin-left 5px
.update .update
navButtonColor() navButtonColor()
height 24px height 24px
@@ -47,6 +47,14 @@
.update-icon .update-icon
color $brand-color color $brand-color
body[data-theme="default"]
.zoom
color $ui-text-color
body[data-theme="white"]
.zoom
color $ui-text-color
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
@@ -80,3 +88,14 @@ body[data-theme="monokai"]
color $ui-monokai-active-color color $ui-monokai-active-color
&:active &:active
color $ui-monokai-active-color color $ui-monokai-active-color
body[data-theme="dracula"]
navButtonColor()
.zoom
border-color $ui-dark-borderColor
color $ui-dracula-text-color
&:hover
transition 0.15s
color $ui-dracula-active-color
&:active
color $ui-dracula-active-color

View File

@@ -5,6 +5,7 @@ import styles from './StatusBar.styl'
import ZoomManager from 'browser/main/lib/ZoomManager' import ZoomManager from 'browser/main/lib/ZoomManager'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import EventEmitter from 'browser/main/lib/eventEmitter'
const electron = require('electron') const electron = require('electron')
const { remote, ipcRenderer } = electron const { remote, ipcRenderer } = electron
@@ -13,6 +14,26 @@ const { dialog } = remote
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0] const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
class StatusBar extends React.Component { class StatusBar extends React.Component {
constructor (props) {
super(props)
this.handleZoomInMenuItem = this.handleZoomInMenuItem.bind(this)
this.handleZoomOutMenuItem = this.handleZoomOutMenuItem.bind(this)
this.handleZoomResetMenuItem = this.handleZoomResetMenuItem.bind(this)
}
componentDidMount () {
EventEmitter.on('status:zoomin', this.handleZoomInMenuItem)
EventEmitter.on('status:zoomout', this.handleZoomOutMenuItem)
EventEmitter.on('status:zoomreset', this.handleZoomResetMenuItem)
}
componentWillUnmount () {
EventEmitter.off('status:zoomin', this.handleZoomInMenuItem)
EventEmitter.off('status:zoomout', this.handleZoomOutMenuItem)
EventEmitter.off('status:zoomreset', this.handleZoomResetMenuItem)
}
updateApp () { updateApp () {
const index = dialog.showMessageBox(remote.getCurrentWindow(), { const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
@@ -48,6 +69,20 @@ class StatusBar extends React.Component {
}) })
} }
handleZoomInMenuItem () {
const zoomFactor = ZoomManager.getZoom() + 0.1
this.handleZoomMenuItemClick(zoomFactor)
}
handleZoomOutMenuItem () {
const zoomFactor = ZoomManager.getZoom() - 0.1
this.handleZoomMenuItemClick(zoomFactor)
}
handleZoomResetMenuItem () {
this.handleZoomMenuItemClick(1.0)
}
render () { render () {
const { config, status } = this.context const { config, status } = this.context

View File

@@ -256,3 +256,25 @@ body[data-theme="monokai"]
input input
background-color $ui-monokai-noteList-backgroundColor background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color color $ui-monokai-text-color
body[data-theme="dracula"]
.root, .root--expanded
background-color $ui-dracula-noteList-backgroundColor
.control
border-color $ui-dracula-borderColor
.control-search
background-color $ui-dracula-noteList-backgroundColor
.control-search-icon
absolute top bottom left
line-height 32px
width 35px
color $ui-dracula-inactive-text-color
background-color $ui-dracula-noteList-backgroundColor
.control-search-input
background-color $ui-dracula-noteList-backgroundColor
input
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color

View File

@@ -6,6 +6,7 @@ import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton' import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import debounce from 'lodash/debounce'
class TopBar extends React.Component { class TopBar extends React.Component {
constructor (props) { constructor (props) {
@@ -25,6 +26,10 @@ class TopBar extends React.Component {
} }
this.codeInitHandler = this.handleCodeInit.bind(this) this.codeInitHandler = this.handleCodeInit.bind(this)
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
maxWait: 1000 / 8
})
} }
componentDidMount () { componentDidMount () {
@@ -94,7 +99,6 @@ class TopBar extends React.Component {
} }
handleKeyUp (e) { handleKeyUp (e) {
const { router } = this.context
// reset states // reset states
this.setState({ this.setState({
isConfirmTranslation: false isConfirmTranslation: false
@@ -106,21 +110,21 @@ class TopBar extends React.Component {
isConfirmTranslation: true isConfirmTranslation: true
}) })
const keyword = this.refs.searchInput.value const keyword = this.refs.searchInput.value
router.push(`/searched/${encodeURIComponent(keyword)}`) this.updateKeyword(keyword)
this.setState({
search: keyword
})
} }
} }
handleSearchChange (e) { handleSearchChange (e) {
const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) { if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push(`/searched/${encodeURIComponent(keyword)}`) const keyword = this.refs.searchInput.value
this.updateKeyword(keyword)
} else { } else {
e.preventDefault() e.preventDefault()
} }
}
updateKeyword (keyword) {
this.context.router.push(`/searched/${encodeURIComponent(keyword)}`)
this.setState({ this.setState({
search: keyword search: keyword
}) })

View File

@@ -18,6 +18,9 @@ body
::-webkit-scrollbar ::-webkit-scrollbar
width 12px width 12px
::-webkit-scrollbar-corner
background-color: transparent;
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.15) background-color rgba(0, 0, 0, 0.15)
@@ -94,6 +97,7 @@ modalBackColor = white
body[data-theme="dark"] body[data-theme="dark"]
background-color $ui-dark-backgroundColor
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3) background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
@@ -132,10 +136,20 @@ body[data-theme="dark"]
.CodeMirror-foldgutter-folded:after .CodeMirror-foldgutter-folded:after
content: "\25B8" content: "\25B8"
.CodeMirror-hover
padding 2px 4px 0 4px
position absolute
z-index 99
.CodeMirror-hyperlink
cursor pointer
.sortableItemHelper .sortableItemHelper
z-index modalZIndex + 5 z-index modalZIndex + 5
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-backgroundColor
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3) background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
@@ -145,6 +159,7 @@ body[data-theme="solarized-dark"]
color: $ui-solarized-dark-text-color color: $ui-solarized-dark-text-color
body[data-theme="monokai"] body[data-theme="monokai"]
background-color $ui-monokai-backgroundColor
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3) background-color rgba(0, 0, 0, 0.3)
.ModalBase .ModalBase
@@ -153,6 +168,18 @@ body[data-theme="monokai"]
.sortableItemHelper .sortableItemHelper
color: $ui-monokai-text-color color: $ui-monokai-text-color
body[data-theme="dracula"]
background-color $ui-dracula-backgroundColor
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-dracula-backgroundColor
.sortableItemHelper
color: $ui-dracula-text-color
body[data-theme="default"] body[data-theme="default"]
.SideNav ::-webkit-scrollbar-thumb .SideNav ::-webkit-scrollbar-thumb
background-color rgba(255, 255, 255, 0.3) background-color rgba(255, 255, 255, 0.3)
@import '../styles/Detail/TagSelect.styl'

View File

@@ -45,7 +45,6 @@ function initAwsMobileAnalytics () {
if (getSendEventCond()) return if (getSendEventCond()) return
AWS.config.credentials.get((err) => { AWS.config.credentials.get((err) => {
if (!err) { if (!err) {
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
recordDynamicCustomEvent('APP_STARTED') recordDynamicCustomEvent('APP_STARTED')
recordStaticCustomEvent() recordStaticCustomEvent()
} }
@@ -58,7 +57,7 @@ function recordDynamicCustomEvent (type, options = {}) {
mobileAnalyticsClient.recordEvent(type, options) mobileAnalyticsClient.recordEvent(type, options)
} catch (analyticsError) { } catch (analyticsError) {
if (analyticsError instanceof ReferenceError) { if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message) console.error(analyticsError.name + ': ' + analyticsError.message)
} }
} }
} }
@@ -71,7 +70,7 @@ function recordStaticCustomEvent () {
}) })
} catch (analyticsError) { } catch (analyticsError) {
if (analyticsError instanceof ReferenceError) { if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message) console.error(analyticsError.name + ': ' + analyticsError.message)
} }
} }
} }

View File

@@ -24,29 +24,42 @@ export const DEFAULT_CONFIG = {
amaEnabled: true, amaEnabled: true,
hotkey: { hotkey: {
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E', toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Command + M' : 'Ctrl + M' toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
toggleMenuBar: 'Alt'
}, },
ui: { ui: {
language: 'en', language: 'en',
theme: 'default', theme: 'default',
showCopyNotification: true, showCopyNotification: true,
disableDirectWrite: false, disableDirectWrite: false,
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE' defaultNote: 'ALWAYS_ASK', // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
showMenuBar: false
}, },
editor: { editor: {
theme: 'base16-light', theme: 'base16-light',
keyMap: 'sublime', keyMap: 'sublime',
fontSize: '14', fontSize: '14',
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas', fontFamily: win ? 'Consolas' : 'Monaco',
indentType: 'space', indentType: 'space',
indentSize: '2', indentSize: '2',
enableRulers: false, enableRulers: false,
rulers: [80, 120], rulers: [80, 120],
displayLineNumbers: true, displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR matchingPairs: '()[]{}\'\'""$$**``',
matchingTriples: '```"""\'\'\'',
explodingPairs: '[]{}``$$',
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
scrollPastEnd: false, scrollPastEnd: false,
type: 'SPLIT', type: 'SPLIT', // 'SPLIT', 'EDITOR_PREVIEW'
fetchUrlTitle: true fetchUrlTitle: true,
enableTableEditor: false,
enableFrontMatterTitle: true,
frontMatterTitleField: 'title',
spellcheck: false,
enableSmartPaste: false
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',
@@ -59,12 +72,14 @@ export const DEFAULT_CONFIG = {
latexBlockClose: '$$', latexBlockClose: '$$',
plantUMLServerAddress: 'http://www.plantuml.com/plantuml', plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
scrollPastEnd: false, scrollPastEnd: false,
scrollSync: true,
smartQuotes: true, smartQuotes: true,
breaks: true, breaks: true,
smartArrows: false, smartArrows: false,
allowCustomCSS: false, allowCustomCSS: false,
customCSS: '', customCSS: '',
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE' sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
lineThroughCheckbox: true
}, },
blog: { blog: {
type: 'wordpress', // Available value: wordpress, add more types in the future plz type: 'wordpress', // Available value: wordpress, add more types in the future plz
@@ -73,7 +88,8 @@ export const DEFAULT_CONFIG = {
token: '', token: '',
username: '', username: '',
password: '' password: ''
} },
coloredTags: {}
} }
function validate (config) { function validate (config) {
@@ -146,6 +162,8 @@ function set (updates) {
document.body.setAttribute('data-theme', 'solarized-dark') document.body.setAttribute('data-theme', 'solarized-dark')
} else if (newConfig.ui.theme === 'monokai') { } else if (newConfig.ui.theme === 'monokai') {
document.body.setAttribute('data-theme', 'monokai') document.body.setAttribute('data-theme', 'monokai')
} else if (newConfig.ui.theme === 'dracula') {
document.body.setAttribute('data-theme', 'dracula')
} else { } else {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }
@@ -193,7 +211,8 @@ function assignConfigValues (originalConfig, rcConfig) {
function rewriteHotkey (config) { function rewriteHotkey (config) {
const keys = [...Object.keys(config.hotkey)] const keys = [...Object.keys(config.hotkey)]
keys.forEach(key => { keys.forEach(key => {
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command') config.hotkey[key] = config.hotkey[key].replace(/Cmd\s/g, 'Command ')
config.hotkey[key] = config.hotkey[key].replace(/Opt\s/g, 'Option ')
}) })
return config return config
} }

View File

@@ -6,11 +6,132 @@ const mdurl = require('mdurl')
const fse = require('fs-extra') const fse = require('fs-extra')
const escapeStringRegexp = require('escape-string-regexp') const escapeStringRegexp = require('escape-string-regexp')
const sander = require('sander') const sander = require('sander')
const url = require('url')
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
const STORAGE_FOLDER_PLACEHOLDER = ':storage' const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments' const DESTINATION_FOLDER = 'attachments'
const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep) const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
/**
* @description
* Create a Image element to get the real size of image.
* @param {File} file the File object dropped.
* @returns {Promise<Image>} Image element created
*/
function getImage (file) {
if (_.isString(file)) {
return new Promise(resolve => {
const img = new Image()
img.onload = () => resolve(img)
img.src = file
})
} else {
return new Promise(resolve => {
const reader = new FileReader()
const img = new Image()
img.onload = () => resolve(img)
reader.onload = e => {
img.src = e.target.result
}
reader.readAsDataURL(file)
})
}
}
/**
* @description
* Get the orientation info from iamges's EXIF data.
* case 1: The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
* case 2: The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
* case 3: The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
* case 4: The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
* case 5: The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
* case 6: The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
* case 7: The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
* case 8: The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
* Other: reserved
* ref: http://sylvana.net/jpegcrop/exif_orientation.html
* @param {File} file the File object dropped.
* @returns {Promise<Number>} Orientation info
*/
function getOrientation (file) {
const getData = arrayBuffer => {
const view = new DataView(arrayBuffer)
// Not start with SOI(Start of image) Marker return fail value
if (view.getUint16(0, false) !== 0xFFD8) return -2
const length = view.byteLength
let offset = 2
while (offset < length) {
const marker = view.getUint16(offset, false)
offset += 2
// Loop and seed for APP1 Marker
if (marker === 0xFFE1) {
// return fail value if it isn't EXIF data
if (view.getUint32(offset += 2, false) !== 0x45786966) {
return -1
}
// Read TIFF header,
// First 2bytes defines byte align of TIFF data.
// If it is 0x4949="II", it means "Intel" type byte align.
// If it is 0x4d4d="MM", it means "Motorola" type byte align
const little = view.getUint16(offset += 6, false) === 0x4949
offset += view.getUint32(offset + 4, little)
const tags = view.getUint16(offset, little) // Get TAG number
offset += 2
for (let i = 0; i < tags; i++) {
// Loop to find Orientation TAG and return the value
if (view.getUint16(offset + (i * 12), little) === 0x0112) {
return view.getUint16(offset + (i * 12) + 8, little)
}
}
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker
break
} else {
offset += view.getUint16(offset, false)
}
}
return -1
}
return new Promise((resolve) => {
const reader = new FileReader()
reader.onload = event => resolve(getData(event.target.result))
reader.readAsArrayBuffer(file.slice(0, 64 * 1024))
})
}
/**
* @description
* Rotate image file to correct direction.
* Create a canvas and draw the image with correct direction, then export to base64 format.
* @param {*} file the File object dropped.
* @return {String} Base64 encoded image.
*/
function fixRotate (file) {
return Promise.all([getImage(file), getOrientation(file)])
.then(([img, orientation]) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (orientation > 4 && orientation < 9) {
canvas.width = img.height
canvas.height = img.width
} else {
canvas.width = img.width
canvas.height = img.height
}
switch (orientation) {
case 2: ctx.transform(-1, 0, 0, 1, img.width, 0); break
case 3: ctx.transform(-1, 0, 0, -1, img.width, img.height); break
case 4: ctx.transform(1, 0, 0, -1, 0, img.height); break
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break
case 6: ctx.transform(0, 1, -1, 0, img.height, 0); break
case 7: ctx.transform(0, -1, -1, 0, img.height, img.width); break
case 8: ctx.transform(0, -1, 1, 0, 0, img.width); break
default: break
}
ctx.drawImage(img, 0, 0)
return canvas.toDataURL()
})
}
/** /**
* @description * @description
@@ -38,26 +159,39 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
} }
try { try {
if (!fs.existsSync(sourceFilePath)) { const isBase64 = typeof sourceFilePath === 'object' && sourceFilePath.type === 'base64'
reject('source file does not exist') if (!isBase64 && !fs.existsSync(sourceFilePath)) {
return reject('source file does not exist')
}
const sourcePath = sourceFilePath.sourceFilePath || sourceFilePath
const sourceURL = url.parse(/^\w+:\/\//.test(sourcePath) ? sourcePath : 'file:///' + sourcePath)
let destinationName
if (useRandomName) {
destinationName = `${uniqueSlug()}${path.extname(sourceURL.pathname) || '.png'}`
} else {
destinationName = path.basename(sourceURL.pathname)
} }
const targetStorage = findStorage.findStorage(storageKey) const targetStorage = findStorage.findStorage(storageKey)
const inputFileStream = fs.createReadStream(sourceFilePath)
let destinationName
if (useRandomName) {
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
} else {
destinationName = path.basename(sourceFilePath)
}
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
createAttachmentDestinationFolder(targetStorage.path, noteKey) createAttachmentDestinationFolder(targetStorage.path, noteKey)
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName)) const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
inputFileStream.pipe(outputFile)
inputFileStream.on('end', () => { if (isBase64) {
resolve(destinationName) const base64Data = sourceFilePath.data.replace(/^data:image\/\w+;base64,/, '')
}) const dataBuffer = Buffer.from(base64Data, 'base64')
outputFile.write(dataBuffer, () => {
resolve(destinationName)
})
} else {
const inputFileStream = fs.createReadStream(sourceFilePath)
inputFileStream.pipe(outputFile)
inputFileStream.on('end', () => {
resolve(destinationName)
})
}
} catch (e) { } catch (e) {
return reject(e) return reject(e)
} }
@@ -107,7 +241,15 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths. * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/ */
function fixLocalURLS (renderedHTML, storagePath) { function fixLocalURLS (renderedHTML, storagePath) {
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', 'g'), function (match) { /*
A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`.
- `STORAGE_FOLDER_PLACEHOLDER` will match `:storage`
- `(?:(?:\\\/|%5C)[-.\\w]+)+` will match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`
- `(?:\\\/|%5C)[-.\\w]+` will either match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564` or `/f939b2c3.jpg`
- `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows.
*/
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[-.\\w]+)+', 'g'), function (match) {
var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g') var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER)) return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
}) })
@@ -133,15 +275,69 @@ function generateAttachmentMarkdown (fileName, path, showPreview) {
* @param {Event} dropEvent DropEvent * @param {Event} dropEvent DropEvent
*/ */
function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
const file = dropEvent.dataTransfer.files[0] let promise
const filePath = file.path if (dropEvent.dataTransfer.files.length > 0) {
const originalFileName = path.basename(filePath) promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => {
const fileType = file['type'] if (file.type.startsWith('image')) {
if (file.type === 'image/gif' || file.type === 'image/svg+xml') {
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
fileName,
title: path.basename(file.path),
isImage: true
}))
} else {
return fixRotate(file)
.then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey)
.then(fileName => ({
fileName,
title: path.basename(file.path),
isImage: true
}))
)
}
} else {
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
fileName,
title: path.basename(file.path),
isImage: false
}))
}
}))
} else {
let imageURL = dropEvent.dataTransfer.getData('text/plain')
copyAttachment(filePath, storageKey, noteKey).then((fileName) => { if (!imageURL) {
const showPreview = fileType.startsWith('image') const match = /<img[^>]*[\s"']src="([^"]+)"/.exec(dropEvent.dataTransfer.getData('text/html'))
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) if (match) {
codeEditor.insertAttachmentMd(imageMd) imageURL = match[1]
}
}
if (!imageURL) {
return
}
promise = Promise.all([getImage(imageURL)
.then(image => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = image.width
canvas.height = image.height
context.drawImage(image, 0, 0)
return copyAttachment({type: 'base64', data: canvas.toDataURL(), sourceFilePath: imageURL}, storageKey, noteKey)
})
.then(fileName => ({
fileName,
title: imageURL,
isImage: true
}))])
}
promise.then(files => {
const attachments = files.filter(file => !!file).map(file => generateAttachmentMarkdown(file.title, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, file.fileName), file.isImage))
codeEditor.insertAttachmentMd(attachments.join('\n'))
}) })
} }
@@ -152,7 +348,7 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
* @param {String} noteKey Key of the current note * @param {String} noteKey Key of the current note
* @param {DataTransferItem} dataTransferItem Part of the past-event * @param {DataTransferItem} dataTransferItem Part of the past-event
*/ */
function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) { function handlePasteImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) {
if (!codeEditor) { if (!codeEditor) {
throw new Error('codeEditor has to be given') throw new Error('codeEditor has to be given')
} }
@@ -189,6 +385,44 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
reader.readAsDataURL(blob) reader.readAsDataURL(blob)
} }
/**
* @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code
* @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code
* @param {String} storageKey Key of the current storage
* @param {String} noteKey Key of the current note
* @param {NativeImage} image The native image
*/
function handlePasteNativeImage (codeEditor, storageKey, noteKey, image) {
if (!codeEditor) {
throw new Error('codeEditor has to be given')
}
if (!storageKey) {
throw new Error('storageKey has to be given')
}
if (!noteKey) {
throw new Error('noteKey has to be given')
}
if (!image) {
throw new Error('image has to be given')
}
const targetStorage = findStorage.findStorage(storageKey)
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
createAttachmentDestinationFolder(targetStorage.path, noteKey)
const imageName = `${uniqueSlug()}.png`
const imagePath = path.join(destinationDir, imageName)
const binaryData = image.toPNG()
fs.writeFileSync(imagePath, binaryData, 'binary')
const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName)
const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true)
codeEditor.insertAttachmentMd(imageMd)
}
/** /**
* @description Returns all attachment paths of the given markdown * @description Returns all attachment paths of the given markdown
* @param {String} markdownContent content in which the attachment paths should be found * @param {String} markdownContent content in which the attachment paths should be found
@@ -256,7 +490,14 @@ function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
* @returns {String} Input without the references * @returns {String} Input without the references
*/ */
function removeStorageAndNoteReferences (input, noteKey) { function removeStorageAndNoteReferences (input, noteKey) {
return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER) return input.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?("|])', 'g'), function (match) {
const temp = match
.replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), path.sep)
.replace(new RegExp(mdurl.encode(path.posix.sep), 'g'), path.sep)
.replace(new RegExp(escapeStringRegexp(path.win32.sep), 'g'), path.sep)
.replace(new RegExp(escapeStringRegexp(path.posix.sep), 'g'), path.sep)
return temp.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + noteKey + ')?', 'g'), DESTINATION_FOLDER)
})
} }
/** /**
@@ -402,7 +643,6 @@ function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
return modifiedLinkText return modifiedLinkText
}) })
} else { } else {
console.log('One if the parameters was null -> Do nothing..')
return Promise.resolve(linkText) return Promise.resolve(linkText)
} }
} }
@@ -412,7 +652,8 @@ module.exports = {
fixLocalURLS, fixLocalURLS,
generateAttachmentMarkdown, generateAttachmentMarkdown,
handleAttachmentDrop, handleAttachmentDrop,
handlePastImageEvent, handlePasteImageEvent,
handlePasteNativeImage,
getAttachmentsInMarkdownContent, getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences, removeStorageAndNoteReferences,

View File

@@ -16,7 +16,7 @@ function copyFile (srcPath, dstPath) {
const dstFolder = path.dirname(dstPath) const dstFolder = path.dirname(dstPath)
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder) if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
const input = fs.createReadStream(srcPath) const input = fs.createReadStream(decodeURI(srcPath))
const output = fs.createWriteStream(dstPath) const output = fs.createWriteStream(dstPath)
output.on('error', reject) output.on('error', reject)

View File

@@ -16,6 +16,7 @@ function validateInput (input) {
switch (input.type) { switch (input.type) {
case 'MARKDOWN_NOTE': case 'MARKDOWN_NOTE':
if (!_.isString(input.content)) input.content = '' if (!_.isString(input.content)) input.content = ''
if (!_.isArray(input.linesHighlighted)) input.linesHighlighted = []
break break
case 'SNIPPET_NOTE': case 'SNIPPET_NOTE':
if (!_.isString(input.description)) input.description = '' if (!_.isString(input.description)) input.description = ''
@@ -23,7 +24,8 @@ function validateInput (input) {
input.snippets = [{ input.snippets = [{
name: '', name: '',
mode: 'text', mode: 'text',
content: '' content: '',
linesHighlighted: []
}] }]
} }
break break

View File

@@ -9,7 +9,8 @@ function createSnippet (snippetFile) {
id: crypto.randomBytes(16).toString('hex'), id: crypto.randomBytes(16).toString('hex'),
name: 'Unnamed snippet', name: 'Unnamed snippet',
prefix: [], prefix: [],
content: '' content: '',
linesHighlighted: []
} }
fetchSnippet(null, snippetFile).then((snippets) => { fetchSnippet(null, snippetFile).then((snippets) => {
snippets.push(newSnippet) snippets.push(newSnippet)

View File

@@ -1,9 +1,9 @@
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData' import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes' import resolveStorageNotes from './resolveStorageNotes'
import exportNote from './exportNote'
import filenamify from 'filenamify' import filenamify from 'filenamify'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs'
/** /**
* @param {String} storageKey * @param {String} storageKey
@@ -45,9 +45,9 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
notes notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE') .filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(snippet => { .forEach(note => {
const notePath = path.join(exportDir, `${filenamify(snippet.title, {replacement: '_'})}.${fileType}`) const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
fs.writeFileSync(notePath, snippet.content) exportNote(note.key, storage.path, note.content, notePath, null)
}) })
return { return {

View File

@@ -4,27 +4,43 @@ import { findStorage } from 'browser/lib/findStorage'
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const attachmentManagement = require('./attachmentManagement')
/** /**
* Export note together with images * Export note together with attachments
* *
* If images is stored in the storage, creates 'images' subfolder in target directory * If attachments are stored in the storage, creates 'attachments' subfolder in target directory
* and copies images to it. Changes links to images in the content of the note * and copies attachments to it. Changes links to images in the content of the note
* *
* @param {String} nodeKey key of the node that should be exported
* @param {String} storageKey or storage path * @param {String} storageKey or storage path
* @param {String} noteContent Content to export * @param {String} noteContent Content to export
* @param {String} targetPath Path to exported file * @param {String} targetPath Path to exported file
* @param {function} outputFormatter * @param {function} outputFormatter
* @return {Promise.<*[]>} * @return {Promise.<*[]>}
*/ */
function exportNote (storageKey, noteContent, targetPath, outputFormatter) { function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatter) {
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
const exportTasks = [] const exportTasks = []
if (!storagePath) { if (!storagePath) {
throw new Error('Storage path is not found') throw new Error('Storage path is not found')
} }
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
noteContent,
storagePath
)
attachmentsAbsolutePaths.forEach(attachment => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
let exportedData = noteContent let exportedData = attachmentManagement.removeStorageAndNoteReferences(
noteContent,
nodeKey
)
if (outputFormatter) { if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks) exportedData = outputFormatter(exportedData, exportTasks)

View File

@@ -0,0 +1,63 @@
import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes'
import filenamify from 'filenamify'
import * as path from 'path'
import * as fs from 'fs'
/**
* @param {String} storageKey
* @param {String} fileType
* @param {String} exportDir
*
* @return {Object}
* ```
* {
* storage: Object,
* fileType: String,
* exportDir: String
* }
* ```
*/
function exportStorage (storageKey, fileType, exportDir) {
let targetStorage
try {
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}
return resolveStorageData(targetStorage)
.then(storage => (
resolveStorageNotes(storage).then(notes => ({storage, notes}))
))
.then(function exportNotes (data) {
const { storage, notes } = data
const folderNamesMapping = {}
storage.folders.forEach(folder => {
const folderExportedDir = path.join(exportDir, filenamify(folder.name, {replacement: '_'}))
folderNamesMapping[folder.key] = folderExportedDir
// make sure directory exists
try {
fs.mkdirSync(folderExportedDir)
} catch (e) {}
})
notes
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
.forEach(markdownNote => {
const folderExportedDir = folderNamesMapping[markdownNote.folder]
const snippetName = `${filenamify(markdownNote.title, {replacement: '_'})}.${fileType}`
const notePath = path.join(folderExportedDir, snippetName)
fs.writeFileSync(notePath, markdownNote.content)
})
return {
storage,
fileType,
exportDir
}
})
}
module.exports = exportStorage

View File

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

View File

@@ -4,6 +4,7 @@ const resolveStorageData = require('./resolveStorageData')
const resolveStorageNotes = require('./resolveStorageNotes') const resolveStorageNotes = require('./resolveStorageNotes')
const consts = require('browser/lib/consts') const consts = require('browser/lib/consts')
const path = require('path') const path = require('path')
const fs = require('fs')
const CSON = require('@rokt33r/season') const CSON = require('@rokt33r/season')
/** /**
* @return {Object} all storages and notes * @return {Object} all storages and notes
@@ -19,11 +20,14 @@ const CSON = require('@rokt33r/season')
* 2. legacy * 2. legacy
* 3. empty directory * 3. empty directory
*/ */
function init () { function init () {
const fetchStorages = function () { const fetchStorages = function () {
let rawStorages let rawStorages
try { try {
rawStorages = JSON.parse(window.localStorage.getItem('storages')) rawStorages = JSON.parse(window.localStorage.getItem('storages'))
// Remove storages who's location is inaccesible.
rawStorages = rawStorages.filter(storage => fs.existsSync(storage.path))
if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.') if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.')
} catch (e) { } catch (e) {
console.warn('Failed to parse cached data from localStorage', e) console.warn('Failed to parse cached data from localStorage', e)
@@ -36,6 +40,7 @@ function init () {
const fetchNotes = function (storages) { const fetchNotes = function (storages) {
const findNotesFromEachStorage = storages const findNotesFromEachStorage = storages
.filter(storage => fs.existsSync(storage.path))
.map((storage) => { .map((storage) => {
return resolveStorageNotes(storage) return resolveStorageNotes(storage)
.then((notes) => { .then((notes) => {
@@ -51,7 +56,11 @@ function init () {
} }
}) })
if (unknownCount > 0) { if (unknownCount > 0) {
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) try {
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
} catch (e) {
console.log('Error writting boostnote.json: ' + e + ' from init.js')
}
} }
return notes return notes
}) })

View File

@@ -69,7 +69,8 @@ function importAll (storage, data) {
isStarred: false, isStarred: false,
title: article.title, title: article.title,
content: '# ' + article.title + '\n\n' + article.content, content: '# ' + article.title + '\n\n' + article.content,
key: noteKey key: noteKey,
linesHighlighted: article.linesHighlighted
} }
notes.push(newNote) notes.push(newNote)
} else { } else {
@@ -87,7 +88,8 @@ function importAll (storage, data) {
snippets: [{ snippets: [{
name: article.mode, name: article.mode,
mode: article.mode, mode: article.mode,
content: article.content content: article.content,
linesHighlighted: article.linesHighlighted
}] }]
} }
notes.push(newNote) notes.push(newNote)

View File

@@ -14,7 +14,6 @@ function renameStorage (key, name) {
cachedStorageList = JSON.parse(localStorage.getItem('storages')) cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages') if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
} catch (err) { } catch (err) {
console.log('error got')
console.error(err) console.error(err)
return Promise.reject(err) return Promise.reject(err)
} }

View File

@@ -31,13 +31,9 @@ function resolveStorageData (storageCache) {
const version = parseInt(storage.version, 10) const version = parseInt(storage.version, 10)
if (version >= 1) { if (version >= 1) {
if (version > 1) {
console.log('The repository version is newer than one of current app.')
}
return Promise.resolve(storage) return Promise.resolve(storage)
} }
console.log('Transform Legacy storage', storage.path)
return migrateFromV6Storage(storage.path) return migrateFromV6Storage(storage.path)
.then(() => storage) .then(() => storage)
} }

View File

@@ -9,7 +9,7 @@ function resolveStorageNotes (storage) {
notePathList = sander.readdirSync(notesDirPath) notePathList = sander.readdirSync(notesDirPath)
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
console.log(notesDirPath, ' doesn\'t exist.') console.error(notesDirPath, ' doesn\'t exist.')
sander.mkdirSync(notesDirPath) sander.mkdirSync(notesDirPath)
} else { } else {
console.warn('Failed to find note dir', notesDirPath, err) console.warn('Failed to find note dir', notesDirPath, err)

View File

@@ -12,7 +12,6 @@ function toggleStorage (key, isOpen) {
cachedStorageList = JSON.parse(localStorage.getItem('storages')) cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages') if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
} catch (err) { } catch (err) {
console.log('error got')
console.error(err) console.error(err)
return Promise.reject(err) return Promise.reject(err)
} }

View File

@@ -39,6 +39,9 @@ function validateInput (input) {
if (input.content != null) { if (input.content != null) {
if (!_.isString(input.content)) validatedInput.content = '' if (!_.isString(input.content)) validatedInput.content = ''
else validatedInput.content = input.content else validatedInput.content = input.content
if (!_.isArray(input.linesHighlighted)) validatedInput.linesHighlighted = []
else validatedInput.linesHighlighted = input.linesHighlighted
} }
return validatedInput return validatedInput
case 'SNIPPET_NOTE': case 'SNIPPET_NOTE':
@@ -51,7 +54,8 @@ function validateInput (input) {
validatedInput.snippets = [{ validatedInput.snippets = [{
name: '', name: '',
mode: 'text', mode: 'text',
content: '' content: '',
linesHighlighted: []
}] }]
} else { } else {
validatedInput.snippets = input.snippets validatedInput.snippets = input.snippets
@@ -96,12 +100,14 @@ function updateNote (storageKey, noteKey, input) {
snippets: [{ snippets: [{
name: '', name: '',
mode: 'text', mode: 'text',
content: '' content: '',
linesHighlighted: []
}] }]
} }
: { : {
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
content: '' content: '',
linesHighlighted: []
} }
noteData.title = '' noteData.title = ''
if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.') if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.')

View File

@@ -12,7 +12,8 @@ function updateSnippet (snippet, snippetFile) {
if ( if (
currentSnippet.name === snippet.name && currentSnippet.name === snippet.name &&
currentSnippet.prefix === snippet.prefix && currentSnippet.prefix === snippet.prefix &&
currentSnippet.content === snippet.content currentSnippet.content === snippet.content &&
currentSnippet.linesHighlighted === snippet.linesHighlighted
) { ) {
// if everything is the same then don't write to disk // if everything is the same then don't write to disk
resolve(snippets) resolve(snippets)
@@ -20,6 +21,7 @@ function updateSnippet (snippet, snippetFile) {
currentSnippet.name = snippet.name currentSnippet.name = snippet.name
currentSnippet.prefix = snippet.prefix currentSnippet.prefix = snippet.prefix
currentSnippet.content = snippet.content currentSnippet.content = snippet.content
currentSnippet.linesHighlighted = (snippet.linesHighlighted)
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => { fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err) if (err) reject(err)
resolve(snippets) resolve(snippets)

View File

@@ -14,7 +14,6 @@ function once (name, listener) {
} }
function emit (name, ...args) { function emit (name, ...args) {
console.log(name)
remote.getCurrentWindow().webContents.send(name, ...args) remote.getCurrentWindow().webContents.send(name, ...args)
} }

View File

@@ -14,14 +14,13 @@ nodeIpc.connectTo(
path.join(app.getPath('userData'), 'boostnote.service'), path.join(app.getPath('userData'), 'boostnote.service'),
function () { function () {
nodeIpc.of.node.on('error', function (err) { nodeIpc.of.node.on('error', function (err) {
console.log(err) console.error(err)
}) })
nodeIpc.of.node.on('connect', function () { nodeIpc.of.node.on('connect', function () {
console.log('Connected successfully')
ipcRenderer.send('config-renew', {config: ConfigManager.get()}) ipcRenderer.send('config-renew', {config: ConfigManager.get()})
}) })
nodeIpc.of.node.on('disconnect', function () { nodeIpc.of.node.on('disconnect', function () {
console.log('disconnected') return
}) })
} }
) )

View File

@@ -3,5 +3,11 @@ import ee from 'browser/main/lib/eventEmitter'
module.exports = { module.exports = {
'toggleMode': () => { 'toggleMode': () => {
ee.emit('topbar:togglemodebutton') ee.emit('topbar:togglemodebutton')
},
'deleteNote': () => {
ee.emit('hotkey:deletenote')
},
'toggleMenuBar': () => {
ee.emit('menubar:togglemenubar')
} }
} }

View File

@@ -128,3 +128,29 @@ body[data-theme="monokai"]
.control-confirmButton .control-confirmButton
colorMonokaiPrimaryButton() colorMonokaiPrimaryButton()
body[data-theme="dracula"]
.root
modalDracula()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-dracula-text-color
.control-folder-label
color $ui-dracula-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorDraculaPrimaryButton()

View File

@@ -1,12 +1,9 @@
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteModal.styl' import styles from './NewNoteModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
class NewNoteModal extends React.Component { class NewNoteModal extends React.Component {
constructor (props) { constructor (props) {
@@ -24,31 +21,10 @@ class NewNoteModal extends React.Component {
} }
handleMarkdownNoteButtonClick (e) { handleMarkdownNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN') const { storage, folder, dispatch, location, params, config } = this.props
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
const { storage, folder, dispatch, location } = this.props setTimeout(this.props.close, 200)
dataApi })
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: ''
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
setTimeout(this.props.close, 200)
})
} }
handleMarkdownNoteButtonKeyDown (e) { handleMarkdownNoteButtonKeyDown (e) {
@@ -59,38 +35,10 @@ class NewNoteModal extends React.Component {
} }
handleSnippetNoteButtonClick (e) { handleSnippetNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET') const { storage, folder, dispatch, location, params, config } = this.props
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
const { storage, folder, dispatch, location } = this.props setTimeout(this.props.close, 200)
})
dataApi
.createNote(storage, {
type: 'SNIPPET_NOTE',
folder: folder,
title: '',
description: '',
snippets: [
{
name: '',
mode: 'text',
content: ''
}
]
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
setTimeout(this.props.close, 200)
})
} }
handleSnippetNoteButtonKeyDown (e) { handleSnippetNoteButtonKeyDown (e) {

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