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

Compare commits

...

326 Commits

Author SHA1 Message Date
Junyoung Choi
ae493cbd0e v0.11.7 2018-06-30 00:51:15 +09:00
Junyoung Choi (Sai)
70d02e9a6d Merge pull request #2125 from johannbre/admonition_bug_fix
Fix rendering of admonitions in Windows
2018-06-27 14:50:54 +09:00
Junyoung Choi (Sai)
6401016424 Merge pull request #2122 from yosmoc/plant_uml_improvement
plantuml non-english character support and timing diagram support
2018-06-27 04:54:21 +09:00
Junyoung Choi (Sai)
cd031a89fb Merge pull request #2105 from romainwn/docs/add-missing-translations-fr
update fr translation
2018-06-27 04:52:52 +09:00
Junyoung Choi (Sai)
aadba79002 Merge pull request #2106 from voidsatisfaction/fix-typo-on-main-app
fix tiny comment typo
2018-06-27 04:43:53 +09:00
Junyoung Choi (Sai)
4a017fd8ae Merge pull request #2066 from PeterDaveHello/patch-1
Improve Traditional Chinese translation
2018-06-27 04:43:27 +09:00
Junyoung Choi (Sai)
e6072c8fe9 Merge pull request #2131 from clone1612/updateElectron
Update Electron to v2.0.3
2018-06-27 04:41:36 +09:00
Jannick Hemelhof
f7fd99ec20 Update electron to v2.0.3 2018-06-26 11:27:37 +02:00
Junyoung Choi (Sai)
836fc13449 Merge pull request #2124 from clone1612/remove-unused
[Refactoring] - Removed unused packages
2018-06-26 06:01:30 +08:00
Junyoung Choi (Sai)
e1f78cd682 Merge pull request #2107 from ehhc/fix_images
One more try to fix the handling of images in the legacy location
2018-06-25 21:33:19 +08:00
ehhc
c69f34836a handle dot in file name 2018-06-25 15:18:45 +02:00
Junyoung Choi (Sai)
48a0db28d7 Merge pull request #2127 from BoostIO/update-issue-template
Update issue template
2018-06-25 20:22:11 +08:00
Kazz Yokomizo
58eeb90158 Update issue template
Add IssueHunt url
2018-06-25 15:46:41 +09:00
Johann Breytenbach
c36689934d Fix rendering of admonitions in Windows 2018-06-24 22:04:36 -07:00
yosmoc
653b985018 plantuml non-english character support and timing diagram support 2018-06-23 14:02:46 +09:00
Junyoung Choi (Sai)
40d90a53b2 Merge pull request #2110 from BoostIO/fix-hotkeys
Rewrite invalid hotkeys when loading
2018-06-21 02:58:12 +09:00
Junyoung Choi
7c3aaff635 Rewrite invalid hotkeys when loading 2018-06-21 02:48:44 +09:00
Junyoung Choi (Sai)
4bb7929229 Merge pull request #2109 from voidsatisfaction/fix-M-key-issue
Fix MacOs 'M' key issue
2018-06-21 02:36:27 +09:00
voidsatisfaction
bdd5b7b3a7 fix: no need semicolon 2018-06-20 21:57:11 +09:00
voidsatisfaction
a2ddb56540 fix: change mode to more desiarable way 2018-06-20 21:45:24 +09:00
voidsatisfaction
7c0097951c fix: m key issue 2018-06-20 21:38:36 +09:00
ehhc
7970016fbf One more try to fix the handling of images in the legacy location 2018-06-20 08:48:55 +02:00
voidsatisfaction
129e3b283d fix tiny typo 2018-06-20 14:29:57 +09:00
Romain Le Quellec
868eefd2cf update fr translation 2018-06-19 22:20:17 +02:00
Sosuke Suzuki
c42e1a0ab4 Merge pull request #2054 from clone1612/electron-2-compatibility
Upgrade Electron to v2.0.2
2018-06-14 14:34:12 +09:00
Sosuke Suzuki
720475dae5 Merge pull request #2075 from yougotwill/info_box_fix
Fixed info panel positioning
2018-06-14 14:19:31 +09:00
Junyoung Choi
259df880ba v0.11.6 2018-06-14 11:02:20 +09:00
Jannick Hemelhof
68a328a364 Removed unused packages 2018-06-13 16:08:18 +02:00
William Grant
3bc21cdb09 fixed positioning of the info panel so it doesn't cover the info button 2018-06-13 12:19:17 +02:00
Sosuke Suzuki
9e8ef70510 Merge pull request #2064 from FlaPS/debounce-markdonw-search
#2042 Debounce for details renderig added
2018-06-12 19:52:36 +09:00
Sosuke Suzuki
5fd822a24d add a new line at end of file 2018-06-12 19:48:09 +09:00
Sosuke Suzuki
8ee4dbbb5c Merge pull request #2065 from FlaPS/fix-blinking-notes-list-on-search
Fix file list blinking on search
2018-06-12 19:38:26 +09:00
Sosuke Suzuki
9522a4d5d9 fix from eslint 2018-06-12 19:33:54 +09:00
Sosuke Suzuki
eefecdefbe Merge pull request #2062 from zhouxiaoxiaoxujian/bug-1977
Add new future resize editor & Preview Panes by dragging.(#1977)
2018-06-12 16:49:59 +09:00
Sosuke Suzuki
84e670e71c remove unnecesarray line 2018-06-12 16:42:18 +09:00
JianXu
c22b69234f fix review code style ,add new line at end of file 2018-06-12 14:12:59 +08:00
Peter Dave Hello
29cd63d3a7 Improve Traditional Chinese translation 2018-06-11 18:40:55 +08:00
JianXu
214ed388f2 remove unnecessary lines 2018-06-11 17:49:36 +08:00
JianXu
0bd3445370 use 'const' instead of 'let' 2018-06-11 17:49:20 +08:00
Kazz Yokomizo
002dc9b017 Merge pull request #2056 from zhouxiaoxiaoxujian/bug-#1945
Fixed Keyboard tag unvisible in monokai theme[#1945]
2018-06-11 14:55:13 +09:00
Kazz Yokomizo
ebf5a03f56 Merge pull request #2060 from yougotwill/dark_theme_empty_note_fix
[Dark Theme]: Fixes the text color for an active note with an empty title
2018-06-11 14:40:19 +09:00
Kazz Yokomizo
2797faafe5 Merge pull request #2058 from kawmra/remove-duplicated-line
Fix en.json
2018-06-11 14:37:21 +09:00
Kazz Yokomizo
84ac739993 Merge pull request #2057 from kawmra/add-some-ja-translations
Add missing Japanese translations
2018-06-11 14:36:44 +09:00
Kazz Yokomizo
13c37f046f Merge pull request #2059 from kawmra/fix-crowdfunding-button-style
Fix styles of button in crowdfunding tab
2018-06-11 14:25:54 +09:00
Shammasov Max
2631cc3747 Fix file list blinking on search 2018-06-11 03:46:39 +03:00
Shammasov Max
5873e8e896 Debounce for details renderig added 2018-06-11 03:34:45 +03:00
JianXu
6dc633c2a1 Add new future resize editor & Preview Panes by dragging.(#1977) 2018-06-10 23:46:01 +08:00
William Grant
8b07126285 Fixes the text color for an active note with an empty title 2018-06-09 14:18:44 +02:00
kawmra
93a120543a Add some Japanese translations 2018-06-09 20:02:22 +09:00
kawmra
af0aa4a567 Make button width flexible 2018-06-09 19:33:40 +09:00
kawmra
58fd1f0c46 Remove a duplicated line 2018-06-09 19:33:03 +09:00
JianXu
030041932e Fixed Keyboard tag unvisible in monokai theme[#1945] 2018-06-09 15:35:59 +08:00
Jannick Hemelhof
8dbf456398 Upgrade to Electron v2.0.2 2018-06-09 08:52:46 +02:00
Junyoung Choi (Sai)
3a90a078ce Merge pull request #2047 from BoostIO/fix-snippets-json-path
Fix wrong path of snippets.json
2018-06-08 15:56:40 +09:00
Junyoung Choi (Sai)
c30957fc9f Merge pull request #1969 from ZeroX-DG/add-zoom-window
added zoom in & zoom out window feature
2018-06-08 15:43:11 +09:00
Junyoung Choi
f6db946c9a Fix wrong path of snippets.json 2018-06-08 15:37:03 +09:00
Junyoung Choi (Sai)
c6c0d4c62a Merge pull request #2039 from Matts966/Hide-copy-to-clipboard-icon-when-printing
Hide copy-to-clipboard buttons when printing.
2018-06-08 15:25:18 +09:00
Junyoung Choi (Sai)
57befc4ccb Merge pull request #2002 from ehhc/paste_storage_link_should_clone_attachment
Copying the link to an attachment should make sure that it is located…
2018-06-08 15:24:28 +09:00
Junyoung Choi (Sai)
e716af75ed Merge branch 'master' into paste_storage_link_should_clone_attachment 2018-06-08 15:21:26 +09:00
Junyoung Choi (Sai)
efe2bea64b Merge pull request #2017 from onurpalaz/master
Added Turkish translation
2018-06-08 15:01:39 +09:00
Junyoung Choi
9b926326ef Add turkish to languages 2018-06-08 14:59:30 +09:00
Junyoung Choi (Sai)
de71033fe2 Merge pull request #2019 from yosmoc/fix_localfile_link
open local file by shell
2018-06-08 14:54:44 +09:00
Junyoung Choi (Sai)
2c10bf251d Merge pull request #1989 from kawmra/create-image-tag-when-pasting-image-url
Create a image tag when pasting image url
2018-06-08 14:50:09 +09:00
Junyoung Choi (Sai)
8af50aa5bd Merge pull request #1928 from hooklife/perfect-zh-CN
update locales/zh-CN
2018-06-08 14:03:52 +09:00
hooklife
05bedfe3d4 Update zh-CN.json 2018-06-07 17:12:52 +08:00
Junyoung Choi (Sai)
a1929dac8a Merge pull request #2041 from kawmra/fix-typo
Fix typo of MarkdownEditor.js
2018-06-07 15:44:03 +09:00
kawmra
834ecc643a Merge branch 'master' into create-image-tag-when-pasting-image-url
# Conflicts:
#	browser/components/CodeEditor.js
2018-06-07 03:01:08 +09:00
kawmra
60baabf7e7 Fix typo 2018-06-07 02:51:04 +09:00
松井誠泰
185a149d74 Hide copy-to-clipboard buttons when printing.
It is less confusing when printing because the pdf file does not have the function to copy.
2018-06-06 19:39:28 +09:00
ehhc
5d62dd2002 Merge branch 'master' of https://github.com/BoostIO/Boostnote into paste_storage_link_should_clone_attachment
# Conflicts:
#	browser/main/lib/dataApi/attachmentManagement.js
#	locales/da.json
#	locales/de.json
#	locales/en.json
#	locales/es-ES.json
#	locales/fa.json
#	locales/fr.json
#	locales/hu.json
#	locales/it.json
#	locales/ja.json
#	locales/ko.json
#	locales/no.json
#	locales/pl.json
#	locales/pt-BR.json
#	locales/pt-PT.json
#	locales/ru.json
#	locales/sq.json
#	locales/zh-CN.json
#	locales/zh-TW.json
2018-06-06 12:03:53 +02:00
hooklife
9eaa6b5cec Update zh-CN.json 2018-06-06 16:11:29 +08:00
hooklife
6ff03bbb95 Merge branch 'master' into perfect-zh-CN 2018-06-06 16:06:32 +08:00
Sosuke Suzuki
9fac6bca64 Merge pull request #1993 from ehhc/smart_arrows
Include markdown-it-smartArrow. Toggle it via settings and disable it…
2018-06-06 12:13:09 +09:00
Junyoung Choi (Sai)
88856b788a Merge pull request #1963 from ehhc/OnBlur_Throws_Exceptions_On_Snippet_Notes
OnBlur throws exceptions if the notetype is snippet -> Fixes #1962
2018-06-06 11:11:27 +09:00
Sosuke Suzuki
eda4e46d9f Merge pull request #2024 from kawmra/fix-yarn-lock
Fix yarn.lock file
2018-06-06 10:19:42 +09:00
ehhc
35bcbbbae4 Merge branch 'master' into smart_arrows 2018-06-05 11:35:56 +02:00
ehhc
22e2c3da1f Merge branch 'master' of https://github.com/BoostIO/Boostnote into paste_storage_link_should_clone_attachment
# Conflicts:
#	browser/components/CodeEditor.js
#	browser/main/lib/dataApi/attachmentManagement.js
2018-06-05 11:28:42 +02:00
ehhc
b526d48946 CodeReview 2018-06-05 11:16:50 +02:00
Junyoung Choi (Sai)
91a95b7c20 Merge pull request #1955 from bimlas/update-electron
Update to the latest Electron version (1.8.7)
2018-06-05 09:59:59 +09:00
Sosuke Suzuki
d634e1124a Merge pull request #1996 from ZeroX-DG/custom-markdown-css
Added custom markdown css
2018-06-04 15:07:16 +09:00
Sosuke Suzuki
309e159df1 Merge pull request #1949 from ehhc/Fix_legacy_attachment_issues
Fix legacy attachment issues
2018-06-04 12:02:03 +09:00
ehhc
ffae53326a Fix for the issues raised in the code review 2018-06-04 11:55:23 +09:00
ehhc
ddd1522e19 Pasting an image should insert a relative attachment file link -> fixes #1953 2018-06-04 11:55:23 +09:00
ehhc
4bc0cccb24 Migrate attachments from /images to /attachments -> fix #1941 2018-06-04 11:55:23 +09:00
Nguyễn Việt Hưng
72fbefa300 moved custom markdown checkbox & fixed codemirror size 2018-06-03 08:51:53 +07:00
kawmra
30378eeb50 Remove unnecessary lines 2018-06-02 23:01:52 +09:00
kawmra
03293c0d25 Fix conflict of yarn.lock 2018-06-02 22:59:49 +09:00
Nguyễn Việt Hưng
2e3f6e39f6 fixed width of custom markdown editor 2018-06-02 11:30:12 +07:00
Junyoung Choi (Sai)
e4d4041c6b Merge pull request #1848 from ZeroX-DG/text-expansion-support
Text expansion support
2018-06-01 14:34:57 +09:00
yosmoc
166a5c10a7 open local file by shell 2018-05-31 22:55:10 +02:00
onurpalaz
1fec81cc3e Added Turkish translation
Added Turkish translation
2018-05-31 10:13:05 +03:00
ehhc
9c247bcb22 Copying the link to an attachment should make sure that it is located in the same note folder -> Fixes #1924 2018-05-29 17:20:14 +02:00
Nguyễn Việt Hưng
fc88a49acc disabled custom css by default 2018-05-29 11:52:00 +07:00
ehhc
8ccf490e9b Include markdown-it-smartArrow. Toggle it via settings and disable it by default since it might affect HTML comments in markdown 2018-05-28 23:05:08 +02:00
Nguyễn Việt Hưng
ea768f982e Added custom markdown css 2018-05-29 00:37:59 +07:00
Nguyễn Việt Hưng
172ea82954 fixed yarn.lock conflict 2018-05-28 15:19:10 +07:00
Nguyễn Việt Hưng
10500c3c1c changed all colors to variables & styled for monokai theme 2018-05-28 15:15:09 +07:00
ehhc
225916fbba Fix for the test broken by the merge commit 2018-05-28 09:57:07 +02:00
ehhc
2fce78422b Merge branch 'master' into OnBlur_Throws_Exceptions_On_Snippet_Notes 2018-05-28 09:00:57 +02:00
ehhc
8132dd6847 Fix for the issues raised in the code review 2018-05-28 08:58:09 +02:00
bimlas
4caee1e103 Update to the latest Electron version (1.8.7)
It inculdes security fixes, bug fixes and improvementts. See:
https://electronjs.org/releases

Reason for upgrading from 1.7.X to 1.8.X:

"We can not reasonably back port every bug fix and every feature to
older versions of Electron. 1.8.x is now a stable release line and if
you require this fix you should update to that stream."

https://github.com/electron/electron/issues/9860#issuecomment-365517773
2018-05-28 08:06:43 +02:00
Kazz Yokomizo
ca0b03e97c Merge pull request #1991 from BoostIO/slack-url
Update slack url
2018-05-28 14:55:39 +09:00
Kazz Yokomizo
f03178bb8d Update slack url 2018-05-28 14:51:57 +09:00
Sosuke Suzuki
d083a86138 Merge pull request #1932 from bimlas/fix-logic-of-state-notemap-set
Refactoring of store.js
2018-05-28 14:50:08 +09:00
Kazz Yokomizo
8216b992ea Merge pull request #1970 from HanDH/master
update locales/ko
2018-05-28 14:47:18 +09:00
Sosuke Suzuki
8e74ee7dde Merge pull request #1987 from johannbre/master
[Feature Request] - 1638 - Support for admonitions
2018-05-28 14:32:58 +09:00
Sosuke Suzuki
b207fe14df Merge pull request #1951 from yosmoc/update_cursor_position_after_link_extend
update cursor position after expanding the link
2018-05-28 14:25:42 +09:00
Sosuke Suzuki
92cfa21be6 Merge pull request #1933 from hooklife/fix-word-wrap
fix tooltip word wrap
2018-05-28 14:24:07 +09:00
hook life
5fd482428a add new line at last. 2018-05-28 13:12:18 +08:00
Sosuke Suzuki
98b09f7edc Merge pull request #1967 from yosmoc/external_link_opened_twice
open external browser once in clicking the link
2018-05-28 12:51:13 +09:00
Johann Breytenbach
7b83a34777 [Feature Request] - 1638 - Support for admonitions
Added support for markup admonitions by including markdown-it-admonition. Added material icon support and updates to styles for rendering of six different types of admonitions.
2018-05-27 20:47:44 -07:00
Sosuke Suzuki
aeb63ec901 Merge pull request #1983 from bimlas/i18n-apply-translations-on-hardcoded-strings
i18n: Apply translations on hardcoded strings and add missing translation to English
2018-05-28 11:23:18 +09:00
Sosuke Suzuki
436093e0b6 Merge pull request #1982 from bimlas/i18n-update-hungarian-translations
i18n: Update Hungarian translations
2018-05-28 11:21:18 +09:00
kawmra
d7ee06ce6d Fix lint error 2018-05-27 20:08:57 +09:00
kawmra
e72003009d Create a image tag when pasting image url 2018-05-27 19:42:41 +09:00
bimlas
cfe8235a36 i18n: Add missing translations to English 2018-05-26 11:51:51 +02:00
bimlas
8c1ac9c5b3 i18n: Update Hungarian translations 2018-05-26 11:49:02 +02:00
bimlas
77e3a7d43a i18n: Apply translations on hardcoded strings 2018-05-26 11:39:16 +02:00
bimlas
53728a0f4a Refactoring changing of folder 2018-05-25 23:16:23 +02:00
bimlas
06f33d9a63 Refactoring changing of "starred" 2018-05-25 23:16:23 +02:00
bimlas
ed2698ecc3 Refactoring removing of unique key from tags 2018-05-25 23:16:23 +02:00
bimlas
0cb5554ae5 Refactoring assignment of unique key to tags 2018-05-25 23:16:23 +02:00
bimlas
89850c0b22 Refactoring Set initialization methods 2018-05-25 23:16:23 +02:00
bimlas
d78b94f4e8 Refactoring update of tag changes 2018-05-25 23:16:23 +02:00
Nguyễn Việt Hưng
c2c50817f1 resolved conflict 2018-05-25 18:42:02 +07:00
Nguyễn Việt Hưng
680c2a2904 changed cssmodule & applied style for solarized dark theme 2018-05-25 18:39:11 +07:00
daehyun
a1085e3863 update locales/ko 2018-05-24 20:04:59 +09:00
Nguyễn Việt Hưng
37f6a05170 added zoom in & zoom out 2018-05-24 16:48:51 +07:00
Junyoung Choi (Sai)
0d296c3b25 Merge pull request #1936 from ehhc/delete_folder
deleting a folder should also delete the attachments -> fixes #1903
2018-05-24 14:45:47 +09:00
Junyoung Choi (Sai)
73caa2508e Merge pull request #1931 from bimlas/1498-refresh-taglist-on-deleting-tag
Refresh taglist to remove unused tags on deleting
2018-05-24 14:44:54 +09:00
Junyoung Choi (Sai)
69e012a6f0 Merge pull request #1939 from ehhc/cloning_a_note_should_clone_attachments
Cloning a note should clone attachments
2018-05-24 14:42:56 +09:00
Junyoung Choi (Sai)
21251a1915 Merge pull request #1965 from yosmoc/tooltip_size_position_fix
standardrize sidebar tooltips
2018-05-24 14:40:35 +09:00
Junyoung Choi (Sai)
67143ba2d5 Merge pull request #1929 from ZeroX-DG/shortcut-mode
Shortcut to toggle mode
2018-05-24 14:01:28 +09:00
yosmoc
d399cba4c0 onContextMenu PropType is missing 2018-05-24 00:24:25 +02:00
yosmoc
9cad7cd025 anchorClickHandler is not valid any more
linkClickHandler takes care of behaviours when clicking a tag.
Currently, both anchorClickHandler and linkClickHandler are registered in a tag. When clicking the ^http link, it opens the link twice.
2018-05-24 00:11:14 +02:00
yosmoc
a593842265 standardrize sidebar tooltips
- show in same place
- same height
2018-05-23 22:49:52 +02:00
ehhc
2f4eb595f6 OnBlur throws exceptions if the notetype is snippet -> Fixes #1962 2018-05-23 19:47:41 +02:00
ehhc
bfcf349ffe Attachment management should only become active on cloning notes if the note was of type MARKDOWN_NOTE -> No Stacktraces otherwise! 2018-05-23 19:24:03 +02:00
yosmoc
2bd78cd47f update cursor position after expanding the link 2018-05-22 21:53:08 +02:00
Nguyễn Việt Hưng
713615e28b applied style for snippetEditor 2018-05-21 18:37:17 +07:00
Nguyễn Việt Hưng
2b2f17525e cleaned up redundant variables, fixed eslint fix command, split snippetList into component 2018-05-21 18:32:41 +07:00
ehhc
cd6233a3d7 Cloning of a note should also clone its attachments -> works if the notes are in different storages now 2018-05-20 15:49:15 +02:00
ehhc
f76224bd17 Cloning of a note should also clone its attachments -> fixes #1904 2018-05-20 15:18:49 +02:00
ehhc
8afa373726 Attachments should be visible immediately 2018-05-20 13:55:10 +02:00
ehhc
199f2202e0 deleting a folder should also delete the attachments -> fixe eslint issue 2018-05-19 20:43:48 +02:00
ehhc
86a6311f75 deleting a folder should also delete the attachments -> fixes #1903 2018-05-19 20:38:10 +02:00
Junyoung Choi
0ca4e6ca4f v0.11.5 2018-05-19 16:45:03 +09:00
hooklife
50bce4892f remove test 2018-05-19 03:46:37 +08:00
hooklife
ca345cf008 fix 2018-05-19 03:44:44 +08:00
hooklife
e0e1290fae fix 2018-05-19 03:40:24 +08:00
hooklife
a84b2611e4 fix 2018-05-19 03:35:58 +08:00
hooklife
ec31fab344 fix 2018-05-19 02:15:40 +08:00
hooklife
1b96eee4de fix 2018-05-19 02:12:18 +08:00
bimlas
a6af5de3e1 Refactor tag removing methods (duplicated code) 2018-05-18 15:18:28 +02:00
bimlas
52b3068330 Refresh taglist to remove unused tags on deleting
When a tag  is deleted from a note, the taglist did not updated: the deleted tag was still in the list. If the
tag was created in the current session, it was removed, worked as expected.

The reason is: until the note is not updated, the tag list is not
initialized for first in the "delete tag button" handler, but it works
in the second case. Tried out using backspace to delete the tag, it
works.
2018-05-18 15:18:09 +02:00
Nguyễn Việt Hưng
0934c08dfe added shortcut manager and allow user to customize toggle mode shortcut 2018-05-18 14:55:42 +07:00
hook life
f0428fde66 修正部分翻译 2018-05-18 15:25:41 +08:00
hook life
b3f57a67c4 修正部分翻译 2018-05-18 15:22:45 +08:00
hook life
8bc2b1262b 修正部分翻译 2018-05-18 15:13:38 +08:00
Nguyễn Việt Hưng
fb24efd3de added shortcut for toggle mode 2018-05-18 10:52:42 +07:00
Nguyễn Việt Hưng
ce594b0b5a added note template feature 2018-05-17 00:14:59 +07:00
Junyoung Choi
7b39ab4ec4 Update yarn.lock 2018-05-17 02:00:02 +09:00
Junyoung Choi (Sai)
f717ed9f66 Merge pull request #1923 from ehhc/Moving_Note_With_Attachment
Move note with attachment to different storage Fix for #1788
2018-05-17 01:43:03 +09:00
Junyoung Choi (Sai)
d3091a5384 Merge branch 'master' into Moving_Note_With_Attachment 2018-05-17 01:28:47 +09:00
Junyoung Choi (Sai)
2a6d950a4b Merge pull request #1902 from ehhc/Issue_1900
Deleting a note should also delete the attachments -> fixes #1900
2018-05-17 01:20:55 +09:00
Junyoung Choi (Sai)
b03b9d5334 Merge pull request #1887 from o-3/master
Add configuration to render newlines as <br> or not
2018-05-17 01:08:52 +09:00
hidaiy
c9cb31bd02 Update CodeMirror version (#1925)
to fix issue#1003
2018-05-16 17:41:20 +09:00
ehhc
905d6860fc really fix the eslinter issue... -.- 2018-05-15 20:39:16 +02:00
ehhc
6f52744b0f Fix eslinter issue 2018-05-15 20:32:35 +02:00
ehhc
007d3e52c5 Merge branch 'master' into Issue_1900 2018-05-15 20:26:10 +02:00
ehhc
f10fa632ca Move note with attachment to different storage Fix for #1788 2018-05-15 20:17:32 +02:00
Junyoung Choi (Sai)
266323b90b Merge pull request #1916 from yosmoc/configuable_plantuml_server
Configurable plantuml server
2018-05-15 20:02:11 +09:00
Junyoung Choi (Sai)
60707a8f45 Merge pull request #1918 from yosmoc/about_jp
fix 'About' translation in ja.json
2018-05-15 20:00:18 +09:00
Junyoung Choi (Sai)
b89896a4e7 Merge pull request #1919 from samlanning/inconsistent-state-update
fix inconsistent updates to react component state
2018-05-15 19:30:22 +09:00
Junyoung Choi (Sai)
d69fd12fb9 Merge pull request #1920 from samlanning/incorrect-state-property-reference
Fix mis-referenced state properties
2018-05-15 16:49:40 +09:00
Junyoung Choi (Sai)
0bdcfa6028 Merge pull request #1899 from ehhc/deleting_of_attachment_link
Deleting of attachments -> fixes #1828 and fixes #740
2018-05-15 15:52:31 +09:00
Junyoung Choi (Sai)
55b8488901 Merge pull request #1915 from yougotwill/note_button_styling
fixed NoteItemSimple styling for the dark themes
2018-05-15 15:49:35 +09:00
Junyoung Choi (Sai)
8ddbf2067b Merge pull request #1921 from BoostIO/fix-move-note
Fix moving note with attachments between different storage
2018-05-15 15:48:36 +09:00
Junyoung Choi
fa5cebda6d Fix moving note with attachments between different storage 2018-05-15 15:45:46 +09:00
Sam Lanning
4fd01b4234 Fix mis-referenced state properties 2018-05-14 16:29:54 -07:00
Sam Lanning
0f354f4f06 fix inconsistent updates to react component state
Executions of setState() may be batched, and so updates to this.state and
this.props can be asynchronous, so references to this.state and this.props
should not be made in the new state, and instead the callback form of
setState() should be used.

These alerts were found using lgtm.com:
https://lgtm.com/projects/g/BoostIO/Boostnote/alerts/?mode=tree&ruleFocus=1819283066

see: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
2018-05-14 16:15:49 -07:00
yosmoc
f94a197828 fix 'About' translation in ja.json 2018-05-14 23:19:33 +02:00
yosmoc
a26ff660b0 add plantuml server setting translation
Currently only translate to en and jp. I haven't translated for other language(just use the original value)
2018-05-14 22:57:05 +02:00
yosmoc
50cc648799 set default plantuml server 2018-05-14 22:56:45 +02:00
yosmoc
a7946805ae configuable plantUML server address 2018-05-14 22:35:38 +02:00
William Grant
f3b2969b42 fixed NoteItemSimple styling for the dark themes 2018-05-14 21:04:18 +02:00
Junyoung Choi (Sai)
d6c3490165 Merge pull request #1895 from yosmoc/move_note_diff_storage_bug
fix moving note across different storage bug
2018-05-14 17:52:29 +09:00
Junyoung Choi (Sai)
6ee594d4d1 Merge pull request #1912 from yosmoc/codemirror-multiplex-support
Codemirror multiplex support
2018-05-14 17:28:37 +09:00
yosmoc
7e1596de30 refactor code duplication 2018-05-13 00:36:03 +02:00
yosmoc
67bba043ed MarkdownPreview also needs to convert the mode name for specific modes 2018-05-13 00:35:32 +02:00
yosmoc
26d7f4923d support multiplex mode
embedded javascript uses combined several modes.
Multiplex mode renders ejs and other formats which uses several modes.
2018-05-13 00:35:01 +02:00
ehhc
e9218d1088 Fix for the broken test 2018-05-10 22:39:00 +02:00
ehhc
03fd1e29e3 Fix for the broken test 2018-05-10 22:30:13 +02:00
ehhc
ff59af6b51 Fix for the broken test 2018-05-10 21:42:23 +02:00
ehhc
73ba8b8b13 Deleting a note should also delete the attachments -> fixes #1900 2018-05-10 21:36:58 +02:00
ehhc
ffc3fb770c Deleting of attachments -> fixes #1828 and fixes #740 2018-05-10 20:27:47 +02:00
yosmoc
d58ea70a95 fix moving note across different storage bug
should update storageNoteMap here.
2018-05-10 06:29:13 +02:00
Kazz Yokomizo
90e8dd038d Merge pull request #1891 from BoostIO/bounty-program
Add bounty program
2018-05-09 10:15:17 +09:00
Kazz Yokomizo
56d1e3edaa Add bounty program 2018-05-09 10:00:07 +09:00
ozone
83a9e54896 Fix snapshot 2018-05-04 01:22:55 -04:00
ozone
ab038b1f31 Add configuration to render line breaks as <br> or not 2018-05-04 00:44:22 -04:00
Nguyễn Việt Hưng
f5a9d3928c disabled code highlight in snippet editor 2018-05-02 10:03:55 +07:00
Nguyễn Việt Hưng
2bc0bce1b5 removed redundant function to get appdata 2018-05-02 09:05:29 +07:00
Junyoung Choi (Sai)
372933fd99 Merge pull request #1873 from yosmoc/trashed_notetag_exist
Not showing unassigned tag in taglist
2018-05-01 14:36:15 -07:00
Junyoung Choi (Sai)
9112347e95 Merge pull request #1870 from cJack1913/master
Fix font color in code block(markdown preview)
2018-05-01 14:35:45 -07:00
Junyoung Choi (Sai)
30548a68e4 Merge pull request #1868 from yosmoc/1820
fix internal note link bug #1820
2018-05-01 12:52:10 -07:00
Junyoung Choi (Sai)
ce052d1691 Merge pull request #1865 from zzdjk6/master
Fix #1780: Use black text with white background for printing and export in all theme
2018-05-01 12:50:35 -07:00
Junyoung Choi (Sai)
765ba8c867 Merge branch 'master' into master 2018-05-01 12:00:50 -07:00
Junyoung Choi (Sai)
a20c0cd49e Merge pull request #1603 from rayou/update-travis-node-version
Updated Travis CI node version - #1602
2018-05-01 11:43:35 -07:00
Junyoung Choi (Sai)
e06ca9a056 Merge pull request #1883 from yosmoc/drag_drop_note
fix drag drop more than one note bug
2018-05-01 10:34:29 -07:00
Junyoung Choi (Sai)
d04048c749 Merge pull request #1874 from ehhc/Issue_1827
Issue 1827
2018-05-01 10:31:34 -07:00
yosmoc
2d0f7589ea select proper notes for dragging
When the dragged target note is included in the selected notes, keeps the current selected notes, otherwise, replace the selected notes by the dragged target
2018-04-30 22:34:15 +02:00
yosmoc
dc60be404a select next note after tranfer note(s) to another folder 2018-04-30 22:30:41 +02:00
Kazz Yokomizo
9ff5cc51f9 Merge pull request #1875 from yougotwill/monokai_theme
Monokai theme
2018-04-27 18:49:17 -07:00
ehhc
e9de8f42e5 Fix eslinter 2018-04-27 09:16:30 +02:00
William Grant
5bd0499ae4 Merge branch 'master' into monokai_theme 2018-04-27 09:06:11 +02:00
ehhc
99e706bcd2 Enable yarn on travis + fix broken test 2018-04-27 09:05:47 +02:00
William Grant
239edb0605 tweaked styling 2018-04-27 09:01:41 +02:00
ehhc
bf3f5a5971 Fixes #1827 -> include attachments in HTML exports of notes 2018-04-27 08:51:47 +02:00
William Grant
92be3f32d6 added monokai theme 2018-04-27 08:10:50 +02:00
Nguyễn Việt Hưng
106f5a53ff added import fs module that was removed by accident 2018-04-27 11:42:34 +07:00
Nguyễn Việt Hưng
8c43f3d567 removed path.sep and use path.join to concatenate path 2018-04-27 11:27:51 +07:00
Nguyễn Việt Hưng
2e09501c8a fixed eslint error 2018-04-27 11:19:37 +07:00
Nguyễn Việt Hưng
a2592e48c8 Merge branch 'text-expansion-support' of https://github.com/ZeroX-DG/Boostnote into text-expansion-support 2018-04-27 11:17:17 +07:00
Nguyễn Việt Hưng
291d76674b refactored snippet dataApi for easy testing and added some test. Fixed old snippet still display when deleted 2018-04-27 11:16:45 +07:00
Nguyễn Việt Hưng
78957cf128 fixed eslint error 2018-04-27 08:25:23 +07:00
Nguyễn Việt Hưng
e88694b049 added fetch snippet
refactored some code

fixed snippet content empty on changed from list

fixed snippet name not updating on list when changed
2018-04-27 08:22:54 +07:00
Junyoung Choi (Sai)
5e7bdf7354 Merge branch 'master' into text-expansion-support 2018-04-26 16:39:52 -07:00
Junyoung Choi
2e3e0bc1d8 Fix lint error 2018-04-26 16:35:47 -07:00
Junyoung Choi (Sai)
b33e6b232c Merge pull request #1863 from PeterDaveHelloKitchen/png-image-optimize
Optimize PNG images losslessly via Google's zopflipng
2018-04-26 16:27:49 -07:00
Junyoung Choi (Sai)
df6b083670 Merge pull request #1852 from ehhc/attachment_refactoring
Fixes #1825 Refactoring of the attachment/image management
2018-04-26 16:26:30 -07:00
Junyoung Choi
05009d40c4 Merge branch 'master' into attachment_refactoring 2018-04-26 16:24:58 -07:00
Junyoung Choi (Sai)
ea27a3b449 Merge pull request #1872 from yosmoc/empty_trash_not_working_in_sidebar_fold
Empty trash menu working in sidebar folded
2018-04-27 07:04:05 +09:00
yosmoc
2831b0bd2a Only showing the tags that size > 0
No meaning to show the tag that has empty List
The tag which is only used in the note(s) in the Trash, tags.size becomes zero. In order to support this case, filtering tagList is needed
2018-04-26 23:55:44 +02:00
yosmoc
32e22dd507 update tagNoteMap when delete / restore the notebook
deleted note should not belong to tagNoteMap
2018-04-26 23:52:31 +02:00
yosmoc
2ee9951853 Empty trash menu working in sidebar folded 2018-04-26 22:29:26 +02:00
Kazz Yokomizo
b1912135ed Merge pull request #1775 from bimlas/narrow-to-related-tags
Show only related tags, select multiple tags
2018-04-26 13:13:24 -07:00
Kazz Yokomizo
bed3d42923 Merge pull request #1824 from ZeroX-DG/fix-paste-image
Fixed image not displaying after paste
2018-04-26 22:39:03 +09:00
bimlas
c4ec69a43f Add tests 2018-04-26 15:38:52 +02:00
bimlas
24b004bb2d Remove obsolete route path 2018-04-26 15:38:52 +02:00
bimlas
84925b24b5 Add option to show only related tags 2018-04-26 15:38:52 +02:00
bimlas
c02b91dfd4 Simplify style 2018-04-26 15:38:52 +02:00
bimlas
066d97220f Cleanup, prettify 2018-04-26 15:38:52 +02:00
bimlas
61ed47dda0 Don't narrow list, add plus sign only for related tags
It's easier to understand by most of the users.

Later I like to add a setting to enable narrowing of tag list to show
only the related ones.
2018-04-26 15:38:51 +02:00
bimlas
68c0f210cc Separate active tags by instead of &
Using `&` to separate tags in path (like
`/tags/currently&selected&tags`) may interfer with tags including `&`
character (like `black&white`). Since ` ` is replaced with `_` when
adding tag to notes, it's ideal separator because it's guaranteed that
tags are not including this character.
2018-04-26 15:38:51 +02:00
bimlas
6c542750f4 Narrow list of tags to related ones
When a tag is selected, the tag list narrows to show only the related
ones: all tags associated to the currently visible notes. Clicking on
the plus sign near another tag narrows the list again to the tags of
notes associated with the firstly AND secondly selected tag. To show
every tags again, press the tag icon on the top-left corner of
Boostnote.

Before:
![screencast](https://i.imgur.com/PwAdhLe.gif)

After:
![screencast](https://i.imgur.com/s3JCaFq.gif)

NOTE: Tags are joined with `&` character (`#` not works) in
`location.pathname` thus it will make the tags with this character
unavailable. Any suggestion to pass multiple values via pathname?
2018-04-26 15:38:47 +02:00
Kazz Yokomizo
25440a26ee Merge pull request #1805 from frankkanis/move-image-note-fix
Fixed moving notes with images
2018-04-26 22:32:21 +09:00
Kazz Yokomizo
ab393b1f6d Merge pull request #1818 from ZeroX-DG/fix-centralize-language
Centralized languages into 1 files
2018-04-26 22:27:35 +09:00
Kazz Yokomizo
e643147b69 Merge pull request #1831 from lboullo0/patch-1
Fix small typo
2018-04-26 22:21:56 +09:00
Kazz Yokomizo
6c4aa71cbc Merge pull request #1834 from ralphchung/edit-locale-zh_TW
Edit the localization file of zh-TW
2018-04-26 22:18:56 +09:00
Kazz Yokomizo
9930ba8748 Merge pull request #1836 from bah2830/TAG_COUNT_FIX
Updated tag note count to float to the right like the storage list
2018-04-26 22:16:35 +09:00
Frank Kanis
fbb8b4687b Reduced nests 2018-04-25 17:53:18 +02:00
cJack1913
01b1c49738 Fix font color in code block(markdown preview) 2018-04-25 17:23:09 +08:00
yosmoc
c9c28eda1b support internal note link 2018-04-24 23:40:59 +02:00
Chen Shenghan
744bcba599 Fix #1780: Use black text with white background for printing and export in all theme 2018-04-25 03:31:36 +12:00
Hung Nguyen
d76db726c4 refactored code according to review 2018-04-24 18:06:10 +07:00
Peter Dave Hello
71ec528a87 Optimize PNG images losslessly via Google's zopflipng 2018-04-24 17:39:18 +08:00
Junyoung Choi (Sai)
fbbc93900e Merge pull request #1858 from zzdjk6/master
Fix #1797: In preview window, open external link in external browser.
2018-04-24 04:58:25 +09:00
Junyoung Choi (Sai)
33b45737c9 Merge pull request #1859 from yosmoc/moment_updateLocale_warning
fix deprecation warning
2018-04-24 04:56:52 +09:00
yosmoc
4a55f78a48 fix deprecation warning
moment.updateLocale will properly replace properties on an existing locale.
2018-04-23 21:08:10 +02:00
Ralph Chung
a82a79e25c Edit the localization file of zh-TW
I've edited the zh-TW.json based on the original version.
The use of words in this document is more in keeping with the habits of how Taiwanese use Chinese.
2018-04-24 00:41:27 +08:00
Chen Shenghan
6ec2124a9c Fix #1797: In preview window, open external link in external browser. 2018-04-24 02:51:58 +12:00
Kazz Yokomizo
1f1ef1440e Merge pull request #1855 from mkal1375/master
Add fa.json in `locales` directory
2018-04-22 14:10:05 +09:00
Hung Nguyen
a7d0a4bdac clean up some redundant code, changed to path.sep, remove some default snippet 2018-04-22 09:27:47 +07:00
Mahdi Kalhor
d2129ffac6 edit some errors in translations. 2018-04-21 19:20:14 +04:30
Mahdi Kalhor
a4782f0663 Add fa.json for persian(farsi) language. 2018-04-21 19:15:10 +04:30
ehhc
16794b9d78 Fixes #1822 -> fix for the broken tests 2018-04-21 16:32:27 +02:00
ehhc
a76aed2d4e Fixes #1822 2018-04-21 14:49:43 +02:00
ehhc
d2163dacf9 Fixes #1825 Refactoring of the attachment/image management 2018-04-21 14:32:24 +02:00
Kazz Yokomizo
158305346f Merge pull request #1850 from BoostIO/slack-url
Remake slack url
2018-04-21 15:56:27 +09:00
Kazz Yokomizo
e692432242 Remake slack url 2018-04-21 15:55:54 +09:00
Hung Nguyen
a7b85b123e fixed get appdata path error 2018-04-21 09:40:32 +07:00
Hung Nguyen
ddcd722598 fix eslint error 2018-04-21 09:07:49 +07:00
Hung Nguyen
358458a937 Fixed appdata path error 2018-04-21 09:04:30 +07:00
Hung Nguyen
8925f7c381 fixed eslint error 2018-04-20 23:58:52 +07:00
Hung Nguyen
ff2e39901a added delete snippet, update snippet, create snippet and save on snippet change 2018-04-20 23:15:17 +07:00
Junyoung Choi (Sai)
90ff0f43ea Merge pull request #1754 from yougotwill/crossplatform_fullscreen_shortcuts
Fullscreen shortcut for non macOS
2018-04-20 13:02:33 +09:00
Junyoung Choi (Sai)
442c352c8d Merge pull request #1842 from davix3f/patch-1
Italian translation
2018-04-20 13:00:50 +09:00
Davide Fiorito
e91b7fb082 Italian translation 2018-04-19 17:54:14 +02:00
Hung Nguyen
88de66a31f fix the same error when drop image 2018-04-19 19:17:10 +07:00
Hung Nguyen
d3b3e45800 improved style for snippet list 2018-04-19 19:05:46 +07:00
Hung Nguyen
50d2f90621 added snippet config in setting 2018-04-19 13:40:33 +07:00
Brent Hughes
8ccf6cb8a3 Updated tag note count to float to the right like the storage list 2018-04-18 10:55:01 -05:00
Hung Nguyen
2e9b478824 added text expansion support 2018-04-18 19:31:10 +07:00
Kazz Yokomizo
89b2d54725 Merge pull request #1832 from dotnsf/translate_ja
locales/ja.json translated in Japanese
2018-04-18 14:33:42 +09:00
K.Kimura
3d0af2d8ca locales/ja.json translated in Japanese 2018-04-18 13:20:20 +09:00
Lucas
813b433f4d Fix small typo 2018-04-17 18:07:27 -03:00
William Grant
0bce96b0c6 Merge branch 'master' into crossplatform_fullscreen_shortcuts 2018-04-17 18:24:35 +02:00
William Grant
1d4f1764fc Changed top:focus-search shortcut 2018-04-17 18:22:54 +02:00
Hung Nguyen
2994420160 Fixed image not displaying after paste 2018-04-17 20:47:13 +07:00
Hung Nguyen
65d8d7282f fetched lastest changes 2018-04-17 10:06:39 +07:00
Hung Nguyen
47af3f09fc Centralized languages into 1 file 2018-04-17 09:59:30 +07:00
Junyoung Choi (Sai)
f4024f4683 Merge pull request #1749 from yougotwill/delete_dialog
Show confirmation dialog when deleting notes from the context menu
2018-04-16 22:03:02 +09:00
Junyoung Choi (Sai)
ee0ed6df7a Merge pull request #1802 from frankkanis/languages-fix
Fixed change some languages
2018-04-16 21:56:32 +09:00
Junyoung Choi (Sai)
d3fbba3572 Merge pull request #1809 from ZeroX-DG/fix-menu-popup-alt
fixed menu popup on alt key pressed
2018-04-16 21:45:03 +09:00
Hung Nguyen
4a6b22f5b7 removed console.log used for debuging 2018-04-15 09:18:12 +07:00
William Grant
d070305002 Merge branch 'master' into delete_dialog 2018-04-14 20:20:17 +02:00
Hung Nguyen
a8500150b0 fixed menu popup on alt 2018-04-14 21:43:19 +07:00
Junyoung Choi (Sai)
02fb1d01ad Merge pull request #1804 from romainwn/feature/add-issue-template
add issue template
2018-04-14 15:20:53 +09:00
Kazz Yokomizo
497dee038f Merge pull request #1807 from ZeroX-DG/master
Edited theme and language init code to avoid code repetition
2018-04-14 14:11:24 +09:00
Hung Nguyen
a4af77f91e fixed eslint error 2018-04-13 21:46:19 +07:00
Hung Nguyen
f2a4e1d230 fixed typo in variable name 2018-04-13 21:00:01 +07:00
Hung Nguyen
8560901f80 Edited theme and language init code to avoid code repetition 2018-04-13 20:39:17 +07:00
Frank Kanis
daea604c60 Added spaces 2018-04-12 21:51:01 +02:00
Frank Kanis
7aedb59f26 Fixed moving notes with images 2018-04-12 21:20:16 +02:00
Unknown
0be1c2f464 remove unnecessary lines 2018-04-12 19:47:02 +02:00
Romain Le Quellec
0dbfaf0e79 fix checkbox 2018-04-12 13:34:26 +02:00
Romain Le Quellec
4147399cda add issue template 2018-04-12 13:25:37 +02:00
Frank Kanis
cc667ac738 Added spacing 2018-04-12 00:27:20 +02:00
Frank Kanis
022915ffc9 Fixed change some languages 2018-04-12 00:10:22 +02:00
Sosuke Suzuki
eafccc4fc4 remove confirmDeletion function 2018-04-10 16:44:04 +09:00
Sosuke Suzuki
ce440351a5 use extracted confirmDeleteNote function 2018-04-10 16:28:05 +09:00
Sosuke Suzuki
be94edde0f extract confirmDeleteNote function 2018-04-10 16:26:50 +09:00
William Grant
a32cfc8aff Revert "updated for separate methods for full screen depending on the platform"
This reverts commit b46b958105.
2018-03-30 13:01:18 +02:00
William Grant
b46b958105 updated for separate methods for full screen depending on the platform 2018-03-30 11:05:20 +02:00
William Grant
11f8cfe0e6 fixed label key-spacing 2018-03-28 19:21:36 +02:00
William Grant
e1e3cc7999 moved label to a better position 2018-03-28 19:17:40 +02:00
William Grant
9a445e34fd fixed trailing spaces 2018-03-27 21:06:59 +02:00
William Grant
ee78e113de Moved 'Toggle Fullscreen' to the View label used non-OS specific fullscreen 2018-03-27 20:52:58 +02:00
William Grant
f0144233f9 Fullscreen shortcut for non macOS is now F11 2018-03-25 23:04:16 +02:00
William Grant
4f98995fe4 ... that second equals 2018-03-25 18:47:21 +02:00
William Grant
56231edc3a fix delete confirmation method 2018-03-25 18:43:54 +02:00
William Grant
871ab428c2 Revert "updated node version in travis.yml"
This reverts commit a9b75f752e.
2018-03-25 18:42:48 +02:00
William Grant
a9b75f752e updated node version in travis.yml 2018-03-25 18:28:02 +02:00
William Grant
191f2cacbf Show confirmation dialog when deleting notes from the context menu if set in preferences 2018-03-25 12:27:04 +02:00
Yu-Hung Ou
8ae7d96cc7 updated Travis CI node version 2018-02-27 23:59:32 +11:00
139 changed files with 5840 additions and 2427 deletions

View File

@@ -19,5 +19,8 @@
"FileReader": true, "FileReader": true,
"localStorage": true, "localStorage": true,
"fetch": true "fetch": true
},
"env": {
"jest": true
} }
} }

View File

@@ -1,8 +1,9 @@
language: node_js language: node_js
node_js: node_js:
- 6 - 7
script: script:
- npm run lint && npm run test - npm run lint && npm run test
- yarn jest
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi' - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
after_success: after_success:
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv - openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv

View File

@@ -1,10 +1,25 @@
# Current behavior
<!-- <!--
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug. Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile. If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
--> -->
# Expected behavior
# Steps to reproduce
1.
2.
3.
# Environment
- Version :
- OS Version and name :
<!-- <!--
Love Boostnote? Please consider supporting us via OpenCollective: Love Boostnote? Please consider supporting us on IssueHunt:
👉 https://opencollective.com/boostnoteio 👉 https://issuehunt.io/repos/53266139
--> -->

View File

@@ -3,34 +3,20 @@ import React from 'react'
import _ from 'lodash' import _ from 'lodash'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
import path from 'path' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import copyImage from 'browser/main/lib/dataApi/copyImage' import convertModeName from 'browser/lib/convertModeName'
import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite' import iconv from 'iconv-lite'
import crypto from 'crypto'
import consts from 'browser/lib/consts'
import fs from 'fs'
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
const buildCMRulers = (rulers, enableRulers) => const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({ column: ruler })) : [] enableRulers ? rulers.map(ruler => ({column: ruler})) : []
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
export default class CodeEditor extends React.Component { export default class CodeEditor extends React.Component {
constructor (props) { constructor (props) {
@@ -52,6 +38,9 @@ export default class CodeEditor extends React.Component {
el = el.parentNode el = el.parentNode
} }
this.props.onBlur != null && this.props.onBlur(e) this.props.onBlur != null && this.props.onBlur(e)
const {storageKey, noteKey} = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
} }
this.pasteHandler = (editor, e) => this.handlePaste(editor, e) this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
this.loadStyleHandler = (e) => { this.loadStyleHandler = (e) => {
@@ -94,8 +83,21 @@ export default class CodeEditor extends React.Component {
componentDidMount () { componentDidMount () {
const { rulers, enableRulers } = this.props const { rulers, enableRulers } = this.props
this.value = this.props.value const expandSnippet = this.expandSnippet.bind(this)
const defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
if (!fs.existsSync(consts.SNIPPET_FILE)) {
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
}
this.value = this.props.value
this.editor = CodeMirror(this.refs.root, { this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers), rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value, value: this.props.value,
@@ -116,6 +118,8 @@ export default class CodeEditor extends React.Component {
Tab: function (cm) { Tab: function (cm) {
const cursor = cm.getCursor() const cursor = cm.getCursor()
const line = cm.getLine(cursor.line) const line = cm.getLine(cursor.line)
const cursorPosition = cursor.ch
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
if (cm.somethingSelected()) cm.indentSelection('add') if (cm.somethingSelected()) cm.indentSelection('add')
else { else {
const tabs = cm.getOption('indentWithTabs') const tabs = cm.getOption('indentWithTabs')
@@ -127,6 +131,16 @@ export default class CodeEditor extends React.Component {
cm.execCommand('insertSoftTab') cm.execCommand('insertSoftTab')
} }
cm.execCommand('goLineEnd') cm.execCommand('goLineEnd')
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
// text expansion on tab key if the char before is alphabet
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
if (expandSnippet(line, cursor, cm, snippets) === false) {
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
}
} else { } else {
if (tabs) { if (tabs) {
cm.execCommand('insertTab') cm.execCommand('insertTab')
@@ -170,6 +184,73 @@ export default class CodeEditor extends React.Component {
CodeMirror.Vim.map('ZZ', ':q', 'normal') CodeMirror.Vim.map('ZZ', ':q', 'normal')
} }
expandSnippet (line, cursor, cm, snippets) {
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
const templateCursorString = ':{}'
for (let i = 0; i < snippets.length; i++) {
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
for (let j = 0; j < snippetLines.length; j++) {
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
if (cursorIndex !== -1) {
cursorLineNumber = j
cursorLinePosition = cursorIndex
cm.replaceRange(
snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
}
}
} else {
cm.replaceRange(
snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
}
return true
}
}
return false
}
getWordBeforeCursor (line, lineNumber, cursorPosition) {
let wordBeforeCursor = ''
const originCursorPosition = cursorPosition
const emptyChars = /\t|\s|\r|\n/
// to prevent the word to expand is long that will crash the whole app
// the safeStop is there to stop user to expand words that longer than 20 chars
const safeStop = 20
while (cursorPosition > 0) {
const currentChar = line.substr(cursorPosition - 1, 1)
// if char is not an empty char
if (!emptyChars.test(currentChar)) {
wordBeforeCursor = currentChar + wordBeforeCursor
} else if (wordBeforeCursor.length >= safeStop) {
throw new Error('Your snippet trigger is too long !')
} else {
break
}
cursorPosition--
}
return {
text: wordBeforeCursor,
range: {
from: {line: lineNumber, ch: originCursorPosition},
to: {line: lineNumber, ch: cursorPosition}
}
}
}
quitEditor () { quitEditor () {
document.querySelector('textarea').blur() document.querySelector('textarea').blur()
} }
@@ -187,7 +268,7 @@ export default class CodeEditor extends React.Component {
componentDidUpdate (prevProps, prevState) { componentDidUpdate (prevProps, prevState) {
let needRefresh = false let needRefresh = false
const { rulers, enableRulers } = this.props const {rulers, enableRulers} = this.props
if (prevProps.mode !== this.props.mode) { if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode) this.setMode(this.props.mode)
} }
@@ -231,7 +312,7 @@ export default class CodeEditor extends React.Component {
} }
setMode (mode) { setMode (mode) {
let syntax = CodeMirror.findModeByName(pass(mode)) let syntax = CodeMirror.findModeByName(convertModeName(mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime) this.editor.setOption('mode', syntax.mime)
@@ -275,28 +356,19 @@ export default class CodeEditor extends React.Component {
this.editor.setCursor(cursor) this.editor.setCursor(cursor)
} }
handleDropImage (e) { handleDropImage (dropEvent) {
e.preventDefault() dropEvent.preventDefault()
const ValidImageTypes = ['image/gif', 'image/jpeg', 'image/png'] const {storageKey, noteKey} = this.props
attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent)
const file = e.dataTransfer.files[0]
const filePath = file.path
const filename = path.basename(filePath)
const fileType = file['type']
copyImage(filePath, this.props.storageKey).then((imagePath) => {
var showPreview = ValidImageTypes.indexOf(fileType) > 0
const imageMd = `${showPreview ? '!' : ''}[${filename}](${path.join('/:storage', imagePath)})`
this.insertImageMd(imageMd)
})
} }
insertImageMd (imageMd) { insertAttachmentMd (imageMd) {
this.editor.replaceSelection(imageMd) this.editor.replaceSelection(imageMd)
} }
handlePaste (editor, e) { handlePaste (editor, e) {
const clipboardData = e.clipboardData const clipboardData = e.clipboardData
const {storageKey, noteKey} = this.props
const dataTransferItem = clipboardData.items[0] const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text') const pastedTxt = clipboardData.getData('text')
const isURL = (str) => { const isURL = (str) => {
@@ -306,38 +378,28 @@ export default class CodeEditor extends React.Component {
const isInLinkTag = (editor) => { const isInLinkTag = (editor) => {
const startCursor = editor.getCursor('start') const startCursor = editor.getCursor('start')
const prevChar = editor.getRange( const prevChar = editor.getRange(
{ line: startCursor.line, ch: startCursor.ch - 2 }, {line: startCursor.line, ch: startCursor.ch - 2},
{ line: startCursor.line, ch: startCursor.ch } {line: startCursor.line, ch: startCursor.ch}
) )
const endCursor = editor.getCursor('end') const endCursor = editor.getCursor('end')
const nextChar = editor.getRange( const nextChar = editor.getRange(
{ line: endCursor.line, ch: endCursor.ch }, {line: endCursor.line, ch: endCursor.ch},
{ line: endCursor.line, ch: endCursor.ch + 1 } {line: endCursor.line, ch: endCursor.ch + 1}
) )
return prevChar === '](' && nextChar === ')' return prevChar === '](' && nextChar === ')'
} }
if (dataTransferItem.type.match('image')) { if (dataTransferItem.type.match('image')) {
const blob = dataTransferItem.getAsFile() attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
const reader = new FileReader()
let base64data
reader.readAsDataURL(blob)
reader.onloadend = () => {
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
const imageName = Math.random().toString(36).slice(-16)
const storagePath = findStorage(this.props.storageKey).path
const imageDir = path.join(storagePath, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
const imagePath = path.join(imageDir, `${imageName}.png`)
fs.writeFile(imagePath, binaryData, 'binary')
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { } else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(e, editor, pastedTxt) this.handlePasteUrl(e, editor, pastedTxt)
} }
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
.then((modifiedText) => {
this.editor.replaceSelection(modifiedText)
})
e.preventDefault()
}
} }
handleScroll (e) { handleScroll (e) {
@@ -351,24 +413,58 @@ export default class CodeEditor extends React.Component {
const taggedUrl = `<${pastedTxt}>` const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl) editor.replaceSelection(taggedUrl)
const isImageReponse = (response) => {
return response.headers.has('content-type') &&
response.headers.get('content-type').match(/^image\/.+$/)
}
const replaceTaggedUrl = (replacement) => {
const value = editor.getValue()
const cursor = editor.getCursor()
const newValue = value.replace(taggedUrl, replacement)
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
editor.setValue(newValue)
editor.setCursor(newCursor)
}
fetch(pastedTxt, { fetch(pastedTxt, {
method: 'get' method: 'get'
}).then((response) => { }).then((response) => {
return this.decodeResponse(response) if (isImageReponse(response)) {
}).then((response) => { return this.mapImageResponse(response, pastedTxt)
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html') } else {
const value = editor.getValue() return this.mapNormalResponse(response, pastedTxt)
const cursor = editor.getCursor() }
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})` }).then((replacement) => {
const newValue = value.replace(taggedUrl, LinkWithTitle) replaceTaggedUrl(replacement)
editor.setValue(newValue)
editor.setCursor(cursor)
}).catch((e) => { }).catch((e) => {
const value = editor.getValue() replaceTaggedUrl(pastedTxt)
const newValue = value.replace(taggedUrl, pastedTxt) })
const cursor = editor.getCursor() }
editor.setValue(newValue)
editor.setCursor(cursor) mapNormalResponse (response, pastedTxt) {
return this.decodeResponse(response).then((body) => {
return new Promise((resolve, reject) => {
try {
const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html')
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
resolve(linkWithTitle)
} catch (e) {
reject(e)
}
})
})
}
mapImageResponse (response, pastedTxt) {
return new Promise((resolve, reject) => {
try {
const url = response.url
const name = url.substring(url.lastIndexOf('/') + 1)
const imageLinkWithName = `![${name}](${pastedTxt})`
resolve(imageLinkWithName)
} catch (e) {
reject(e)
}
}) })
} }
@@ -398,11 +494,12 @@ export default class CodeEditor extends React.Component {
} }
render () { render () {
const { className, fontSize } = this.props const {className, fontSize} = this.props
let fontFamily = this.props.fontFamily let fontFamily = this.props.fontFamily
fontFamily = _.isString(fontFamily) && fontFamily.length > 0 fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily) ? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily : defaultEditorFontFamily
const width = this.props.width
return ( return (
<div <div
className={className == null className={className == null
@@ -413,7 +510,8 @@ export default class CodeEditor extends React.Component {
tabIndex='-1' tabIndex='-1'
style={{ style={{
fontFamily: fontFamily.join(', '), fontFamily: fontFamily.join(', '),
fontSize: fontSize fontSize: fontSize,
width: width
}} }}
onDrop={(e) => this.handleDropImage(e)} onDrop={(e) => this.handleDropImage(e)}
/> />

View File

@@ -5,7 +5,7 @@ import styles from './MarkdownEditor.styl'
import CodeEditor from 'browser/components/CodeEditor' 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'
class MarkdownEditor extends React.Component { class MarkdownEditor extends React.Component {
constructor (props) { constructor (props) {
@@ -223,7 +223,7 @@ class MarkdownEditor extends React.Component {
} }
render () { render () {
const { className, value, config, storageKey } = this.props const {className, value, config, storageKey, noteKey} = 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
@@ -263,6 +263,7 @@ class MarkdownEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)} onBlur={(e) => this.handleBlur(e)}
@@ -282,6 +283,8 @@ class MarkdownEditor extends React.Component {
indentSize={editorIndentSize} indentSize={editorIndentSize}
scrollPastEnd={config.preview.scrollPastEnd} scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes} smartQuotes={config.preview.smartQuotes}
smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize} sanitize={config.preview.sanitize}
ref='preview' ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
@@ -293,6 +296,9 @@ class MarkdownEditor extends React.Component {
onCheckboxClick={(e) => this.handleCheckboxClick(e)} onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification} showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
/> />
</div> </div>
) )

View File

@@ -10,18 +10,23 @@ import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams' import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper' import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard' 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'
const { remote } = require('electron') const { remote } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const { app } = remote const { app } = remote
const path = require('path') const path = require('path')
const fileUrl = require('file-url')
const dialog = remote.dialog const dialog = remote.dialog
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1] const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = 'file://' + (process.env.NODE_ENV === 'production' const appPath = fileUrl(process.env.NODE_ENV === 'production'
? app.getAppPath() ? app.getAppPath()
: path.resolve()) : path.resolve())
const CSS_FILES = [ const CSS_FILES = [
@@ -29,7 +34,7 @@ const CSS_FILES = [
`${appPath}/node_modules/codemirror/lib/codemirror.css` `${appPath}/node_modules/codemirror/lib/codemirror.css`
] ]
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) { function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
return ` return `
@font-face { @font-face {
font-family: 'Lato'; font-family: 'Lato';
@@ -49,7 +54,19 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scro
font-weight: 700; font-weight: 700;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
}
${allowCustomCSS ? customCSS : ''}
${markdownStyle} ${markdownStyle}
body { body {
font-family: '${fontFamily.join("','")}'; font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px; font-size: ${fontSize}px;
@@ -101,6 +118,16 @@ h2 {
body p { body p {
white-space: normal; white-space: normal;
} }
@media print {
body[data-theme="${theme}"] {
color: #000;
background-color: #fff;
}
.clipboardButton {
display: none
}
}
` `
} }
@@ -113,7 +140,6 @@ if (!OSX) {
defaultFontFamily.unshift('meiryo') defaultFontFamily.unshift('meiryo')
} }
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
export default class MarkdownPreview extends React.Component { export default class MarkdownPreview extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -123,7 +149,6 @@ export default class MarkdownPreview extends React.Component {
this.mouseUpHandler = (e) => this.handleMouseUp(e) this.mouseUpHandler = (e) => this.handleMouseUp(e)
this.DoubleClickHandler = (e) => this.handleDoubleClick(e) this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true}) this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e) this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText() this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd() this.saveAsMdHandler = () => this.handleSaveAsMd()
@@ -136,29 +161,14 @@ export default class MarkdownPreview extends React.Component {
} }
initMarkdown () { initMarkdown () {
const { smartQuotes, sanitize } = this.props const { smartQuotes, sanitize, breaks } = this.props
this.markdown = new Markdown({ this.markdown = new Markdown({
typographer: smartQuotes, typographer: smartQuotes,
sanitize sanitize,
breaks
}) })
} }
handlePreviewAnchorClick (e) {
e.preventDefault()
e.stopPropagation()
const anchor = e.target.closest('a')
const href = anchor.getAttribute('href')
if (_.isString(href) && href.match(/^#/)) {
const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
if (targetElement != null) {
this.getWindow().scrollTo(0, targetElement.offsetTop)
}
} else {
shell.openExternal(href)
}
}
handleCheckboxClick (e) { handleCheckboxClick (e) {
this.props.onCheckboxClick(e) this.props.onCheckboxClick(e)
} }
@@ -206,11 +216,13 @@ export default class MarkdownPreview extends React.Component {
handleSaveAsHtml () { handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => { this.exportAsDocument('html', (noteContent, exportTasks) => {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams() const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
const body = this.markdown.render(escapeHtmlCharacters(noteContent))
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) => {
file = file.replace('file://', '') file = file.replace('file://', '')
@@ -219,6 +231,13 @@ 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) => {
@@ -322,7 +341,10 @@ export default class MarkdownPreview extends React.Component {
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
if (prevProps.value !== this.props.value) this.rewriteIframe() if (prevProps.value !== this.props.value) this.rewriteIframe()
if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) { if (prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize ||
prevProps.smartArrows !== this.props.smartArrows ||
prevProps.breaks !== this.props.breaks) {
this.initMarkdown() this.initMarkdown()
this.rewriteIframe() this.rewriteIframe()
} }
@@ -333,14 +355,16 @@ export default class MarkdownPreview extends React.Component {
prevProps.lineNumber !== this.props.lineNumber || prevProps.lineNumber !== this.props.lineNumber ||
prevProps.showCopyNotification !== this.props.showCopyNotification || prevProps.showCopyNotification !== this.props.showCopyNotification ||
prevProps.theme !== this.props.theme || prevProps.theme !== this.props.theme ||
prevProps.scrollPastEnd !== this.props.scrollPastEnd) { prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
prevProps.customCSS !== this.props.customCSS) {
this.applyStyle() this.applyStyle()
this.rewriteIframe() this.rewriteIframe()
} }
} }
getStyleParams () { getStyleParams () {
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
let { fontFamily, codeBlockFontFamily } = this.props let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0 fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily) ? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
@@ -349,14 +373,14 @@ export default class MarkdownPreview extends React.Component {
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily) ? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily : defaultCodeBlockFontFamily
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
} }
applyStyle () { applyStyle () {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams() const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme) this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
} }
GetCodeThemeLink (theme) { GetCodeThemeLink (theme) {
@@ -369,9 +393,6 @@ export default class MarkdownPreview extends React.Component {
} }
rewriteIframe () { rewriteIframe () {
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
el.removeEventListener('click', this.anchorClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
el.removeEventListener('click', this.checkboxClickHandler) el.removeEventListener('click', this.checkboxClickHandler)
}) })
@@ -380,7 +401,7 @@ export default class MarkdownPreview extends React.Component {
el.removeEventListener('click', this.linkClickHandler) el.removeEventListener('click', this.linkClickHandler)
}) })
const { theme, indentSize, showCopyNotification, storagePath } = this.props const { theme, indentSize, showCopyNotification, storagePath, noteKey } = 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)
@@ -391,36 +412,25 @@ export default class MarkdownPreview extends React.Component {
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
}) })
} }
this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value) let renderedHTML = this.markdown.render(value)
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
this.fixDecodedURI(el)
el.href = this.markdown.normalizeLinkText(el.href)
if (!/\/:storage/.test(el.href)) return
el.href = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.href)))}`
el.addEventListener('click', this.anchorClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
el.addEventListener('click', this.checkboxClickHandler) el.addEventListener('click', this.checkboxClickHandler)
}) })
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
el.addEventListener('click', this.linkClickHandler) el.addEventListener('click', this.linkClickHandler)
}) })
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
el.src = this.markdown.normalizeLinkText(el.src)
if (!/\/:storage/.test(el.src)) return
el.src = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
})
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme) codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
? codeBlockTheme ? codeBlockTheme
: 'default' : 'default'
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
let syntax = CodeMirror.findModeByName(el.className) let syntax = CodeMirror.findModeByName(convertModeName(el.className))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => { CodeMirror.requireMode(syntax.mode, () => {
const content = htmlTextHelper.decodeEntities(el.innerHTML) const content = htmlTextHelper.decodeEntities(el.innerHTML)
@@ -462,7 +472,7 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = '' el.innerHTML = ''
diagram.drawSVG(el, opts) diagram.drawSVG(el, opts)
_.forEach(el.querySelectorAll('a'), (el) => { _.forEach(el.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.anchorClickHandler) el.addEventListener('click', this.linkClickHandler)
}) })
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@@ -478,7 +488,7 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = '' el.innerHTML = ''
diagram.drawSVG(el, {theme: 'simple'}) diagram.drawSVG(el, {theme: 'simple'})
_.forEach(el.querySelectorAll('a'), (el) => { _.forEach(el.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.anchorClickHandler) el.addEventListener('click', this.linkClickHandler)
}) })
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@@ -523,22 +533,44 @@ export default class MarkdownPreview extends React.Component {
} }
handlelinkClick (e) { handlelinkClick (e) {
const noteHash = e.target.href.split('/').pop() e.preventDefault()
e.stopPropagation()
const href = e.target.href
const linkHash = href.split('/').pop()
const regexNoteInternalLink = /main.html#(.+)/
if (regexNoteInternalLink.test(linkHash)) {
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
if (targetElement != null) {
this.getWindow().scrollTo(0, targetElement.offsetTop)
}
return
}
// this will match the new uuid v4 hash and the old hash // this will match the new uuid v4 hash and the old hash
// e.g. // e.g.
// :note:1c211eb7dcb463de6490 and // :note:1c211eb7dcb463de6490 and
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c // :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/ const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
if (regexIsNoteLink.test(noteHash)) { if (regexIsNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', noteHash.replace(':note:', '')) eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
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
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/ const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
if (regexIsLegacyNoteLink.test(noteHash)) { if (regexIsLegacyNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', noteHash.split('-')[1]) eventEmitter.emit('list:jump', linkHash.split('-')[1])
return
} }
// other case
shell.openExternal(href)
} }
render () { render () {
@@ -561,9 +593,12 @@ MarkdownPreview.propTypes = {
onDoubleClick: PropTypes.func, onDoubleClick: PropTypes.func,
onMouseUp: PropTypes.func, onMouseUp: PropTypes.func,
onMouseDown: PropTypes.func, onMouseDown: PropTypes.func,
onContextMenu: PropTypes.func,
className: PropTypes.string, className: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
showCopyNotification: PropTypes.bool, showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string, storagePath: PropTypes.string,
smartQuotes: PropTypes.bool smartQuotes: PropTypes.bool,
smartArrows: PropTypes.bool,
breaks: PropTypes.bool
} }

View File

@@ -14,6 +14,10 @@ class MarkdownSplitEditor extends React.Component {
this.focus = () => this.refs.code.focus() this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload() this.reload = () => this.refs.code.reload()
this.userScroll = true this.userScroll = true
this.state = {
isSliderFocused: false,
codeEditorWidthInPercent: 50
}
} }
handleOnChange () { handleOnChange () {
@@ -87,20 +91,60 @@ class MarkdownSplitEditor extends React.Component {
} }
} }
handleMouseMove (e) {
if (this.state.isSliderFocused) {
const rootRect = this.refs.root.getBoundingClientRect()
const rootWidth = rootRect.width
const offset = rootRect.left
let newCodeEditorWidthInPercent = (e.pageX - offset) / rootWidth * 100
// limit minSize to 10%, maxSize to 90%
if (newCodeEditorWidthInPercent <= 10) {
newCodeEditorWidthInPercent = 10
}
if (newCodeEditorWidthInPercent >= 90) {
newCodeEditorWidthInPercent = 90
}
this.setState({
codeEditorWidthInPercent: newCodeEditorWidthInPercent
})
}
}
handleMouseUp (e) {
e.preventDefault()
this.setState({
isSliderFocused: false
})
}
handleMouseDown (e) {
e.preventDefault()
this.setState({
isSliderFocused: true
})
}
render () { render () {
const { config, value, storageKey } = this.props const {config, value, storageKey, noteKey} = 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
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {} const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none' previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
return ( return (
<div styleName='root'> <div styleName='root' ref='root'
onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)}>
<CodeEditor <CodeEditor
styleName='codeEditor' styleName='codeEditor'
ref='code' ref='code'
width={this.state.codeEditorWidthInPercent + '%'}
mode='GitHub Flavored Markdown' mode='GitHub Flavored Markdown'
value={value} value={value}
theme={config.editor.theme} theme={config.editor.theme}
@@ -115,9 +159,13 @@ class MarkdownSplitEditor extends React.Component {
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
storageKey={storageKey} storageKey={storageKey}
noteKey={noteKey}
onChange={this.handleOnChange.bind(this)} onChange={this.handleOnChange.bind(this)}
onScroll={this.handleScroll.bind(this)} onScroll={this.handleScroll.bind(this)}
/> />
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
<div styleName='slider-hitbox' />
</div>
<MarkdownPreview <MarkdownPreview
style={previewStyle} style={previewStyle}
styleName='preview' styleName='preview'
@@ -130,6 +178,8 @@ class MarkdownSplitEditor extends React.Component {
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd} scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes} smartQuotes={config.preview.smartQuotes}
smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize} sanitize={config.preview.sanitize}
ref='preview' ref='preview'
tabInde='0' tabInde='0'
@@ -138,6 +188,9 @@ class MarkdownSplitEditor extends React.Component {
onScroll={this.handleScroll.bind(this)} onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification} showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
/> />
</div> </div>
) )

View File

@@ -3,7 +3,14 @@
height 100% height 100%
font-size 30px font-size 30px
display flex display flex
.codeEditor .slider
width 50% absolute top bottom
.preview top -2px
width 50% width 0
z-index 0
.slider-hitbox
absolute top bottom left right
width 7px
left -3px
z-index 10
cursor col-resize

View File

@@ -321,3 +321,76 @@ body[data-theme="solarized-dark"]
.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="monokai"]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
.item
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
&:active
transition 0.15s
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
color $ui-monokai-text-color
.item-wrapper
border-color alpha($ui-monokai-button-backgroundColor, 60%)
.item--active
border-color $ui-monokai-borderColor
background-color $ui-monokai-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-monokai-text-color
.item-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
&:hover
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
color #c0392b
.item-bottom-tagList-item
background-color alpha(#fff, 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

@@ -104,6 +104,7 @@ body[data-theme="dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 20%) background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color color $ui-dark-text-color
.item-simple-title .item-simple-title
.item-simple-title-empty
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
transition 0.15s transition 0.15s
@@ -117,6 +118,7 @@ body[data-theme="dark"]
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
.item-simple-title .item-simple-title
.item-simple-title-empty
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
transition 0.15s transition 0.15s
@@ -132,6 +134,7 @@ body[data-theme="dark"]
.item-simple-wrapper .item-simple-wrapper
border-color transparent border-color transparent
.item-simple-title .item-simple-title
.item-simple-title-empty
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
color $ui-dark-text-color color $ui-dark-text-color
@@ -165,9 +168,10 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-noteList-backgroundColor background-color $ui-solarized-dark-noteList-backgroundColor
&:hover &:hover
transition 0.15s transition 0.15s
// background-color alpha($ui-dark-button--active-backgroundColor, 20%) background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.item-simple-title .item-simple-title
.item-simple-title-empty
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
transition 0.15s transition 0.15s
@@ -178,9 +182,10 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
&:active &:active
transition 0.15s transition 0.15s
background-color $ui-solarized-dark-button--active-backgroundColor // background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-solarized-dark-text-color color $ui-dark-text-color
.item-simple-title .item-simple-title
.item-simple-title-empty
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
transition 0.15s transition 0.15s
@@ -192,11 +197,13 @@ body[data-theme="solarized-dark"]
.item-simple--active .item-simple--active
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-button--active-backgroundColor background-color $ui-solarized-dark-tag-backgroundColor
.item-simple-wrapper .item-simple-wrapper
border-color transparent border-color transparent
.item-simple-title .item-simple-title
.item-simple-title-empty
.item-simple-title-icon .item-simple-title-icon
color $ui-dark-text-color
.item-simple-bottom-time .item-simple-bottom-time
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
@@ -207,8 +214,75 @@ body[data-theme="solarized-dark"]
color #c0392b color #c0392b
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
background-color alpha(#fff, 20%) background-color alpha(#fff, 20%)
.item-simple-right .item-simple-title
float right color $ui-dark-text-color
.item-simple-right-storageName border-bottom $ui-dark-borderColor
padding-left 4px .item-simple-right
opacity 0.4 float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
body[data-theme="monokai"]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
.item-simple
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
&:hover
transition 0.15s
background-color alpha($ui-monokai-button-backgroundColor, 60%)
color $ui-monokai-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#fff, 20%)
color $ui-monokai-text-color
&:active
transition 0.15s
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
color $ui-monokai-text-color
.item-simple--active
border-color $ui-monokai-borderColor
background-color $ui-monokai-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-monokai-text-color
.item-simple-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 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

@@ -41,3 +41,14 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor background-color $ui-solarized-dark-button-backgroundColor
&:hover &:hover
color #5CB85C color #5CB85C
body[data-theme="monokai"]
.notification-area
background-color none
.notification-link
color $ui-monokai-text-color
border none
background-color $ui-monokai-button-backgroundColor
&:hover
color #5CB85C

View File

@@ -51,7 +51,7 @@ const SideNavFilter = ({
</button> </button>
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'} <button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick} onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
> >
<div styleName='iconWrap'> <div styleName='iconWrap'>
<img src={isTrashedActive <img src={isTrashedActive
@@ -60,7 +60,7 @@ const SideNavFilter = ({
} }
/> />
</div> </div>
<span onContextMenu={handleFilterButtonContextMenu} styleName='menu-button-label'>{i18n.__('Trash')}</span> <span styleName='menu-button-label'>{i18n.__('Trash')}</span>
<span styleName='counters'>{counterDelNote}</span> <span styleName='counters'>{counterDelNote}</span>
</button> </button>

View File

@@ -18,7 +18,7 @@
.iconWrap .iconWrap
width 20px width 20px
text-align center text-align center
.counters .counters
float right float right
color $ui-inactive-text-color color $ui-inactive-text-color
@@ -68,10 +68,9 @@
.menu-button-label .menu-button-label
position fixed position fixed
display inline-block display inline-block
height 32px height 36px
left 44px left 44px
padding 0 10px padding 0 10px
margin-top -8px
margin-left 0 margin-left 0
overflow ellipsis overflow ellipsis
z-index 10 z-index 10
@@ -222,4 +221,46 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.menu-button-label .menu-button-label
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.menu-button
&:active
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button--active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.menu-button-label
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button-label
color $ui-monokai-text-color
.menu-button-star--active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.menu-button-label
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button-label
color $ui-monokai-text-color
.menu-button-trash--active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.menu-button-label
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button-label
color $ui-monokai-text-color

View File

@@ -55,10 +55,10 @@ class SnippetTab extends React.Component {
this.handleRename() this.handleRename()
break break
case 27: case 27:
this.setState({ this.setState((prevState, props) => ({
name: this.props.snippet.name, name: props.snippet.name,
isRenaming: false isRenaming: false
}) }))
break break
} }
} }

View File

@@ -58,8 +58,8 @@
opacity 0 opacity 0
border-top-right-radius 2px border-top-right-radius 2px
border-bottom-right-radius 2px border-bottom-right-radius 2px
height 26px height 34px
line-height 26px line-height 32px
.folderList-item:hover, .folderList-item--active:hover .folderList-item:hover, .folderList-item--active:hover
.folderList-item-tooltip .folderList-item-tooltip
@@ -138,3 +138,22 @@ body[data-theme="solarized-dark"]
&:hover &:hover
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor background-color $ui-solarized-dark-button-backgroundColor
body[data-theme="monokai"]
.folderList-item
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
&:active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
&:active
background-color $ui-monokai-button-backgroundColor
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor

View File

@@ -9,16 +9,26 @@ import CSSModules from 'browser/lib/CSSModules'
/** /**
* @param {string} name * @param {string} name
* @param {Function} handleClickTagListItem * @param {Function} handleClickTagListItem
* @param {Function} handleClickNarrowToTag
* @param {bool} isActive * @param {bool} isActive
* @param {bool} isRelated
*/ */
const TagListItem = ({name, handleClickTagListItem, isActive, count}) => ( const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}> <div styleName='tagList-itemContainer'>
<span styleName='tagList-item-name'> {isRelated
{`# ${name}`} ? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
<span styleName='tagList-item-count'> {count}</span> <i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
</span> </button>
</button> : <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
}
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-name'>
{`# ${name}`}
<span styleName='tagList-item-count'>{count}</span>
</span>
</button>
</div>
) )
TagListItem.propTypes = { TagListItem.propTypes = {

View File

@@ -1,5 +1,9 @@
.tagList-itemContainer
display flex
.tagList-item .tagList-item
display flex display flex
flex 1
width 100% width 100%
height 26px height 26px
background-color transparent background-color transparent
@@ -20,9 +24,16 @@
color $ui-button-default-color color $ui-button-default-color
background-color $ui-button-default--active-backgroundColor background-color $ui-button-default--active-backgroundColor
.tagList-itemNarrow
composes tagList-item
flex none
width 20px
padding 0 4px
.tagList-item-active .tagList-item-active
background-color $ui-button-default--active-backgroundColor background-color $ui-button-default--active-backgroundColor
display flex display flex
flex 1
width 100% width 100%
height 26px height 26px
padding 0 padding 0
@@ -36,10 +47,16 @@
background-color alpha($ui-button-default--active-backgroundColor, 60%) background-color alpha($ui-button-default--active-backgroundColor, 60%)
transition 0.2s transition 0.2s
.tagList-itemNarrow-active
composes tagList-item-active
flex none
width 20px
padding 0 4px
.tagList-item-name .tagList-item-name
display block display block
flex 1 flex 1
padding 0 15px padding 0 8px 0 4px
height 26px height 26px
line-height 26px line-height 26px
border-width 0 0 0 2px border-width 0 0 0 2px
@@ -49,7 +66,10 @@
text-overflow ellipsis text-overflow ellipsis
.tagList-item-count .tagList-item-count
padding 0 3px float right
line-height 26px
padding-right 15px
font-size 13px
body[data-theme="white"] body[data-theme="white"]
.tagList-item .tagList-item

View File

@@ -47,5 +47,15 @@ body[data-theme="solarized-dark"]
.progressBar .progressBar
background-color: #2aa198 background-color: #2aa198
.percentageText
color #fdf6e3
body[data-theme="monokai"]
.percentageBar
background-color #f92672
.progressBar
background-color: #373831
.percentageText .percentageText
color #fdf6e3 color #fdf6e3

View File

@@ -199,7 +199,6 @@ ol
&>li>ul, &>li>ol &>li>ul, &>li>ol
margin 0 margin 0
code code
color #CC305F
padding 0.2em 0.4em padding 0.2em 0.4em
background-color #f7f7f7 background-color #f7f7f7
border-radius 3px border-radius 3px
@@ -294,6 +293,84 @@ kbd
line-height 1 line-height 1
padding 3px 5px padding 3px 5px
$admonition
box-shadow 0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)
position relative
margin 1.5625em 0
padding 0 1.2rem
border-left .4rem solid #448aff
border-radius .2rem
overflow auto
html .admonition>:last-child
margin-bottom 1.2rem
.admonition .admonition
margin 1em 0
.admonition p
margin-top: 0.5em
$admonition-icon
position absolute
left 1.2rem
font-family: "Material Icons"
font-weight: normal;
font-style: normal;
font-size: 24px
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
$admonition-title
margin 0 -1.2rem
padding .8rem 1.2rem .8rem 4rem
border-bottom .1rem solid rgba(68,138,255,.1)
background-color rgba(68,138,255,.1)
font-weight 700
.admonition>.admonition-title:last-child
margin-bottom 0
admonition_types = {
note: {color: #0288D1, icon: "note"},
hint: {color: #009688, icon: "info_outline"},
danger: {color: #c2185b, icon: "block"},
caution: {color: #ffa726, icon: "warning"},
error: {color: #d32f2f, icon: "error_outline"},
attention: {color: #455a64, icon: "priority_high"}
}
for name, val in admonition_types
.admonition.{name}
@extend $admonition
border-left-color: val[color]
.admonition.{name}>.admonition-title
@extend $admonition-title
border-bottom-color: .1rem solid rgba(val[color], 0.2)
background-color: rgba(val[color], 0.2)
.admonition.{name}>.admonition-title:before
@extend $admonition-icon
color: val[color]
content: val[icon]
themeDarkBackground = darken(#21252B, 10%) themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9 themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%) themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -371,3 +448,32 @@ 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
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
themeMonokaiTableHead = themeMonokaiTableEven
themeMonokaiTableBorder = themeDarkBorder
body[data-theme="monokai"]
color $ui-monokai-text-color
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
table
thead
tr
background-color themeMonokaiTableHead
th
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeMonokaiTableOdd
tr:nth-child(2n)
background-color themeMonokaiTableEven
td
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
kbd
background-color themeDarkBackground

78
browser/lib/Languages.js Normal file
View File

@@ -0,0 +1,78 @@
const languages = [
{
name: 'Albanian',
locale: 'sq'
},
{
name: 'Chinese (zh-CN)',
locale: 'zh-CN'
},
{
name: 'Chinese (zh-TW)',
locale: 'zh-TW'
},
{
name: 'Danish',
locale: 'da'
},
{
name: 'English',
locale: 'en'
},
{
name: 'French',
locale: 'fr'
},
{
name: 'German',
locale: 'de'
},
{
name: 'Hungarian',
locale: 'hu'
},
{
name: 'Japanese',
locale: 'ja'
},
{
name: 'Korean',
locale: 'ko'
},
{
name: 'Norwegian',
locale: 'no'
},
{
name: 'Polish',
locale: 'pl'
},
{
name: 'Portuguese',
locale: 'pt'
},
{
name: 'Russian',
locale: 'ru'
},
{
name: 'Spanish',
locale: 'es-ES'
}, {
name: 'Turkish',
locale: 'tr'
}
]
module.exports = {
getLocales () {
return languages.reduce(function (localeList, locale) {
localeList.push(locale.locale)
return localeList
}, [])
},
getLanguages () {
return languages
}
}

View File

@@ -0,0 +1,23 @@
import electron from 'electron'
import i18n from 'browser/lib/i18n'
const { remote } = electron
const { dialog } = remote
export function confirmDeleteNote (confirmDeletion, permanent) {
if (confirmDeletion || permanent) {
const alertConfig = {
ype: 'warning',
message: i18n.__('Confirm note deletion'),
detail: i18n.__('This will permanently remove this note.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
}
const dialogButtonIndex = dialog.showMessageBox(
remote.getCurrentWindow(), alertConfig
)
return dialogButtonIndex === 0
}
return true
}

View File

@@ -12,6 +12,10 @@ const themes = fs.readdirSync(themePath)
}) })
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light') themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
const snippetFile = process.env.NODE_ENV !== 'test'
? path.join(app.getPath('userData'), 'snippets.json')
: '' // return nothing as we specified different path to snippets.json in test
const consts = { const consts = {
FOLDER_COLORS: [ FOLDER_COLORS: [
'#E10051', '#E10051',
@@ -31,7 +35,8 @@ const consts = {
'Dodger Blue', 'Dodger Blue',
'Violet Eggplant' 'Violet Eggplant'
], ],
THEMES: ['default'].concat(themes) THEMES: ['default'].concat(themes),
SNIPPET_FILE: snippetFile
} }
module.exports = consts module.exports = consts

View File

@@ -0,0 +1,14 @@
export default function convertModeName (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}

View File

@@ -1,11 +1,12 @@
const path = require('path') const path = require('path')
const { remote } = require('electron') const { remote } = require('electron')
const { app } = remote const { app } = remote
const { getLocales } = require('./Languages.js')
// load package for localization // load package for localization
const i18n = new (require('i18n-2'))({ const i18n = new (require('i18n-2'))({
// setup some locales - other locales default to the first locale // setup some locales - other locales default to the first locale
locales: ['en', 'sq', 'zh-CN', 'zh-TW', 'da', 'fr', 'de', 'hu', 'ja', 'ko', 'no', 'pl', 'pt', 'es-ES'], locales: getLocales(),
extension: '.json', extension: '.json',
directory: process.env.NODE_ENV === 'production' directory: process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './locales') ? path.join(app.getAppPath(), './locales')

View File

@@ -2,10 +2,11 @@ import markdownit from 'markdown-it'
import sanitize from './markdown-it-sanitize-html' import sanitize from './markdown-it-sanitize-html'
import emoji from 'markdown-it-emoji' import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math' import math from '@rokt33r/markdown-it-math'
import smartArrows from 'markdown-it-smartarrows'
import _ from 'lodash' import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex' import katex from 'katex'
import {lastFindInArray} from './utils' import { lastFindInArray } from './utils'
function createGutter (str, firstLineNumber) { function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1 if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
@@ -25,7 +26,7 @@ class Markdown {
linkify: true, linkify: true,
html: true, html: true,
xhtmlOut: true, xhtmlOut: true,
breaks: true, breaks: config.preview.breaks,
highlight: function (str, lang) { highlight: function (str, lang) {
const delimiter = ':' const delimiter = ':'
const langInfo = lang.split(delimiter) const langInfo = lang.split(delimiter)
@@ -141,15 +142,18 @@ class Markdown {
} }
}) })
this.md.use(require('markdown-it-kbd')) this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'))
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'), '', {
generateSource: function (umlCode) { generateSource: function (umlCode) {
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
const s = unescape(encodeURIComponent(umlCode)) const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64( const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9) deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
) )
return `http://www.plantuml.com/plantuml/svg/${zippedCode}` return `${serverAddress}/${zippedCode}`
} }
}) })
@@ -211,6 +215,10 @@ class Markdown {
return true return true
}) })
if (config.preview.smartArrows) {
this.md.use(smartArrows)
}
// Add line number attribute for scrolling // Add line number attribute for scrolling
const originalRender = this.md.renderer.render const originalRender = this.md.renderer.render
this.md.renderer.render = (tokens, options, env) => { this.md.renderer.render = (tokens, options, env) => {
@@ -234,10 +242,6 @@ class Markdown {
if (!_.isString(content)) content = '' if (!_.isString(content)) content = ''
return this.md.render(content) return this.md.render(content)
} }
normalizeLinkText (linkText) {
return this.md.normalizeLinkText(linkText)
}
} }
export default Markdown export default Markdown

0
browser/lib/markdown2.js Normal file
View File

View File

@@ -54,7 +54,25 @@ export function escapeHtmlCharacters (text) {
: html : html
} }
export function isObjectEqual (a, b) {
const aProps = Object.getOwnPropertyNames(a)
const bProps = Object.getOwnPropertyNames(b)
if (aProps.length !== bProps.length) {
return false
}
for (var i = 0; i < aProps.length; i++) {
const propName = aProps[i]
if (a[propName] !== b[propName]) {
return false
}
}
return true
}
export default { export default {
lastFindInArray, lastFindInArray,
escapeHtmlCharacters escapeHtmlCharacters,
isObjectEqual
} }

View File

@@ -30,3 +30,10 @@ body[data-theme="solarized-dark"]
border-left 1px solid $ui-solarized-dark-borderColor border-left 1px solid $ui-solarized-dark-borderColor
.empty-message .empty-message
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root
background-color $ui-monokai-noteDetail-backgroundColor
border-left 1px solid $ui-monokai-borderColor
.empty-message
color $ui-monokai-text-color

View File

@@ -133,3 +133,29 @@ body[data-theme="dark"]
color $ui-dark-button--active-color color $ui-dark-button--active-color
.search-optionList-item-name-surfix .search-optionList-item-name-surfix
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
body[data-theme="monokai"]
.root
color $ui-dark-text-color
&:hover
color white
background-color $ui-monokai-button--hover-backgroundColor
border-color $ui-monokai-borderColor
.search-optionList
color white
border-color $ui-monokai-borderColor
background-color $ui-monokai-button-backgroundColor
.search-optionList-item
&:hover
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
.search-optionList-item--active
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-button--active-color
&:hover
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-button--active-color
.search-optionList-item-name-surfix
color $ui-monokai-inactive-text-color

View File

@@ -11,6 +11,7 @@
.control-infoButton-panel .control-infoButton-panel
z-index 200 z-index 200
margin-top 0px margin-top 0px
top: 50px
right 25px right 25px
position absolute position absolute
padding 20px 25px 0 25px padding 20px 25px 0 25px
@@ -215,3 +216,43 @@ body[data-theme="solarized-dark"]
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
&:hover &:hover
color $ui-solarized-ark-text-color color $ui-solarized-ark-text-color
body[data-theme="monokai"]
.control-infoButton-panel
background-color $ui-monokai-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-monokai-noteList-backgroundColor
.modification-date
color $ui-monokai-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-monokai-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-monokai-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-monokai-borderColor, 20%)
color $ui-monokai-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-monokai-borderColor, 20%)
color $ui-monokai-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-monokai-text-color

View File

@@ -28,6 +28,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter' import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus' import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags' import striptags from 'striptags'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
class MarkdownNoteDetail extends React.Component { class MarkdownNoteDetail extends React.Component {
constructor (props) { constructor (props) {
@@ -54,10 +55,14 @@ class MarkdownNoteDetail extends React.Component {
componentDidMount () { componentDidMount () {
ee.on('topbar:togglelockbutton', this.toggleLockButton) ee.on('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:togglemodebutton', () => {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType)
})
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) { if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
this.setState({ this.setState({
note: Object.assign({}, nextProps.note) note: Object.assign({}, nextProps.note)
@@ -181,10 +186,10 @@ class MarkdownNoteDetail extends React.Component {
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
const { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
const { confirmDeletion } = this.props const { confirmDeletion } = this.props.config.ui
if (isTrashed) { if (isTrashed) {
if (confirmDeletion(true)) { if (confirmDeleteNote(confirmDeletion, true)) {
const {note, dispatch} = this.props const {note, dispatch} = this.props
dataApi dataApi
.deleteNote(note.storage, note.key) .deleteNote(note.storage, note.key)
@@ -201,7 +206,7 @@ class MarkdownNoteDetail extends React.Component {
.then(() => ee.emit('list:next')) .then(() => ee.emit('list:next'))
} }
} else { } else {
if (confirmDeletion()) { if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true note.isTrashed = true
this.setState({ this.setState({
@@ -272,6 +277,7 @@ class MarkdownNoteDetail extends React.Component {
handleSwitchMode (type) { handleSwitchMode (type) {
this.setState({ editorType: type }, () => { this.setState({ editorType: type }, () => {
this.focus()
const newConfig = Object.assign({}, this.props.config) const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type newConfig.editor.type = type
ConfigManager.set(newConfig) ConfigManager.set(newConfig)
@@ -288,6 +294,7 @@ class MarkdownNoteDetail extends React.Component {
config={config} config={config}
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
@@ -297,6 +304,7 @@ class MarkdownNoteDetail extends React.Component {
config={config} config={config}
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key}
onChange={this.handleUpdateContent.bind(this)} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/> />
@@ -437,8 +445,7 @@ MarkdownNoteDetail.propTypes = {
style: PropTypes.shape({ style: PropTypes.shape({
left: PropTypes.number left: PropTypes.number
}), }),
ignorePreviewPointerEvents: PropTypes.bool, ignorePreviewPointerEvents: PropTypes.bool
confirmDeletion: PropTypes.bool.isRequired
} }
export default CSSModules(MarkdownNoteDetail, styles) export default CSSModules(MarkdownNoteDetail, styles)

View File

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

View File

@@ -98,3 +98,7 @@ body[data-theme="solarized-dark"]
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"]
.info
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor

View File

@@ -18,6 +18,7 @@ import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash' import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle' import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton' import RestoreButton from './RestoreButton'
@@ -27,21 +28,7 @@ import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed' import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter' import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -81,7 +68,7 @@ class SnippetNoteDetail extends React.Component {
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) { if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
const nextNote = Object.assign({ const nextNote = Object.assign({
description: '' description: ''
@@ -197,10 +184,10 @@ class SnippetNoteDetail extends React.Component {
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
const { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
const { confirmDeletion } = this.props const { confirmDeletion } = this.props.config.ui
if (isTrashed) { if (isTrashed) {
if (confirmDeletion(true)) { if (confirmDeleteNote(confirmDeletion, true)) {
const {note, dispatch} = this.props const {note, dispatch} = this.props
dataApi dataApi
.deleteNote(note.storage, note.key) .deleteNote(note.storage, note.key)
@@ -217,7 +204,7 @@ class SnippetNoteDetail extends React.Component {
.then(() => ee.emit('list:next')) .then(() => ee.emit('list:next'))
} }
} else { } else {
if (confirmDeletion()) { if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true note.isTrashed = true
this.setState({ this.setState({
@@ -381,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
name: mode name: mode
}) })
} }
this.setState({note: Object.assign(this.state.note, {snippets: snippets})}) this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({ this.setState(state => ({
note: this.state.note note: state.note
}, () => { }), () => {
this.save() this.save()
}) })
} }
@@ -394,11 +381,11 @@ 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].mode = name snippets[index].mode = name
this.setState({note: Object.assign(this.state.note, {snippets: snippets})}) this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({ this.setState(state => ({
note: this.state.note note: state.note
}, () => { }), () => {
this.save() this.save()
}) })
@@ -412,10 +399,10 @@ 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
this.setState({note: Object.assign(this.state.note, {snippets: snippets})}) this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({ this.setState(state => ({
note: this.state.note note: state.note
}, () => { }), () => {
this.save() this.save()
}) })
} }
@@ -610,17 +597,17 @@ class SnippetNoteDetail extends React.Component {
} }
jumpNextTab () { jumpNextTab () {
this.setState({ this.setState(state => ({
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
}, () => { }), () => {
this.focusEditor() this.focusEditor()
}) })
} }
jumpPrevTab () { jumpPrevTab () {
this.setState({ this.setState(state => ({
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
}, () => { }), () => {
this.focusEditor() this.focusEditor()
}) })
} }
@@ -676,7 +663,7 @@ 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(pass(snippet.mode)) let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return <div styleName='tabView' return <div styleName='tabView'
@@ -883,8 +870,7 @@ SnippetNoteDetail.propTypes = {
style: PropTypes.shape({ style: PropTypes.shape({
left: PropTypes.number left: PropTypes.number
}), }),
ignorePreviewPointerEvents: PropTypes.bool, ignorePreviewPointerEvents: PropTypes.bool
confirmDeletion: PropTypes.bool.isRequired
} }
export default CSSModules(SnippetNoteDetail, styles) export default CSSModules(SnippetNoteDetail, styles)

View File

@@ -152,4 +152,21 @@ body[data-theme="solarized-dark"]
.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
body[data-theme="monokai"]
.root
border-left 1px solid $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor
.body
background-color $ui-monokai-noteDetail-backgroundColor
.body .description textarea
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
border 1px solid $ui-monokai-borderColor
.tabList
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color

View File

@@ -44,16 +44,9 @@ class TagSelect extends React.Component {
} }
removeLastTag () { removeLastTag () {
let { value } = this.props this.removeTagByCallback((value) => {
value.pop()
value = _.isArray(value) })
? value.slice()
: []
value.pop()
value = _.uniq(value)
this.value = value
this.props.onChange()
} }
reset () { reset () {
@@ -96,15 +89,22 @@ class TagSelect extends React.Component {
} }
handleTagRemoveButtonClick (tag) { handleTagRemoveButtonClick (tag) {
return (e) => { this.removeTagByCallback((value, tag) => {
let { value } = this.props
value.splice(value.indexOf(tag), 1) value.splice(value.indexOf(tag), 1)
value = _.uniq(value) }, tag)
}
this.value = value removeTagByCallback (callback, tag = null) {
this.props.onChange() let { value } = this.props
}
value = _.isArray(value)
? value.slice()
: []
callback(value, tag)
value = _.uniq(value)
this.value = value
this.props.onChange()
} }
render () { render () {
@@ -118,7 +118,7 @@ class TagSelect extends React.Component {
> >
<span styleName='tag-label'>#{tag}</span> <span styleName='tag-label'>#{tag}</span>
<button styleName='tag-removeButton' <button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)} onClick={(e) => this.handleTagRemoveButtonClick(tag)}
> >
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' /> <img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
</button> </button>

View File

@@ -81,4 +81,20 @@ body[data-theme="solarized-dark"]
.newTag .newTag
border-color none border-color none
background-color transparent background-color transparent
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.tag
background-color $ui-monokai-button-backgroundColor
.tag-removeButton
border-color $ui-button--focus-borderColor
background-color transparent
.tag-label
color $ui-monokai-text-color
.newTag
border-color none
background-color transparent
color $ui-monokai-text-color

View File

@@ -56,3 +56,10 @@ body[data-theme="solarized-dark"]
.active .active
background-color #1EC38B background-color #1EC38B
box-shadow 2px 0px 7px #222222 box-shadow 2px 0px 7px #222222
body[data-theme="monokai"]
.control-toggleModeButton
background-color #272822
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222

View File

@@ -8,6 +8,7 @@ import SnippetNoteDetail from './SnippetNoteDetail'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import StatusBar from '../StatusBar' import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import debounceRender from 'react-debounce-render'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -33,26 +34,6 @@ class Detail extends React.Component {
ee.off('detail:delete', this.deleteHandler) ee.off('detail:delete', this.deleteHandler)
} }
confirmDeletion (permanent) {
if (this.props.config.ui.confirmDeletion || permanent) {
const electron = require('electron')
const { remote } = electron
const { dialog } = remote
const alertConfig = {
type: 'warning',
message: i18n.__('Confirm note deletion'),
detail: i18n.__('This will permanently remove this note.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
}
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
return dialogueButtonIndex === 0
}
return true
}
render () { render () {
const { location, data, config } = this.props const { location, data, config } = this.props
let note = null let note = null
@@ -82,7 +63,6 @@ class Detail extends React.Component {
<SnippetNoteDetail <SnippetNoteDetail
note={note} note={note}
config={config} config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root' ref='root'
{..._.pick(this.props, [ {..._.pick(this.props, [
'dispatch', 'dispatch',
@@ -99,7 +79,6 @@ class Detail extends React.Component {
<MarkdownNoteDetail <MarkdownNoteDetail
note={note} note={note}
config={config} config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root' ref='root'
{..._.pick(this.props, [ {..._.pick(this.props, [
'dispatch', 'dispatch',
@@ -121,4 +100,4 @@ Detail.propTypes = {
ignorePreviewPointerEvents: PropTypes.bool ignorePreviewPointerEvents: PropTypes.bool
} }
export default CSSModules(Detail, styles) export default debounceRender(CSSModules(Detail, styles))

View File

@@ -15,6 +15,8 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import store from 'browser/main/store' import store from 'browser/main/store'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager'
const path = require('path') const path = require('path')
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -140,47 +142,25 @@ class Main extends React.Component {
componentDidMount () { componentDidMount () {
const { dispatch, config } = this.props const { dispatch, config } = this.props
if (config.ui.theme === 'dark') { const supportedThemes = [
document.body.setAttribute('data-theme', 'dark') 'dark',
} else if (config.ui.theme === 'white') { 'white',
document.body.setAttribute('data-theme', 'white') 'solarized-dark',
} else if (config.ui.theme === 'solarized-dark') { 'monokai'
document.body.setAttribute('data-theme', 'solarized-dark') ]
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
document.body.setAttribute('data-theme', config.ui.theme)
} else { } else {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }
if (config.ui.language === 'sq') {
i18n.setLocale('sq') if (getLocales().indexOf(config.ui.language) !== -1) {
} else if (config.ui.language === 'zh-CN') { i18n.setLocale(config.ui.language)
i18n.setLocale('zh-CN')
} else if (config.ui.language === 'zh-TW') {
i18n.setLocale('zh-TW')
} else if (config.ui.language === 'da') {
i18n.setLocale('da')
} else if (config.ui.language === 'fr') {
i18n.setLocale('fr')
} else if (config.ui.language === 'de') {
i18n.setLocale('de')
} else if (config.ui.language === 'hu') {
i18n.setLocale('hu')
} else if (config.ui.language === 'ja') {
i18n.setLocale('ja')
} else if (config.ui.language === 'ko') {
i18n.setLocale('ko')
} else if (config.ui.language === 'no') {
i18n.setLocale('no')
} else if (config.ui.language === 'pl') {
i18n.setLocale('pl')
} else if (config.ui.language === 'pt') {
i18n.setLocale('pt')
} else if (config.ui.language === 'ru') {
i18n.setLocale('ru')
} else if (config.ui.language === 'es-ES') {
i18n.setLocale('es-ES')
} else { } else {
i18n.setLocale('en') i18n.setLocale('en')
} }
applyShortcuts()
// Reload all data // Reload all data
dataApi.init() dataApi.init()
.then((data) => { .then((data) => {

View File

@@ -74,4 +74,8 @@ body[data-theme="dark"]
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root, .root--expanded .root, .root--expanded
background-color $ui-solarized-dark-noteList-backgroundColor background-color $ui-solarized-dark-noteList-backgroundColor
body[data-theme="monokai"]
.root, .root--expanded
background-color $ui-monokai-noteList-backgroundColor

View File

@@ -113,4 +113,28 @@ body[data-theme="solarized-dark"]
.control-button--active .control-button--active
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
&:active &:active
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
.control
background-color $ui-monokai-noteList-backgroundColor
border-color $ui-monokai-borderColor
.control-sortBy-select
&:hover
transition 0.2s
color $ui-monokai-text-color
.control-button
color $ui-monokai-inactive-text-color
&:hover
color $ui-monokai-text-color
.control-button--active
color $ui-monokai-text-color
&:active
color $ui-monokai-text-color

View File

@@ -2,11 +2,13 @@
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'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import NoteItem from 'browser/components/NoteItem' import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple' import NoteItemSimple from 'browser/components/NoteItemSimple'
@@ -18,6 +20,7 @@ import copy from 'copy-to-clipboard'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import Markdown from '../../lib/markdown' import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote const { Menu, MenuItem, dialog } = remote
@@ -281,8 +284,8 @@ class NoteList extends React.Component {
ee.emit('detail:focus') ee.emit('detail:focus')
} }
// F or S key // L or S key
if (e.keyCode === 70 || e.keyCode === 83) { if (e.keyCode === 76 || e.keyCode === 83) {
e.preventDefault() e.preventDefault()
ee.emit('top:focus-search') ee.emit('top:focus-search')
} }
@@ -342,11 +345,10 @@ class NoteList extends React.Component {
} }
if (location.pathname.match(/\/tags/)) { if (location.pathname.match(/\/tags/)) {
const listOfTags = params.tagname.split(' ')
return data.noteMap.map(note => { return data.noteMap.map(note => {
return note return note
}).filter(note => { }).filter(note => listOfTags.every(tag => note.tags.includes(tag)))
return note.tags.includes(params.tagname)
})
} }
return this.getContextNotes() return this.getContextNotes()
@@ -455,12 +457,19 @@ class NoteList extends React.Component {
} }
handleDragStart (e, note) { handleDragStart (e, note) {
const { selectedNoteKeys } = this.state let { selectedNoteKeys } = this.state
const noteKey = getNoteKey(note)
if (!selectedNoteKeys.includes(noteKey)) {
selectedNoteKeys = []
selectedNoteKeys.push(noteKey)
}
const notes = this.notes.map((note) => Object.assign({}, note)) const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes) const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData) e.dataTransfer.setData('note', noteData)
this.setState({ selectedNoteKeys: [] }) this.selectNextNote()
} }
handleNoteContextMenu (e, uniqueKey) { handleNoteContextMenu (e, uniqueKey) {
@@ -585,16 +594,11 @@ class NoteList extends React.Component {
const notes = this.notes.map((note) => Object.assign({}, note)) const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0] const firstNote = selectedNotes[0]
const { confirmDeletion } = this.props.config.ui
if (firstNote.isTrashed) { if (firstNote.isTrashed) {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note' if (!confirmDeleteNote(confirmDeletion, true)) return
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Confirm note deletion'),
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (dialogueButtonIndex === 1) return
Promise.all( Promise.all(
selectedNotes.map((note) => { selectedNotes.map((note) => {
return dataApi return dataApi
@@ -615,6 +619,8 @@ class NoteList extends React.Component {
}) })
console.log('Notes were all deleted') console.log('Notes were all deleted')
} else { } else {
if (!confirmDeleteNote(confirmDeletion, false)) return
Promise.all( Promise.all(
selectedNotes.map((note) => { selectedNotes.map((note) => {
note.isTrashed = true note.isTrashed = true
@@ -658,6 +664,10 @@ class NoteList extends React.Component {
title: firstNote.title + ' ' + i18n.__('copy'), title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content content: firstNote.content
}) })
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
return note
})
.then((note) => { .then((note) => {
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
@@ -918,7 +928,7 @@ class NoteList extends React.Component {
if (note.isTrashed !== true || location.pathname === '/trashed') return true if (note.isTrashed !== true || location.pathname === '/trashed') return true
}) })
moment.locale('en', { moment.updateLocale('en', {
relativeTime: { relativeTime: {
future: 'in %s', future: 'in %s',
past: '%s ago', past: '%s ago',
@@ -939,15 +949,24 @@ class NoteList extends React.Component {
const viewType = this.getViewType() const viewType = this.getViewType()
const autoSelectFirst =
notes.length === 1 ||
selectedNoteKeys.length === 0 ||
notes.every(note => !selectedNoteKeys.includes(note.key))
const noteList = notes const noteList = notes
.map(note => { .map((note, index) => {
if (note == null) { if (note == null) {
return null return null
} }
const isDefault = config.listStyle === 'DEFAULT' const isDefault = config.listStyle === 'DEFAULT'
const uniqueKey = getNoteKey(note) const uniqueKey = getNoteKey(note)
const isActive = selectedNoteKeys.includes(uniqueKey)
const isActive =
selectedNoteKeys.includes(uniqueKey) ||
notes.length === 1 ||
(autoSelectFirst && index === 0)
const dateDisplay = moment( const dateDisplay = moment(
config.sortBy === 'CREATED_AT' config.sortBy === 'CREATED_AT'
? note.createdAt : note.updatedAt ? note.createdAt : note.updatedAt
@@ -1049,4 +1068,4 @@ NoteList.propTypes = {
}) })
} }
export default CSSModules(NoteList, styles) export default debounceRender(CSSModules(NoteList, styles))

View File

@@ -48,4 +48,5 @@ body[data-theme="dark"]
line-height normal line-height normal
border-radius 2px border-radius 2px
opacity 0 opacity 0
transition 0.1s transition 0.1s
white-space nowrap

View File

@@ -117,3 +117,8 @@ body[data-theme="solarized-dark"]
.root, .root--folded .root, .root--folded
background-color $ui-solarized-dark-backgroundColor background-color $ui-solarized-dark-backgroundColor
border-right 1px solid $ui-solarized-dark-borderColor border-right 1px solid $ui-solarized-dark-borderColor
body[data-theme="monokai"]
.root, .root--folded
background-color $ui-monokai-backgroundColor
border-right 1px solid $ui-monokai-borderColor

View File

@@ -14,6 +14,8 @@ import i18n from 'browser/lib/i18n'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, dialog } = remote const { Menu, dialog } = remote
const escapeStringRegexp = require('escape-string-regexp')
const path = require('path')
class StorageItem extends React.Component { class StorageItem extends React.Component {
constructor (props) { constructor (props) {
@@ -201,7 +203,7 @@ class StorageItem extends React.Component {
createdNoteData.forEach((newNote) => { createdNoteData.forEach((newNote) => {
dispatch({ dispatch({
type: 'MOVE_NOTE', type: 'MOVE_NOTE',
originNote: noteData.find((note) => note.content === newNote.content), originNote: noteData.find((note) => note.content === newNote.oldContent),
note: newNote note: newNote
}) })
}) })
@@ -223,7 +225,8 @@ 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) => {
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key))) let 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 noteSet = folderNoteMap.get(storage.key + '-' + folder.key) const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = 0 let noteCount = 0
@@ -253,7 +256,7 @@ class StorageItem extends React.Component {
) )
}) })
const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$')) const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
return ( return (
<div styleName={isFolded ? 'root--folded' : 'root'} <div styleName={isFolded ? 'root--folded' : 'root'}

View File

@@ -44,7 +44,7 @@
height 36px height 36px
padding-left 25px padding-left 25px
padding-right 15px padding-right 15px
line-height 22px line-height 36px
cursor pointer cursor pointer
font-size 14px font-size 14px
border none border none
@@ -147,7 +147,7 @@ body[data-theme="dark"]
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
&:active &:active
color $ui-dark-text-color color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
.header--active .header--active
.header-addFolderButton .header-addFolderButton
@@ -180,7 +180,7 @@ body[data-theme="dark"]
&:active, &:active:hover &:active, &:active:hover
color $ui-dark-text-color color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor

View File

@@ -29,6 +29,7 @@
border-radius 2px border-radius 2px
opacity 0 opacity 0
transition 0.1s transition 0.1s
white-space nowrap
body[data-theme="white"] body[data-theme="white"]
.non-active-button .non-active-button

View File

@@ -145,20 +145,29 @@ 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)
let tagList = _.sortBy(data.tagNoteMap.map( let tagList = _.sortBy(data.tagNoteMap.map(
(tag, name) => ({name, size: tag.size})), (tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
['name'] ), ['name']).filter(
tag => tag.size > 0
) )
if (config.sortTagsBy === 'COUNTER') { if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size)) tagList = _.sortBy(tagList, item => (0 - item.size))
} }
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
tagList = tagList.filter(
tag => tag.related
)
}
return ( return (
tagList.map(tag => { tagList.map(tag => {
return ( return (
<TagListItem <TagListItem
name={tag.name} name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)} handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)} isActive={this.getTagActive(location.pathname, tag.name)}
isRelated={tag.related}
key={tag.name} key={tag.name}
count={tag.size} count={tag.size}
/> />
@@ -167,10 +176,30 @@ class SideNav extends React.Component {
) )
} }
getRelatedTags (activeTags, noteMap) {
if (activeTags.length === 0) {
return new Set()
}
const relatedNotes = noteMap.map(
note => ({key: note.key, tags: note.tags})
).filter(
note => activeTags.every(tag => note.tags.includes(tag))
)
const relatedTags = new Set()
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
return relatedTags
}
getTagActive (path, tag) { getTagActive (path, tag) {
return this.getActiveTags(path).includes(tag)
}
getActiveTags (path) {
const pathSegments = path.split('/') const pathSegments = path.split('/')
const pathTag = pathSegments[pathSegments.length - 1] const tags = pathSegments[pathSegments.length - 1]
return pathTag === tag return (tags === 'alltags')
? []
: tags.split(' ')
} }
handleClickTagListItem (name) { handleClickTagListItem (name) {
@@ -192,6 +221,19 @@ class SideNav extends React.Component {
}) })
} }
handleClickNarrowToTag (tag) {
const { router } = this.context
const { location } = this.props
const listOfTags = this.getActiveTags(location.pathname)
const indexOfTag = listOfTags.indexOf(tag)
if (indexOfTag > -1) {
listOfTags.splice(indexOfTag, 1)
} else {
listOfTags.push(tag)
}
router.push(`/tags/${listOfTags.join(' ')}`)
}
emptyTrash (entries) { emptyTrash (entries) {
const { dispatch } = this.props const { dispatch } = this.props
const deletionPromises = entries.map((note) => { const deletionPromises = entries.map((note) => {

View File

@@ -69,3 +69,14 @@ body[data-theme="dark"]
navDarkButtonColor() navDarkButtonColor()
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
border-left 1px solid $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
body[data-theme="monokai"]
navButtonColor()
.zoom
border-color $ui-dark-borderColor
color $ui-monokai-text-color
&:hover
transition 0.15s
color $ui-monokai-active-color
&:active
color $ui-monokai-active-color

View File

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

View File

@@ -134,4 +134,10 @@ body[data-theme="solarized-dark"]
.sortableItemHelper .sortableItemHelper
color: $ui-solarized-dark-text-color color: $ui-solarized-dark-text-color
body[data-theme="monokai"]
.ModalBase
.modalBack
background-color $ui-monokai-backgroundColor
.sortableItemHelper
color: $ui-monokai-text-color

View File

@@ -24,6 +24,45 @@ document.addEventListener('dragover', function (e) {
e.stopPropagation() e.stopPropagation()
}) })
// prevent menu from popup when alt pressed
// but still able to toggle menu when only alt is pressed
let isAltPressing = false
let isAltWithMouse = false
let isAltWithOtherKey = false
let isOtherKey = false
document.addEventListener('keydown', function (e) {
if (e.key === 'Alt') {
isAltPressing = true
if (isOtherKey) {
isAltWithOtherKey = true
}
} else {
if (isAltPressing) {
isAltWithOtherKey = true
}
isOtherKey = true
}
})
document.addEventListener('mousedown', function (e) {
if (isAltPressing) {
isAltWithMouse = true
}
})
document.addEventListener('keyup', function (e) {
if (e.key === 'Alt') {
if (isAltWithMouse || isAltWithOtherKey) {
e.preventDefault()
}
isAltWithMouse = false
isAltWithOtherKey = false
isAltPressing = false
isOtherKey = false
}
})
document.addEventListener('click', function (e) { document.addEventListener('click', function (e) {
const className = e.target.className const className = e.target.className
if (!className && typeof (className) !== 'string') return if (!className && typeof (className) !== 'string') return

View File

@@ -1,6 +1,7 @@
import _ from 'lodash' import _ from 'lodash'
import RcParser from 'browser/lib/RcParser' import RcParser from 'browser/lib/RcParser'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32' const win = global.process.platform === 'win32'
@@ -20,7 +21,8 @@ export const DEFAULT_CONFIG = {
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL' listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true, amaEnabled: true,
hotkey: { hotkey: {
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E' toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Command + M' : 'Ctrl + M'
}, },
ui: { ui: {
language: 'en', language: 'en',
@@ -53,8 +55,13 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$', latexInlineClose: '$',
latexBlockOpen: '$$', latexBlockOpen: '$$',
latexBlockClose: '$$', latexBlockClose: '$$',
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
scrollPastEnd: false, scrollPastEnd: false,
smartQuotes: true, smartQuotes: true,
breaks: true,
smartArrows: false,
allowCustomCSS: false,
customCSS: '',
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE' sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
}, },
blog: { blog: {
@@ -135,6 +142,8 @@ function set (updates) {
document.body.setAttribute('data-theme', 'white') document.body.setAttribute('data-theme', 'white')
} else if (newConfig.ui.theme === 'solarized-dark') { } else if (newConfig.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark') document.body.setAttribute('data-theme', 'solarized-dark')
} else if (newConfig.ui.theme === 'monokai') {
document.body.setAttribute('data-theme', 'monokai')
} else { } else {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }
@@ -163,6 +172,7 @@ function set (updates) {
ipcRenderer.send('config-renew', { ipcRenderer.send('config-renew', {
config: get() config: get()
}) })
ee.emit('config-renew')
} }
function assignConfigValues (originalConfig, rcConfig) { function assignConfigValues (originalConfig, rcConfig) {
@@ -172,6 +182,17 @@ function assignConfigValues (originalConfig, rcConfig) {
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui) config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor) config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview) config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
rewriteHotkey(config)
return config
}
function rewriteHotkey (config) {
const keys = [...Object.keys(config.hotkey)]
keys.forEach(key => {
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
})
return config return config
} }

View File

@@ -0,0 +1,424 @@
const uniqueSlug = require('unique-slug')
const fs = require('fs')
const path = require('path')
const findStorage = require('browser/lib/findStorage')
const mdurl = require('mdurl')
const fse = require('fs-extra')
const escapeStringRegexp = require('escape-string-regexp')
const sander = require('sander')
import i18n from 'browser/lib/i18n'
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments'
/**
* @description
* Copies a copy of an attachment to the storage folder specified by the given key and return the generated attachment name.
* Renames the file to match a unique file name.
*
* @param {String} sourceFilePath The source path of the attachment to be copied
* @param {String} storageKey Storage key of the destination storage
* @param {String} noteKey Key of the current note. Will be used as subfolder in :storage
* @param {boolean} useRandomName determines whether a random filename for the new file is used. If false the source file name is used
* @return {Promise<String>} name (inclusive extension) of the generated file
*/
function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = true) {
return new Promise((resolve, reject) => {
if (!sourceFilePath) {
reject('sourceFilePath has to be given')
}
if (!storageKey) {
reject('storageKey has to be given')
}
if (!noteKey) {
reject('noteKey has to be given')
}
try {
if (!fs.existsSync(sourceFilePath)) {
reject('source file does not exist')
}
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)
createAttachmentDestinationFolder(targetStorage.path, noteKey)
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
inputFileStream.pipe(outputFile)
inputFileStream.on('end', () => {
resolve(destinationName)
})
} catch (e) {
return reject(e)
}
})
}
function createAttachmentDestinationFolder (destinationStoragePath, noteKey) {
let destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER)
if (!fs.existsSync(destinationDir)) {
fs.mkdirSync(destinationDir)
}
destinationDir = path.join(destinationStoragePath, DESTINATION_FOLDER, noteKey)
if (!fs.existsSync(destinationDir)) {
fs.mkdirSync(destinationDir)
}
}
/**
* @description Moves attachments from the old location ('/images') to the new one ('/attachments/noteKey)
* @param renderedHTML HTML of the current note
* @param storagePath Storage path of the current note
* @param noteKey Key of the current note
*/
function migrateAttachments (renderedHTML, storagePath, noteKey) {
if (sander.existsSync(path.join(storagePath, 'images'))) {
const attachments = getAttachmentsInContent(renderedHTML) || []
if (attachments !== []) {
createAttachmentDestinationFolder(storagePath, noteKey)
}
for (const attachment of attachments) {
const attachmentBaseName = path.basename(attachment)
const possibleLegacyPath = path.join(storagePath, 'images', attachmentBaseName)
if (sander.existsSync(possibleLegacyPath)) {
const destinationPath = path.join(storagePath, DESTINATION_FOLDER, attachmentBaseName)
if (!sander.existsSync(destinationPath)) {
sander.copyFileSync(possibleLegacyPath).to(destinationPath)
}
}
}
}
}
/**
* @description Fixes the URLs embedded in the generated HTML so that they again refer actual local files.
* @param {String} renderedHTML HTML in that the links should be fixed
* @param {String} storagePath Path of the current storage
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/
function fixLocalURLS (renderedHTML, storagePath) {
return renderedHTML.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
}
/**
* @description Generates the markdown code for a given attachment
* @param {String} fileName Name of the attachment
* @param {String} path Path of the attachment
* @param {Boolean} showPreview Indicator whether the generated markdown should show a preview of the image. Note that at the moment only previews for images are supported
* @returns {String} Generated markdown code
*/
function generateAttachmentMarkdown (fileName, path, showPreview) {
return `${showPreview ? '!' : ''}[${fileName}](${path})`
}
/**
* @description Handles the drop-event of a file. Includes the necessary markdown code and copies the file to the corresponding storage folder.
* The method calls {CodeEditor#insertAttachmentMd()} to include the generated markdown at the needed place!
* @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 {Event} dropEvent DropEvent
*/
function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
const file = dropEvent.dataTransfer.files[0]
const filePath = file.path
const originalFileName = path.basename(filePath)
const fileType = file['type']
copyAttachment(filePath, storageKey, noteKey).then((fileName) => {
const showPreview = fileType.startsWith('image')
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview)
codeEditor.insertAttachmentMd(imageMd)
})
}
/**
* @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 {DataTransferItem} dataTransferItem Part of the past-event
*/
function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem) {
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 (!dataTransferItem) {
throw new Error('dataTransferItem has to be given')
}
const blob = dataTransferItem.getAsFile()
const reader = new FileReader()
let base64data
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)
reader.onloadend = function () {
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
fs.writeFileSync(imagePath, binaryData, 'binary')
const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName)
const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true)
codeEditor.insertAttachmentMd(imageMd)
}
reader.readAsDataURL(blob)
}
/**
* @description Returns all attachment paths of the given markdown
* @param {String} markdownContent content in which the attachment paths should be found
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
*/
function getAttachmentsInContent (markdownContent) {
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
const regexp = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(' + escapeStringRegexp(path.sep) + '|/)' + '?([a-zA-Z0-9]|-)*' + '(' + escapeStringRegexp(path.sep) + '|/)' + '([a-zA-Z0-9]|\\.)+(\\.[a-zA-Z0-9]+)?', 'g')
return preparedInput.match(regexp)
}
/**
* @description Returns an array of the absolute paths of the attachments referenced in the given markdown code
* @param {String} markdownContent content in which the attachment paths should be found
* @param {String} storagePath path of the current storage
* @returns {String[]} Absolute paths of the referenced attachments
*/
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
const temp = getAttachmentsInContent(markdownContent) || []
const result = []
for (const relativePath of temp) {
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
}
return result
}
/**
* @description Moves the attachments of the current note to the new location.
* Returns a modified version of the given content so that the links to the attachments point to the new note key.
* @param {String} oldPath Source of the note to be moved
* @param {String} newPath Destination of the note to be moved
* @param {String} noteKey Old note key
* @param {String} newNoteKey New note key
* @param {String} noteContent Content of the note to be moved
* @returns {String} Modified version of noteContent in which the paths of the attachments are fixed
*/
function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
const src = path.join(oldPath, DESTINATION_FOLDER, noteKey)
const dest = path.join(newPath, DESTINATION_FOLDER, newNoteKey)
if (fse.existsSync(src)) {
fse.moveSync(src, dest)
}
return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey)
}
/**
* Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one
* @param noteContent content that should be modified
* @param oldNoteKey note key to be replaced
* @param newNoteKey note key serving as a replacement
* @returns {String} modified note content
*/
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
if (noteContent) {
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
}
return noteContent
}
/**
* @description Deletes all :storage and noteKey references from the given input.
* @param input Input in which the references should be deleted
* @param noteKey Key of the current note
* @returns {String} Input without the references
*/
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)
}
/**
* @description Deletes the attachment folder specified by the given storageKey and noteKey
* @param storageKey Key of the storage of the note to be deleted
* @param noteKey Key of the note to be deleted
*/
function deleteAttachmentFolder (storageKey, noteKey) {
const storagePath = findStorage.findStorage(storageKey)
const noteAttachmentPath = path.join(storagePath.path, DESTINATION_FOLDER, noteKey)
sander.rimrafSync(noteAttachmentPath)
}
/**
* @description Deletes all attachments stored in the attachment folder of the give not that are not referenced in the markdownContent
* @param markdownContent Content of the note. All unreferenced notes will be deleted
* @param storageKey StorageKey of the current note. Is used to determine the belonging attachment folder.
* @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder.
*/
function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) {
if (storageKey == null || noteKey == null || markdownContent == null) {
return
}
const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
const attachmentsInNote = getAttachmentsInContent(markdownContent)
const attachmentsInNoteOnlyFileNames = []
if (attachmentsInNote) {
for (let i = 0; i < attachmentsInNote.length; i++) {
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
}
}
if (fs.existsSync(attachmentFolder)) {
fs.readdir(attachmentFolder, (err, files) => {
if (err) {
console.error('Error reading directory "' + attachmentFolder + '". Error:')
console.error(err)
return
}
files.forEach(file => {
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
fs.unlink(absolutePathOfFile, (err) => {
if (err) {
console.error('Could not delete "%s"', absolutePathOfFile)
console.error(err)
return
}
console.info('File "' + absolutePathOfFile + '" deleted because it was not included in the content of the note')
})
}
})
})
} else {
console.info('Attachment folder ("' + attachmentFolder + '") did not exist..')
}
}
/**
* Clones the attachments of a given note.
* Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
* @param oldNote Note that is being cloned
* @param newNote Clone of the note
*/
function cloneAttachments (oldNote, newNote) {
if (newNote.type === 'MARKDOWN_NOTE') {
const oldStorage = findStorage.findStorage(oldNote.storage)
const newStorage = findStorage.findStorage(newNote.storage)
const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || []
const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key)
if (!sander.existsSync(destinationFolder)) {
sander.mkdirSync(destinationFolder)
}
for (const attachment of attachmentsPaths) {
const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
sander.copyFileSync(attachment).to(destination)
}
newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
} else {
console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs')
}
}
function generateFileNotFoundMarkdown () {
return '**' + i18n.__('⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠') + '**'
}
/**
* Determines whether a given text is a link to an boostnote attachment
* @param text Text that might contain a attachment link
* @return {Boolean} Result of the test
*/
function isAttachmentLink (text) {
if (text) {
return text.match(new RegExp('.*\\[.*\\]\\( *' + escapeStringRegexp(STORAGE_FOLDER_PLACEHOLDER) + escapeStringRegexp(path.sep) + '.*\\).*', 'gi')) != null
}
return false
}
/**
* @description Handles the paste of an attachment link. Copies the referenced attachment to the location belonging to the new note.
* Returns a modified version of the pasted text so that it matches the copied attachment (resp. the new location)
* @param storageKey StorageKey of the current note
* @param noteKey NoteKey of the currentNote
* @param linkText Text that was pasted
* @return {Promise<String>} Promise returning the modified text
*/
function handleAttachmentLinkPaste (storageKey, noteKey, linkText) {
if (storageKey != null && noteKey != null && linkText != null) {
const storagePath = findStorage.findStorage(storageKey).path
const attachments = getAttachmentsInContent(linkText) || []
const replaceInstructions = []
const copies = []
for (const attachment of attachments) {
const absPathOfAttachment = attachment.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))
copies.push(
sander.exists(absPathOfAttachment)
.then((fileExists) => {
if (!fileExists) {
const fileNotFoundRegexp = new RegExp('!?' + escapeStringRegexp('[') + '[\\w|\\d|\\s|\\.]*\\]\\(\\s*' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + escapeStringRegexp(')'))
replaceInstructions.push({regexp: fileNotFoundRegexp, replacement: this.generateFileNotFoundMarkdown()})
return Promise.resolve()
}
return this.copyAttachment(absPathOfAttachment, storageKey, noteKey)
.then((fileName) => {
const replaceLinkRegExp = new RegExp(escapeStringRegexp('(') + ' *' + STORAGE_FOLDER_PLACEHOLDER + '[\\w|\\d|\\-|' + escapeStringRegexp(path.sep) + ']*' + escapeStringRegexp(path.basename(absPathOfAttachment)) + ' *' + escapeStringRegexp(')'))
replaceInstructions.push({
regexp: replaceLinkRegExp,
replacement: '(' + path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName) + ')'
})
return Promise.resolve()
})
})
)
}
return Promise.all(copies).then(() => {
let modifiedLinkText = linkText
for (const replaceInstruction of replaceInstructions) {
modifiedLinkText = modifiedLinkText.replace(replaceInstruction.regexp, replaceInstruction.replacement)
}
return modifiedLinkText
})
} else {
console.log('One if the parameters was null -> Do nothing..')
return Promise.resolve(linkText)
}
}
module.exports = {
copyAttachment,
fixLocalURLS,
generateAttachmentMarkdown,
handleAttachmentDrop,
handlePastImageEvent,
getAttachmentsInContent,
getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences,
deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote,
moveAttachments,
cloneAttachments,
isAttachmentLink,
handleAttachmentLinkPaste,
generateFileNotFoundMarkdown,
migrateAttachments,
STORAGE_FOLDER_PLACEHOLDER,
DESTINATION_FOLDER
}

View File

@@ -1,32 +0,0 @@
const fs = require('fs')
const path = require('path')
const { findStorage } = require('browser/lib/findStorage')
/**
* @description Copy an image and return the path.
* @param {String} filePath
* @param {String} storageKey
* @param {Boolean} rename create new filename or leave the old one
* @return {Promise<any>} an image path
*/
function copyImage (filePath, storageKey, rename = true) {
return new Promise((resolve, reject) => {
try {
const targetStorage = findStorage(storageKey)
const inputImage = fs.createReadStream(filePath)
const imageExt = path.extname(filePath)
const imageName = rename ? Math.random().toString(36).slice(-16) : path.basename(filePath, imageExt)
const basename = `${imageName}${imageExt}`
const imageDir = path.join(targetStorage.path, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
const outputImage = fs.createWriteStream(path.join(imageDir, basename))
inputImage.pipe(outputImage)
resolve(basename)
} catch (e) {
return reject(e)
}
})
}
module.exports = copyImage

View File

@@ -0,0 +1,26 @@
import fs from 'fs'
import crypto from 'crypto'
import consts from 'browser/lib/consts'
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
function createSnippet (snippetFile) {
return new Promise((resolve, reject) => {
const newSnippet = {
id: crypto.randomBytes(16).toString('hex'),
name: 'Unnamed snippet',
prefix: [],
content: ''
}
fetchSnippet(null, snippetFile).then((snippets) => {
snippets.push(newSnippet)
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err)
resolve(newSnippet)
})
}).catch((err) => {
reject(err)
})
})
}
module.exports = createSnippet

View File

@@ -5,6 +5,7 @@ const resolveStorageNotes = require('./resolveStorageNotes')
const CSON = require('@rokt33r/season') const CSON = require('@rokt33r/season')
const sander = require('sander') const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage') const { findStorage } = require('browser/lib/findStorage')
const deleteSingleNote = require('./deleteNote')
/** /**
* @param {String} storageKey * @param {String} storageKey
@@ -49,11 +50,7 @@ function deleteFolder (storageKey, folderKey) {
const deleteAllNotes = targetNotes const deleteAllNotes = targetNotes
.map(function deleteNote (note) { .map(function deleteNote (note) {
const notePath = path.join(storage.path, 'notes', note.key + '.cson') return deleteSingleNote(storageKey, note.key)
return sander.unlink(notePath)
.catch(function (err) {
console.warn('Failed to delete', notePath, err)
})
}) })
return Promise.all(deleteAllNotes) return Promise.all(deleteAllNotes)
.then(() => storage) .then(() => storage)

View File

@@ -1,6 +1,7 @@
const resolveStorageData = require('./resolveStorageData') const resolveStorageData = require('./resolveStorageData')
const path = require('path') const path = require('path')
const sander = require('sander') const sander = require('sander')
const attachmentManagement = require('./attachmentManagement')
const { findStorage } = require('browser/lib/findStorage') const { findStorage } = require('browser/lib/findStorage')
function deleteNote (storageKey, noteKey) { function deleteNote (storageKey, noteKey) {
@@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) {
storageKey storageKey
} }
}) })
.then(function deleteAttachments (storageInfo) {
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
return storageInfo
})
} }
module.exports = deleteNote module.exports = deleteNote

View File

@@ -0,0 +1,17 @@
import fs from 'fs'
import consts from 'browser/lib/consts'
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
function deleteSnippet (snippet, snippetFile) {
return new Promise((resolve, reject) => {
fetchSnippet(null, snippetFile).then((snippets) => {
snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id)
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err)
resolve(snippet)
})
})
})
}
module.exports = deleteSnippet

View File

@@ -1,13 +1,9 @@
import copyFile from 'browser/main/lib/dataApi/copyFile' import copyFile from 'browser/main/lib/dataApi/copyFile'
import {findStorage} from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import filenamify from 'filenamify'
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
const IMAGES_FOLDER_NAME = 'images'
/** /**
* Export note together with images * Export note together with images
* *
@@ -28,21 +24,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
throw new Error('Storage path is not found') throw new Error('Storage path is not found')
} }
let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => { let exportedData = noteContent
dstFilename = filenamify(dstFilename, {replacement: '_'})
if (!path.extname(dstFilename)) {
dstFilename += path.extname(srcFilename)
}
const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename)
exportTasks.push({
src: path.join(IMAGES_FOLDER_NAME, srcFilename),
dst: dstRelativePath
})
return `![${dstFilename}](${dstRelativePath})`
})
if (outputFormatter) { if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks) exportedData = outputFormatter(exportedData, exportTasks)

View File

@@ -0,0 +1,20 @@
import fs from 'fs'
import consts from 'browser/lib/consts'
function fetchSnippet (id, snippetFile) {
return new Promise((resolve, reject) => {
fs.readFile(snippetFile || consts.SNIPPET_FILE, 'utf8', (err, data) => {
if (err) {
reject(err)
}
const snippets = JSON.parse(data)
if (id) {
const snippet = snippets.find(snippet => { return snippet.id === id })
resolve(snippet)
}
resolve(snippets)
})
})
}
module.exports = fetchSnippet

View File

@@ -13,6 +13,10 @@ const dataApi = {
deleteNote: require('./deleteNote'), deleteNote: require('./deleteNote'),
moveNote: require('./moveNote'), moveNote: require('./moveNote'),
migrateFromV5Storage: require('./migrateFromV5Storage'), migrateFromV5Storage: require('./migrateFromV5Storage'),
createSnippet: require('./createSnippet'),
deleteSnippet: require('./deleteSnippet'),
updateSnippet: require('./updateSnippet'),
fetchSnippet: require('./fetchSnippet'),
_migrateFromV6Storage: require('./migrateFromV6Storage'), _migrateFromV6Storage: require('./migrateFromV6Storage'),
_resolveStorageData: require('./resolveStorageData'), _resolveStorageData: require('./resolveStorageData'),

View File

@@ -6,7 +6,7 @@ const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen') const keygen = require('browser/lib/keygen')
const sander = require('sander') const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage') const { findStorage } = require('browser/lib/findStorage')
const copyImage = require('./copyImage') const attachmentManagement = require('./attachmentManagement')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) { function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage let oldStorage, newStorage
@@ -64,32 +64,20 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
noteData.key = newNoteKey noteData.key = newNoteKey
noteData.storage = newStorageKey noteData.storage = newStorageKey
noteData.updatedAt = new Date() noteData.updatedAt = new Date()
noteData.oldContent = noteData.content
return noteData return noteData
}) })
.then(function moveImages (noteData) { .then(function moveAttachments (noteData) {
const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi if (oldStorage.path === newStorage.path) {
let match = searchImagesRegex.exec(noteData.content) return noteData
const moveTasks = []
while (match != null) {
const [, filename] = match
const oldPath = path.join(oldStorage.path, 'images', filename)
moveTasks.push(
copyImage(oldPath, noteData.storage, false)
.then(() => {
fs.unlinkSync(oldPath)
})
)
// find next occurence
match = searchImagesRegex.exec(noteData.content)
} }
return Promise.all(moveTasks).then(() => noteData) noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
return noteData
}) })
.then(function writeAndReturn (noteData) { .then(function writeAndReturn (noteData) {
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage'])) CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage', 'oldContent']))
return noteData return noteData
}) })
.then(function deleteOldNote (data) { .then(function deleteOldNote (data) {

View File

@@ -0,0 +1,33 @@
import fs from 'fs'
import consts from 'browser/lib/consts'
function updateSnippet (snippet, snippetFile) {
return new Promise((resolve, reject) => {
const snippets = JSON.parse(fs.readFileSync(snippetFile || consts.SNIPPET_FILE, 'utf-8'))
for (let i = 0; i < snippets.length; i++) {
const currentSnippet = snippets[i]
if (currentSnippet.id === snippet.id) {
if (
currentSnippet.name === snippet.name &&
currentSnippet.prefix === snippet.prefix &&
currentSnippet.content === snippet.content
) {
// if everything is the same then don't write to disk
resolve(snippets)
} else {
currentSnippet.name = snippet.name
currentSnippet.prefix = snippet.prefix
currentSnippet.content = snippet.content
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
if (err) reject(err)
resolve(snippets)
})
}
}
}
})
}
module.exports = updateSnippet

View File

@@ -0,0 +1,7 @@
import ee from 'browser/main/lib/eventEmitter'
module.exports = {
'toggleMode': () => {
ee.emit('topbar:togglemodebutton')
}
}

View File

@@ -0,0 +1,40 @@
import Mousetrap from 'mousetrap'
import CM from 'browser/main/lib/ConfigManager'
import ee from 'browser/main/lib/eventEmitter'
import { isObjectEqual } from 'browser/lib/utils'
require('mousetrap-global-bind')
import functions from './shortcut'
let shortcuts = CM.get().hotkey
ee.on('config-renew', function () {
// only update if hotkey changed !
const newHotkey = CM.get().hotkey
if (!isObjectEqual(newHotkey, shortcuts)) {
updateShortcut(newHotkey)
}
})
function updateShortcut (newHotkey) {
Mousetrap.reset()
shortcuts = newHotkey
applyShortcuts(newHotkey)
}
function formatShortcut (shortcut) {
return shortcut.toLowerCase().replace(/ /g, '')
}
function applyShortcuts (shortcuts) {
for (const shortcut in shortcuts) {
const toggler = formatShortcut(shortcuts[shortcut])
// only bind if the function for that shortcut exists
if (functions[shortcut]) {
Mousetrap.bindGlobal(toggler, functions[shortcut])
}
}
}
applyShortcuts(CM.get().hotkey)
module.exports = applyShortcuts

View File

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

View File

@@ -81,3 +81,19 @@ body[data-theme="solarized-dark"]
.description .description
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root
background-color transparent
.header
color $ui-monokai-text-color
.control-button
border-color $ui-monokai-borderColor
color $ui-monokai-text-color
background-color transparent
&:focus
colorDarkPrimaryButton()
.description
color $ui-monokai-text-color

View File

@@ -133,6 +133,11 @@ colorSolarizedDarkControl()
background-color $ui-solarized-dark-button-backgroundColor background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
colorMonokaiControl()
border none
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
body[data-theme="dark"] body[data-theme="dark"]
.root .root
@@ -189,4 +194,29 @@ body[data-theme="solarized-dark"]
select, .group-section-control-input select, .group-section-control-input
colorSolarizedDarkControl() colorSolarizedDarkControl()
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
.group-header
color $ui-monokai-text-color
border-color $ui-monokai-borderColor
.group-header2
color $ui-monokai-text-color
.group-section-control-input
border-color $ui-monokai-borderColor
.group-control
border-color $ui-monokai-borderColor
.group-control-leftButton
colorDarkDefaultButton()
border-color $ui-monokai-borderColor
.group-control-rightButton
colorMonokaiPrimaryButton()
.group-hint
colorMonokaiControl()
.group-section-control
select, .group-section-control-input
colorMonokaiControl()

View File

@@ -11,11 +11,12 @@ p
font-size 16px font-size 16px
.cf-link .cf-link
width 250px
height 35px height 35px
border-radius 2px border-radius 2px
border none border none
background-color alpha(#1EC38B, 90%) background-color alpha(#1EC38B, 90%)
padding-left 20px
padding-right 20px
&:hover &:hover
background-color #1EC38B background-color #1EC38B
transition 0.2s transition 0.2s
@@ -33,4 +34,10 @@ body[data-theme="solarized-dark"]
.root .root
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
p p
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
p
color $ui-monokai-text-color

View File

@@ -126,3 +126,26 @@ body[data-theme="solarized-dark"]
.folderItem-right-dangerButton .folderItem-right-dangerButton
colorSolarizedDarkPrimaryButton() colorSolarizedDarkPrimaryButton()
body[data-theme="monokai"]
.folderItem
&:hover
background-color $ui-monokai-button-backgroundColor
.folderItem-left-danger
color $danger-color
.folderItem-left-key
color $ui-dark-inactive-text-color
.folderItem-left-colorButton
colorMonokaiPrimaryButton()
.folderItem-right-button
colorMonokaiPrimaryButton()
.folderItem-right-confirmButton
colorMonokaiPrimaryButton()
.folderItem-right-dangerButton
colorMonokaiPrimaryButton()

View File

@@ -67,7 +67,8 @@ class HotkeyTab extends React.Component {
handleHotkeyChange (e) { handleHotkeyChange (e) {
const { config } = this.state const { config } = this.state
config.hotkey = { config.hotkey = {
toggleMain: this.refs.toggleMain.value toggleMain: this.refs.toggleMain.value,
toggleMode: this.refs.toggleMode.value
} }
this.setState({ this.setState({
config config
@@ -115,6 +116,17 @@ class HotkeyTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Toggle editor mode')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}
ref='toggleMode'
value={config.hotkey.toggleMode}
type='text'
/>
</div>
</div>
<div styleName='group-control'> <div styleName='group-control'>
<button styleName='group-control-leftButton' <button styleName='group-control-leftButton'
onClick={(e) => this.handleHintToggleButtonClick(e)} onClick={(e) => this.handleHintToggleButtonClick(e)}

View File

@@ -68,3 +68,10 @@ body[data-theme="solarized-dark"]
.list .list
a a
color $ui-solarized-dark-active-color color $ui-solarized-dark-active-color
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
.list
a
color $ui-monokai-active-color

View File

@@ -116,3 +116,26 @@ body[data-theme="solarized-dark"]
&:hover &:hover
color white color white
body[data-theme="monokai"]
.root
background-color transparent
.top-bar
background-color transparent
border-color $ui-monokai-borderColor
p
color $ui-monokai-text-color
.nav
background-color transparent
border-color $ui-monokai-borderColor
.nav-button
background-color transparent
color $ui-monokai-text-color
&:hover
color $ui-monokai-text-color
.nav-button--active
@extend .nav-button
color $ui-monokai-button--active-color
background-color $ui-monokai-button--active-backgroundColor
&:hover
color white

View File

@@ -0,0 +1,90 @@
import CodeMirror from 'codemirror'
import React from 'react'
import _ from 'lodash'
import styles from './SnippetTab.styl'
import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
class SnippetEditor extends React.Component {
componentDidMount () {
this.props.onRef(this)
const { rulers, enableRulers } = this.props
this.cm = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
lineNumbers: this.props.displayLineNumbers,
lineWrapping: true,
theme: this.props.theme,
indentUnit: this.props.indentSize,
tabSize: this.props.indentSize,
indentWithTabs: this.props.indentType !== 'space',
keyMap: this.props.keyMap,
scrollPastEnd: this.props.scrollPastEnd,
dragDrop: false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: true,
mode: 'null'
})
this.cm.setSize('100%', '100%')
let changeDelay = null
this.cm.on('change', () => {
this.snippet.content = this.cm.getValue()
clearTimeout(changeDelay)
changeDelay = setTimeout(() => {
this.saveSnippet()
}, 500)
})
}
componentWillUnmount () {
this.props.onRef(undefined)
}
onSnippetChanged (newSnippet) {
this.snippet = newSnippet
this.cm.setValue(this.snippet.content)
}
onSnippetNameOrPrefixChanged (newSnippet) {
this.snippet.name = newSnippet.name
this.snippet.prefix = newSnippet.prefix.toString().replace(/\s/g, '').split(',')
this.saveSnippet()
}
saveSnippet () {
dataApi.updateSnippet(this.snippet).catch((err) => { throw err })
}
render () {
const { fontSize } = this.props
let fontFamily = this.props.fontFamily
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily
return (
<div styleName='SnippetEditor' ref='root' tabIndex='-1' style={{
fontFamily: fontFamily.join(', '),
fontSize: fontSize
}} />
)
}
}
SnippetEditor.defaultProps = {
readOnly: false,
theme: 'xcode',
keyMap: 'sublime',
fontSize: 14,
fontFamily: 'Monaco, Consolas',
indentSize: 4,
indentType: 'space'
}
export default CSSModules(SnippetEditor, styles)

View File

@@ -0,0 +1,87 @@
import React from 'react'
import styles from './SnippetTab.styl'
import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi'
import i18n from 'browser/lib/i18n'
import eventEmitter from 'browser/main/lib/eventEmitter'
const { remote } = require('electron')
const { Menu, MenuItem } = remote
class SnippetList extends React.Component {
constructor (props) {
super(props)
this.state = {
snippets: []
}
}
componentDidMount () {
this.reloadSnippetList()
eventEmitter.on('snippetList:reload', this.reloadSnippetList.bind(this))
}
reloadSnippetList () {
dataApi.fetchSnippet().then(snippets => this.setState({snippets}))
}
handleSnippetContextMenu (snippet) {
const menu = new Menu()
menu.append(new MenuItem({
label: i18n.__('Delete snippet'),
click: () => {
this.deleteSnippet(snippet)
}
}))
menu.popup()
}
deleteSnippet (snippet) {
dataApi.deleteSnippet(snippet).then(() => {
this.reloadSnippetList()
this.props.onSnippetDeleted(snippet)
}).catch(err => { throw err })
}
handleSnippetClick (snippet) {
this.props.onSnippetClick(snippet)
}
createSnippet () {
dataApi.createSnippet().then(() => {
this.reloadSnippetList()
// scroll to end of list when added new snippet
const snippetList = document.getElementById('snippets')
snippetList.scrollTop = snippetList.scrollHeight
}).catch(err => { throw err })
}
render () {
const { snippets } = this.state
return (
<div styleName='snippet-list'>
<div styleName='group-section'>
<div styleName='group-section-control'>
<button styleName='group-control-button' onClick={() => this.createSnippet()}>
<i className='fa fa-plus' /> {i18n.__('New Snippet')}
</button>
</div>
</div>
<ul id='snippets' styleName='snippets'>
{
snippets.map((snippet) => (
<li
styleName='snippet-item'
key={snippet.id}
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
onClick={() => this.handleSnippetClick(snippet)}>
{snippet.name}
</li>
))
}
</ul>
</div>
)
}
}
export default CSSModules(SnippetList, styles)

View File

@@ -0,0 +1,116 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SnippetTab.styl'
import SnippetEditor from './SnippetEditor'
import i18n from 'browser/lib/i18n'
import dataApi from 'browser/main/lib/dataApi'
import SnippetList from './SnippetList'
import eventEmitter from 'browser/main/lib/eventEmitter'
class SnippetTab extends React.Component {
constructor (props) {
super(props)
this.state = {
currentSnippet: null
}
this.changeDelay = null
}
handleSnippetNameOrPrefixChange () {
clearTimeout(this.changeDelay)
this.changeDelay = setTimeout(() => {
// notify the snippet editor that the name or prefix of snippet has been changed
this.snippetEditor.onSnippetNameOrPrefixChanged(this.state.currentSnippet)
eventEmitter.emit('snippetList:reload')
}, 500)
}
handleSnippetClick (snippet) {
const { currentSnippet } = this.state
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
// notify the snippet editor to load the content of the new snippet
this.snippetEditor.onSnippetChanged(changedSnippet)
this.setState({currentSnippet: changedSnippet})
})
}
}
onSnippetNameOrPrefixChanged (e, type) {
const newSnippet = Object.assign({}, this.state.currentSnippet)
if (type === 'name') {
newSnippet.name = e.target.value
} else {
newSnippet.prefix = e.target.value
}
this.setState({ currentSnippet: newSnippet })
this.handleSnippetNameOrPrefixChange()
}
handleDeleteSnippet (snippet) {
// prevent old snippet still display when deleted
if (snippet.id === this.state.currentSnippet.id) {
this.setState({currentSnippet: null})
}
}
render () {
const { config, storageKey } = this.props
const { currentSnippet } = this.state
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
return (
<div styleName='root'>
<div styleName='header'>{i18n.__('Snippets')}</div>
<SnippetList
onSnippetClick={this.handleSnippetClick.bind(this)}
onSnippetDeleted={this.handleDeleteSnippet.bind(this)} />
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={currentSnippet ? currentSnippet.name : ''}
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'name') }}
type='text' />
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Snippet prefix')}</div>
<div styleName='group-section-control'>
<input
styleName='group-section-control-input'
value={currentSnippet ? currentSnippet.prefix : ''}
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'prefix') }}
type='text' />
</div>
</div>
<div styleName='snippet-editor-section'>
<SnippetEditor
storageKey={storageKey}
theme={config.editor.theme}
keyMap={config.editor.keyMap}
fontFamily={config.editor.fontFamily}
fontSize={editorFontSize}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd}
onRef={ref => { this.snippetEditor = ref }} />
</div>
</div>
</div>
)
}
}
SnippetTab.PropTypes = {
}
export default CSSModules(SnippetTab, styles)

View File

@@ -0,0 +1,180 @@
@import('./Tab')
@import('./ConfigTab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
.group
margin-bottom 45px
.group-header
@extend .header
color $ui-text-color
.group-header2
font-size 20px
color $ui-text-color
margin-bottom 15px
margin-top 30px
.group-section
margin-bottom 20px
display flex
line-height 30px
.group-section-label
width 150px
text-align left
margin-right 10px
font-size 14px
.group-section-control
flex 1
margin-left 5px
.group-section-control select
outline none
border 1px solid $ui-borderColor
font-size 16px
height 30px
width 250px
margin-bottom 5px
background-color transparent
.group-section-control-input
height 30px
vertical-align middle
width 400px
font-size $tab--button-font-size
border solid 1px $border-color
border-radius 2px
padding 0 5px
outline none
&:disabled
background-color $ui-input--disabled-backgroundColor
.group-control-button
height 30px
border none
border-top-right-radius 2px
border-bottom-right-radius 2px
colorPrimaryButton()
vertical-align middle
padding 0 20px
.group-checkBoxSection
margin-bottom 15px
display flex
line-height 30px
padding-left 15px
.group-control
padding-top 10px
box-sizing border-box
height 40px
text-align right
:global
.alert
display inline-block
position absolute
top 60px
right 15px
font-size 14px
.success
color #1EC38B
.error
color red
.warning
color #FFA500
.snippet-list
width 30%
height calc(100% - 200px)
position absolute
.snippets
height calc(100% - 8px)
overflow scroll
background: #f5f5f5
.snippet-item
height 50px
font-size 15px
line-height 50px
padding 0 5%
cursor pointer
position relative
&::after
width 90%
height 1px
background rgba(0, 0, 0, 0.1)
position absolute
top 100%
left 5%
content ''
&:hover
background darken(#f5f5f5, 5)
.snippet-detail
width 70%
height calc(100% - 200px)
position absolute
left 33%
.SnippetEditor
position absolute
width 100%
height 90%
body[data-theme="default"], body[data-theme="white"]
.snippets
background $ui-backgroundColor
.snippet-item
color black
&::after
background $ui-borderColor
&:hover
background darken($ui-backgroundColor, 5)
body[data-theme="dark"]
.snippets
background $ui-dark-backgroundColor
.snippet-item
color white
&::after
background $ui-dark-borderColor
&:hover
background darken($ui-dark-backgroundColor, 5)
.snippet-detail
color white
body[data-theme="solarized-dark"]
.snippets
background $ui-solarized-dark-backgroundColor
.snippet-item
color white
&::after
background $ui-solarized-dark-borderColor
&:hover
background darken($ui-solarized-dark-backgroundColor, 5)
.snippet-detail
color white
body[data-theme="monokai"]
.snippets
background $ui-monokai-backgroundColor
.snippet-item
color White
&::after
background $ui-monokai-borderColor
&:hover
background darken($ui-monokai-backgroundColor, 5)
.snippet-detail
color white

View File

@@ -182,7 +182,7 @@ class StoragesTab extends React.Component {
<div styleName='addStorage-body-section-path'> <div styleName='addStorage-body-section-path'>
<input styleName='addStorage-body-section-path-input' <input styleName='addStorage-body-section-path-input'
ref='addStoragePath' ref='addStoragePath'
placeholder='Select Folder' placeholder={i18n.__('Select Folder')}
value={this.state.newStorage.path} value={this.state.newStorage.path}
onChange={(e) => this.handleAddStorageChange(e)} onChange={(e) => this.handleAddStorageChange(e)}
/> />

View File

@@ -199,3 +199,40 @@ body[data-theme="solarized-dark"]
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
.folderList-item
border-bottom $ui-monokai-borderColor
.folderList-empty
color $ui-monokai-text-color
.list-empty
color $ui-monokai-text-color
.list-control-addStorageButton
border-color $ui-monokai-button-backgroundColor
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.addStorage-header
color $ui-monokai-text-color
border-color $ui-monokai-borderColor
.addStorage-body-section-name-input
border-color $$ui-monokai-borderColor
.addStorage-body-section-type-description
color $ui-monokai-text-color
.addStorage-body-section-path-button
colorPrimaryButton()
.addStorage-body-control
border-color $ui-monokai-borderColor
.addStorage-body-control-createButton
colorDarkPrimaryButton()
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-monokai-borderColor

View File

@@ -10,6 +10,7 @@ import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { getLanguages } from 'browser/lib/Languages'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -27,6 +28,8 @@ class UiTab extends React.Component {
componentDidMount () { componentDidMount () {
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript') CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
this.customCSSCM.getCodeMirror().setSize('400px', '400px')
this.handleSettingDone = () => { this.handleSettingDone = () => {
this.setState({UiAlert: { this.setState({UiAlert: {
type: 'success', type: 'success',
@@ -65,6 +68,7 @@ class UiTab extends React.Component {
language: this.refs.uiLanguage.value, language: this.refs.uiLanguage.value,
showCopyNotification: this.refs.showCopyNotification.checked, showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked, confirmDeletion: this.refs.confirmDeletion.checked,
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
disableDirectWrite: this.refs.uiD2w != null disableDirectWrite: this.refs.uiD2w != null
? this.refs.uiD2w.checked ? this.refs.uiD2w.checked
: false : false
@@ -92,9 +96,14 @@ class UiTab extends React.Component {
latexInlineClose: this.refs.previewLatexInlineClose.value, latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value, latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value, latexBlockClose: this.refs.previewLatexBlockClose.value,
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked, scrollPastEnd: this.refs.previewScrollPastEnd.checked,
smartQuotes: this.refs.previewSmartQuotes.checked, smartQuotes: this.refs.previewSmartQuotes.checked,
sanitize: this.refs.previewSanitize.value breaks: this.refs.previewBreaks.checked,
smartArrows: this.refs.previewSmartArrows.checked,
sanitize: this.refs.previewSanitize.value,
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
customCSS: this.customCSSCM.getCodeMirror().getValue()
} }
} }
@@ -155,6 +164,7 @@ class UiTab extends React.Component {
const { config, codemirrorTheme } = this.state const { config, codemirrorTheme } = this.state
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};' const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none' const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
const customCSS = config.preview.customCSS
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='group'> <div styleName='group'>
@@ -170,6 +180,7 @@ class UiTab extends React.Component {
<option value='default'>{i18n.__('Default')}</option> <option value='default'>{i18n.__('Default')}</option>
<option value='white'>{i18n.__('White')}</option> <option value='white'>{i18n.__('White')}</option>
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option> <option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
<option value='monokai'>{i18n.__('Monokai')}</option>
<option value='dark'>{i18n.__('Dark')}</option> <option value='dark'>{i18n.__('Dark')}</option>
</select> </select>
</div> </div>
@@ -182,21 +193,9 @@ class UiTab extends React.Component {
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
ref='uiLanguage' ref='uiLanguage'
> >
<option value='sq'>{i18n.__('Albanian')}</option> {
<option value='zh-CN'>{i18n.__('Chinese (zh-CN)')}</option> getLanguages().map((language) => <option value={language.locale} key={language.locale}>{i18n.__(language.name)}</option>)
<option value='zh-TW'>{i18n.__('Chinese (zh-TW)')}</option> }
<option value='da'>{i18n.__('Danish')}</option>
<option value='en'>{i18n.__('English')}</option>
<option value='fr'>{i18n.__('French')}</option>
<option value='de'>{i18n.__('German')}</option>
<option value='hu'>{i18n.__('Hungarian')}</option>
<option value='ja'>{i18n.__('Japanese')}</option>
<option value='ko'>{i18n.__('Korean')}</option>
<option value='no'>{i18n.__('Norwegian')}</option>
<option value='pl'>{i18n.__('Polish')}</option>
<option value='pt'>{i18n.__('Portuguese')}</option>
<option value='ru'>{i18n.__('Russian')}</option>
<option value='es'>{i18n.__('Spanish')}</option>
</select> </select>
</div> </div>
</div> </div>
@@ -221,6 +220,16 @@ class UiTab extends React.Component {
{i18n.__('Show a confirmation dialog when deleting notes')} {i18n.__('Show a confirmation dialog when deleting notes')}
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.ui.showOnlyRelatedTags}
ref='showOnlyRelatedTags'
type='checkbox'
/>&nbsp;
{i18n.__('Show only related tags')}
</label>
</div>
{ {
global.process.platform === 'win32' global.process.platform === 'win32'
? <div styleName='group-checkBoxSection'> ? <div styleName='group-checkBoxSection'>
@@ -231,7 +240,7 @@ class UiTab extends React.Component {
disabled={OSX} disabled={OSX}
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
Disable Direct Write(It will be applied after restarting) {i18n.__('Disable Direct Write (It will be applied after restarting)')}
</label> </label>
</div> </div>
: null : null
@@ -471,7 +480,27 @@ class UiTab extends React.Component {
ref='previewSmartQuotes' ref='previewSmartQuotes'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
Enable smart quotes {i18n.__('Enable smart quotes')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.breaks}
ref='previewBreaks'
type='checkbox'
/>&nbsp;
{i18n.__('Render newlines in Markdown paragraphs as <br>')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.smartArrows}
ref='previewSmartArrows'
type='checkbox'
/>&nbsp;
{i18n.__('Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.')}
</label> </label>
</div> </div>
@@ -543,6 +572,33 @@ class UiTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('PlantUML Server')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
ref='previewPlantUMLServerAddress'
value={config.preview.plantUMLServerAddress}
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Custom CSS')}
</div>
<div styleName='group-section-control'>
<input onChange={(e) => this.handleUIChange(e)}
checked={config.preview.allowCustomCSS}
ref='previewAllowCustomCSS'
type='checkbox'
/>&nbsp;
{i18n.__('Allow custom CSS for preview')}
<ReactCodeMirror onChange={e => this.handleUIChange(e)} ref={e => (this.customCSSCM = e)} value={config.preview.customCSS} options={{ lineNumbers: true, mode: 'css', theme: codemirrorTheme }} />
</div>
</div>
<div styleName='group-control'> <div styleName='group-control'>
<button styleName='group-control-rightButton' <button styleName='group-control-rightButton'

View File

@@ -6,6 +6,7 @@ import UiTab from './UiTab'
import InfoTab from './InfoTab' import InfoTab from './InfoTab'
import Crowdfunding from './Crowdfunding' import Crowdfunding from './Crowdfunding'
import StoragesTab from './StoragesTab' import StoragesTab from './StoragesTab'
import SnippetTab from './SnippetTab'
import Blog from './Blog' import Blog from './Blog'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -86,6 +87,14 @@ class Preferences extends React.Component {
haveToSave={alert => this.setState({BlogAlert: alert})} haveToSave={alert => this.setState({BlogAlert: alert})}
/> />
) )
case 'SNIPPET':
return (
<SnippetTab
dispatch={dispatch}
config={config}
data={data}
/>
)
case 'STORAGES': case 'STORAGES':
default: default:
return ( return (
@@ -123,7 +132,8 @@ class Preferences extends React.Component {
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert}, {target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
{target: 'INFO', label: i18n.__('About')}, {target: 'INFO', label: i18n.__('About')},
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')}, {target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert} {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert},
{target: 'SNIPPET', label: i18n.__('Snippets')}
] ]
const navButtons = tabs.map((tab) => { const navButtons = tabs.map((tab) => {

View File

@@ -38,29 +38,13 @@ function data (state = defaultDataMap(), action) {
if (note.isTrashed) { if (note.isTrashed) {
state.trashedSet.add(uniqueKey) state.trashedSet.add(uniqueKey)
} }
const storageNoteList = getOrInitItem(state.storageNoteMap, note.storage)
let storageNoteList = state.storageNoteMap.get(note.storage)
if (storageNoteList == null) {
storageNoteList = new Set(storageNoteList)
state.storageNoteMap.set(note.storage, storageNoteList)
}
storageNoteList.add(uniqueKey) storageNoteList.add(uniqueKey)
let folderNoteSet = state.folderNoteMap.get(folderKey) const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
if (folderNoteSet == null) {
folderNoteSet = new Set(folderNoteSet)
state.folderNoteMap.set(folderKey, folderNoteSet)
}
folderNoteSet.add(uniqueKey) folderNoteSet.add(uniqueKey)
note.tags.forEach((tag) => { assignToTags(note.tags, state, uniqueKey)
let tagNoteList = state.tagNoteMap.get(tag)
if (tagNoteList == null) {
tagNoteList = new Set(tagNoteList)
state.tagNoteMap.set(tag, tagNoteList)
}
tagNoteList.add(uniqueKey)
})
}) })
return state return state
case 'UPDATE_NOTE': case 'UPDATE_NOTE':
@@ -74,23 +58,19 @@ function data (state = defaultDataMap(), action) {
state.noteMap = new Map(state.noteMap) state.noteMap = new Map(state.noteMap)
state.noteMap.set(uniqueKey, note) state.noteMap.set(uniqueKey, note)
if (oldNote == null || oldNote.isStarred !== note.isStarred) { updateStarredChange(oldNote, note, state, uniqueKey)
state.starredSet = new Set(state.starredSet)
if (note.isStarred) {
state.starredSet.add(uniqueKey)
} else {
state.starredSet.delete(uniqueKey)
}
}
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) { if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
state.trashedSet = new Set(state.trashedSet) state.trashedSet = new Set(state.trashedSet)
if (note.isTrashed) { if (note.isTrashed) {
state.trashedSet.add(uniqueKey) state.trashedSet.add(uniqueKey)
state.starredSet.delete(uniqueKey) state.starredSet.delete(uniqueKey)
removeFromTags(note.tags, state, uniqueKey)
} else { } else {
state.trashedSet.delete(uniqueKey) state.trashedSet.delete(uniqueKey)
assignToTags(note.tags, state, uniqueKey)
if (note.isStarred) { if (note.isStarred) {
state.starredSet.add(uniqueKey) state.starredSet.add(uniqueKey)
} }
@@ -107,54 +87,12 @@ function data (state = defaultDataMap(), action) {
} }
// Update foldermap if folder changed or post created // Update foldermap if folder changed or post created
if (oldNote == null || oldNote.folder !== note.folder) { updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
state.folderNoteMap = new Map(state.folderNoteMap)
let folderNoteSet = state.folderNoteMap.get(folderKey)
folderNoteSet = new Set(folderNoteSet)
folderNoteSet.add(uniqueKey)
state.folderNoteMap.set(folderKey, folderNoteSet)
if (oldNote != null) {
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
oldFolderNoteList = new Set(oldFolderNoteList)
oldFolderNoteList.delete(uniqueKey)
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
}
}
if (oldNote != null) { if (oldNote != null) {
const discardedTags = _.difference(oldNote.tags, note.tags) updateTagChanges(oldNote, note, state, uniqueKey)
const addedTags = _.difference(note.tags, oldNote.tags)
if (discardedTags.length + addedTags.length > 0) {
state.tagNoteMap = new Map(state.tagNoteMap)
discardedTags.forEach((tag) => {
let tagNoteList = state.tagNoteMap.get(tag)
if (tagNoteList != null) {
tagNoteList = new Set(tagNoteList)
tagNoteList.delete(uniqueKey)
state.tagNoteMap.set(tag, tagNoteList)
}
})
addedTags.forEach((tag) => {
let tagNoteList = state.tagNoteMap.get(tag)
tagNoteList = new Set(tagNoteList)
tagNoteList.add(uniqueKey)
state.tagNoteMap.set(tag, tagNoteList)
})
}
} else { } else {
state.tagNoteMap = new Map(state.tagNoteMap) assignToTags(note.tags, state, uniqueKey)
note.tags.forEach((tag) => {
let tagNoteList = state.tagNoteMap.get(tag)
if (tagNoteList == null) {
tagNoteList = new Set(tagNoteList)
state.tagNoteMap.set(tag, tagNoteList)
}
tagNoteList.add(uniqueKey)
})
} }
return state return state
@@ -202,26 +140,10 @@ function data (state = defaultDataMap(), action) {
originFolderList.delete(originKey) originFolderList.delete(originKey)
state.folderNoteMap.set(originFolderKey, originFolderList) state.folderNoteMap.set(originFolderKey, originFolderList)
// From tagMap removeFromTags(originNote.tags, state, originKey)
if (originNote.tags.length > 0) {
state.tagNoteMap = new Map(state.tagNoteMap)
originNote.tags.forEach((tag) => {
let noteSet = state.tagNoteMap.get(tag)
noteSet = new Set(noteSet)
noteSet.delete(originKey)
state.tagNoteMap.set(tag, noteSet)
})
}
} }
if (oldNote == null || oldNote.isStarred !== note.isStarred) { updateStarredChange(oldNote, note, state, uniqueKey)
state.starredSet = new Set(state.starredSet)
if (note.isStarred) {
state.starredSet.add(uniqueKey)
} else {
state.starredSet.delete(uniqueKey)
}
}
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) { if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
state.trashedSet = new Set(state.trashedSet) state.trashedSet = new Set(state.trashedSet)
@@ -238,59 +160,17 @@ function data (state = defaultDataMap(), action) {
let noteSet = state.storageNoteMap.get(note.storage) let noteSet = state.storageNoteMap.get(note.storage)
noteSet = new Set(noteSet) noteSet = new Set(noteSet)
noteSet.add(uniqueKey) noteSet.add(uniqueKey)
state.folderNoteMap.set(folderKey, noteSet) state.storageNoteMap.set(folderKey, noteSet)
} }
// Update foldermap if folder changed or post created // Update foldermap if folder changed or post created
if (oldNote == null || oldNote.folder !== note.folder) { updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
state.folderNoteMap = new Map(state.folderNoteMap)
let folderNoteList = state.folderNoteMap.get(folderKey)
folderNoteList = new Set(folderNoteList)
folderNoteList.add(uniqueKey)
state.folderNoteMap.set(folderKey, folderNoteList)
if (oldNote != null) {
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
oldFolderNoteList = new Set(oldFolderNoteList)
oldFolderNoteList.delete(uniqueKey)
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
}
}
// Remove from old folder map // Remove from old folder map
if (oldNote != null) { if (oldNote != null) {
const discardedTags = _.difference(oldNote.tags, note.tags) updateTagChanges(oldNote, note, state, uniqueKey)
const addedTags = _.difference(note.tags, oldNote.tags)
if (discardedTags.length + addedTags.length > 0) {
state.tagNoteMap = new Map(state.tagNoteMap)
discardedTags.forEach((tag) => {
let tagNoteList = state.tagNoteMap.get(tag)
if (tagNoteList != null) {
tagNoteList = new Set(tagNoteList)
tagNoteList.delete(uniqueKey)
state.tagNoteMap.set(tag, tagNoteList)
}
})
addedTags.forEach((tag) => {
let tagNoteList = state.tagNoteMap.get(tag)
tagNoteList = new Set(tagNoteList)
tagNoteList.add(uniqueKey)
state.tagNoteMap.set(tag, tagNoteList)
})
}
} else { } else {
state.tagNoteMap = new Map(state.tagNoteMap) assignToTags(note.tags, state, uniqueKey)
note.tags.forEach((tag) => {
let tagNoteList = state.tagNoteMap.get(tag)
if (tagNoteList == null) {
tagNoteList = new Set(tagNoteList)
state.tagNoteMap.set(tag, tagNoteList)
}
tagNoteList.add(uniqueKey)
})
} }
return state return state
@@ -329,16 +209,7 @@ function data (state = defaultDataMap(), action) {
folderSet.delete(uniqueKey) folderSet.delete(uniqueKey)
state.folderNoteMap.set(folderKey, folderSet) state.folderNoteMap.set(folderKey, folderSet)
// From tagMap removeFromTags(targetNote.tags, state, uniqueKey)
if (targetNote.tags.length > 0) {
state.tagNoteMap = new Map(state.tagNoteMap)
targetNote.tags.forEach((tag) => {
let noteSet = state.tagNoteMap.get(tag)
noteSet = new Set(noteSet)
noteSet.delete(uniqueKey)
state.tagNoteMap.set(tag, noteSet)
})
}
} }
state.noteMap = new Map(state.noteMap) state.noteMap = new Map(state.noteMap)
state.noteMap.delete(uniqueKey) state.noteMap.delete(uniqueKey)
@@ -402,9 +273,7 @@ function data (state = defaultDataMap(), action) {
// Delete key from tag map // Delete key from tag map
state.tagNoteMap = new Map(state.tagNoteMap) state.tagNoteMap = new Map(state.tagNoteMap)
note.tags.forEach((tag) => { note.tags.forEach((tag) => {
let tagNoteSet = state.tagNoteMap.get(tag) const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
tagNoteSet = new Set(tagNoteSet)
state.tagNoteMap.set(tag, tagNoteSet)
tagNoteSet.delete(noteKey) tagNoteSet.delete(noteKey)
}) })
} }
@@ -431,11 +300,7 @@ function data (state = defaultDataMap(), action) {
state.starredSet.add(uniqueKey) state.starredSet.add(uniqueKey)
} }
let storageNoteList = state.storageNoteMap.get(note.storage) const storageNoteList = getOrInitItem(state.tagNoteMap, note.storage)
if (storageNoteList == null) {
storageNoteList = new Set(storageNoteList)
state.storageNoteMap.set(note.storage, storageNoteList)
}
storageNoteList.add(uniqueKey) storageNoteList.add(uniqueKey)
let folderNoteSet = state.folderNoteMap.get(folderKey) let folderNoteSet = state.folderNoteMap.get(folderKey)
@@ -446,11 +311,7 @@ function data (state = defaultDataMap(), action) {
folderNoteSet.add(uniqueKey) folderNoteSet.add(uniqueKey)
note.tags.forEach((tag) => { note.tags.forEach((tag) => {
let tagNoteSet = state.tagNoteMap.get(tag) const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
if (tagNoteSet == null) {
tagNoteSet = new Set(tagNoteSet)
state.tagNoteMap.set(tag, tagNoteSet)
}
tagNoteSet.add(uniqueKey) tagNoteSet.add(uniqueKey)
}) })
}) })
@@ -541,6 +402,73 @@ function status (state = defaultStatus, action) {
return state return state
} }
function updateStarredChange (oldNote, note, state, uniqueKey) {
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
state.starredSet = new Set(state.starredSet)
if (note.isStarred) {
state.starredSet.add(uniqueKey)
} else {
state.starredSet.delete(uniqueKey)
}
}
}
function updateFolderChange (oldNote, note, state, folderKey, uniqueKey) {
if (oldNote == null || oldNote.folder !== note.folder) {
state.folderNoteMap = new Map(state.folderNoteMap)
let folderNoteList = state.folderNoteMap.get(folderKey)
folderNoteList = new Set(folderNoteList)
folderNoteList.add(uniqueKey)
state.folderNoteMap.set(folderKey, folderNoteList)
if (oldNote != null) {
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
oldFolderNoteList = new Set(oldFolderNoteList)
oldFolderNoteList.delete(uniqueKey)
state.folderNoteMap.set(oldFolderKey, oldFolderNoteList)
}
}
}
function updateTagChanges (oldNote, note, state, uniqueKey) {
const discardedTags = _.difference(oldNote.tags, note.tags)
const addedTags = _.difference(note.tags, oldNote.tags)
if (discardedTags.length + addedTags.length > 0) {
removeFromTags(discardedTags, state, uniqueKey)
assignToTags(addedTags, state, uniqueKey)
}
}
function assignToTags (tags, state, uniqueKey) {
state.tagNoteMap = new Map(state.tagNoteMap)
tags.forEach((tag) => {
const tagNoteList = getOrInitItem(state.tagNoteMap, tag)
tagNoteList.add(uniqueKey)
})
}
function removeFromTags (tags, state, uniqueKey) {
state.tagNoteMap = new Map(state.tagNoteMap)
tags.forEach(tag => {
let tagNoteList = state.tagNoteMap.get(tag)
if (tagNoteList != null) {
tagNoteList = new Set(tagNoteList)
tagNoteList.delete(uniqueKey)
state.tagNoteMap.set(tag, tagNoteList)
}
})
}
function getOrInitItem (target, key) {
let results = target.get(key)
if (results == null) {
results = new Set()
target.set(key, results)
}
return results
}
const reducer = combineReducers({ const reducer = combineReducers({
data, data,
config, config,

View File

@@ -118,6 +118,16 @@ colorSolarizedDarkPrimaryButton()
&:active:hover &:active:hover
background-color $dark-primary-button-background--active background-color $dark-primary-button-background--active
colorMonokaiPrimaryButton()
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
border none
&:hover
background-color $dark-primary-button-background--hover
&:active
&:active:hover
background-color $dark-primary-button-background--active
// Danger button(Brand color) // Danger button(Brand color)
$danger-button-background = #c9302c $danger-button-background = #c9302c
@@ -348,3 +358,29 @@ modalSolarizedDark()
background-color $ui-solarized-dark-backgroundColor background-color $ui-solarized-dark-backgroundColor
overflow hidden overflow hidden
border-radius $modal-border-radius border-radius $modal-border-radius
/******* Monokai theme ********/
$ui-monokai-backgroundColor = #272822
$ui-monokai-noteList-backgroundColor = #272822
$ui-monokai-noteDetail-backgroundColor = #272822
$ui-monokai-text-color = #f8f8f2
$ui-monokai-active-color = #f92672
$ui-monokai-borderColor = #373831
$ui-monokai-tag-backgroundColor = #f92672
$ui-monokai-button-backgroundColor = #373831
$ui-monokai-button--active-color = white
$ui-monokai-button--active-backgroundColor = #f92672
$ui-monokai-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
$ui-monokai-button--focus-borderColor = lighten(#369DCD, 25%)
modalmonokai()
position relative
z-index $modal-z-index
width 100%
background-color $ui-monokai-backgroundColor
overflow hidden
border-radius $modal-border-radius

View File

@@ -90,7 +90,7 @@ app.on('ready', function () {
mainWindow.setMenu(menu) mainWindow.setMenu(menu)
} }
// Check update every hour // Check update every day
setInterval(function () { setInterval(function () {
checkUpdate() checkUpdate()
}, 1000 * 60 * 60 * 24) }, 1000 * 60 * 60 * 24)
@@ -106,7 +106,7 @@ app.on('ready', function () {
checkUpdate() checkUpdate()
} }
}) })
}, 10000) }, 10 * 1000)
ipcServer = require('./ipcServer') ipcServer = require('./ipcServer')
ipcServer.server.start() ipcServer.server.start()
}) })

View File

@@ -252,10 +252,27 @@ const view = {
}, },
{ {
label: 'Focus Search', label: 'Focus Search',
accelerator: 'Control+S', accelerator: 'CommandOrControl+Shift+L',
click () { click () {
mainWindow.webContents.send('top:focus-search') mainWindow.webContents.send('top:focus-search')
} }
},
{
type: 'separator'
},
{
label: 'Toggle Full Screen',
accelerator: macOS ? 'Command+Control+F' : 'F11',
click () {
mainWindow.setFullScreen(!mainWindow.isFullScreen())
}
},
{
role: 'zoomin',
accelerator: macOS ? 'CommandOrControl+Plus' : 'Control+='
},
{
role: 'zoomout'
} }
] ]
} }
@@ -265,7 +282,7 @@ let editorFocused
// Define extra shortcut keys // Define extra shortcut keys
mainWindow.webContents.on('before-input-event', (event, input) => { mainWindow.webContents.on('before-input-event', (event, input) => {
// Synonyms for Search (Find) // Synonyms for Search (Find)
if (input.control && input.key === 'f' && input.type === 'keyDown') { if (input.control && input.key === 'l' && input.type === 'keyDown') {
if (!editorFocused) { if (!editorFocused) {
mainWindow.webContents.send('top:focus-search') mainWindow.webContents.send('top:focus-search')
event.preventDefault() event.preventDefault()
@@ -285,11 +302,6 @@ const window = {
accelerator: 'Command+M', accelerator: 'Command+M',
selector: 'performMiniaturize:' selector: 'performMiniaturize:'
}, },
{
label: 'Toggle Full Screen',
accelerator: 'Command+Control+F',
selector: 'toggleFullScreen:'
},
{ {
label: 'Close', label: 'Close',
accelerator: 'Command+W', accelerator: 'Command+W',

View File

@@ -17,7 +17,7 @@ const mainWindow = new BrowserWindow({
autoHideMenuBar: showMenu, autoHideMenuBar: showMenu,
webPreferences: { webPreferences: {
zoomFactor: 1.0, zoomFactor: 1.0,
blinkFeatures: 'OverlayScrollbars' enableBlinkFeatures: 'OverlayScrollbars'
}, },
icon: path.resolve(__dirname, '../resources/app.png') icon: path.resolve(__dirname, '../resources/app.png')
}) })

View File

@@ -77,6 +77,7 @@
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script> <script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script> <script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
<script src="../node_modules/codemirror/addon/mode/simple.js"></script> <script src="../node_modules/codemirror/addon/mode/simple.js"></script>
<script src="../node_modules/codemirror/addon/mode/multiplex.js"></script>
<script src="../node_modules/codemirror/keymap/sublime.js"></script> <script src="../node_modules/codemirror/keymap/sublime.js"></script>
<script src="../node_modules/codemirror/keymap/vim.js"></script> <script src="../node_modules/codemirror/keymap/vim.js"></script>
<script src="../node_modules/codemirror/keymap/emacs.js"></script> <script src="../node_modules/codemirror/keymap/emacs.js"></script>
@@ -114,7 +115,7 @@
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script> <script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
<script type='text/javascript'> <script type='text/javascript'>
const electron = require('electron') const electron = require('electron')
electron.webFrame.setZoomLevelLimits(1, 1) electron.webFrame.setVisualZoomLevelLimits(1, 1)
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot') var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
? 'http://localhost:8080/assets/main.js' ? 'http://localhost:8080/assets/main.js'
: '../compiled/main.js' : '../compiled/main.js'

View File

@@ -62,6 +62,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter", "LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter", "LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter", "LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Community", "Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter", "Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -149,5 +150,7 @@
"Sanitization": "Sanitization", "Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)", "Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles", "Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags" "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
} }

View File

@@ -62,6 +62,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Ende Kennzeichen", "LaTeX Inline Close Delimiter": "LaTeX Inline Ende Kennzeichen",
"LaTeX Block Open Delimiter": "LaTeX Block Beginn Kennzeichen", "LaTeX Block Open Delimiter": "LaTeX Block Beginn Kennzeichen",
"LaTeX Block Close Delimiter": "LaTeX Block Ende Kennzeichen", "LaTeX Block Close Delimiter": "LaTeX Block Ende Kennzeichen",
"PlantUML Server": "PlantUML Server",
"Community": "Community", "Community": "Community",
"Subscribe to Newsletter": "Newsletter abonnieren", "Subscribe to Newsletter": "Newsletter abonnieren",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -204,5 +205,7 @@
"Unnamed": "Unbenannt", "Unnamed": "Unbenannt",
"Rename": "Umbenennen", "Rename": "Umbenennen",
"Folder Name": "Ordnername", "Folder Name": "Ordnername",
"No tags": "Keine Tags" "No tags": "Keine Tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
} }

View File

@@ -4,9 +4,10 @@
"Preferences": "Preferences", "Preferences": "Preferences",
"Make a note": "Make a note", "Make a note": "Make a note",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl", "Ctrl(^)": "Ctrl(^)",
"to create a new note": "to create a new note", "to create a new note": "to create a new note",
"Toggle Mode": "Toggle Mode", "Toggle Mode": "Toggle Mode",
"Add tag...": "Add tag...",
"Trash": "Trash", "Trash": "Trash",
"MODIFICATION DATE": "MODIFICATION DATE", "MODIFICATION DATE": "MODIFICATION DATE",
"Words": "Words", "Words": "Words",
@@ -20,9 +21,12 @@
".html": ".html", ".html": ".html",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Help": "Help",
"Hide Help": "Hide Help",
"Storages": "Storages", "Storages": "Storages",
"Add Storage Location": "Add Storage Location", "Add Storage Location": "Add Storage Location",
"Add Folder": "Add Folder", "Add Folder": "Add Folder",
"Select Folder": "Select Folder",
"Open Storage folder": "Open Storage folder", "Open Storage folder": "Open Storage folder",
"Unlink": "Unlink", "Unlink": "Unlink",
"Edit": "Edit", "Edit": "Edit",
@@ -34,6 +38,8 @@
"Solarized Dark": "Solarized Dark", "Solarized Dark": "Solarized Dark",
"Dark": "Dark", "Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes", "Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
"Show only related tags": "Show only related tags",
"Editor Theme": "Editor Theme", "Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Font Size", "Editor Font Size": "Editor Font Size",
"Editor Font Family": "Editor Font Family", "Editor Font Family": "Editor Font Family",
@@ -51,6 +57,7 @@
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap", "⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
"Show line numbers in the editor": "Show line numbers in the editor", "Show line numbers in the editor": "Show line numbers in the editor",
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line", "Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
"Enable smart quotes": "Enable smart quotes",
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor", "Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
"Preview": "Preview", "Preview": "Preview",
"Preview Font Size": "Preview Font Size", "Preview Font Size": "Preview Font Size",
@@ -62,6 +69,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter", "LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter", "LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter", "LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Community", "Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter", "Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -126,6 +134,7 @@
"Storage": "Storage", "Storage": "Storage",
"Hotkeys": "Hotkeys", "Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote", "Show/Hide Boostnote": "Show/Hide Boostnote",
"Toggle editor mode": "Toggle editor mode",
"Restore": "Restore", "Restore": "Restore",
"Permanent Delete": "Permanent Delete", "Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion", "Confirm note deletion": "Confirm note deletion",
@@ -145,12 +154,26 @@
"UserName": "UserName", "UserName": "UserName",
"Password": "Password", "Password": "Password",
"Russian": "Russian", "Russian": "Russian",
"Hungarian": "Hungarian",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Add Storage": "Add Storage",
"Name": "Name",
"Type": "Type",
"File System": "File System",
"Setting up 3rd-party cloud storage integration:": "Setting up 3rd-party cloud storage integration:",
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "Location",
"Add": "Add",
"Unlink Storage": "Unlink Storage",
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.",
"Editor Rulers": "Editor Rulers", "Editor Rulers": "Editor Rulers",
"Enable": "Enable", "Enable": "Enable",
"Disable": "Disable", "Disable": "Disable",
"Sanitization": "Sanitization", "Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)", "Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Render newlines in Markdown paragraphs as <br>": "Render newlines in Markdown paragraphs as <br>",
"Allow styles": "Allow styles", "Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags" "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
} }

View File

@@ -62,6 +62,7 @@
"LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea", "LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea",
"LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX", "LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX",
"LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX", "LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX",
"PlantUML Server": "PlantUML Server",
"Community": "Comunidad", "Community": "Comunidad",
"Subscribe to Newsletter": "Suscribirse al boletín", "Subscribe to Newsletter": "Suscribirse al boletín",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -87,7 +88,7 @@
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote es utilizado en alrededor de 200 países y regiones diferentes por una increíble comunidad de desarrolladores.", "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote es utilizado en alrededor de 200 países y regiones diferentes por una increíble comunidad de desarrolladores.",
"To continue supporting this growth, and to satisfy community expectations,": "Para continuar apoyando este crecimiento y satisfacer las expectativas de la comunidad,", "To continue supporting this growth, and to satisfy community expectations,": "Para continuar apoyando este crecimiento y satisfacer las expectativas de la comunidad,",
"we would like to invest more time and resources in this project.": "nos gustaría invertir más tiempo y recursos en este proyecto.", "we would like to invest more time and resources in this project.": "nos gustaría invertir más tiempo y recursos en este proyecto.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si te gusta este proyecto y ves potencial en él, ¡puedes ayudar apoyándonos en OpenCollective!", "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si te gusta este proyecto y ves su potencial, ¡puedes ayudar apoyándonos en OpenCollective!",
"Thanks,": "Gracias,", "Thanks,": "Gracias,",
"Boostnote maintainers": "Equipo de Boostnote", "Boostnote maintainers": "Equipo de Boostnote",
"Support via OpenCollective": "Contribuir vía OpenCollective", "Support via OpenCollective": "Contribuir vía OpenCollective",
@@ -149,5 +150,7 @@
"Sanitization": "Saneamiento", "Sanitization": "Saneamiento",
"Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)", "Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)",
"Allow styles": "Permitir estilos", "Allow styles": "Permitir estilos",
"Allow dangerous html tags": "Permitir etiques html peligrosas" "Allow dangerous html tags": "Permitir etiquetas html peligrosas",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown."
} }

159
locales/fa.json Normal file
View File

@@ -0,0 +1,159 @@
{
"Notes": "یادداشت ها",
"Tags": "تگ ها",
"Preferences": "تنظیمات",
"Make a note": "یک یادداشت بنویس",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "برای ساخت یک یادداشت",
"Toggle Mode": "تغییر حالت نمایش",
"Trash": "سطل آشغال",
"MODIFICATION DATE": "تاریخ تغییر",
"Words": "کلمات",
"Letters": "حروف",
"STORAGE": "ذخیره سازی",
"FOLDER": "پوشه",
"CREATION DATE": "تاریخ ایجاد",
"NOTE LINK": "لینک یادداشت",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "پرینت",
"Your preferences for Boostnote": "تنظیمات شما برای boostnote",
"Storages": "ذخیره سازی",
"Add Storage Location": "افزودن محل ذخیره سازی",
"Add Folder": "ساخت پوشه",
"Open Storage folder": "بازکردن پوشه ذخیره سازی",
"Unlink": "حذف لینک",
"Edit": "ویرایش",
"Delete": "حذف",
"Interface": "رابط کاربری",
"Interface Theme": "تم رابط کاربری",
"Default": "پیش فرض",
"White": "روشن",
"Solarized Dark": "سولارایز",
"Dark": "تاریک",
"Show a confirmation dialog when deleting notes": "هنگام حذف یادداشت ها یک پیام تایید نمایش بده.",
"Editor Theme": "تم ویرایشگر",
"Editor Font Size": "اندازه فونت ویرایشگر",
"Editor Font Family": "فونت ویرایشگر",
"Editor Indent Style": "حالت فاصله گذاری ویرایشگر",
"Spaces": "Spaces",
"Tabs": "Tabs",
"Switch to Preview": "دیدن پیش نمایش",
"When Editor Blurred": "وقتی ویرایشگر از حالت ویرایش خارج شد ",
"When Editor Blurred, Edit On Double Click": "وقتی ویرایشگر از حالت ویرایش خارج شد و با دبل کلیک ویرایش کنید.",
"On Right Click": "راست کلیک",
"Editor Keymap": "ویرایشگر Keymap",
"default": "پیش فرض",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ برنامه را دوباره راه اندازی کنید keymap لطفا بعد از تغییر",
"Show line numbers in the editor": "شماره خطوط در ویرایشگر را نمایش بده.",
"Allow editor to scroll past the last line": "اجازه بده ویرایشگر بعد از آخرین خط اسکرول کند.",
"Bring in web page title when pasting URL on editor": "هنگامی که آدرس اینترنتی در ویرایشگر اضافه شد عنوان آنرا نمایش بده",
"Preview": "پیش نمایش",
"Preview Font Size": "اندازه فونتِ پیش نمایش",
"Preview Font Family": " فونتِ پیش نمایش",
"Code block Theme": "تم بخش کد",
"Allow preview to scroll past the last line": "اجازه بده پیش نمایش بعد از آخرین خط اسکرول کند.",
"Show line numbers for preview code blocks": "شماره خطوط در پیش نمایش را نمایش بده.",
"LaTeX Inline Open Delimiter": "جداکننده آغازین لاتکس خطی",
"LaTeX Inline Close Delimiter": "جداکننده پایانی لاتکس خطی",
"LaTeX Block Open Delimiter": "جداکننده آغازین بلوک لاتکس ",
"LaTeX Block Close Delimiter": "جداکننده آغازین بلوک لاتکس ",
"PlantUML Server": "PlantUML Server",
"Community": "کامینیتی",
"Subscribe to Newsletter": "اشتراک در خبرنامه",
"GitHub": "گیت هاب",
"Blog": "بلاگ",
"Facebook Group": "گروه فیسبوک",
"Twitter": "توییتر",
"About": "درباره",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "یک دفترچه یادداشت متن باز ساخته شده برای برنامه نویسانی مثل تو.",
"Website": "وبسایت",
"Development": "توسعه",
" : Development configurations for Boostnote.": " : پیکربندی توسعه برای Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "لایسنس: GPL v3",
"Analytics": "تجزیه و تحلیل",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Bosstnote اطلاعات ناشناس را برای بهبود عملکرد برنامه جمع آوری می‌کند.اطلاعات شخصی شما مثل محتوای یادداشت ها هرگز برای هیچ هدفی جمع آوری نمی‌شوند",
"You can see how it works on ": "میتوانید ببینید چگونه کار میکند. ",
"You can choose to enable or disable this option.": "میتوانید این گزینه را فعال یا غیرفعال کنید.",
"Enable analytics to help improve Boostnote":".تجزیه تحلیل داده ها را برای کمک به بهبود برنامه فعال کن",
"Crowdfunding": "جمع سپاری (سرمایه گذاری جمعی )",
"Dear everyone,": "عزیزان,",
"Thank you for using Boostnote!": "از شما بخاطر استفاده از boostnote ممنونیم!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "در ۲۰۰ کشور مختلف دنیا مورد توسط جمعی از برنامه نویسان بی نظیر مورد استفاده قرار میگیرد. Boostnote",
"To continue supporting this growth, and to satisfy community expectations,": "برای حمایت از این رشد ، و برآورده شدن انتظارات کامینیتی,",
"we would like to invest more time and resources in this project.": "ما می خواهیم زمان و منابع بیشتری را در این پروژه سرمایه گذاری کنیم.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "اگر این پروژه را دوست دارید و پتانسیلی در آن می‌بینید، میتوانید مارا در اوپن‌ کالکتیو حمایت کنید.",
"Thanks,": "با تشکر,",
"Boostnote maintainers": "Boostnote نگهدارندگان",
"Support via OpenCollective": "حمایت کنید OpenCollective از طریق",
"Language": "زبان",
"English": "انگلیسی",
"German": "آلمانی",
"French": "فرانسوی",
"Show \"Saved to Clipboard\" notification when copying": "نمایش \"ذخیره در کلیپ‌بورد\" اطلاع رسانی هنگام کپی کردن",
"All Notes": "همه یادداشت ها",
"Starred": "ستاره دار",
"Are you sure to ": " مطمئن هستید که",
" delete": "حذف ",
"this folder?": "این پوشه ؟",
"Confirm": "تایید",
"Cancel": "انصراف",
"Markdown Note": "Markdown یادداشتِ",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "این قالب برای ساخت سند های متنی است. چک لیست ها و تکه کد ها و بلاک های لاتکس قابل استفاده اند.",
"Snippet Note": "Snippet یادداشتِ",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "این قالب برای ساخت تکه کد هاست. چند تکه کد میتوانند تبدیل به یک یادداشت شوند.",
"Tab to switch format": "را بزنید Tab برای تغییر فرمت",
"Updated": "بروزرسانی شد",
"Created": "ایجاد شد",
"Alphabetically": "بر اساس حروف الفبا",
"Counter": "شمارشگر",
"Default View": "نمایش پیش‌فرض",
"Compressed View": "نمایش فشرده",
"Search": "جستجو",
"Blog Type": "نوع وبلاگ",
"Blog Address": "آدرس وبلاگ",
"Save": "ذخیره",
"Auth": "هویت",
"Authentication Method": "متد احراز هویت",
"JWT": "JWT",
"USER": "کاربر",
"Token": "توکن",
"Storage": "ذخیره سازی",
"Hotkeys": "کلید های میانبر",
"Show/Hide Boostnote": "Boostnote نمایش/پنهان کردن",
"Restore": "بازگرداندن به حالت اول",
"Permanent Delete": "حذف بدون بازگشت",
"Confirm note deletion": ".حذف یادداشت را تایید کنید",
"This will permanently remove this note.": ".این کار یادداشت را بطور دائمی حذف خواهد کرد",
"Successfully applied!": "!با موفقیت اجرا شد",
"Albanian": "آلبانی",
"Chinese (zh-CN)": "چینی (zh-CN)",
"Chinese (zh-TW)": "چینی (zh-TW)",
"Danish": "دانمارکی",
"Japanese": "ژاپنی",
"Korean": "کره ای",
"Norwegian": "نروژی",
"Polish": "لهستانی",
"Portuguese": "پرتغالی",
"Spanish": "اسپانیایی",
"You have to save!": "!باید ذخیره کنید",
"UserName": "نام کاربری",
"Password": "رمز عبور",
"Russian": "روسی",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "فعال",
"Disable": "غیرفعال",
"Sanitization": "پاکسازی کردن",
"Only allow secure html tags (recommended)": "(فقط تگ های امن اچ تی ام ال مجاز اند.(پیشنهاد میشود",
"Allow styles": "حالت های مجاز",
"Allow dangerous html tags": "تگ های خطرناک اچ‌ تی ام ال مجاز اند",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
}

View File

@@ -61,6 +61,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter", "LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter", "LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter", "LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Communauté", "Community": "Communauté",
"Subscribe to Newsletter": "Souscrire à la newsletter", "Subscribe to Newsletter": "Souscrire à la newsletter",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -140,14 +141,16 @@
"Portuguese": "Portugais", "Portuguese": "Portugais",
"Spanish": "Espagnol", "Spanish": "Espagnol",
"You have to save!": "Il faut sauvegarder !", "You have to save!": "Il faut sauvegarder !",
"Russian": "Russian", "Russian": "Russe",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers", "Editor Rulers": "Règles dans l'éditeur",
"Enable": "Enable", "Enable": "Activer",
"Disable": "Disable", "Disable": "Désactiver",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line", "Allow preview to scroll past the last line": "Permettre de scroller après la dernière ligne dans l'aperçu",
"Sanitization": "Sanitization", "Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)", "Only allow secure html tags (recommended)": "N'accepter que les tags html sécurisés (recommandé)",
"Allow styles": "Allow styles", "Allow styles": "Accepter les styles",
"Allow dangerous html tags": "Allow dangerous html tags" "Allow dangerous html tags": "Accepter les tags html dangereux",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠"
} }

View File

@@ -7,6 +7,7 @@
"Ctrl(^)": "Ctrl", "Ctrl(^)": "Ctrl",
"to create a new note": "hogy létrehozz egy jegyzetet", "to create a new note": "hogy létrehozz egy jegyzetet",
"Toggle Mode": "Mód Váltás", "Toggle Mode": "Mód Váltás",
"Add tag...": "Tag hozzáadása...",
"Trash": "Lomtár", "Trash": "Lomtár",
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA", "MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
"Words": "Szó", "Words": "Szó",
@@ -20,9 +21,12 @@
".html": ".html", ".html": ".html",
"Print": "Nyomtatás", "Print": "Nyomtatás",
"Your preferences for Boostnote": "Boostnote beállításaid", "Your preferences for Boostnote": "Boostnote beállításaid",
"Help": "Súgó",
"Hide Help": "Súgó Elrejtése",
"Storages": "Tárolók", "Storages": "Tárolók",
"Add Storage Location": "Tároló Hozzáadása", "Add Storage Location": "Tároló Hozzáadása",
"Add Folder": "Könyvtár Hozzáadása", "Add Folder": "Könyvtár Hozzáadása",
"Select Folder": "Könyvtár Kiválasztása",
"Open Storage folder": "Tároló Megnyitása", "Open Storage folder": "Tároló Megnyitása",
"Unlink": "Tároló Leválasztása", "Unlink": "Tároló Leválasztása",
"Edit": "Szerkesztés", "Edit": "Szerkesztés",
@@ -34,6 +38,8 @@
"Solarized Dark": "Solarized Dark", "Solarized Dark": "Solarized Dark",
"Dark": "Sötét", "Dark": "Sötét",
"Show a confirmation dialog when deleting notes": "Kérjen megerősítést a jegyzetek törlése előtt", "Show a confirmation dialog when deleting notes": "Kérjen megerősítést a jegyzetek törlése előtt",
"Disable Direct Write (It will be applied after restarting)": "Jegyzet Azonnali Mentésének Tiltása (Újraindítás igényel)",
"Show only related tags": "Csak a kapcsolódó tag-ek megjelenítése",
"Editor Theme": "Szerkesztő Témája", "Editor Theme": "Szerkesztő Témája",
"Editor Font Size": "Szerkesztő Betűmérete", "Editor Font Size": "Szerkesztő Betűmérete",
"Editor Font Family": "Szerkesztő Betűtípusa", "Editor Font Family": "Szerkesztő Betűtípusa",
@@ -51,6 +57,7 @@
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Kérlek, indítsd újra a programot a kiosztás megváltoztatása után", "⚠️ Please restart boostnote after you change the keymap": "⚠️ Kérlek, indítsd újra a programot a kiosztás megváltoztatása után",
"Show line numbers in the editor": "Mutatassa a sorszámokat a szerkesztőben", "Show line numbers in the editor": "Mutatassa a sorszámokat a szerkesztőben",
"Allow editor to scroll past the last line": "A szerkesztőben az utolsó sor alá is lehessen görgetni", "Allow editor to scroll past the last line": "A szerkesztőben az utolsó sor alá is lehessen görgetni",
"Enable smart quotes": "Idézőjelek párjának automatikus beírása",
"Bring in web page title when pasting URL on editor": "Weboldal főcímének lekérdezése URL cím beillesztésekor", "Bring in web page title when pasting URL on editor": "Weboldal főcímének lekérdezése URL cím beillesztésekor",
"Preview": "Megtekintés", "Preview": "Megtekintés",
"Preview Font Size": "Megtekintés Betűmérete", "Preview Font Size": "Megtekintés Betűmérete",
@@ -62,6 +69,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója", "LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója", "LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója", "LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
"PlantUML Server": "PlantUML Server",
"Community": "Közösség", "Community": "Közösség",
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre", "Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -126,6 +134,7 @@
"Storage": "Tároló", "Storage": "Tároló",
"Hotkeys": "Gyorsbillentyűk", "Hotkeys": "Gyorsbillentyűk",
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése", "Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
"Toggle editor mode": "Szerkesztő mód váltása",
"Restore": "Visszaállítás", "Restore": "Visszaállítás",
"Permanent Delete": "Végleges Törlés", "Permanent Delete": "Végleges Törlés",
"Confirm note deletion": "Törlés megerősítése", "Confirm note deletion": "Törlés megerősítése",
@@ -145,8 +154,8 @@
"UserName": "FelhasznaloNev", "UserName": "FelhasznaloNev",
"Password": "Jelszo", "Password": "Jelszo",
"Russian": "Russian", "Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Hungarian": "Hungarian", "Hungarian": "Hungarian",
"Command(⌘)": "Command(⌘)",
"Add Storage": "Tároló hozzáadása", "Add Storage": "Tároló hozzáadása",
"Name": "Név", "Name": "Név",
"Type": "Típus", "Type": "Típus",
@@ -155,6 +164,17 @@
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup", "Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "Hely", "Location": "Hely",
"Add": "Hozzáadás", "Add": "Hozzáadás",
"Select Folder": "Könyvtár Kiválasztása",
"Unlink Storage": "Tároló Leválasztása", "Unlink Storage": "Tároló Leválasztása",
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "A leválasztás eltávolítja ezt a tárolót a Boostnote-ból. Az adatok nem lesznek törölve, kérlek manuálisan töröld a könyvtárat a merevlemezről, ha szükséges." "Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "A leválasztás eltávolítja ezt a tárolót a Boostnote-ból. Az adatok nem lesznek törölve, kérlek manuálisan töröld a könyvtárat a merevlemezről, ha szükséges.",
"Editor Rulers": "Szerkesztő Margók",
"Enable": "Engedélyezés",
"Disable": "Tiltás",
"Sanitization": "Tisztítás",
"Only allow secure html tags (recommended)": "Csak a biztonságos html tag-ek engedélyezése (ajánlott)",
"Render newlines in Markdown paragraphs as <br>": "Az újsor karaktert <br> soremelésként jelenítse meg a Markdown jegyzetekben",
"Allow styles": "Stílusok engedélyezése",
"Allow dangerous html tags": "Veszélyes html tag-ek engedélyezése",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
} }

159
locales/it.json Normal file
View File

@@ -0,0 +1,159 @@
{
"Notes": "Note",
"Tags": "Tags",
"Preferences": "Preferenze",
"Make a note": "Crea una nota",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "per creare una nuova nota",
"Toggle Mode": "Cambia Modalità",
"Trash": "Cestino",
"MODIFICATION DATE": "DATA DI MODIFICA",
"Words": "Parole",
"Letters": "Lettere",
"STORAGE": "POSIZIONE",
"FOLDER": "CARTELLA",
"CREATION DATE": "DATA DI CREAZIONE",
"NOTE LINK": "LINK NOTA",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Stampa",
"Your preferences for Boostnote": "Le tue preferenze per Boostnote",
"Storages": "Posizioni",
"Add Storage Location": "Aggiungi posizione",
"Add Folder": "Aggiungi cartella",
"Open Storage folder": "Apri cartella di memoria",
"Unlink": "Scollega",
"Edit": "Modifica",
"Delete": "Elimina",
"Interface": "Interfaccia",
"Interface Theme": "Tema interfaccia",
"Default": "Default",
"White": "White",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Mostra finestra di conferma quando elimini delle note",
"Editor Theme": "Tema dell'Editor",
"Editor Font Size": "Dimensione font dell'editor",
"Editor Font Family": "Famiglia del font dell'editor",
"Editor Indent Style": "Stile di indentazione dell'editor",
"Spaces": "Spazi",
"Tabs": "Tabs",
"Switch to Preview": "Passa all'anteprima",
"When Editor Blurred": "Quando l'editor è sfocato",
"When Editor Blurred, Edit On Double Click": "Quando l'Editor è sfocato, Modifica facendo doppio click",
"On Right Click": "Cliccando con il tasto destro",
"Editor Keymap": "keymapping dell'editor",
"default": "default",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Riavvia Boostnote dopo aver cambiato il keymapping",
"Show line numbers in the editor": "Mostra numero di linea nell'editor",
"Allow editor to scroll past the last line": "Consenti scrolling oltre l'ultima linea nell'editor",
"Bring in web page title when pasting URL on editor": "Mostra il titolo della pagina web quando incolli un URL nell'editor",
"Preview": "Anteprima",
"Preview Font Size": "Dimensione font nell'anteprima",
"Preview Font Family": "Famiglia del font dell'anteprima",
"Code block Theme": "Tema blocco di codice",
"Allow preview to scroll past the last line": "Consenti scrolling oltre l'ultima linea",
"Show line numbers for preview code blocks": "Mostra numero di linea per i blocchi di codice nell'Anteprima",
"LaTeX Inline Open Delimiter": "Delimitatore inline per apertura LaTex",
"LaTeX Inline Close Delimiter": "Delimitatore inline per chiusura LaTex",
"LaTeX Block Open Delimiter": "Delimitatore apertura LaTex",
"LaTeX Block Close Delimiter": "Delimitatore chiusura LaTex",
"PlantUML Server": "PlantUML Server",
"Community": "Community",
"Subscribe to Newsletter": "Iscriviti alla Newsletter",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Gruppo Facebook",
"Twitter": "Twitter",
"About": "About",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "Un'app open-source per prendere appunti, fatta per sviluppatori come te.",
"Website": "Sito Web",
"Development": "Sviluppo",
" : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "Licenza: GPL v3",
"Analytics": "Statistiche",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.",
"You can see how it works on ": "Ypuoi vedere come su ",
"You can choose to enable or disable this option.": "Puoi scegliere se attivare o disattivare questa opzione.",
"Enable analytics to help improve Boostnote": "Attiva raccolta dati per aiutare a migliorare Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Cari utenti,",
"Thank you for using Boostnote!": "Grazie per stare utilizzando Boostnote!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote è usato in circa 200 Paesi da una fantastica community di sviluppatori.",
"To continue supporting this growth, and to satisfy community expectations,": "Per continuare a supportarne la crescita, e per soddisfare le aspettative della comunità,",
"we would like to invest more time and resources in this project.": "ci piacerebbe investire più tempo e risorse in questo progetto.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Se ti piace questo progetto e ci vedi del potenziale, puoi aiutarci dandodci supporto su OpenCollective!",
"Thanks,": "Grazie,",
"Boostnote maintainers": "I mantainers di Boostnote",
"Support via OpenCollective": "Supporta su OpenCollective",
"Language": "Lingua",
"English": "Inglese",
"German": "Tedesco",
"French": "Francese",
"Show \"Saved to Clipboard\" notification when copying": "Mostra la notifica \"Salvato negli Appunti\" quando copi:",
"All Notes": "Tutte le note",
"Starred": "Contribuite",
"Are you sure to ": "Sei sicuro di ",
" delete": " eliminare",
"this folder?": "questa cartella?",
"Confirm": "Conferma",
"Cancel": "Cancella",
"Markdown Note": "Nota in Markdown",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Questo formato è per creare documenti di testo. Sono disponibili checklist, blocchi di codice and blocchi in Latex",
"Snippet Note": "Nota Snippet",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Questo formato è per creare snippets. Più snippet possono essere raccolti in un'unica nota.",
"Tab to switch format": "Premi Tab per cambiare formato",
"Updated": "Aggiornato",
"Created": "Creato",
"Alphabetically": "Ordine alfabetico",
"Counter": "Contatore",
"Default View": "Visione di default",
"Compressed View": "Visione compressa",
"Search": "Cerca",
"Blog Type": "Tipo di blog",
"Blog Address": "Indirizzo del blog",
"Save": "Salva",
"Auth": "Autorizzazione",
"Authentication Method": "Metodo di autenticazione",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Storage",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Mostra/Nascondi Boostnote",
"Restore": "Ripristina",
"Permanent Delete": "Elimina permanentemente",
"Confirm note deletion": "Conferma eliiminazione della nota",
"This will permanently remove this note.": "Questo eliminerà permanentemente questa nota.",
"Successfully applied!": "Applicato con successo!",
"Albanian": "Albanese",
"Chinese (zh-CN)": "Cinese (zh-CN)",
"Chinese (zh-TW)": "Cinese (zh-TW)",
"Danish": "Danese",
"Japanese": "Giapponese",
"Korean": "Koreano",
"Norwegian": "Novergese",
"Polish": "Polacco",
"Portuguese": "Portoghese",
"Spanish": "Spagnolo",
"You have to save!": "Devi salvare!",
"UserName": "UserName",
"Password": "Password",
"Russian": "Russo",
"Command(⌘)": "Comando(⌘)",
"Editor Rulers": "Regole dell'editor",
"Enable": "Abilita",
"Disable": "Disabilia",
"Sanitization": "Bonifica",
"Only allow secure html tags (recommended)": "Consenti solo tag HTML sicuri (raccomandato)",
"Allow styles": "Consenti stili",
"Allow dangerous html tags": "Consenti tag HTML pericolosi",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
}

View File

@@ -1,153 +1,179 @@
{ {
"Notes": "Notes", "Notes": "ノート",
"Tags": "Tags", "Tags": "タグ",
"Preferences": "Preferences", "Preferences": "設定",
"Make a note": "Make a note", "Make a note": "ノート作成",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl", "Ctrl(^)": "Ctrl",
"to create a new note": "to create a new note", "to create a new note": "ノートを新規に作成",
"Toggle Mode": "Toggle Mode", "Toggle Mode": "モード切替",
"Trash": "Trash", "Add tag...": "タグを追加...",
"MODIFICATION DATE": "MODIFICATION DATE", "Trash": "ゴミ箱",
"Words": "Words", "MODIFICATION DATE": "修正日",
"Letters": "Letters", "Words": "ワード",
"STORAGE": "STORAGE", "Letters": "文字",
"FOLDER": "FOLDER", "STORAGE": "ストレージ",
"CREATION DATE": "CREATION DATE", "FOLDER": "フォルダ",
"NOTE LINK": "NOTE LINK", "CREATION DATE": "作成日",
"NOTE LINK": "リンク",
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
"Print": "Print", "Print": "印刷",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Boostnoteの個人設定",
"Storages": "Storages", "Help": "ヘルプ",
"Add Storage Location": "Add Storage Location", "Hide Help": "ヘルプを隠す",
"Add Folder": "Add Folder", "Storages": "ストレージ",
"Open Storage folder": "Open Storage folder", "Add Storage Location": "ストレージロケーションを追加",
"Unlink": "Unlink", "Add Folder": "フォルダを追加",
"Edit": "Edit", "Select Folder": "フォルダを選択",
"Delete": "Delete", "Open Storage folder": "ストレージフォルダを開く",
"Interface": "Interface", "Unlink": "リンク解除",
"Interface Theme": "Interface Theme", "Edit": "編集",
"Default": "Default", "Delete": "削除",
"White": "White", "Interface": "インターフェース",
"Solarized Dark": "Solarized Dark", "Interface Theme": "インターフェーステーマ",
"Dark": "Dark", "Default": "デフォルト",
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes", "White": "白",
"Editor Theme": "Editor Theme", "Solarized Dark": "明灰",
"Editor Font Size": "Editor Font Size", "Dark": "暗灰",
"Editor Font Family": "Editor Font Family", "Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
"Editor Indent Style": "Editor Indent Style", "Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
"Spaces": "Spaces", "Show only related tags": "関連するタグのみ表示する",
"Tabs": "Tabs", "Editor Theme": "エディタのテーマ",
"Switch to Preview": "Switch to Preview", "Editor Font Size": "エディタのフォントサイズ",
"When Editor Blurred": "When Editor Blurred", "Editor Font Family": "エディタのフォント",
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click", "Editor Indent Style": "エディタのインデント方法",
"On Right Click": "On Right Click", "Spaces": "スペース",
"Editor Keymap": "Editor Keymap", "Tabs": "タブ",
"default": "default", "Switch to Preview": "プレビューへ移動",
"When Editor Blurred": "エディタがフォーカスを失った時",
"When Editor Blurred, Edit On Double Click": "エディタがフォーカスを失った時、ダブルクリックで編集",
"On Right Click": "右クリック",
"Editor Keymap": "エディタのキーマップ",
"default": "デフォルト",
"vim": "vim", "vim": "vim",
"emacs": "emacs", "emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap", "⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください",
"Show line numbers in the editor": "Show line numbers in the editor", "Show line numbers in the editor": "エディタ内に行番号を表示",
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line", "Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする",
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor", "Enable smart quotes": "スマートクォートを有効にする",
"Preview": "Preview", "Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する",
"Preview Font Size": "Preview Font Size", "Preview": "プレビュー",
"Preview Font Family": "Preview Font Family", "Preview Font Size": "プレビュー時フォントサイズ",
"Code block Theme": "Code block Theme", "Preview Font Family": "プレビュー時フォント",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line", "Code block Theme": "コードブロックのテーマ",
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks", "Allow preview to scroll past the last line": "プレビュー時に最終行以降にスクロールできるようにする",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter", "Show line numbers for preview code blocks": "プレビュー時のコードブロック内に行番号を表示する",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter", "LaTeX Inline Open Delimiter": "LaTeX 開始デリミタ(インライン)",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter", "LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter", "LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
"Community": "Community", "LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
"Subscribe to Newsletter": "Subscribe to Newsletter", "PlantUML Server": "PlantUML サーバー",
"Community": "コミュニティ",
"Subscribe to Newsletter": "ニュースレターを購読する",
"GitHub": "GitHub", "GitHub": "GitHub",
"Blog": "Blog", "Blog": "ブログ",
"Facebook Group": "Facebook Group", "Facebook Group": "Facebook グループ",
"Twitter": "Twitter", "Twitter": "Twitter",
"About": "About", "About": "Boostnote について",
"Boostnote": "Boostnote", "Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.", "An open source note-taking app made for programmers just like you.": "あなたのようなプログラマー向けのオープンソースメモ書きアプリケーション",
"Website": "Website", "Website": "ウェブサイト",
"Development": "Development", "Development": "開発",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Boostnote の開発構成",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "ライセンス: GPL v3",
"Analytics": "Analytics", "Analytics": "解析",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.", "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。",
"You can see how it works on ": "You can see how it works on ", "You can see how it works on ": "どのように動くかをこちらで確認できます ",
"You can choose to enable or disable this option.": "You can choose to enable or disable this option.", "You can choose to enable or disable this option.": "このオプションは有効/無効を選択できます。",
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote", "Enable analytics to help improve Boostnote": "Boostnote の機能向上のための解析機能を有効にする",
"Crowdfunding": "Crowdfunding", "Crowdfunding": "クラウドファンディング",
"Dear everyone,": "Dear everyone,", "Dear everyone,": "みなさまへ",
"Thank you for using Boostnote!": "Thank you for using Boostnote!", "Thank you for using Boostnote!": "Boostnote を利用いただき、ありがとうございます!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.", "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote はおよそ 200 の国と地域において、開発者コミュニティを中心に利用されています。",
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,", "To continue supporting this growth, and to satisfy community expectations,": "この成長を持続し、またコミュニティからの要望に答えるため、",
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.", "we would like to invest more time and resources in this project.": "私達はこのプロジェクトにより多くの時間とリソースを投資したいと考えています。",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!", "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "もしあなたがこのプロジェクトとそのポテンシャルを気に入っていただけたのであれば、OpenCollective を通じて支援いただくことができます!",
"Thanks,": "Thanks,", "Thanks,": "ありがとうございます。",
"Boostnote maintainers": "Boostnote maintainers", "Boostnote maintainers": "Boostnote メンテナンスチーム",
"Support via OpenCollective": "Support via OpenCollective", "Support via OpenCollective": "OpenCollective を通じて支援します",
"Language": "Language", "Language": "言語",
"English": "English", "English": "英語",
"German": "German", "German": "ドイツ語",
"French": "French", "French": "フランス語",
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying", "Show \"Saved to Clipboard\" notification when copying": "クリップボードコピー時に \"クリップボードに保存\" 通知を表示する",
"All Notes": "All Notes", "All Notes": "すべてのノート",
"Starred": "Starred", "Starred": "スター付き",
"Are you sure to ": "Are you sure to ", "Are you sure to ": "本当に ",
" delete": " delete", " delete": "このフォルダを",
"this folder?": "this folder?", "this folder?": "削除しますか?",
"Confirm": "Confirm", "Confirm": "確認",
"Cancel": "Cancel", "Cancel": "キャンセル",
"Markdown Note": "Markdown Note", "Markdown Note": "マークダウン",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.", "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "このフォーマットはテキスト文書を作成することを目的としています。チェックリストや比較的長いコード、LaTeX にも向いています。",
"Snippet Note": "Snippet Note", "Snippet Note": "スニペット",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.", "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "このフォーマットは短いコードスニペットを作成することを目的としています。複数のコードスニペットを1つのグループにまとめて1つのノートとして扱うことも可能です。",
"Tab to switch format": "Tab to switch format", "Tab to switch format": "フォーマット切り替えタブ",
"Updated": "Updated", "Updated": "更新日時",
"Created": "Created", "Created": "作成日時",
"Alphabetically": "Alphabetically", "Alphabetically": "アルファベット順",
"Default View": "Default View", "Counter": "数順",
"Compressed View": "Compressed View", "Default View": "デフォルトビュー",
"Search": "Search", "Compressed View": "圧縮ビュー",
"Blog Type": "Blog Type", "Search": "検索",
"Blog Address": "Blog Address", "Blog Type": "ブログの種類",
"Save": "Save", "Blog Address": "ブログのアドレス",
"Auth": "Auth", "Save": "保存",
"Authentication Method": "Authentication Method", "Auth": "認証",
"Authentication Method": "認証方法",
"JWT": "JWT", "JWT": "JWT",
"USER": "USER", "USER": "ユーザー",
"Token": "Token", "Token": "トークン",
"Storage": "Storage", "Storage": "ストレージ",
"Hotkeys": "Hotkeys", "Hotkeys": "ホットキー",
"Show/Hide Boostnote": "Show/Hide Boostnote", "Show/Hide Boostnote": "Boostnote の表示/非表示",
"Restore": "Restore", "Toggle editor mode": "エディタモードの切替",
"Permanent Delete": "Permanent Delete", "Restore": "リストア",
"Confirm note deletion": "Confirm note deletion", "Permanent Delete": "永久に削除",
"This will permanently remove this note.": "This will permanently remove this note.", "Confirm note deletion": "ノート削除確認",
"Successfully applied!": "Successfully applied!", "This will permanently remove this note.": "本当にこのノートを削除します。",
"Albanian": "Albanian", "Successfully applied!": "成功しました!",
"Chinese (zh-CN)": "Chinese (zh-CN)", "Albanian": "アルバニア語",
"Chinese (zh-TW)": "Chinese (zh-TW)", "Chinese (zh-CN)": "簡体字中国語 (zh-CN)",
"Danish": "Danish", "Chinese (zh-TW)": "繁体字中国語 (zh-TW)",
"Japanese": "Japanese", "Danish": "デンマーク語",
"Korean": "Korean", "Japanese": "日本語",
"Norwegian": "Norwegian", "Korean": "韓国語",
"Polish": "Polish", "Norwegian": "ノルウェー語",
"Portuguese": "Portuguese", "Polish": "ポーランド語",
"Spanish": "Spanish", "Portuguese": "ポルトガル語",
"You have to save!": "You have to save!", "Spanish": "スペイン語",
"Russian": "Russian", "You have to save!": "保存してください!",
"Command(⌘)": "Command(⌘)", "UserName": "ユーザー名",
"Editor Rulers": "Editor Rulers", "Password": "パスワード",
"Enable": "Enable", "Russian": "ロシア語",
"Disable": "Disable", "Hungarian": "ハンガリー語",
"Sanitization": "Sanitization", "Command(⌘)": "コマンド(⌘)",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)", "Add Storage": "ストレージを追加",
"Allow styles": "Allow styles", "Name": "名前",
"Allow dangerous html tags": "Allow dangerous html tags" "Type": "種類",
"File System": "ファイルシステム",
"Setting up 3rd-party cloud storage integration:": "サードパーティのクラウドストレージとの統合を設定する:",
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "ロケーション",
"Add": "追加",
"Unlink Storage": "ストレージのリンクを解除",
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "リンクの解除ではBoostnoteからリンクされたストレージを削除しますが、データは削除されません。データを削除する場合はご自身でハードドライブからフォルダを削除してください。",
"Editor Rulers": "罫線",
"Enable": "有効",
"Disable": "無効",
"Sanitization": "サニタイズ",
"Only allow secure html tags (recommended)": "安全なHTMLタグのみ利用を許可する推奨",
"Render newlines in Markdown paragraphs as <br>": "Markdown 中の改行でプレビューも改行する",
"Allow styles": "スタイルを許可する",
"Allow dangerous html tags": "安全でないHTMLタグの利用を許可する",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "テキストの矢印を綺麗な記号に変換する ⚠ この設定はMarkdown内でのHTMLコメントに干渉します。",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ このノートのストレージに存在しない添付ファイルへのリンクを貼り付けました。添付ファイルへのリンクの貼り付けは同一ストレージ内でのみサポートされています。代わりに添付ファイルをドラッグアンドドロップしてください! ⚠"
} }

View File

@@ -62,6 +62,7 @@
"LaTeX Inline Close Delimiter": "LaTeX 인라인 블록 닫기 기호", "LaTeX Inline Close Delimiter": "LaTeX 인라인 블록 닫기 기호",
"LaTeX Block Open Delimiter": "LaTeX 블록 열기 기호", "LaTeX Block Open Delimiter": "LaTeX 블록 열기 기호",
"LaTeX Block Close Delimiter": "LaTeX 블록 닫기 기호", "LaTeX Block Close Delimiter": "LaTeX 블록 닫기 기호",
"PlantUML Server": "PlantUML Server",
"Community": "커뮤니티", "Community": "커뮤니티",
"Subscribe to Newsletter": "뉴스레터 구독", "Subscribe to Newsletter": "뉴스레터 구독",
"GitHub": "깃허브", "GitHub": "깃허브",
@@ -143,7 +144,7 @@
"You have to save!": "저장해주세요!", "You have to save!": "저장해주세요!",
"Russian": "Russian", "Russian": "Russian",
"Command(⌘)": "Command(⌘)", "Command(⌘)": "Command(⌘)",
"Delete Folder": "폴더 삭", "Delete Folder": "폴더 삭",
"This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.", "This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.",
"UserName": "유저명", "UserName": "유저명",
"Password": "패스워드", "Password": "패스워드",
@@ -155,5 +156,7 @@
"Sanitization": "허용 태그 범위", "Sanitization": "허용 태그 범위",
"Only allow secure html tags (recommended)": "안전한 HTML 태그만 허용 (추천)", "Only allow secure html tags (recommended)": "안전한 HTML 태그만 허용 (추천)",
"Allow styles": "style 태그, 속성까지 허용", "Allow styles": "style 태그, 속성까지 허용",
"Allow dangerous html tags": "모든 위험한 태그 허용" "Allow dangerous html tags": "모든 위험한 태그 허용",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
} }

View File

@@ -62,6 +62,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter", "LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter", "LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter", "LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Community", "Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter", "Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub", "GitHub": "GitHub",
@@ -148,5 +149,7 @@
"Sanitization": "Sanitization", "Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)", "Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles", "Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags" "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠"
} }

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