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

Compare commits

..

307 Commits

Author SHA1 Message Date
Kazz Yokomizo
58eeb90158 Update issue template
Add IssueHunt url
2018-06-25 15:46:41 +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
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
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
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
138 changed files with 5820 additions and 2321 deletions

View File

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

View File

@@ -1,8 +1,9 @@
language: node_js
node_js:
- 6
- 7
script:
- 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'
after_success:
- 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.
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:
👉 https://opencollective.com/boostnoteio
Love Boostnote? Please consider supporting us on IssueHunt:
👉 https://issuehunt.io/repos/53266139
-->

View File

@@ -3,34 +3,20 @@ import React from 'react'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
import crypto from 'crypto'
import consts from 'browser/lib/consts'
import fs from 'fs'
const { ipcRenderer } = require('electron')
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
const buildCMRulers = (rulers, enableRulers) =>
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
}
}
enableRulers ? rulers.map(ruler => ({column: ruler})) : []
export default class CodeEditor extends React.Component {
constructor (props) {
@@ -52,6 +38,9 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
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.loadStyleHandler = (e) => {
@@ -94,8 +83,21 @@ export default class CodeEditor extends React.Component {
componentDidMount () {
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, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value,
@@ -116,6 +118,8 @@ export default class CodeEditor extends React.Component {
Tab: function (cm) {
const cursor = cm.getCursor()
const line = cm.getLine(cursor.line)
const cursorPosition = cursor.ch
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
if (cm.somethingSelected()) cm.indentSelection('add')
else {
const tabs = cm.getOption('indentWithTabs')
@@ -127,6 +131,16 @@ export default class CodeEditor extends React.Component {
cm.execCommand('insertSoftTab')
}
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 {
if (tabs) {
cm.execCommand('insertTab')
@@ -170,6 +184,73 @@ export default class CodeEditor extends React.Component {
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 () {
document.querySelector('textarea').blur()
}
@@ -187,7 +268,7 @@ export default class CodeEditor extends React.Component {
componentDidUpdate (prevProps, prevState) {
let needRefresh = false
const { rulers, enableRulers } = this.props
const {rulers, enableRulers} = this.props
if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode)
}
@@ -231,7 +312,7 @@ export default class CodeEditor extends React.Component {
}
setMode (mode) {
let syntax = CodeMirror.findModeByName(pass(mode))
let syntax = CodeMirror.findModeByName(convertModeName(mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime)
@@ -275,28 +356,19 @@ export default class CodeEditor extends React.Component {
this.editor.setCursor(cursor)
}
handleDropImage (e) {
e.preventDefault()
const ValidImageTypes = ['image/gif', 'image/jpeg', 'image/png']
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)
})
handleDropImage (dropEvent) {
dropEvent.preventDefault()
const {storageKey, noteKey} = this.props
attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent)
}
insertImageMd (imageMd) {
insertAttachmentMd (imageMd) {
this.editor.replaceSelection(imageMd)
}
handlePaste (editor, e) {
const clipboardData = e.clipboardData
const {storageKey, noteKey} = this.props
const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text')
const isURL = (str) => {
@@ -306,38 +378,28 @@ export default class CodeEditor extends React.Component {
const isInLinkTag = (editor) => {
const startCursor = editor.getCursor('start')
const prevChar = editor.getRange(
{ line: startCursor.line, ch: startCursor.ch - 2 },
{ line: startCursor.line, ch: startCursor.ch }
{line: startCursor.line, ch: startCursor.ch - 2},
{line: startCursor.line, ch: startCursor.ch}
)
const endCursor = editor.getCursor('end')
const nextChar = editor.getRange(
{ line: endCursor.line, ch: endCursor.ch },
{ line: endCursor.line, ch: endCursor.ch + 1 }
{line: endCursor.line, ch: endCursor.ch},
{line: endCursor.line, ch: endCursor.ch + 1}
)
return prevChar === '](' && nextChar === ')'
}
if (dataTransferItem.type.match('image')) {
const blob = dataTransferItem.getAsFile()
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)
}
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(e, editor, pastedTxt)
}
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
.then((modifiedText) => {
this.editor.replaceSelection(modifiedText)
})
e.preventDefault()
}
}
handleScroll (e) {
@@ -351,24 +413,58 @@ export default class CodeEditor extends React.Component {
const taggedUrl = `<${pastedTxt}>`
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, {
method: 'get'
}).then((response) => {
return this.decodeResponse(response)
}).then((response) => {
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
const value = editor.getValue()
const cursor = editor.getCursor()
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
const newValue = value.replace(taggedUrl, LinkWithTitle)
editor.setValue(newValue)
editor.setCursor(cursor)
if (isImageReponse(response)) {
return this.mapImageResponse(response, pastedTxt)
} else {
return this.mapNormalResponse(response, pastedTxt)
}
}).then((replacement) => {
replaceTaggedUrl(replacement)
}).catch((e) => {
const value = editor.getValue()
const newValue = value.replace(taggedUrl, pastedTxt)
const cursor = editor.getCursor()
editor.setValue(newValue)
editor.setCursor(cursor)
replaceTaggedUrl(pastedTxt)
})
}
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 () {
const { className, fontSize } = this.props
const {className, fontSize} = this.props
let fontFamily = this.props.fontFamily
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily
const width = this.props.width
return (
<div
className={className == null
@@ -413,7 +510,8 @@ export default class CodeEditor extends React.Component {
tabIndex='-1'
style={{
fontFamily: fontFamily.join(', '),
fontSize: fontSize
fontSize: fontSize,
width: width
}}
onDrop={(e) => this.handleDropImage(e)}
/>

View File

@@ -5,7 +5,7 @@ import styles from './MarkdownEditor.styl'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter'
import {findStorage} from 'browser/lib/findStorage'
import { findStorage } from 'browser/lib/findStorage'
class MarkdownEditor extends React.Component {
constructor (props) {
@@ -223,7 +223,7 @@ class MarkdownEditor extends React.Component {
}
render () {
const { className, value, config, storageKey } = this.props
const {className, value, config, storageKey, noteKey} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -263,6 +263,7 @@ class MarkdownEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
@@ -282,6 +283,8 @@ class MarkdownEditor extends React.Component {
indentSize={editorIndentSize}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
@@ -293,6 +296,9 @@ class MarkdownEditor extends React.Component {
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
/>
</div>
)

View File

@@ -10,12 +10,15 @@ import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
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 attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const { app } = remote
const path = require('path')
const dialog = remote.dialog
@@ -29,7 +32,7 @@ const CSS_FILES = [
`${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 `
@font-face {
font-family: 'Lato';
@@ -49,7 +52,19 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scro
font-weight: 700;
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}
body {
font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px;
@@ -101,6 +116,16 @@ h2 {
body p {
white-space: normal;
}
@media print {
body[data-theme="${theme}"] {
color: #000;
background-color: #fff;
}
.clipboardButton {
display: none
}
}
`
}
@@ -113,7 +138,6 @@ if (!OSX) {
defaultFontFamily.unshift('meiryo')
}
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
@@ -123,7 +147,6 @@ export default class MarkdownPreview extends React.Component {
this.mouseUpHandler = (e) => this.handleMouseUp(e)
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
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.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd()
@@ -136,29 +159,14 @@ export default class MarkdownPreview extends React.Component {
}
initMarkdown () {
const { smartQuotes, sanitize } = this.props
const { smartQuotes, sanitize, breaks } = this.props
this.markdown = new Markdown({
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) {
this.props.onCheckboxClick(e)
}
@@ -206,11 +214,13 @@ export default class MarkdownPreview extends React.Component {
handleSaveAsHtml () {
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 attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
files.forEach((file) => {
file = file.replace('file://', '')
@@ -219,6 +229,13 @@ export default class MarkdownPreview extends React.Component {
dst: 'css'
})
})
attachmentsAbsolutePaths.forEach((attachment) => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
let styles = ''
files.forEach((file) => {
@@ -322,7 +339,10 @@ export default class MarkdownPreview extends React.Component {
componentDidUpdate (prevProps) {
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.rewriteIframe()
}
@@ -333,14 +353,16 @@ export default class MarkdownPreview extends React.Component {
prevProps.lineNumber !== this.props.lineNumber ||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
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.rewriteIframe()
}
}
getStyleParams () {
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
@@ -349,14 +371,14 @@ export default class MarkdownPreview extends React.Component {
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
}
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('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) {
@@ -369,9 +391,6 @@ export default class MarkdownPreview extends React.Component {
}
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) => {
el.removeEventListener('click', this.checkboxClickHandler)
})
@@ -380,7 +399,7 @@ export default class MarkdownPreview extends React.Component {
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
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
@@ -391,36 +410,25 @@ export default class MarkdownPreview extends React.Component {
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
})
}
this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
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)
})
let renderedHTML = this.markdown.render(value)
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
el.addEventListener('click', this.checkboxClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
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
: 'default'
_.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')
CodeMirror.requireMode(syntax.mode, () => {
const content = htmlTextHelper.decodeEntities(el.innerHTML)
@@ -462,7 +470,7 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = ''
diagram.drawSVG(el, opts)
_.forEach(el.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.anchorClickHandler)
el.addEventListener('click', this.linkClickHandler)
})
} catch (e) {
console.error(e)
@@ -478,7 +486,7 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = ''
diagram.drawSVG(el, {theme: 'simple'})
_.forEach(el.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.anchorClickHandler)
el.addEventListener('click', this.linkClickHandler)
})
} catch (e) {
console.error(e)
@@ -523,22 +531,44 @@ export default class MarkdownPreview extends React.Component {
}
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
// e.g.
// :note:1c211eb7dcb463de6490 and
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
if (regexIsNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
if (regexIsNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
return
}
// this will match the old link format storage.key-note.key
// e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
if (regexIsLegacyNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.split('-')[1])
if (regexIsLegacyNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', linkHash.split('-')[1])
return
}
// other case
shell.openExternal(href)
}
render () {
@@ -561,9 +591,12 @@ MarkdownPreview.propTypes = {
onDoubleClick: PropTypes.func,
onMouseUp: PropTypes.func,
onMouseDown: PropTypes.func,
onContextMenu: PropTypes.func,
className: PropTypes.string,
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
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.reload = () => this.refs.code.reload()
this.userScroll = true
this.state = {
isSliderFocused: false,
codeEditorWidthInPercent: 50
}
}
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 () {
const { config, value, storageKey } = this.props
const {config, value, storageKey, noteKey} = this.props
const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
return (
<div styleName='root'>
<div styleName='root' ref='root'
onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)}>
<CodeEditor
styleName='codeEditor'
ref='code'
width={this.state.codeEditorWidthInPercent + '%'}
mode='GitHub Flavored Markdown'
value={value}
theme={config.editor.theme}
@@ -115,9 +159,13 @@ class MarkdownSplitEditor extends React.Component {
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
storageKey={storageKey}
noteKey={noteKey}
onChange={this.handleOnChange.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
style={previewStyle}
styleName='preview'
@@ -130,6 +178,8 @@ class MarkdownSplitEditor extends React.Component {
lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
smartArrows={config.preview.smartArrows}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
ref='preview'
tabInde='0'
@@ -138,6 +188,9 @@ class MarkdownSplitEditor extends React.Component {
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
/>
</div>
)

View File

@@ -3,7 +3,14 @@
height 100%
font-size 30px
display flex
.codeEditor
width 50%
.preview
width 50%
.slider
absolute top bottom
top -2px
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
color $ui-inactive-text-color
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%)
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -117,6 +118,7 @@ body[data-theme="dark"]
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -132,6 +134,7 @@ body[data-theme="dark"]
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-dark-text-color
@@ -165,9 +168,10 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-noteList-backgroundColor
&:hover
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
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -178,9 +182,10 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
&:active
transition 0.15s
background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-solarized-dark-text-color
// background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -192,11 +197,13 @@ body[data-theme="solarized-dark"]
.item-simple--active
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-button--active-backgroundColor
background-color $ui-solarized-dark-tag-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
color $ui-dark-text-color
.item-simple-bottom-time
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
@@ -207,8 +214,75 @@ body[data-theme="solarized-dark"]
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
.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
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
&:hover
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 styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick}
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
>
<div styleName='iconWrap'>
<img src={isTrashedActive
@@ -60,7 +60,7 @@ const SideNavFilter = ({
}
/>
</div>
<span onContextMenu={handleFilterButtonContextMenu} styleName='menu-button-label'>{i18n.__('Trash')}</span>
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
<span styleName='counters'>{counterDelNote}</span>
</button>

View File

@@ -18,7 +18,7 @@
.iconWrap
width 20px
text-align center
.counters
float right
color $ui-inactive-text-color
@@ -68,10 +68,9 @@
.menu-button-label
position fixed
display inline-block
height 32px
height 36px
left 44px
padding 0 10px
margin-top -8px
margin-left 0
overflow ellipsis
z-index 10
@@ -222,4 +221,46 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
.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()
break
case 27:
this.setState({
name: this.props.snippet.name,
this.setState((prevState, props) => ({
name: props.snippet.name,
isRenaming: false
})
}))
break
}
}

View File

@@ -58,8 +58,8 @@
opacity 0
border-top-right-radius 2px
border-bottom-right-radius 2px
height 26px
line-height 26px
height 34px
line-height 32px
.folderList-item:hover, .folderList-item--active:hover
.folderList-item-tooltip
@@ -138,3 +138,22 @@ body[data-theme="solarized-dark"]
&:hover
color $ui-solarized-dark-text-color
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 {Function} handleClickTagListItem
* @param {Function} handleClickNarrowToTag
* @param {bool} isActive
* @param {bool} isRelated
*/
const TagListItem = ({name, handleClickTagListItem, isActive, count}) => (
<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>
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
<div styleName='tagList-itemContainer'>
{isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
</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 = {

View File

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

View File

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

View File

@@ -199,7 +199,6 @@ ol
&>li>ul, &>li>ol
margin 0
code
color #CC305F
padding 0.2em 0.4em
background-color #f7f7f7
border-radius 3px
@@ -294,6 +293,82 @@ kbd
line-height 1
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-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: {border-color: #448aff, title-color: rgba(68,138,255,.1), icon: "note"},
hint: {border-color: #00bfa5, title-color: rgba(0,191,165,.1), icon: "info"},
danger: {border-color: #ff1744, title-color: rgba(255,23,68,.1), icon: "block"},
caution: {border-color: #ff9100, title-color: rgba(255,145,0,.1), icon: "warning"},
error: {border-color: #ff1744, title-color: rgba(255,23,68,.1), icon: "error"},
attention: {border-color: #64dd17, title-color: rgba(100,221,23,.1), icon: "priority_high"}
}
for name, val in admonition_types
.admonition.{name}
@extend $admonition
border-left-color: val[border-color]
.admonition.{name}>.admonition-title
@extend $admonition-title
border-bottom-color: .1rem solid val[title-color]
background-color: val[title-color]
.admonition.{name}>.admonition-title:before
@extend $admonition-icon
color: val[border-color]
content: val[icon]
themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -371,3 +446,32 @@ body[data-theme="solarized-dark"]
border-color themeSolarizedDarkTableBorder
&:last-child
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')
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 = {
FOLDER_COLORS: [
'#E10051',
@@ -31,7 +35,8 @@ const consts = {
'Dodger Blue',
'Violet Eggplant'
],
THEMES: ['default'].concat(themes)
THEMES: ['default'].concat(themes),
SNIPPET_FILE: snippetFile
}
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 { remote } = require('electron')
const { app } = remote
const { getLocales } = require('./Languages.js')
// load package for localization
const i18n = new (require('i18n-2'))({
// 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',
directory: process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './locales')

View File

@@ -2,10 +2,11 @@ import markdownit from 'markdown-it'
import sanitize from './markdown-it-sanitize-html'
import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math'
import smartArrows from 'markdown-it-smartarrows'
import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex'
import {lastFindInArray} from './utils'
import { lastFindInArray } from './utils'
function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
@@ -25,7 +26,7 @@ class Markdown {
linkify: true,
html: true,
xhtmlOut: true,
breaks: true,
breaks: config.preview.breaks,
highlight: function (str, lang) {
const 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-admonition'))
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
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 zippedCode = deflate.encode64(
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
})
if (config.preview.smartArrows) {
this.md.use(smartArrows)
}
// Add line number attribute for scrolling
const originalRender = this.md.renderer.render
this.md.renderer.render = (tokens, options, env) => {
@@ -234,10 +242,6 @@ class Markdown {
if (!_.isString(content)) content = ''
return this.md.render(content)
}
normalizeLinkText (linkText) {
return this.md.normalizeLinkText(linkText)
}
}
export default Markdown

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

View File

@@ -54,7 +54,25 @@ export function escapeHtmlCharacters (text) {
: 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 {
lastFindInArray,
escapeHtmlCharacters
escapeHtmlCharacters,
isObjectEqual
}

View File

@@ -30,3 +30,10 @@ body[data-theme="solarized-dark"]
border-left 1px solid $ui-solarized-dark-borderColor
.empty-message
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
.search-optionList-item-name-surfix
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
z-index 200
margin-top 0px
top: 50px
right 25px
position absolute
padding 20px 25px 0 25px
@@ -215,3 +216,43 @@ body[data-theme="solarized-dark"]
color $ui-dark-inactive-text-color
&:hover
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 { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
class MarkdownNoteDetail extends React.Component {
constructor (props) {
@@ -54,10 +55,14 @@ class MarkdownNoteDetail extends React.Component {
componentDidMount () {
ee.on('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:togglemodebutton', () => {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType)
})
}
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()
this.setState({
note: Object.assign({}, nextProps.note)
@@ -181,10 +186,10 @@ class MarkdownNoteDetail extends React.Component {
handleTrashButtonClick (e) {
const { note } = this.state
const { isTrashed } = note
const { confirmDeletion } = this.props
const { confirmDeletion } = this.props.config.ui
if (isTrashed) {
if (confirmDeletion(true)) {
if (confirmDeleteNote(confirmDeletion, true)) {
const {note, dispatch} = this.props
dataApi
.deleteNote(note.storage, note.key)
@@ -201,7 +206,7 @@ class MarkdownNoteDetail extends React.Component {
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true
this.setState({
@@ -272,6 +277,7 @@ class MarkdownNoteDetail extends React.Component {
handleSwitchMode (type) {
this.setState({ editorType: type }, () => {
this.focus()
const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type
ConfigManager.set(newConfig)
@@ -288,6 +294,7 @@ class MarkdownNoteDetail extends React.Component {
config={config}
value={note.content}
storageKey={note.storage}
noteKey={note.key}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
@@ -297,6 +304,7 @@ class MarkdownNoteDetail extends React.Component {
config={config}
value={note.content}
storageKey={note.storage}
noteKey={note.key}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
@@ -437,8 +445,7 @@ MarkdownNoteDetail.propTypes = {
style: PropTypes.shape({
left: PropTypes.number
}),
ignorePreviewPointerEvents: PropTypes.bool,
confirmDeletion: PropTypes.bool.isRequired
ignorePreviewPointerEvents: PropTypes.bool
}
export default CSSModules(MarkdownNoteDetail, styles)

View File

@@ -71,3 +71,8 @@ body[data-theme="solarized-dark"]
.root
border-left 1px solid $ui-solarized-dark-borderColor
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
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 _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
@@ -27,21 +28,7 @@ import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
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
}
}
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
const electron = require('electron')
const { remote } = electron
@@ -81,7 +68,7 @@ class SnippetNoteDetail extends React.Component {
}
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()
const nextNote = Object.assign({
description: ''
@@ -197,10 +184,10 @@ class SnippetNoteDetail extends React.Component {
handleTrashButtonClick (e) {
const { note } = this.state
const { isTrashed } = note
const { confirmDeletion } = this.props
const { confirmDeletion } = this.props.config.ui
if (isTrashed) {
if (confirmDeletion(true)) {
if (confirmDeleteNote(confirmDeletion, true)) {
const {note, dispatch} = this.props
dataApi
.deleteNote(note.storage, note.key)
@@ -217,7 +204,7 @@ class SnippetNoteDetail extends React.Component {
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true
this.setState({
@@ -381,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
name: mode
})
}
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -394,11 +381,11 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
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({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
@@ -412,10 +399,10 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -610,17 +597,17 @@ class SnippetNoteDetail extends React.Component {
}
jumpNextTab () {
this.setState({
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
jumpPrevTab () {
this.setState({
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
@@ -676,7 +663,7 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, 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')
return <div styleName='tabView'
@@ -883,8 +870,7 @@ SnippetNoteDetail.propTypes = {
style: PropTypes.shape({
left: PropTypes.number
}),
ignorePreviewPointerEvents: PropTypes.bool,
confirmDeletion: PropTypes.bool.isRequired
ignorePreviewPointerEvents: PropTypes.bool
}
export default CSSModules(SnippetNoteDetail, styles)

View File

@@ -152,4 +152,21 @@ body[data-theme="solarized-dark"]
.tabList
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 () {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
value.pop()
value = _.uniq(value)
this.value = value
this.props.onChange()
this.removeTagByCallback((value) => {
value.pop()
})
}
reset () {
@@ -96,15 +89,22 @@ class TagSelect extends React.Component {
}
handleTagRemoveButtonClick (tag) {
return (e) => {
let { value } = this.props
this.removeTagByCallback((value, tag) => {
value.splice(value.indexOf(tag), 1)
value = _.uniq(value)
}, tag)
}
this.value = value
this.props.onChange()
}
removeTagByCallback (callback, tag = null) {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
callback(value, tag)
value = _.uniq(value)
this.value = value
this.props.onChange()
}
render () {
@@ -118,7 +118,7 @@ class TagSelect extends React.Component {
>
<span styleName='tag-label'>#{tag}</span>
<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' />
</button>

View File

@@ -81,4 +81,20 @@ body[data-theme="solarized-dark"]
.newTag
border-color none
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
background-color #1EC38B
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 StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n'
import debounceRender from 'react-debounce-render'
const OSX = global.process.platform === 'darwin'
@@ -33,26 +34,6 @@ class Detail extends React.Component {
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 () {
const { location, data, config } = this.props
let note = null
@@ -82,7 +63,6 @@ class Detail extends React.Component {
<SnippetNoteDetail
note={note}
config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root'
{..._.pick(this.props, [
'dispatch',
@@ -99,7 +79,6 @@ class Detail extends React.Component {
<MarkdownNoteDetail
note={note}
config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root'
{..._.pick(this.props, [
'dispatch',
@@ -121,4 +100,4 @@ Detail.propTypes = {
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 store from 'browser/main/store'
import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -140,47 +142,25 @@ class Main extends React.Component {
componentDidMount () {
const { dispatch, config } = this.props
if (config.ui.theme === 'dark') {
document.body.setAttribute('data-theme', 'dark')
} else if (config.ui.theme === 'white') {
document.body.setAttribute('data-theme', 'white')
} else if (config.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
const supportedThemes = [
'dark',
'white',
'solarized-dark',
'monokai'
]
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
document.body.setAttribute('data-theme', config.ui.theme)
} else {
document.body.setAttribute('data-theme', 'default')
}
if (config.ui.language === 'sq') {
i18n.setLocale('sq')
} else if (config.ui.language === 'zh-CN') {
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')
if (getLocales().indexOf(config.ui.language) !== -1) {
i18n.setLocale(config.ui.language)
} else {
i18n.setLocale('en')
}
applyShortcuts()
// Reload all data
dataApi.init()
.then((data) => {

View File

@@ -74,4 +74,8 @@ body[data-theme="dark"]
body[data-theme="solarized-dark"]
.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
color $ui-solarized-dark-text-color
&: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 React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import debounceRender from 'react-debounce-render'
import styles from './NoteList.styl'
import moment from 'moment'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import ConfigManager from 'browser/main/lib/ConfigManager'
import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple'
@@ -18,6 +20,7 @@ import copy from 'copy-to-clipboard'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
@@ -281,8 +284,8 @@ class NoteList extends React.Component {
ee.emit('detail:focus')
}
// F or S key
if (e.keyCode === 70 || e.keyCode === 83) {
// L or S key
if (e.keyCode === 76 || e.keyCode === 83) {
e.preventDefault()
ee.emit('top:focus-search')
}
@@ -342,11 +345,10 @@ class NoteList extends React.Component {
}
if (location.pathname.match(/\/tags/)) {
const listOfTags = params.tagname.split(' ')
return data.noteMap.map(note => {
return note
}).filter(note => {
return note.tags.includes(params.tagname)
})
}).filter(note => listOfTags.every(tag => note.tags.includes(tag)))
}
return this.getContextNotes()
@@ -455,12 +457,19 @@ class NoteList extends React.Component {
}
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 selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData)
this.setState({ selectedNoteKeys: [] })
this.selectNextNote()
}
handleNoteContextMenu (e, uniqueKey) {
@@ -585,16 +594,11 @@ class NoteList extends React.Component {
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const { confirmDeletion } = this.props.config.ui
if (firstNote.isTrashed) {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
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
if (!confirmDeleteNote(confirmDeletion, true)) return
Promise.all(
selectedNotes.map((note) => {
return dataApi
@@ -615,6 +619,8 @@ class NoteList extends React.Component {
})
console.log('Notes were all deleted')
} else {
if (!confirmDeleteNote(confirmDeletion, false)) return
Promise.all(
selectedNotes.map((note) => {
note.isTrashed = true
@@ -658,6 +664,10 @@ class NoteList extends React.Component {
title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content
})
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
return note
})
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
@@ -918,7 +928,7 @@ class NoteList extends React.Component {
if (note.isTrashed !== true || location.pathname === '/trashed') return true
})
moment.locale('en', {
moment.updateLocale('en', {
relativeTime: {
future: 'in %s',
past: '%s ago',
@@ -939,15 +949,24 @@ class NoteList extends React.Component {
const viewType = this.getViewType()
const autoSelectFirst =
notes.length === 1 ||
selectedNoteKeys.length === 0 ||
notes.every(note => !selectedNoteKeys.includes(note.key))
const noteList = notes
.map(note => {
.map((note, index) => {
if (note == null) {
return null
}
const isDefault = config.listStyle === 'DEFAULT'
const uniqueKey = getNoteKey(note)
const isActive = selectedNoteKeys.includes(uniqueKey)
const isActive =
selectedNoteKeys.includes(uniqueKey) ||
notes.length === 1 ||
(autoSelectFirst && index === 0)
const dateDisplay = moment(
config.sortBy === 'CREATED_AT'
? 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
border-radius 2px
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
background-color $ui-solarized-dark-backgroundColor
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 { Menu, dialog } = remote
const escapeStringRegexp = require('escape-string-regexp')
const path = require('path')
class StorageItem extends React.Component {
constructor (props) {
@@ -201,7 +203,7 @@ class StorageItem extends React.Component {
createdNoteData.forEach((newNote) => {
dispatch({
type: 'MOVE_NOTE',
originNote: noteData.find((note) => note.content === newNote.content),
originNote: noteData.find((note) => note.content === newNote.oldContent),
note: newNote
})
})
@@ -223,7 +225,8 @@ class StorageItem extends React.Component {
const { folderNoteMap, trashedSet } = data
const SortableStorageItemChild = SortableElement(StorageItemChild)
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)
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 (
<div styleName={isFolded ? 'root--folded' : 'root'}

View File

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

View File

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

View File

@@ -145,20 +145,29 @@ class SideNav extends React.Component {
tagListComponent () {
const { data, location, config } = this.props
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
let tagList = _.sortBy(data.tagNoteMap.map(
(tag, name) => ({name, size: tag.size})),
['name']
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
), ['name']).filter(
tag => tag.size > 0
)
if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size))
}
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
tagList = tagList.filter(
tag => tag.related
)
}
return (
tagList.map(tag => {
return (
<TagListItem
name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)}
isRelated={tag.related}
key={tag.name}
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) {
return this.getActiveTags(path).includes(tag)
}
getActiveTags (path) {
const pathSegments = path.split('/')
const pathTag = pathSegments[pathSegments.length - 1]
return pathTag === tag
const tags = pathSegments[pathSegments.length - 1]
return (tags === 'alltags')
? []
: tags.split(' ')
}
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) {
const { dispatch } = this.props
const deletionPromises = entries.map((note) => {

View File

@@ -69,3 +69,14 @@ body[data-theme="dark"]
navDarkButtonColor()
border-color $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
background-color $ui-solarized-dark-noteList-backgroundColor
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
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()
})
// 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) {
const className = e.target.className
if (!className && typeof (className) !== 'string') return

View File

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

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

View File

@@ -1,6 +1,7 @@
const resolveStorageData = require('./resolveStorageData')
const path = require('path')
const sander = require('sander')
const attachmentManagement = require('./attachmentManagement')
const { findStorage } = require('browser/lib/findStorage')
function deleteNote (storageKey, noteKey) {
@@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) {
storageKey
}
})
.then(function deleteAttachments (storageInfo) {
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
return storageInfo
})
}
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 {findStorage} from 'browser/lib/findStorage'
import filenamify from 'filenamify'
import { findStorage } from 'browser/lib/findStorage'
const fs = require('fs')
const path = require('path')
const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
const IMAGES_FOLDER_NAME = 'images'
/**
* Export note together with images
*
@@ -28,21 +24,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
throw new Error('Storage path is not found')
}
let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => {
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})`
})
let exportedData = noteContent
if (outputFormatter) {
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'),
moveNote: require('./moveNote'),
migrateFromV5Storage: require('./migrateFromV5Storage'),
createSnippet: require('./createSnippet'),
deleteSnippet: require('./deleteSnippet'),
updateSnippet: require('./updateSnippet'),
fetchSnippet: require('./fetchSnippet'),
_migrateFromV6Storage: require('./migrateFromV6Storage'),
_resolveStorageData: require('./resolveStorageData'),

View File

@@ -6,7 +6,7 @@ const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
const copyImage = require('./copyImage')
const attachmentManagement = require('./attachmentManagement')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage
@@ -64,32 +64,20 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
noteData.key = newNoteKey
noteData.storage = newStorageKey
noteData.updatedAt = new Date()
noteData.oldContent = noteData.content
return noteData
})
.then(function moveImages (noteData) {
const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
let match = searchImagesRegex.exec(noteData.content)
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)
.then(function moveAttachments (noteData) {
if (oldStorage.path === newStorage.path) {
return noteData
}
return Promise.all(moveTasks).then(() => noteData)
noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
return 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
})
.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
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
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
color $ui-solarized-dark-text-color
colorMonokaiControl()
border none
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
body[data-theme="dark"]
.root
@@ -189,4 +194,29 @@ body[data-theme="solarized-dark"]
select, .group-section-control-input
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
.cf-link
width 250px
height 35px
border-radius 2px
border none
background-color alpha(#1EC38B, 90%)
padding-left 20px
padding-right 20px
&:hover
background-color #1EC38B
transition 0.2s
@@ -33,4 +34,10 @@ body[data-theme="solarized-dark"]
.root
color $ui-solarized-dark-text-color
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
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) {
const { config } = this.state
config.hotkey = {
toggleMain: this.refs.toggleMain.value
toggleMain: this.refs.toggleMain.value,
toggleMode: this.refs.toggleMode.value
}
this.setState({
config
@@ -115,6 +116,17 @@ class HotkeyTab extends React.Component {
/>
</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'>
<button styleName='group-control-leftButton'
onClick={(e) => this.handleHintToggleButtonClick(e)}

View File

@@ -68,3 +68,10 @@ body[data-theme="solarized-dark"]
.list
a
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
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'>
<input styleName='addStorage-body-section-path-input'
ref='addStoragePath'
placeholder='Select Folder'
placeholder={i18n.__('Select Folder')}
value={this.state.newStorage.path}
onChange={(e) => this.handleAddStorageChange(e)}
/>

View File

@@ -199,3 +199,40 @@ body[data-theme="solarized-dark"]
colorDarkDefaultButton()
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 _ from 'lodash'
import i18n from 'browser/lib/i18n'
import { getLanguages } from 'browser/lib/Languages'
const OSX = global.process.platform === 'darwin'
@@ -27,6 +28,8 @@ class UiTab extends React.Component {
componentDidMount () {
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
this.customCSSCM.getCodeMirror().setSize('400px', '400px')
this.handleSettingDone = () => {
this.setState({UiAlert: {
type: 'success',
@@ -65,6 +68,7 @@ class UiTab extends React.Component {
language: this.refs.uiLanguage.value,
showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked,
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
disableDirectWrite: this.refs.uiD2w != null
? this.refs.uiD2w.checked
: false
@@ -92,9 +96,14 @@ class UiTab extends React.Component {
latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value,
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
scrollPastEnd: this.refs.previewScrollPastEnd.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 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 customCSS = config.preview.customCSS
return (
<div styleName='root'>
<div styleName='group'>
@@ -170,6 +180,7 @@ class UiTab extends React.Component {
<option value='default'>{i18n.__('Default')}</option>
<option value='white'>{i18n.__('White')}</option>
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
<option value='monokai'>{i18n.__('Monokai')}</option>
<option value='dark'>{i18n.__('Dark')}</option>
</select>
</div>
@@ -182,21 +193,9 @@ class UiTab extends React.Component {
onChange={(e) => this.handleUIChange(e)}
ref='uiLanguage'
>
<option value='sq'>{i18n.__('Albanian')}</option>
<option value='zh-CN'>{i18n.__('Chinese (zh-CN)')}</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>
{
getLanguages().map((language) => <option value={language.locale} key={language.locale}>{i18n.__(language.name)}</option>)
}
</select>
</div>
</div>
@@ -221,6 +220,16 @@ class UiTab extends React.Component {
{i18n.__('Show a confirmation dialog when deleting notes')}
</label>
</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'
? <div styleName='group-checkBoxSection'>
@@ -231,7 +240,7 @@ class UiTab extends React.Component {
disabled={OSX}
type='checkbox'
/>&nbsp;
Disable Direct Write(It will be applied after restarting)
{i18n.__('Disable Direct Write (It will be applied after restarting)')}
</label>
</div>
: null
@@ -471,7 +480,27 @@ class UiTab extends React.Component {
ref='previewSmartQuotes'
type='checkbox'
/>&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>
</div>
@@ -543,6 +572,33 @@ class UiTab extends React.Component {
/>
</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'>
<button styleName='group-control-rightButton'

View File

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

View File

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

View File

@@ -118,6 +118,16 @@ colorSolarizedDarkPrimaryButton()
&:active:hover
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-background = #c9302c
@@ -348,3 +358,29 @@ modalSolarizedDark()
background-color $ui-solarized-dark-backgroundColor
overflow hidden
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

@@ -252,10 +252,27 @@ const view = {
},
{
label: 'Focus Search',
accelerator: 'Control+S',
accelerator: 'CommandOrControl+Shift+L',
click () {
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
mainWindow.webContents.on('before-input-event', (event, input) => {
// Synonyms for Search (Find)
if (input.control && input.key === 'f' && input.type === 'keyDown') {
if (input.control && input.key === 'l' && input.type === 'keyDown') {
if (!editorFocused) {
mainWindow.webContents.send('top:focus-search')
event.preventDefault()
@@ -285,11 +302,6 @@ const window = {
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Toggle Full Screen',
accelerator: 'Command+Control+F',
selector: 'toggleFullScreen:'
},
{
label: 'Close',
accelerator: 'Command+W',

View File

@@ -17,7 +17,7 @@ const mainWindow = new BrowserWindow({
autoHideMenuBar: showMenu,
webPreferences: {
zoomFactor: 1.0,
blinkFeatures: 'OverlayScrollbars'
enableBlinkFeatures: 'OverlayScrollbars'
},
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/loadmode.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/vim.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 type='text/javascript'>
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')
? 'http://localhost:8080/assets/main.js'
: '../compiled/main.js'

View File

@@ -62,6 +62,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
@@ -149,5 +150,7 @@
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"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 Block Open Delimiter": "LaTeX Block Beginn Kennzeichen",
"LaTeX Block Close Delimiter": "LaTeX Block Ende Kennzeichen",
"PlantUML Server": "PlantUML Server",
"Community": "Community",
"Subscribe to Newsletter": "Newsletter abonnieren",
"GitHub": "GitHub",
@@ -204,5 +205,7 @@
"Unnamed": "Unbenannt",
"Rename": "Umbenennen",
"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",
"Make a note": "Make a note",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"Ctrl(^)": "Ctrl(^)",
"to create a new note": "to create a new note",
"Toggle Mode": "Toggle Mode",
"Add tag...": "Add tag...",
"Trash": "Trash",
"MODIFICATION DATE": "MODIFICATION DATE",
"Words": "Words",
@@ -20,9 +21,12 @@
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Help": "Help",
"Hide Help": "Hide Help",
"Storages": "Storages",
"Add Storage Location": "Add Storage Location",
"Add Folder": "Add Folder",
"Select Folder": "Select Folder",
"Open Storage folder": "Open Storage folder",
"Unlink": "Unlink",
"Edit": "Edit",
@@ -34,6 +38,8 @@
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"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 Font Size": "Editor Font Size",
"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",
"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",
"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",
"Preview": "Preview",
"Preview Font Size": "Preview Font Size",
@@ -62,6 +69,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
@@ -126,6 +134,7 @@
"Storage": "Storage",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote",
"Toggle editor mode": "Toggle editor mode",
"Restore": "Restore",
"Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion",
@@ -145,12 +154,26 @@
"UserName": "UserName",
"Password": "Password",
"Russian": "Russian",
"Hungarian": "Hungarian",
"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",
"Enable": "Enable",
"Disable": "Disable",
"Sanitization": "Sanitization",
"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 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 Block Open Delimiter": "Delimitado de apertura bloque LaTeX",
"LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX",
"PlantUML Server": "PlantUML Server",
"Community": "Comunidad",
"Subscribe to Newsletter": "Suscribirse al boletín",
"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.",
"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.",
"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,",
"Boostnote maintainers": "Equipo de Boostnote",
"Support via OpenCollective": "Contribuir vía OpenCollective",
@@ -149,5 +150,7 @@
"Sanitization": "Saneamiento",
"Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)",
"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 Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Communauté",
"Subscribe to Newsletter": "Souscrire à la newsletter",
"GitHub": "GitHub",
@@ -149,5 +150,7 @@
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"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

@@ -7,6 +7,7 @@
"Ctrl(^)": "Ctrl",
"to create a new note": "hogy létrehozz egy jegyzetet",
"Toggle Mode": "Mód Váltás",
"Add tag...": "Tag hozzáadása...",
"Trash": "Lomtár",
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
"Words": "Szó",
@@ -20,9 +21,12 @@
".html": ".html",
"Print": "Nyomtatás",
"Your preferences for Boostnote": "Boostnote beállításaid",
"Help": "Súgó",
"Hide Help": "Súgó Elrejtése",
"Storages": "Tárolók",
"Add Storage Location": "Tároló 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",
"Unlink": "Tároló Leválasztása",
"Edit": "Szerkesztés",
@@ -34,6 +38,8 @@
"Solarized Dark": "Solarized Dark",
"Dark": "Sötét",
"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 Font Size": "Szerkesztő Betűmérete",
"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",
"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",
"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",
"Preview": "Megtekintés",
"Preview Font Size": "Megtekintés Betűmérete",
@@ -62,6 +69,7 @@
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
"PlantUML Server": "PlantUML Server",
"Community": "Közösség",
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
"GitHub": "GitHub",
@@ -126,6 +134,7 @@
"Storage": "Tároló",
"Hotkeys": "Gyorsbillentyűk",
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
"Toggle editor mode": "Szerkesztő mód váltása",
"Restore": "Visszaállítás",
"Permanent Delete": "Végleges Törlés",
"Confirm note deletion": "Törlés megerősítése",
@@ -145,8 +154,8 @@
"UserName": "FelhasznaloNev",
"Password": "Jelszo",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Hungarian": "Hungarian",
"Command(⌘)": "Command(⌘)",
"Add Storage": "Tároló hozzáadása",
"Name": "Név",
"Type": "Típus",
@@ -155,6 +164,17 @@
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "Hely",
"Add": "Hozzáadás",
"Select Folder": "Könyvtár Kivá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",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "Make a note",
"Notes": "ノート",
"Tags": "タグ",
"Preferences": "設定",
"Make a note": "ノート作成",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "to create a new note",
"Toggle Mode": "Toggle Mode",
"Trash": "Trash",
"MODIFICATION DATE": "MODIFICATION DATE",
"Words": "Words",
"Letters": "Letters",
"STORAGE": "STORAGE",
"FOLDER": "FOLDER",
"CREATION DATE": "CREATION DATE",
"NOTE LINK": "NOTE LINK",
"to create a new note": "ノートを新規に作成",
"Toggle Mode": "モード切替",
"Add tag...": "タグを追加...",
"Trash": "ゴミ箱",
"MODIFICATION DATE": "修正日",
"Words": "ワード",
"Letters": "文字",
"STORAGE": "ストレージ",
"FOLDER": "フォルダ",
"CREATION DATE": "作成日",
"NOTE LINK": "リンク",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Storages": "Storages",
"Add Storage Location": "Add Storage Location",
"Add Folder": "Add Folder",
"Open Storage folder": "Open Storage folder",
"Unlink": "Unlink",
"Edit": "Edit",
"Delete": "Delete",
"Interface": "Interface",
"Interface Theme": "Interface Theme",
"Default": "Default",
"White": "White",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
"Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Font Size",
"Editor Font Family": "Editor Font Family",
"Editor Indent Style": "Editor Indent Style",
"Spaces": "Spaces",
"Tabs": "Tabs",
"Switch to Preview": "Switch to Preview",
"When Editor Blurred": "When Editor Blurred",
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
"On Right Click": "On Right Click",
"Editor Keymap": "Editor Keymap",
"default": "default",
"Print": "印刷",
"Your preferences for Boostnote": "Boostnoteの個人設定",
"Help": "ヘルプ",
"Hide Help": "ヘルプを隠す",
"Storages": "ストレージ",
"Add Storage Location": "ストレージロケーションを追加",
"Add Folder": "フォルダを追加",
"Select Folder": "フォルダを選択",
"Open Storage folder": "ストレージフォルダを開く",
"Unlink": "リンク解除",
"Edit": "編集",
"Delete": "削除",
"Interface": "インターフェース",
"Interface Theme": "インターフェーステーマ",
"Default": "デフォルト",
"White": "白",
"Solarized Dark": "明灰",
"Dark": "暗灰",
"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": "関連するタグのみ表示する",
"Editor Theme": "エディタのテーマ",
"Editor Font Size": "エディタのフォントサイズ",
"Editor Font Family": "エディタのフォント",
"Editor Indent Style": "エディタのインデント方法",
"Spaces": "スペース",
"Tabs": "タブ",
"Switch to Preview": "プレビューへ移動",
"When Editor Blurred": "エディタがフォーカスを失った時",
"When Editor Blurred, Edit On Double Click": "エディタがフォーカスを失った時、ダブルクリックで編集",
"On Right Click": "右クリック",
"Editor Keymap": "エディタのキーマップ",
"default": "デフォルト",
"vim": "vim",
"emacs": "emacs",
"⚠️ 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",
"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",
"Preview": "Preview",
"Preview Font Size": "Preview Font Size",
"Preview Font Family": "Preview Font Family",
"Code block Theme": "Code block Theme",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください",
"Show line numbers in the editor": "エディタ内に行番号を表示",
"Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする",
"Enable smart quotes": "スマートクォートを有効にする",
"Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する",
"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 開始デリミタ(インライン)",
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
"PlantUML Server": "PlantUML サーバー",
"Community": "コミュニティ",
"Subscribe to Newsletter": "ニュースレターを購読する",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Blog": "ブログ",
"Facebook Group": "Facebook グループ",
"Twitter": "Twitter",
"About": "About",
"About": "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.",
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for 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": "License: GPL v3",
"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.",
"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.",
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Dear everyone,",
"Thank you for using Boostnote!": "Thank you for using 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.",
"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.",
"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!",
"Thanks,": "Thanks,",
"Boostnote maintainers": "Boostnote maintainers",
"Support via OpenCollective": "Support via OpenCollective",
"Language": "Language",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying",
"All Notes": "All Notes",
"Starred": "Starred",
"Are you sure to ": "Are you sure to ",
" delete": " delete",
"this folder?": "this folder?",
"Confirm": "Confirm",
"Cancel": "Cancel",
"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.",
"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.",
"Tab to switch format": "Tab to switch format",
"Updated": "Updated",
"Created": "Created",
"Alphabetically": "Alphabetically",
"Default View": "Default View",
"Compressed View": "Compressed View",
"Search": "Search",
"Blog Type": "Blog Type",
"Blog Address": "Blog Address",
"Save": "Save",
"Auth": "Auth",
"Authentication Method": "Authentication Method",
"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.": "Boostnote はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。",
"You can see how it works on ": "どのように動くかをこちらで確認できます ",
"You can choose to enable or disable this option.": "このオプションは有効/無効を選択できます。",
"Enable analytics to help improve Boostnote": "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 はおよそ 200 の国と地域において、開発者コミュニティを中心に利用されています。",
"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!": "もしあなたがこのプロジェクトとそのポテンシャルを気に入っていただけたのであれば、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": "マークダウン",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "このフォーマットはテキスト文書を作成することを目的としています。チェックリストや比較的長いコード、LaTeX にも向いています。",
"Snippet Note": "スニペット",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "このフォーマットは短いコードスニペットを作成することを目的としています。複数のコードスニペットを1つのグループにまとめて1つのノートとして扱うことも可能です。",
"Tab to switch format": "フォーマット切り替えタブ",
"Updated": "更新日時",
"Created": "作成日時",
"Alphabetically": "アルファベット順",
"Counter": "数順",
"Default View": "デフォルトビュー",
"Compressed View": "圧縮ビュー",
"Search": "検索",
"Blog Type": "ブログの種類",
"Blog Address": "ブログのアドレス",
"Save": "保存",
"Auth": "認証",
"Authentication Method": "認証方法",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Storage",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote",
"Restore": "Restore",
"Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion",
"This will permanently remove this note.": "This will permanently remove this note.",
"Successfully applied!": "Successfully applied!",
"Albanian": "Albanian",
"Chinese (zh-CN)": "Chinese (zh-CN)",
"Chinese (zh-TW)": "Chinese (zh-TW)",
"Danish": "Danish",
"Japanese": "Japanese",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Polish": "Polish",
"Portuguese": "Portuguese",
"Spanish": "Spanish",
"You have to save!": "You have to save!",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags"
"USER": "ユーザー",
"Token": "トークン",
"Storage": "ストレージ",
"Hotkeys": "ホットキー",
"Show/Hide Boostnote": "Boostnote の表示/非表示",
"Toggle editor mode": "エディタモードの切替",
"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": "ロシア語",
"Hungarian": "ハンガリー語",
"Command(⌘)": "コマンド(⌘)",
"Add Storage": "ストレージを追加",
"Name": "名前",
"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 Block Open Delimiter": "LaTeX 블록 열기 기호",
"LaTeX Block Close Delimiter": "LaTeX 블록 닫기 기호",
"PlantUML Server": "PlantUML Server",
"Community": "커뮤니티",
"Subscribe to Newsletter": "뉴스레터 구독",
"GitHub": "깃허브",
@@ -143,7 +144,7 @@
"You have to save!": "저장해주세요!",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Delete Folder": "폴더 삭",
"Delete Folder": "폴더 삭",
"This will delete all notes in the folder and can not be undone.": "폴더의 모든 노트를 지우게 되고, 되돌릴 수 없습니다.",
"UserName": "유저명",
"Password": "패스워드",
@@ -155,5 +156,7 @@
"Sanitization": "허용 태그 범위",
"Only allow secure html tags (recommended)": "안전한 HTML 태그만 허용 (추천)",
"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 Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
@@ -148,5 +149,7 @@
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"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 Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"PlantUML Server": "PlantUML Server",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
@@ -148,5 +149,7 @@
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"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