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

Compare commits

..

250 Commits

Author SHA1 Message Date
Junyoung Choi
7ab81608e8 v0.11.3 2018-03-22 23:07:28 +09:00
Junyoung Choi (Sai)
017e40fcb9 Merge pull request #1722 from BoostIO/disable-i18n-dev-mode
Disable devMode by default
2018-03-22 23:01:49 +09:00
Junyoung Choi
29309fbaa3 Disable devMode by default 2018-03-22 23:01:20 +09:00
Junyoung Choi (Sai)
b766b08bf5 Merge pull request #1709 from rayou/add-editor-rulers
Added editor rulers
2018-03-22 22:54:55 +09:00
Junyoung Choi
e796e00963 Merge branch 'master' into add-editor-rulers 2018-03-22 22:30:03 +09:00
Junyoung Choi (Sai)
10136df977 Merge pull request #1713 from frankkanis/master
Update text for internationalization
2018-03-22 22:09:15 +09:00
Junyoung Choi (Sai)
44ec107ce8 Merge pull request #1715 from BoostIO/sanitize-options
Make sanitization configurable
2018-03-22 21:57:37 +09:00
Junyoung Choi (Sai)
c7fb5b0475 Merge pull request #1718 from rayou/fix-1705-wrap-long-latex
Wrap long LaTeX formula in Preview
2018-03-22 21:57:21 +09:00
Junyoung Choi (Sai)
785735ccf5 Merge pull request #1721 from BoostIO/fix-quit
Always quit app when all window closed
2018-03-22 21:54:13 +09:00
Junyoung Choi (Sai)
f0736ccf8d Merge pull request #1720 from bimlas/i18n-hu-hungarian
i18n: Add Hungarian (HU) translations
2018-03-22 21:50:23 +09:00
Junyoung Choi
a24a7e03be Quit app when all window closed 2018-03-22 20:59:19 +09:00
bimlas
be235e5204 i18n: Add HU to locales 2018-03-22 12:35:42 +01:00
bimlas
57ec598672 i18n: Add Hungarian (HU) translations 2018-03-22 12:35:42 +01:00
Yu-Hung Ou
2627c09cda added new local strings to all locals 2018-03-22 21:44:26 +11:00
Yu-Hung Ou
36e63bb8a9 wrap long LaTeX formula in Preview 2018-03-22 21:33:32 +11:00
Junyoung Choi
5ea24f650b Make sanitization configurable 2018-03-22 12:01:16 +09:00
Junyoung Choi (Sai)
8c792ce7a1 Merge pull request #1714 from BoostIO/fix-lint-errors
Fix lint errors
2018-03-22 11:10:52 +09:00
Frank Kanis
055969f5c6 Update text for internationalization 2018-03-21 22:17:34 +01:00
Yu-Hung Ou
bdb9349b52 updated en local 2018-03-21 22:32:05 +11:00
Yu-Hung Ou
8b11b57ec5 allow users to enable/disable editor rulers. default: disable 2018-03-21 22:31:46 +11:00
Yu-Hung Ou
29888c89ad added CSS style for rulers to make it fit most of the themes 2018-03-21 21:11:26 +11:00
Yu-Hung Ou
281fb2afd3 added CodeMirror ruler support 2018-03-21 21:11:26 +11:00
Yu-Hung Ou
fbb7839f83 added Editor Rulers field in Preferences 2018-03-21 21:11:26 +11:00
Junyoung Choi (Sai)
d3f9c170ac Merge pull request #1703 from Joel-Costamagna/patch-1
typo
2018-03-21 15:28:38 +09:00
Joël COSTAMAGNA
2a784deb4b typo 2018-03-20 11:46:33 -04:00
Junyoung Choi (Sai)
ab8b6d806d Merge pull request #1700 from BoostIO/add-korean
Add korean
2018-03-20 16:36:54 +09:00
Junyoung Choi
6c4206a2dd Fix lint errors 2018-03-20 16:18:18 +09:00
Junyoung Choi
0d61d9cee4 Add missing locale 2018-03-20 15:10:55 +09:00
Junyoung Choi
48bdb9e818 Do not use devMode on production 2018-03-20 15:10:35 +09:00
Junyoung Choi
cfe447a403 Add Korean 2018-03-20 15:04:56 +09:00
Junyoung Choi (Sai)
2c30f0e487 Merge pull request #1684 from kawmra/prevent-fetching-title-in-link-tag
Prevent generating a link tag when pasting URL to inside of a link tag.
2018-03-20 10:32:59 +09:00
Junyoung Choi (Sai)
147211478c Merge pull request #1698 from nlopin/fix_export_folder
Fix filenames with forbidden symbols during export #1613
2018-03-19 23:23:47 +09:00
Junyoung Choi (Sai)
35b7674e69 Merge pull request #1696 from frankkanis/master
Fix Copy Note Link in Context Menu
2018-03-19 22:58:38 +09:00
Junyoung Choi (Sai)
55075b2a96 Merge pull request #1566 from nlopin/new-snippet-tabbar
New snippet tabbar
2018-03-19 21:27:37 +09:00
Nikolay Lopin
6577599959 Replace forbidden filename symbols to _ for image export 2018-03-19 00:33:23 +03:00
Nikolay Lopin
0c4e72e507 Replace forbidden filename symbols to _ 2018-03-19 00:28:36 +03:00
Nikolay Lopin
9bf96e943d Add filenamify dependency 2018-03-19 00:22:07 +03:00
Nikolay Lopin
2811843e70 Merge branch 'master' into new-snippet-tabbar
# Conflicts:
#	browser/main/Detail/SnippetNoteDetail.js
2018-03-18 21:26:12 +03:00
Frank Kanis
842ece2a8a Fix Copy Note Link in Context Menu 2018-03-18 11:14:22 +01:00
Junyoung Choi (Sai)
222de03ea3 Merge pull request #1678 from rayou/add-unit-tests-for-lib-markdown
Added unit tests for lib/markdown
2018-03-18 15:24:48 +09:00
Junyoung Choi (Sai)
d31ae9f3fc Merge pull request #1692 from romainwn/master
add missing i18n preview word + fr translation
2018-03-18 15:23:55 +09:00
Yu-Hung Ou
1dd7644e12 added smart quotes unit test for lib/markdown 2018-03-18 15:36:57 +11:00
Romain Le Quellec
2cee54f70a fix missing import 2018-03-17 10:23:44 +01:00
Romain Le Quellec
a8431fae96 add missing i18n preview word + fr translation 2018-03-17 10:16:51 +01:00
Yu-Hung Ou
03d11b7b58 Merge remote-tracking branch 'upstream/master' into add-unit-tests-for-lib-markdown 2018-03-17 18:54:18 +11:00
Junyoung Choi (Sai)
316e2eeefb Merge pull request #1640 from SiSchae/localization
Localization
2018-03-17 11:13:52 +09:00
Junyoung Choi (Sai)
c21e19337a Merge pull request #1691 from rayou/fix-smart-quotes-post-to-blog
Fixed smart quotes when posting a note to blog
2018-03-17 01:04:50 +09:00
Yu-Hung Ou
b9cab0dae8 Init markdown lib only when it's needed when posting a note to blog 2018-03-16 23:33:40 +11:00
Yu-Hung Ou
33b3299ca2 changed the default options of markdown lib to follow config 2018-03-16 23:32:18 +11:00
Yu-Hung Ou
00ba38beba reload config every time when markdown lib initiated 2018-03-16 23:30:57 +11:00
Yu-Hung Ou
433fce286e added electron as global variable to pass eslint check 2018-03-16 23:11:43 +11:00
kawmra
b36322bba4 Prevent generating a link tag when pasting URL to inside of a link tag. 2018-03-16 00:11:47 +09:00
Yu-Hung Ou
c147e0a789 mocked electron in ava unit tests 2018-03-15 22:14:21 +11:00
Yu-Hung Ou
8bf5d02624 Revert "updated lib/consts to access electron remote.app only when in production mode"
This reverts commit bbcd674516.
2018-03-15 22:12:22 +11:00
Simon
35616c1ccd Merge pull request #1 from nlopin/patch-1
Add russian translation
2018-03-15 09:47:09 +01:00
Nikolai Lopin
7c8939ecb8 Add russian translation 2018-03-15 02:23:15 +03:00
Simon
ccb0302d3f Resolved Merge issue; Added Russian; 2018-03-14 13:13:47 +01:00
Simon
0cfd048013 Merge branch 'master' into localization 2018-03-14 12:49:40 +01:00
Yu-Hung Ou
f72b4f0249 added unit test for checkbox markdown syntax 2018-03-14 21:47:07 +11:00
Yu-Hung Ou
b5cb209f14 added browser testing support to ava 2018-03-14 21:46:20 +11:00
Yu-Hung Ou
1af374439d added unit tests for lib/markdown 2018-03-14 20:37:25 +11:00
Yu-Hung Ou
bbcd674516 updated lib/consts to access electron remote.app only when in production mode 2018-03-14 20:37:25 +11:00
Yu-Hung Ou
3cba71b7a8 upgraded ava to 0.25.0 2018-03-14 20:37:25 +11:00
Yu-Hung Ou
6a1e9c5818 removed katex script tag from main.html, use import instead 2018-03-14 20:37:25 +11:00
Junyoung Choi (Sai)
ede41d01b4 Merge pull request #1676 from lijinglue/hotfix/markdown-render-in-blog-post
hotfix: update markdown render in blog post feature
2018-03-14 13:56:18 +09:00
Junyoung Choi (Sai)
826a67b550 Merge pull request #1677 from BoostIO/allow-more
Allow b tag and style attribute
2018-03-14 13:09:38 +09:00
Junyoung Choi
f3d59a9b61 Allow b tag and style attribute 2018-03-14 13:03:57 +09:00
lijinglue
65434453b8 hotfix: update markdown render in blog post feature 2018-03-14 00:30:29 +08:00
Junyoung Choi
b8658abbad v0.11.2 2018-03-13 23:12:32 +09:00
Junyoung Choi (Sai)
0387b33f6c Merge pull request #1673 from BoostIO/update-whitelist
Use same settings of github
2018-03-13 23:02:52 +09:00
Junyoung Choi (Sai)
41042fe64e Merge pull request #1664 from mirsch/fix-copy-note-link-from-list
fix "copy note link from note list" link format
2018-03-13 23:02:44 +09:00
Junyoung Choi (Sai)
1efc3bb15d Merge pull request #1663 from BoostIO/note-list-ui
Fix note list UI
2018-03-13 22:45:51 +09:00
Junyoung Choi
0816a9410b Use same settings of github 2018-03-13 22:41:17 +09:00
Junyoung Choi (Sai)
cb823eaaa6 Merge pull request #1670 from rayou/fix-1661-move-folder-cursor-style
Added draggable folder handle #1661
2018-03-13 22:04:53 +09:00
Junyoung Choi (Sai)
44f027bf18 Merge pull request #1671 from BoostIO/more-whitelist
Add table and details stuff to whitelist
2018-03-13 22:02:06 +09:00
Junyoung Choi
571d159a1f Add table and details stuff to whitelist 2018-03-13 22:00:07 +09:00
Yu-Hung Ou
a30d977727 added draggable folder handle 2018-03-13 23:42:14 +11:00
Simon
847ad2d781 Merge branch 'master' into localization 2018-03-13 09:24:53 +01:00
mirsch
da9067a672 fix "copy note link from note list" introduced in #1583 to reflect changes from #1636 2018-03-12 15:23:37 +01:00
Kazu Yokomizo
5ed5950ed5 Fix note list UI 2018-03-12 22:14:06 +09:00
Junyoung Choi
f53c182cfb Fix version 2018-03-12 18:50:22 +09:00
Junyoung Choi
bb2978b370 v0.11.1 2018-03-12 17:53:16 +09:00
Junyoung Choi (Sai)
66ae4a5163 Merge pull request #1660 from JKetelaar/bugfix/grammar-change
Fixed English typo & grammar
2018-03-12 17:44:40 +09:00
Jeroen Ketelaar
f435595ad8 Fixed English typo & grammar 2018-03-12 09:42:44 +01:00
Junyoung Choi (Sai)
4bec4596fe Merge pull request #1659 from BoostIO/fix-checkbox
Allow checkbox
2018-03-12 17:41:29 +09:00
Junyoung Choi
10b86aec49 Allow checkbox 2018-03-12 17:40:27 +09:00
Simon
ecabd37cbb Setting default language in DEFAULT_CONFIG 2018-03-12 07:26:19 +01:00
Junyoung Choi (Sai)
f3cabfcb4d Merge pull request #1656 from BoostIO/feature-v0-11-0
v0.11.0
2018-03-12 11:45:12 +09:00
Junyoung Choi
46fc010cf9 v0.11.0 2018-03-12 11:34:17 +09:00
Junyoung Choi (Sai)
e370c76521 Merge pull request #1655 from rayou/fix-1609-missing-classname
fixed missing classname in lib/markdown highlight render function
2018-03-12 11:32:18 +09:00
Junyoung Choi (Sai)
1c283f88d0 Merge pull request #1636 from mirsch/remove-volatile-hash-from-note-links
remove volatile hash from note links - #1623
2018-03-12 11:26:41 +09:00
Yu-Hung Ou
cfffa1b10a fixed missing classname in lib/markdown highlight render function 2018-03-11 16:22:04 +11:00
Junyoung Choi (Sai)
8aaae4de49 Merge pull request #1650 from Paalon/katex_update
Update KaTeX Library from 0.8.3 to 0.9.0.
2018-03-10 18:43:18 +09:00
Simon
4f6c35713e Added languages; better German translation 2018-03-09 17:30:11 +01:00
paalon
8fb0b5b572 Update KaTeX Library from 0.8.3 to 0.9.0. 2018-03-10 00:36:00 +09:00
Junyoung Choi (Sai)
12262671ee Merge pull request #1649 from rayou/fix-1562-change-dummy-data-minimum-count
Changed the minimum count of dummy data (folders and notes)
2018-03-09 23:51:39 +09:00
Junyoung Choi (Sai)
3165c62985 Merge pull request #1621 from rayou/feature/add-smartquotes-toggle
Added support for toggling smart quotes in preview
2018-03-09 23:37:42 +09:00
Junyoung Choi
1ca1166f74 Merge branch 'master' into feature/add-smartquotes-toggle 2018-03-09 23:30:54 +09:00
Junyoung Choi (Sai)
7b4d2f3b97 Merge pull request #1617 from pfftdammitchris/folder-dragging
Folders in the side nav are now draggable
2018-03-09 23:17:41 +09:00
Junyoung Choi (Sai)
ccce75a047 Merge pull request #1616 from pfftdammitchris/master
Sort tags alphabetically
2018-03-09 21:33:38 +09:00
Yu-Hung Ou
410e82e375 increased the minimum count of dummy notes to 2 2018-03-09 23:28:01 +11:00
Yu-Hung Ou
bd385ec062 increased the minimum count of dummy folders to 2 2018-03-09 23:27:49 +11:00
Junyoung Choi (Sai)
638227ef25 Merge pull request #1614 from RomanKlopsch/master
Fix missing progress bar in note list
2018-03-09 20:51:34 +09:00
Junyoung Choi (Sai)
e70b35126b Merge pull request #1648 from rayou/fix-1609-wrong-codeblock-style
fixed code block style in exported HTML file
2018-03-09 20:50:22 +09:00
Simon
4d41c7f37f Changed default language to en 2018-03-09 07:39:08 +01:00
Yu-Hung Ou
d01a7b16a1 fixed codeblock style in exported HTML file 2018-03-08 23:40:13 +11:00
Kazz Yokomizo
326d873279 Merge pull request #1646 from BoostIO/update-readme
Update-readme
2018-03-08 11:38:19 +09:00
Kazz Yokomizo
74c32bed29 Update-readme 2018-03-08 11:35:41 +09:00
Nikolay Lopin
a222fd0786 Add animation for scrolling with buttons 2018-03-08 01:02:49 +03:00
Yu-Hung Ou
c633ba9497 Merge branch 'master' into feature/add-smartquotes-toggle 2018-03-07 22:25:39 +11:00
Simon
40b5472866 Adjustments for Travis CI 2018-03-07 08:37:55 +01:00
Simon
a413e273ca Added strings in notifications 2018-03-07 08:20:05 +01:00
Simon
0f82085cae Adjustments for Travis CI 2018-03-07 07:10:49 +01:00
Junyoung Choi (Sai)
fa1ab04409 Merge pull request #1643 from mirsch/export-responsive-html
Export responsive html, fixes #1442
2018-03-07 10:16:14 +09:00
mirsch
f370508c93 fixes #1442, Export responsive html 2018-03-07 01:08:10 +01:00
Nikolay Lopin
7472019422 Change property responsible for scrolling from "offsetLeft" to "scrollLeft" 2018-03-07 01:55:04 +03:00
Simon
aa3597881a added more strings 2018-03-06 15:56:48 +01:00
Simon
a36841e501 Implemented language switch in Interface Config Tab 2018-03-06 15:04:04 +01:00
Simon
fe9afc8952 extracted all strings from html lines 2018-03-06 14:07:04 +01:00
Simon
63d39a81aa Merge branch 'localization' of github.com:SiSchae/Boostnote into localization
hoffe es läuft
2018-03-06 10:02:29 +01:00
Simon
b11dc2ca20 first testing of translation 2018-03-06 09:54:37 +01:00
Junyoung Choi (Sai)
10f5ad6127 Merge pull request #1639 from thedavidgay/add-charset-to-html-export
add UTF-8 tag to HTML export (fixes #1624)
2018-03-06 17:30:04 +09:00
David Gay
4800a88cf6 add UTF-8 tag to HTML export (fixes #1624) 2018-03-05 21:22:42 -05:00
Junyoung Choi (Sai)
9d8bc40594 Merge pull request #1634 from Redsandro/xssSecurity
SECURITY HOTFIX - Remove XSS attack
2018-03-06 03:08:19 +09:00
Junyoung Choi (Sai)
7d3d96a6e0 Merge pull request #1612 from kawmra/fix-title-decoding
Properly decoding for the title of pasted website
2018-03-05 11:36:53 +09:00
Junyoung Choi
4c99429d76 Merge branch 'master' into fix-title-decoding 2018-03-05 11:36:31 +09:00
Junyoung Choi (Sai)
96fbd7572c Merge pull request #1611 from romainwn/feat/addFullScreen
add Toggle FullScreen feat + shortcut
2018-03-05 11:28:28 +09:00
Junyoung Choi (Sai)
b012bba6d6 Merge pull request #1601 from rayou/fix-missing-zoom-button
fixed missing zoom button - #1598
2018-03-05 11:27:27 +09:00
Junyoung Choi (Sai)
45d90b2530 Merge pull request #1594 from clone1612/list_fix
Fix insertion in Markdown lists - #1588 & 1605
2018-03-05 11:25:46 +09:00
Junyoung Choi (Sai)
a8b946a07a Merge pull request #1593 from Redsandro/searchFixes
Add search tweaks; closes 1525 closes 1167
2018-03-05 11:23:48 +09:00
Junyoung Choi (Sai)
18392c4f23 Merge pull request #1591 from Redsandro/scrollSyncFix
Fixed bad merge; closes #1586; related to #1521
2018-03-05 11:16:42 +09:00
Junyoung Choi (Sai)
88e7869284 Merge pull request #1584 from pfftdammitchris/codeeditor-snippet-options-overlapping
Fixes UI overlapping in the snippet editor (bottom options)
2018-03-05 11:14:26 +09:00
Junyoung Choi (Sai)
3c8332c448 Merge pull request #1583 from pfftdammitchris/copylink-notelist
Adds the ability to copy the note link from the note list
2018-03-05 11:13:00 +09:00
Junyoung Choi
207f95693a Merge branch 'master' into copylink-notelist 2018-03-05 11:12:42 +09:00
Junyoung Choi (Sai)
de15120bf8 Merge pull request #1579 from nlopin/move-pictures-between-storages
Move images between storages together with note
2018-03-05 11:06:32 +09:00
Junyoung Choi (Sai)
1dcf11e1c4 Merge pull request #1575 from lurong-hkg/feature/publish-to-wordpress
allow publishing markdown to wordpress
2018-03-05 10:55:05 +09:00
mirsch
b74ba22c44 adjust keygen to use uuid only for notes (uuid on storage/folders woud need more refactoring) 2018-03-05 00:02:30 +01:00
mirsch
fa2d34dcfc fix delete and empty trash 2018-03-04 23:28:18 +01:00
mirsch
0280a5f09e use uuid in keygen, remove storage.key from note hash 2018-03-04 22:21:14 +01:00
Nikolay Lopin
a35f876f4f Merge branch 'master' into new-snippet-tabbar 2018-03-05 00:06:57 +03:00
Sander Steenhuis
c22b7f81c1 Allow iframe size, allow fullscreen 2018-03-04 17:49:17 +01:00
Sander Steenhuis
9344fd78d8 Remove xss attack; closes #1443 at least partially 2018-03-04 17:28:41 +01:00
Yu-Hung Ou
e89c0a3e61 added support for toggling smart quotes in preview 2018-03-02 00:06:58 +11:00
pfftdammitchris
5aa7ef5738 Folders in the side nav are now draggable 2018-02-28 18:33:14 -08:00
pfftdammitchris
88ac6c6fc6 Sort tags alphabetically 2018-02-28 16:34:42 -08:00
Roman Klopsch
a537f9762b Fix missing progress bar in note list 2018-02-28 16:00:42 +01:00
Romain Le Quellec
aab192223a add Toggle FullScreen feat + shortcut 2018-02-28 11:39:45 +01:00
kawmra
6439810d03 Decode the fetched body correctly if content-type was provided 2018-02-28 02:09:35 +09:00
Yu-Hung Ou
c5c78763d1 fixed missing zoom button 2018-02-27 23:17:15 +11:00
Jannick Hemelhof
012908e32d Linting fixes 2018-02-26 16:43:13 +01:00
Jannick Hemelhof
1f1e545538 Fix for list insertion 2018-02-26 16:32:47 +01:00
Sander Steenhuis
5b8519b2b8 Bind up/down to prev/next event; closes #1167 2018-02-26 16:30:14 +01:00
Sander Steenhuis
6502158716 Add search clear options; closes #1525 2018-02-26 16:30:08 +01:00
lurong
7c525ab7a6 fix lint 2018-02-26 21:27:42 +08:00
Lu Rong
778e3d9799 Merge pull request #1 from lijinglue/feature/publish-to-wordpress
checking if return is valid before creating the blog attr in note
2018-02-26 21:21:15 +08:00
lijinglue
e5f986a959 checking if return is valid before creating the blog attr in note 2018-02-26 21:19:55 +08:00
Sander Steenhuis
d76ecd5423 Fixed bad merge; closes #1586; related to #1521 2018-02-26 13:20:24 +01:00
Markus Deuerlein
6bcb6398f8 German translation: minor corrections (#1585)
* minor corrections

some german improvements and corrections of the translation

* update link to docs/de/debug.md

* minor corrections

* minor corrections

* minor corrections

* minor corrections

* minor corrections
2018-02-26 21:08:03 +09:00
pfftdammitchris
6c341d8fa5 Fixes UI overlapping in the snippet editor (bottom options) 2018-02-25 07:41:50 -08:00
pfftdammitchris
0d5e57eeab Adding the ability to copy the note link from the note list 2018-02-25 07:17:01 -08:00
lurong
aef603ed8c rebase and prefer const in node list 2018-02-25 22:50:51 +08:00
lurong
97600e526b remove the first occurrence of title in content when publishing to WP 2018-02-25 22:44:57 +08:00
lurong
f3f6095d81 allow publishing markdown to wordpress 2018-02-25 22:44:57 +08:00
Junyoung Choi (Sai)
fb7280127c Merge pull request #1581 from BoostIO/v0.10.0
v0.10.0
2018-02-25 15:51:59 +09:00
Junyoung Choi
60df509fc6 v0.10.0 2018-02-25 14:30:57 +09:00
Junyoung Choi (Sai)
ced237fbda Merge pull request #1556 from pfftdammitchris/all-notes-storage-labels
Add storage labels to 'All Notes' and figure a better solution to the dull storage notes list view
2018-02-25 14:27:35 +09:00
Nikolay Lopin
9473a26892 Move images between storages together with note
1. Added new step of moving note's images to `moveNote` function
2. Changed behaviour of sidebar navigation after move finished

Issue #1578
2018-02-25 02:47:28 +03:00
Nikolay Lopin
e5825e898d allow to copy images without generating new name 2018-02-25 02:45:59 +03:00
Nikolay Lopin
8d59bd9f71 Remove redundant symbol from regexp 2018-02-25 02:42:15 +03:00
pfftdammitchris
8f90bb83e7 Added title attributes to support long folder names and foldernames are no longer overflowing nearby elements 2018-02-24 08:42:02 -08:00
pfftdammitchris
ba888a7165 Moved viewType to outer scope so it invokes once (to save resources) 2018-02-24 07:10:46 -08:00
Christopher Tran
9591a7cac2 Merge branch 'master' into all-notes-storage-labels 2018-02-24 06:40:25 -08:00
Junyoung Choi (Sai)
e8fd53d8b7 Merge pull request #1567 from pfftdammitchris/fix-toggle-view-css-positioning
Fixed ToggleMode button overlapping CSS issue
2018-02-24 16:44:41 +09:00
Junyoung Choi (Sai)
3b7c36b1c9 Merge pull request #1559 from pfftdammitchris/tags-note-count
Added note counts to tags view in side nav
2018-02-24 16:19:53 +09:00
Junyoung Choi (Sai)
2750a3ef30 Merge pull request #1558 from pfftdammitchris/empty-trash
Added option Empty Trash
2018-02-24 16:18:43 +09:00
Junyoung Choi (Sai)
7d59f51244 Merge pull request #1549 from nlopin/restore-note-enhancing
Restore note UX enhancements
2018-02-24 16:05:00 +09:00
Nikolay Lopin
a3ec6f470a Add horizontal scroll for touch devices 2018-02-24 01:03:25 +03:00
Nikolay Lopin
8e85a33dac Show Arrows at snippets tab bar only when needed 2018-02-24 00:49:21 +03:00
Nikolay Lopin
77542597f5 onClick listener fix 2018-02-23 10:26:48 +03:00
pfftdammitchris
2a26e8a010 Added folder labels to note items when viewing a specific storage 2018-02-22 17:31:31 -08:00
pfftdammitchris
8b4c1a6c38 Merge branch 'master' of https://github.com/BoostIO/Boostnote into all-notes-storage-labels 2018-02-22 16:16:43 -08:00
Junyoung Choi (Sai)
7d7e277cc6 Merge pull request #1533 from forestail/display-filename-in-codeblock
Display filename in codeblock (resend)
2018-02-22 11:06:20 +09:00
Junyoung Choi (Sai)
cb1da609a4 Merge pull request #1524 from Redsandro/highlightSearch
Highlight global search matches on code editor
2018-02-22 11:06:06 +09:00
Junyoung Choi
e4fbdb35a3 Rename a variable name again 2018-02-22 10:54:12 +09:00
Junyoung Choi
c3586372e0 Rename variables 2018-02-22 10:49:59 +09:00
Junyoung Choi
808d193543 Merge remote-tracking branch 'origin/master' into highlightSearch 2018-02-22 09:56:09 +09:00
Junyoung Choi (Sai)
0b65b70af2 Merge pull request #1570 from BoostIO/fix-memory-lick
Fix memory lickage
2018-02-22 09:43:43 +09:00
Junyoung Choi
deab34abd6 Fix memory lick 2018-02-22 09:39:56 +09:00
Junyoung Choi (Sai)
646cded650 Merge pull request #1521 from Redsandro/scrollsync
Sync Split Editor scroll position
2018-02-22 07:36:41 +09:00
Sander Steenhuis
9047320301 Update CodeEditor.js - Forgot curly brace
Forgot curly brace when merging master
2018-02-21 19:17:38 +01:00
Sander Steenhuis
f16b054f01 Merge branch 'master' into scrollsync 2018-02-21 19:04:24 +01:00
Junyoung Choi (Sai)
fea856202d Merge pull request #1509 from Antogin/feat/display-url-title
Feat/display url title
2018-02-21 16:33:34 +09:00
pfftdammitchris
4f15cc3f08 Fixed ToggleMode button overlapping CSS issue
The absolute positioning of the toggle mode button was creating a static overlapping position issue with the top bar. This fix solves that problem by removing the static positioning and coupling the button component with the buttons to the right
2018-02-20 21:09:43 -08:00
Nikolay Lopin
1769dd959e Revert spaces 2018-02-21 03:21:40 +03:00
Nikolay Lopin
1253b81a01 Introduce tabs with minimal width 2018-02-21 03:08:40 +03:00
Sander Steenhuis
3b524f6aba Highlight global search matches on code editor 2018-02-20 22:46:13 +01:00
Sander Steenhuis
051ccad29a Rename variables 2018-02-20 22:36:38 +01:00
Sander Steenhuis
c82eba05d1 Sync Split Editor scroll position 2018-02-20 22:31:53 +01:00
pfftdammitchris
74af199afc Added note counts to tags view in side nav 2018-02-19 13:08:09 -08:00
pfftdammitchris
c2afdba659 Removed useless url route check 2018-02-19 12:41:36 -08:00
pfftdammitchris
e6ae45f133 Added option Empty Trash 2018-02-19 12:27:04 -08:00
pfftdammitchris
129ef6766b Add storage labels to 'All Notes'
Added storage labelings to 'All Notes' in Default and Compressed views
2018-02-19 10:17:36 -08:00
Masahide Morio
3194a7b1a2 followed eslintrc 2018-02-19 23:02:25 +09:00
Kazz Yokomizo
46a733ba5b Merge pull request #1543 from nlopin/edit-on-dblckick
Switch from preview to edit by double click
2018-02-19 15:56:22 +09:00
Kazz Yokomizo
466844fc55 Merge pull request #1528 from Redsandro/selectionOnSearch
UX: Keep selection synced with note visibility
2018-02-19 15:07:25 +09:00
Kazz Yokomizo
0cc793e3fe Merge pull request #1554 from BoostIO/change-blog-url
Change the blog link to Boostlog from Medium
2018-02-19 15:05:00 +09:00
Kazu Yokomizo
0fdfb385a4 Change blog to Boostlog from Medium 2018-02-19 15:01:08 +09:00
Nikolay Lopin
419a4c6b2d Add "Restore note" item to menu in Trash view. 2018-02-17 00:38:19 +03:00
Nikolay Lopin
55e9441547 Introduce RestoreButton component instead of plain JSX 2018-02-17 00:37:36 +03:00
Kazz Yokomizo
7abff6ded4 Merge pull request #1527 from Redsandro/dont_count_trashed_notes
Trashed notes should not be counted
2018-02-15 11:49:20 +09:00
Kazz Yokomizo
a648d310a5 Merge pull request #1539 from pfftdammitchris/master
Added title attributes to elements to show information on hover --- more like a native app feel to it
2018-02-15 11:40:26 +09:00
Kazz Yokomizo
4b56fa56b5 Merge pull request #1544 from nlopin/global-pin-to-top
Show pinned notes in All notes view and allow to pin them there
2018-02-15 11:33:44 +09:00
Nikolay Lopin
18bf700936 Show pinned notes in All notes view and allow to pin them there
All restrictions of pin functionality were removed. Now user can see pinned notes and also pin notes right from 'home'. Technically, a note still pins to the storage it belong.

#1506
2018-02-14 23:35:11 +03:00
Nikolay Lopin
67ddff736c Switch from preview to edit by double click
User want to click markdown to work with text (for example copy).
If they have "Switch to Preview" setting in "When Editor Blurred",
they enter edit mode after click. To fix that issue I introduced new
value to the "Switch to Preview" setting that do that thing. User can
enter edit mode by double click on the editor and leave it on blur.

Unfortunately, it's impossible to switch both ways by double click
because the editor mode is listening for double click event.

#1523
2018-02-13 23:43:11 +03:00
Kazz Yokomizo
1bba125a2a Merge pull request #1542 from BoostIO/change-company-name
Change the company name
2018-02-13 13:44:18 +09:00
Kazu Yokomizo
187acad592 Change the company name 2018-02-13 13:37:04 +09:00
Sander Steenhuis
5e07c7b3e1 Update selection on new note; closes #1507 2018-02-12 18:15:05 +01:00
pfftdammitchris
cd942cf8b6 Added title attributes to elements to show quick information on each element---like a native app 2018-02-11 19:12:28 -08:00
Masahide Morio
c43589fe6a Add file name and any first line number in code block (fixed) 2018-02-11 02:28:03 +09:00
Junyoung Choi (Sai)
d4594eff3b Merge pull request #1502 from Matts966/feature/new_line_completion
I fixed the behavior of the editor when the enter key is pushed.
2018-02-10 22:53:29 +09:00
Junyoung Choi (Sai)
809a0e846b Merge pull request #1531 from nlopin/export-note-with-images
Remove direct css styling todo lists from MarkdownPreview component
2018-02-10 22:39:10 +09:00
Nikolay Lopin
7f00ce2598 Merge remote-tracking branch 'origin/export-note-with-images' into export-note-with-images 2018-02-10 14:05:34 +03:00
Nikolay Lopin
d7d77dbfe9 Fix wrong styling of todo items in exported HTML
The issue happened because styles connected with todo list were applied directly to HTML in Preview component. I added class to `li` tag of each todo item and style it from css.
2018-02-10 14:03:02 +03:00
Junyoung Choi (Sai)
7a124c74cc Merge pull request #1483 from andyklimczak/delete-note
Fix permanently deleting note
2018-02-10 18:47:41 +09:00
Junyoung Choi (Sai)
f8549f4643 Merge pull request #1501 from Matts966/typo/fix_a_typo_of_log
very easy fix.
2018-02-10 18:47:22 +09:00
Junyoung Choi (Sai)
0476ff70da Merge pull request #1306 from nlopin/export-note-with-images
Export note with local images
2018-02-10 18:45:53 +09:00
Junyoung Choi
5ec541c3c1 Manipulate left margin of task item to hide circle 2018-02-10 18:29:57 +09:00
Sander Steenhuis
7b920348f3 UX: Keep selection synced with note visibility 2018-02-10 01:30:14 +01:00
Sander Steenhuis
51b1ef41a1 Trashed notes should not be counted 2018-02-09 23:10:40 +01:00
Nikolay Lopin
12447effc9 Reject promise if write to file failed 2018-02-06 22:19:06 +03:00
Georges Indrianjafy
dc1b059a9d clean up 2018-02-06 19:12:25 +02:00
Georges Indrianjafy
a676ebf46c fix(editor): replace [] with <> 2018-02-06 18:35:11 +02:00
Nikolay Lopin
338f9fb5a9 Semicolon fix 2018-02-05 13:04:01 +03:00
Nikolay Lopin
98c9132d4a Merge branch 'master' into export-note-with-images
# Conflicts:
#	browser/components/MarkdownPreview.js
2018-02-05 12:59:45 +03:00
Georges Indrianjafy
a054f12492 Merge branch 'master' into feat/display-url-title
# Conflicts:
#	browser/components/CodeEditor.js
#	browser/finder/NoteDetail.js
2018-02-05 11:50:14 +02:00
Georges Indrianjafy
c42ac1df1b chore(editor): resolve lint issues 2018-02-05 11:00:56 +02:00
Georges Indrianjafy
d9d46cda1c feat(editor): extract pasting url method 2018-02-05 10:59:36 +02:00
Nikolay Lopin
f678a17505 Export images together with document 2018-02-05 02:08:33 +03:00
Nikolay Lopin
3da4bb69ce Export images together with document 2018-02-05 02:08:09 +03:00
Georges Indrianjafy
3e2b876c59 fix(config): refrase 2018-02-04 17:49:23 +02:00
Georges Indrianjafy
10daf2599d chore: fix lint isues 2018-02-04 17:39:32 +02:00
Georges Indrianjafy
56d6eb7077 feat(config): add ablility to opt out of fetch url 2018-02-04 13:58:02 +02:00
Georges Indrianjafy
9b54272f8e feat(code-editor): fetch title when pasting url 2018-02-04 13:57:00 +02:00
松井誠泰
685e003db8 delete log. 2018-02-04 10:12:55 +09:00
松井誠泰
701fc0bc80 I fixed the behavior of the editor.
On enter key pushed, if already the line contains a token in markdown list, no completion is needed, I think.

By this change, node_modules/codemirror/addon/edit/continueList.js could be trashed, but I can't because of the gitignore.
2018-02-04 10:03:50 +09:00
松井誠泰
e5518ac0fa very easy fix. 2018-02-04 09:14:31 +09:00
Andy Klimczak
f04ad1e702 list:moved -> list:next 2018-01-31 22:17:36 -05:00
Andy Klimczak
dc03bb76e6 Fix 2018-01-30 23:15:16 -05:00
Andy Klimczak
d894bb4aab Fix permanently deleting note
- Permanently deleting note would not remove note from list until after
refresh
- Fix so permanently deleted note is removed list
2018-01-29 21:51:57 -05:00
Nikolay Lopin
83da07a941 Export note with local images
Looks through the note and searches for local images. Copies them to ‘images’ folder in the export path and replaces affected links.

#1261
2017-12-17 21:39:34 +03:00
120 changed files with 6452 additions and 2205 deletions

View File

@@ -1,3 +1,4 @@
node_modules/
compiled/
dist/
extra_scripts/

View File

@@ -3,7 +3,9 @@
"plugins": ["react"],
"rules": {
"no-useless-escape": 0,
"prefer-const": "warn",
"prefer-const": ["warn", {
"destructuring": "all"
}],
"no-unused-vars": "warn",
"no-undef": "warn",
"no-lone-blocks": "warn",

View File

@@ -17,4 +17,3 @@ deploy:
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
skip_cleanup: true

View File

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

View File

@@ -7,10 +7,15 @@ import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
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) {
@@ -31,8 +36,13 @@ export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.changeHandler = (e) => this.handleChange(e)
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
this.blurHandler = (editor, e) => {
ipcRenderer.send('editor:focused', false)
if (e == null) return null
let el = e.relatedTarget
while (el != null) {
@@ -47,12 +57,47 @@ export default class CodeEditor extends React.Component {
this.loadStyleHandler = (e) => {
this.editor.refresh()
}
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
}
handleSearch (msg) {
const cm = this.editor
const component = this
if (component.searchState) cm.removeOverlay(component.searchState)
if (msg.length < 3) return
cm.operation(function () {
component.searchState = makeOverlay(msg, 'searching')
cm.addOverlay(component.searchState)
function makeOverlay (query, style) {
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
return {
token: function (stream) {
query.lastIndex = stream.pos
var match = query.exec(stream.string)
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1
return style
} else if (match) {
stream.pos = match.index
} else {
stream.skipToEnd()
}
}
}
}
})
}
componentDidMount () {
const { rulers, enableRulers } = this.props
this.value = this.props.value
this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value,
lineNumbers: this.props.displayLineNumbers,
lineWrapping: true,
@@ -92,7 +137,7 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'newlineAndIndentContinueMarkdownList',
Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': (cm) => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
@@ -104,9 +149,14 @@ export default class CodeEditor extends React.Component {
this.setMode(this.props.mode)
this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler)
eventEmitter.on('top:search', this.searchHandler)
eventEmitter.emit('code:init')
this.editor.on('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler)
@@ -123,15 +173,19 @@ export default class CodeEditor extends React.Component {
}
componentWillUnmount () {
this.editor.off('focus', this.focusHandler)
this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler)
this.editor.off('paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler)
this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)
}
componentDidUpdate (prevProps, prevState) {
let needRefresh = false
const { rulers, enableRulers } = this.props
if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode)
}
@@ -149,6 +203,10 @@ export default class CodeEditor extends React.Component {
needRefresh = true
}
if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) {
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
}
if (prevProps.indentSize !== this.props.indentSize) {
this.editor.setOption('indentUnit', this.props.indentSize)
this.editor.setOption('tabSize', this.props.indentSize)
@@ -231,11 +289,29 @@ export default class CodeEditor extends React.Component {
}
handlePaste (editor, e) {
const dataTransferItem = e.clipboardData.items[0]
if (!dataTransferItem.type.match('image')) return
const clipboardData = e.clipboardData
const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text')
const isURL = (str) => {
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
return matcher.test(str)
}
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 }
)
const endCursor = editor.getCursor('end')
const nextChar = editor.getRange(
{ 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 window.FileReader()
const reader = new FileReader()
let base64data
reader.readAsDataURL(blob)
@@ -252,6 +328,66 @@ export default class CodeEditor extends React.Component {
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(e, editor, pastedTxt)
}
}
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handlePasteUrl (e, editor, pastedTxt) {
e.preventDefault()
const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl)
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)
}).catch((e) => {
const value = editor.getValue()
const newValue = value.replace(taggedUrl, pastedTxt)
const cursor = editor.getCursor()
editor.setValue(newValue)
editor.setCursor(cursor)
})
}
decodeResponse (response) {
const headers = response.headers
const _charset = headers.has('content-type')
? this.extractContentTypeCharset(headers.get('content-type'))
: undefined
return response.arrayBuffer().then((buff) => {
return new Promise((resolve, reject) => {
try {
const charset = _charset !== undefined && iconv.encodingExists(_charset) ? _charset : 'utf-8'
resolve(iconv.decode(new Buffer(buff), charset).toString())
} catch (e) {
reject(e)
}
})
})
}
extractContentTypeCharset (contentType) {
return contentType.split(';').filter((str) => {
return str.trim().toLowerCase().startsWith('charset')
}).map((str) => {
return str.replace(/['"]/g, '').split('=')[1]
})[0]
}
render () {
@@ -280,6 +416,8 @@ export default class CodeEditor extends React.Component {
CodeEditor.propTypes = {
value: PropTypes.string,
enableRulers: PropTypes.bool,
rulers: PropTypes.arrayOf(Number),
mode: PropTypes.string,
className: PropTypes.string,
onBlur: PropTypes.func,

View File

@@ -92,7 +92,9 @@ class MarkdownEditor extends React.Component {
if (this.state.isLocked) return
this.setState({ keyPressed: new Set() })
const { config } = this.props
if (config.editor.switchPreview === 'BLUR') {
if (config.editor.switchPreview === 'BLUR' ||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
) {
const cursorPosition = this.refs.code.editor.getCursor()
this.setState({
status: 'PREVIEW'
@@ -104,6 +106,20 @@ class MarkdownEditor extends React.Component {
}
}
handleDoubleClick (e) {
if (this.state.isLocked) return
this.setState({keyPressed: new Set()})
const { config } = this.props
if (config.editor.switchPreview === 'DBL_CLICK') {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
})
}
}
handlePreviewMouseDown (e) {
this.previewMouseDownedAt = new Date()
}
@@ -242,9 +258,12 @@ class MarkdownEditor extends React.Component {
fontSize={editorFontSize}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
/>
@@ -262,8 +281,11 @@ class MarkdownEditor extends React.Component {
lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
sanitize={config.preview.sanitize}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)}
tabIndex='0'
value={this.state.renderValue}
onMouseUp={(e) => this.handlePreviewMouseUp(e)}

141
browser/components/MarkdownPreview.js Normal file → Executable file
View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import markdown from 'browser/lib/markdown'
import Markdown from 'browser/lib/markdown'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
@@ -9,10 +9,10 @@ import Raphael from 'raphael'
import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
const { remote } = require('electron')
const { app } = remote
@@ -23,6 +23,10 @@ const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
? app.getAppPath()
: path.resolve())
const CSS_FILES = [
`${appPath}/node_modules/katex/dist/katex.min.css`,
`${appPath}/node_modules/codemirror/lib/codemirror.css`
]
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
return `
@@ -116,6 +120,8 @@ export default class MarkdownPreview extends React.Component {
this.contextMenuHandler = (e) => this.handleContextMenu(e)
this.mouseDownHandler = (e) => this.handleMouseDown(e)
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()
@@ -124,6 +130,16 @@ export default class MarkdownPreview extends React.Component {
this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handlelinkClick.bind(this)
this.initMarkdown = this.initMarkdown.bind(this)
this.initMarkdown()
}
initMarkdown () {
const { smartQuotes, sanitize } = this.props
this.markdown = new Markdown({
typographer: smartQuotes,
sanitize
})
}
handlePreviewAnchorClick (e) {
@@ -146,13 +162,21 @@ export default class MarkdownPreview extends React.Component {
this.props.onCheckboxClick(e)
}
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handleContextMenu (e) {
if (!this.props.onContextMenu) return
this.props.onContextMenu(e)
}
handleDoubleClick (e) {
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
}
handleMouseDown (e) {
if (!this.props.onMouseDown) return
if (e.target != null) {
switch (e.target.tagName) {
case 'A':
@@ -180,8 +204,35 @@ export default class MarkdownPreview extends React.Component {
}
handleSaveAsHtml () {
this.exportAsDocument('html', (value) => {
return this.refs.root.contentWindow.document.documentElement.outerHTML
this.exportAsDocument('html', (noteContent, exportTasks) => {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
const body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach((file) => {
file = file.replace('file://', '')
exportTasks.push({
src: file,
dst: 'css'
})
})
let styles = ''
files.forEach((file) => {
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
})
return `<html>
<head>
<meta charset="UTF-8">
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
<style id="style">${inlineStyles}</style>
${styles}
</head>
<body>${body}</body>
</html>`
})
}
@@ -189,20 +240,26 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType, formatter) {
exportAsDocument (fileType, contentFormatter) {
const options = {
filters: [
{name: 'Documents', extensions: [fileType]}
],
properties: ['openFile', 'createDirectory']
}
const value = formatter ? formatter.call(this, this.props.value) : this.props.value
dialog.showSaveDialog(remote.getCurrentWindow(), options,
(filename) => {
if (filename) {
fs.writeFile(filename, value, (err) => {
if (err) throw err
const content = this.props.value
const storage = this.props.storagePath
exportNote(storage, content, filename, contentFormatter)
.then((res) => {
dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`})
}).catch((err) => {
dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export')
throw err
})
}
})
@@ -222,20 +279,26 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
this.refs.root.contentWindow.document.head.innerHTML = `
let styles = `
<style id='style'></style>
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" id="codeTheme">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
`
CSS_FILES.forEach((file) => {
styles += `<link rel="stylesheet" href="${file}">`
})
this.refs.root.contentWindow.document.head.innerHTML = styles
this.rewriteIframe()
this.applyStyle()
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('scroll', this.scrollHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
@@ -246,8 +309,10 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('scroll', this.scrollHandler)
eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
@@ -256,6 +321,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) {
this.initMarkdown()
this.rewriteIframe()
}
if (prevProps.fontFamily !== this.props.fontFamily ||
prevProps.fontSize !== this.props.fontSize ||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
@@ -269,7 +338,7 @@ export default class MarkdownPreview extends React.Component {
}
}
applyStyle () {
getStyleParams () {
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
@@ -279,15 +348,21 @@ export default class MarkdownPreview extends React.Component {
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
this.setCodeTheme(codeBlockTheme)
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
}
applyStyle () {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams()
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
}
setCodeTheme (theme) {
GetCodeThemeLink (theme) {
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
? theme
: 'elegant'
this.getWindow().document.getElementById('codeTheme').href = theme.startsWith('solarized')
return theme.startsWith('solarized')
? `${appPath}/node_modules/codemirror/theme/solarized.css`
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
}
@@ -315,11 +390,7 @@ export default class MarkdownPreview extends React.Component {
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
})
}
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
el.parentNode.parentNode.style.listStyleType = 'none'
})
this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
@@ -335,9 +406,9 @@ export default class MarkdownPreview extends React.Component {
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
el.src = markdown.normalizeLinkText(el.src)
el.src = this.markdown.normalizeLinkText(el.src)
if (!/\/:storage/.test(el.src)) return
el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
el.src = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
})
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
@@ -364,9 +435,9 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = ''
if (codeBlockTheme.indexOf('solarized') === 0) {
const [refThema, color] = codeBlockTheme.split(' ')
el.parentNode.className += ` cm-s-${refThema} cm-s-${color} CodeMirror`
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
} else {
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
el.parentNode.className += ` cm-s-${codeBlockTheme}`
}
CodeMirror.runMode(content, syntax.mime, el, {
tabSize: indentSize
@@ -449,9 +520,20 @@ export default class MarkdownPreview extends React.Component {
handlelinkClick (e) {
const noteHash = e.target.href.split('/').pop()
const regexIsNoteLink = /^(.{20})-(.{20})$/
// 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)
eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
}
// 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])
}
}
@@ -478,5 +560,6 @@ MarkdownPreview.propTypes = {
className: PropTypes.string,
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string
storagePath: PropTypes.string,
smartQuotes: PropTypes.bool
}

View File

@@ -2,6 +2,7 @@ import React from 'react'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage'
import _ from 'lodash'
import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules'
@@ -12,6 +13,7 @@ class MarkdownSplitEditor extends React.Component {
this.value = props.value
this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload()
this.userScroll = true
}
handleOnChange () {
@@ -19,6 +21,49 @@ class MarkdownSplitEditor extends React.Component {
this.props.onChange()
}
handleScroll (e) {
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
if (this.userScroll) {
if (e.doc) {
srcTop = _.get(e, 'doc.scrollTop')
srcHeight = _.get(e, 'doc.height')
targetTop = _.get(previewDoc, 'body.scrollTop')
targetHeight = _.get(previewDoc, 'body.scrollHeight')
} else {
srcTop = _.get(previewDoc, 'body.scrollTop')
srcHeight = _.get(previewDoc, 'body.scrollHeight')
targetTop = _.get(codeDoc, 'scrollTop')
targetHeight = _.get(codeDoc, 'height')
}
const distance = (targetHeight * srcTop / srcHeight) - targetTop
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
this.userScroll = false
let frame = 0
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos = time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => { this.userScroll = true }, refractory)
}
frame++
}, framerate)
}
}
handleCheckboxClick (e) {
e.preventDefault()
e.stopPropagation()
@@ -65,9 +110,13 @@ class MarkdownSplitEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
storageKey={storageKey}
onChange={this.handleOnChange.bind(this)}
onScroll={this.handleScroll.bind(this)}
/>
<MarkdownPreview
style={previewStyle}
@@ -80,10 +129,13 @@ class MarkdownSplitEditor extends React.Component {
codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
sanitize={config.preview.sanitize}
ref='preview'
tabInde='0'
value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>

View File

@@ -46,14 +46,25 @@ const TagElementList = (tags) => {
* @param {Function} handleDragStart
* @param {string} dateDisplay
*/
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
const NoteItem = ({
isActive,
note,
dateDisplay,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storageName,
folderName,
viewType
}) => (
<div styleName={isActive
? 'item--active'
: 'item'
}
key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
key={note.key}
onClick={e => handleNoteClick(e, note.key)}
onContextMenu={e => handleNoteContextMenu(e, note.key)}
onDragStart={e => handleDragStart(e, note)}
draggable='true'
>
@@ -68,24 +79,34 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteCont
: <span styleName='item-title-empty'>Empty</span>
}
</div>
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
<div styleName='item-middle-app-meta'>
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
{viewType === 'ALL' && storageName}
{viewType === 'STORAGE' && folderName}
</div>
</div>
</div>}
<div styleName='item-bottom-time'>{dateDisplay}</div>
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags)
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>No tags</span>
}
</div>
<div>
{note.isStarred
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
}
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
}
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags)
: <span styleName='item-bottom-tagList-empty' />
}
</div>
</div>
</div>
@@ -102,7 +123,11 @@ NoteItem.propTypes = {
title: PropTypes.string.isrequired,
tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired
isTrashed: PropTypes.bool.isRequired,
blog: {
blogLink: PropTypes.string,
blogId: PropTypes.number
}
}),
handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired,

View File

@@ -90,6 +90,26 @@ $control-height = 30px
font-weight normal
color $ui-inactive-text-color
.item-middle
font-size 13px
padding-left 2px
padding-bottom 2px
.item-middle-time
color $ui-inactive-text-color
display inline-block
.item-middle-app-meta
float right
.item-middle-app-meta-label
opacity 0.4
color $ui-inactive-text-color
padding 0 3px
white-space nowrap
text-overflow ellipsis
overflow hidden
max-width 200px
.item-bottom
position relative
bottom 0px
@@ -97,7 +117,7 @@ $control-height = 30px
font-size 12px
line-height 20px
overflow ellipsis
display flex
display block
.item-bottom-tagList
flex 1
@@ -125,10 +145,8 @@ $control-height = 30px
.item-star
position absolute
right -6px
bottom 23px
width 16px
height 16px
right 2px
top 5px
color alpha($ui-favorite-star-button-color, 60%)
font-size 12px
padding 0
@@ -136,10 +154,8 @@ $control-height = 30px
.item-pin
position absolute
right 0px
bottom 2px
width 34px
height 34px
right 25px
top 7px
color #E54D42
font-size 14px
padding 0

View File

@@ -14,14 +14,23 @@ import styles from './NoteItemSimple.styl'
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart
*/
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
const NoteItemSimple = ({
isActive,
isAllNotesView,
note,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storage
}) => (
<div styleName={isActive
? 'item-simple--active'
: 'item-simple'
}
key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
key={note.key}
onClick={e => handleNoteClick(e, note.key)}
onContextMenu={e => handleNoteContextMenu(e, note.key)}
onDragStart={e => handleDragStart(e, note)}
draggable='true'
>
@@ -30,7 +39,7 @@ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
}
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''
}
@@ -38,6 +47,11 @@ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu
? note.title
: <span styleName='item-simple-title-empty'>Empty</span>
}
{isAllNotesView && <div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>
{storage.name}
</span>
</div>}
</div>
</div>
)

View File

@@ -207,3 +207,8 @@ 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

View File

@@ -5,6 +5,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SideNavFilter.styl'
import i18n from 'browser/lib/i18n'
/**
* @param {boolean} isFolded
@@ -17,7 +18,7 @@ import styles from './SideNavFilter.styl'
const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
counterTotalNote, counterStarredNote
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
}) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
@@ -31,7 +32,7 @@ const SideNavFilter = ({
}
/>
</div>
<span styleName='menu-button-label'>All Notes</span>
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
<span styleName='counters'>{counterTotalNote}</span>
</button>
@@ -45,7 +46,7 @@ const SideNavFilter = ({
}
/>
</div>
<span styleName='menu-button-label'>Starred</span>
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
<span styleName='counters'>{counterStarredNote}</span>
</button>
@@ -59,7 +60,7 @@ const SideNavFilter = ({
}
/>
</div>
<span styleName='menu-button-label'>Trash</span>
<span onContextMenu={handleFilterButtonContextMenu} styleName='menu-button-label'>{i18n.__('Trash')}</span>
<span styleName='counters'>{counterDelNote}</span>
</button>

View File

@@ -1,6 +1,7 @@
.root
position relative
flex 1
min-width 70px
overflow hidden
&:hover
.deleteButton
@@ -21,7 +22,7 @@
height 29px
overflow ellipsis
text-align left
padding-right 30px
padding-right 23px
border none
background-color transparent
transition 0.15s

View File

@@ -6,6 +6,18 @@ import React from 'react'
import styles from './StorageItem.styl'
import CSSModules from 'browser/lib/CSSModules'
import _ from 'lodash'
import { SortableHandle } from 'react-sortable-hoc'
const DraggableIcon = SortableHandle(({ className }) => (
<i className={`fa ${className}`} />
))
const FolderIcon = ({ className, color, isActive }) => {
const iconStyle = isActive ? 'fa-folder-open-o' : 'fa-folder-o'
return (
<i className={`fa ${iconStyle} ${className}`} style={{ color: color }} />
)
}
/**
* @param {boolean} isActive
@@ -21,34 +33,54 @@ import _ from 'lodash'
* @return {React.Component}
*/
const StorageItem = ({
isActive, handleButtonClick, handleContextMenu, folderName,
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
}) => (
<button styleName={isActive
? 'folderList-item--active'
: 'folderList-item'
}
styles,
isActive,
handleButtonClick,
handleContextMenu,
folderName,
folderColor,
isFolded,
noteCount,
handleDrop,
handleDragEnter,
handleDragLeave
}) => {
return (
<button
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
onClick={handleButtonClick}
onContextMenu={handleContextMenu}
onDrop={handleDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
<span styleName={isFolded
? 'folderList-item-name--folded' : 'folderList-item-name'
}>
<text style={{color: folderColor, paddingRight: '10px'}}>{isActive ? <i className='fa fa-folder-open-o' /> : <i className='fa fa-folder-o' />}</text>{isFolded ? _.truncate(folderName, {length: 1, omission: ''}) : folderName}
{!isFolded && (
<DraggableIcon className={styles['folderList-item-reorder']} />
)}
<span
styleName={
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
}
>
<FolderIcon
styleName='folderList-item-icon'
color={folderColor}
isActive={isActive}
/>
{isFolded
? _.truncate(folderName, { length: 1, omission: '' })
: folderName}
</span>
{(!isFolded && _.isNumber(noteCount)) &&
{!isFolded &&
_.isNumber(noteCount) && (
<span styleName='folderList-item-noteCount'>{noteCount}</span>
}
{isFolded &&
<span styleName='folderList-item-tooltip'>
{folderName}
</span>
}
)}
{isFolded && (
<span styleName='folderList-item-tooltip'>{folderName}</span>
)}
</button>
)
}
StorageItem.propTypes = {
isActive: PropTypes.bool.isRequired,

View File

@@ -13,6 +13,7 @@
border none
overflow ellipsis
font-size 14px
align-items: center
&:first-child
margin-top 0
&:hover
@@ -34,9 +35,7 @@
.folderList-item-name
display block
flex 1
padding 0 12px
height 26px
line-height 26px
padding-right: 10px
border-width 0 0 0 2px
border-style solid
border-color transparent
@@ -69,9 +68,20 @@
.folderList-item-name--folded
@extend .folderList-item-name
padding-left 7px
text
.folderList-item-icon
font-size 9px
.folderList-item-icon
padding-right: 10px
.folderList-item-reorder
font-size: 9px
padding: 10px 8px 10px 9px;
color: rgba(147, 147, 149, 0.3)
cursor: ns-resize
&:before
content: "\f142 \f142"
body[data-theme="white"]
.folderList-item
color $ui-inactive-text-color

View File

@@ -12,10 +12,11 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {bool} isActive
*/
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
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>
)

View File

@@ -48,6 +48,9 @@
overflow hidden
text-overflow ellipsis
.tagList-item-count
padding 0 3px
body[data-theme="white"]
.tagList-item
color $ui-inactive-text-color
@@ -63,6 +66,8 @@ body[data-theme="white"]
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
.tagList-item-count
color $ui-text-color
body[data-theme="dark"]
.tagList-item
@@ -82,3 +87,5 @@ body[data-theme="dark"]
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
.tagList-item-count
color $ui-dark-button--active-color

View File

@@ -58,7 +58,7 @@ body
.katex
font 400 1.2em 'KaTeX_Main'
line-height 1.2em
white-space nowrap
white-space initial
text-indent 0
.katex .mfrac>.vlist>span:nth-child(2)
top 0 !important
@@ -76,7 +76,7 @@ body
justify-content left
li
label.taskListItem
margin-left -2em
margin-left -1.8em
&.checked
text-decoration line-through
opacity 0.5
@@ -178,6 +178,8 @@ ul
margin-bottom 1em
li
display list-item
&.taskListItem
list-style none
p
margin 0
&>li>ul, &>li>ol
@@ -218,6 +220,7 @@ pre
background-color white
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
@@ -227,6 +230,13 @@ pre
padding 0
border none
border-radius 0
&>span.filename
width 100%
border-radius: 5px 0px 0px 0px
margin -8px 100% 8px -8px
padding 0px 6px
background-color #777;
color white
&>span.lineNumber
display none
font-size 1em

View File

9
browser/lib/i18n.js Normal file
View File

@@ -0,0 +1,9 @@
// 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'],
extension: '.json',
devMode: false
})
export default i18n

View File

@@ -1,7 +1,11 @@
const crypto = require('crypto')
const _ = require('lodash')
const uuidv4 = require('uuid/v4')
module.exports = function (length) {
if (!_.isFinite(length)) length = 10
module.exports = function (uuid) {
if (typeof uuid === typeof true && uuid) {
return uuidv4()
}
const length = 10
return crypto.randomBytes(length).toString('hex')
}

View File

@@ -0,0 +1,23 @@
'use strict'
import sanitizeHtml from 'sanitize-html'
module.exports = function sanitizePlugin (md, options) {
options = options || {}
md.core.ruler.after('linkify', 'sanitize_inline', state => {
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
if (state.tokens[tokenIdx].type === 'html_block') {
state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
}
if (state.tokens[tokenIdx].type === 'inline') {
const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
if (inlineTokens[childIdx].type === 'html_inline') {
inlineTokens[childIdx].content = sanitizeHtml(inlineTokens[childIdx].content, options)
}
}
}
}
})
}

View File

@@ -1,46 +1,111 @@
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 _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex'
import {lastFindInArray} from './utils'
// FIXME We should not depend on global variable.
const katex = window.katex
const config = ConfigManager.get()
function createGutter (str) {
const lc = (str.match(/\n/g) || []).length
function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
const lines = []
for (let i = 1; i <= lc; i++) {
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
}
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
}
var md = markdownit({
typographer: true,
class Markdown {
constructor (options = {}) {
const config = ConfigManager.get()
const defaultOptions = {
typographer: config.preview.smartQuotes,
linkify: true,
html: true,
xhtmlOut: true,
breaks: true,
highlight: function (str, lang) {
if (lang === 'flowchart') {
const delimiter = ':'
const langInfo = lang.split(delimiter)
const langType = langInfo[0]
const fileName = langInfo[1] || ''
const firstLineNumber = parseInt(langInfo[2], 10)
if (langType === 'flowchart') {
return `<pre class="flowchart">${str}</pre>`
}
if (lang === 'sequence') {
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
return '<pre class="code">' +
createGutter(str) +
'<code class="' + lang + '">' +
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
},
sanitize: 'STRICT'
}
const updatedOptions = Object.assign(defaultOptions, options)
this.md = markdownit(updatedOptions)
if (updatedOptions.sanitize !== 'NONE') {
const allowedTags = ['iframe', 'input', 'b',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
]
const allowedAttributes = [
'abbr', 'accept', 'accept-charset',
'accesskey', 'action', 'align', 'alt', 'axis',
'border', 'cellpadding', 'cellspacing', 'char',
'charoff', 'charset', 'checked',
'clear', 'cols', 'colspan', 'color',
'compact', 'coords', 'datetime', 'dir',
'disabled', 'enctype', 'for', 'frame',
'headers', 'height', 'hreflang',
'hspace', 'ismap', 'label', 'lang',
'maxlength', 'media', 'method',
'multiple', 'name', 'nohref', 'noshade',
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
'rows', 'rowspan', 'rules', 'scope',
'selected', 'shape', 'size', 'span',
'start', 'summary', 'tabindex', 'target',
'title', 'type', 'usemap', 'valign', 'value',
'vspace', 'width', 'itemprop'
]
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
allowedTags.push('style')
allowedAttributes.push('style')
}
// Sanitize use rinput before other plugins
this.md.use(sanitize, {
allowedTags,
allowedAttributes: {
'*': allowedAttributes,
'a': ['href'],
'div': ['itemscope', 'itemtype'],
'blockquote': ['cite'],
'del': ['cite'],
'ins': ['cite'],
'q': ['cite'],
'img': ['src', 'width', 'height'],
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
'input': ['type', 'id', 'checked']
},
allowedIframeHostnames: ['www.youtube.com']
})
md.use(emoji, {
}
this.md.use(emoji, {
shortcuts: {}
})
md.use(math, {
this.md.use(math, {
inlineOpen: config.preview.latexInlineOpen,
inlineClose: config.preview.latexInlineClose,
blockOpen: config.preview.latexBlockOpen,
@@ -64,10 +129,10 @@ md.use(math, {
return output
}
})
md.use(require('markdown-it-imsize'))
md.use(require('markdown-it-footnote'))
md.use(require('markdown-it-multimd-table'))
md.use(require('markdown-it-named-headers'), {
this.md.use(require('markdown-it-imsize'))
this.md.use(require('markdown-it-footnote'))
this.md.use(require('markdown-it-multimd-table'))
this.md.use(require('markdown-it-named-headers'), {
slugify: (header) => {
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
@@ -75,10 +140,10 @@ md.use(require('markdown-it-named-headers'), {
.replace(/\-+$/, '')
}
})
md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-kbd'))
const deflate = require('markdown-it-plantuml/lib/deflate')
md.use(require('markdown-it-plantuml'), '', {
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
@@ -89,7 +154,7 @@ md.use(require('markdown-it-plantuml'), '', {
})
// Override task item
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
let nextLine = startLine + 1
const terminatorRules = state.md.block.ruler.getRules('paragraph')
@@ -125,6 +190,13 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
if (liToken) {
if (!liToken.attrs) {
liToken.attrs = []
}
liToken.attrs.push(['class', 'taskListItem'])
}
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}
}
@@ -140,8 +212,8 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
})
// Add line number attribute for scrolling
const originalRender = md.renderer.render
md.renderer.render = function render (tokens, options, env) {
const originalRender = this.md.renderer.render
this.md.renderer.render = (tokens, options, env) => {
tokens.forEach((token) => {
switch (token.type) {
case 'heading_open':
@@ -151,23 +223,22 @@ md.renderer.render = function render (tokens, options, env) {
token.attrPush(['data-line', token.map[0]])
}
})
const result = originalRender.call(md.renderer, tokens, options, env)
const result = originalRender.call(this.md.renderer, tokens, options, env)
return result
}
// FIXME We should not depend on global variable.
window.md = md
function normalizeLinkText (linkText) {
return md.normalizeLinkText(linkText)
window.md = this.md
}
const markdown = {
render: function markdown (content) {
render (content) {
if (!_.isString(content)) content = ''
const renderedContent = md.render(content)
return renderedContent
},
normalizeLinkText
return this.md.render(content)
}
export default markdown
normalizeLinkText (linkText) {
return this.md.normalizeLinkText(linkText)
}
}
export default Markdown

11
browser/lib/utils.js Normal file
View File

@@ -0,0 +1,11 @@
export function lastFindInArray (array, callback) {
for (let i = array.length - 1; i >= 0; --i) {
if (callback(array[i], i, array)) {
return array[i]
}
}
}
export default {
lastFindInArray
}

View File

@@ -3,6 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FolderSelect.styl'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class FolderSelect extends React.Component {
constructor (props) {
@@ -249,7 +250,7 @@ class FolderSelect extends React.Component {
<input styleName='search-input'
ref='search'
value={this.state.search}
placeholder='Folder...'
placeholder={i18n.__('Folder...')}
onChange={(e) => this.handleSearchInputChange(e)}
onBlur={(e) => this.handleSearchInputBlur(e)}
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}

View File

@@ -2,13 +2,14 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n'
const FullscreenButton = ({
onClick
}) => (
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
</button>
)

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoButton.styl'
import i18n from 'browser/lib/i18n'
const InfoButton = ({
onClick
@@ -10,7 +11,7 @@ const InfoButton = ({
onClick={(e) => onClick(e)}
>
<img className='infoButton' src='../resources/icon/icon-info.svg' />
<span styleName='tooltip'>Info</span>
<span styleName='tooltip'>{i18n.__('Info')}</span>
</button>
)

View File

@@ -3,6 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
import copy from 'copy-to-clipboard'
import i18n from 'browser/lib/i18n'
class InfoPanel extends React.Component {
copyNoteLink () {
@@ -19,7 +20,7 @@ class InfoPanel extends React.Component {
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
</div>
<hr />
@@ -29,11 +30,11 @@ class InfoPanel extends React.Component {
: <div styleName='count-wrap'>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
<p styleName='infoPanel-sub-count'>Words</p>
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
</div>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
<p styleName='infoPanel-sub-count'>Letters</p>
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
</div>
</div>
}
@@ -45,17 +46,17 @@ class InfoPanel extends React.Component {
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p>
</div>
<div>
<p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p>
</div>
<div>
@@ -63,7 +64,7 @@ class InfoPanel extends React.Component {
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<i className='fa fa-clipboard' />
</button>
<p styleName='infoPanel-sub'>NOTE LINK</p>
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
</div>
<hr />
@@ -71,22 +72,22 @@ class InfoPanel extends React.Component {
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o' />
<p>.md</p>
<p>{i18n.__('.md')}</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o' />
<p>.txt</p>
<p>{i18n.__('.txt')}</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>.html</p>
<p>{i18n.__('.html')}</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-print' />
<p>Print</p>
<p>{i18n.__('Print')}</p>
</button>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
@@ -9,24 +10,24 @@ const InfoPanelTrashed = ({
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
</div>
<hr />
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p>
</div>
<div>
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p>
</div>
<div id='export-wrap'>

22
browser/main/Detail/MarkdownNoteDetail.js Normal file → Executable file
View File

@@ -19,6 +19,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton'
@@ -68,11 +69,8 @@ class MarkdownNoteDetail extends React.Component {
}
componentWillUnmount () {
if (this.saveQueue != null) this.saveNow()
}
componentDidUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
if (this.saveQueue != null) this.saveNow()
}
handleUpdateTag () {
@@ -141,7 +139,7 @@ class MarkdownNoteDetail extends React.Component {
hashHistory.replace({
pathname: location.pathname,
query: {
key: newNote.storage + '-' + newNote.key
key: newNote.key
}
})
this.setState({
@@ -198,8 +196,9 @@ class MarkdownNoteDetail extends React.Component {
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
ee.once('list:next', dispatchHandler)
})
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
@@ -323,10 +322,7 @@ class MarkdownNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -361,12 +357,10 @@ class MarkdownNoteDetail extends React.Component {
value={this.state.note.tags}
onChange={this.handleUpdateTag.bind(this)}
/>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
@@ -399,7 +393,7 @@ class MarkdownNoteDetail extends React.Component {
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](${location.query.key})`}
noteLink={`[${note.title}](:note:${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}

View File

@@ -7,6 +7,7 @@
background-color $ui-noteDetail-backgroundColor
box-shadow none
padding 20px 40px
overflow hidden
.lock-button
padding-bottom 3px

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const PermanentDeleteButton = ({
onClick
@@ -10,7 +11,7 @@ const PermanentDeleteButton = ({
onClick={(e) => onClick(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Permanent Delete</span>
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
</button>
)

View File

@@ -0,0 +1,22 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RestoreButton.styl'
import i18n from 'browser/lib/i18n'
const RestoreButton = ({
onClick
}) => (
<button styleName='control-restoreButton'
onClick={onClick}
>
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
<span styleName='tooltip'>{i18n.__('Restore')}</span>
</button>
)
RestoreButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(RestoreButton, styles)

View File

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

View File

@@ -20,11 +20,13 @@ import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton'
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) {
@@ -52,12 +54,30 @@ class SnippetNoteDetail extends React.Component {
this.state = {
isMovingNote: false,
snippetIndex: 0,
showArrows: false,
enableLeftArrow: false,
enableRightArrow: false,
note: Object.assign({
description: ''
}, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
})
}
this.scrollToNextTabThreshold = 0.7
}
componentDidMount () {
const visibleTabs = this.visibleTabs
const allTabs = this.allTabs
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
this.setState({
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
}
componentWillReceiveProps (nextProps) {
@@ -77,6 +97,7 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + index].reload()
})
if (this.refs.tags) this.refs.tags.reset()
this.setState(this.getArrowsState())
})
}
}
@@ -146,7 +167,7 @@ class SnippetNoteDetail extends React.Component {
hashHistory.replace({
pathname: location.pathname,
query: {
key: newNote.storage + '-' + newNote.key
key: newNote.key
}
})
this.setState({
@@ -191,8 +212,9 @@ class SnippetNoteDetail extends React.Component {
noteKey: data.noteKey
})
}
ee.once('list:moved', dispatchHandler)
ee.once('list:next', dispatchHandler)
})
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
@@ -226,6 +248,51 @@ class SnippetNoteDetail extends React.Component {
ee.emit('editor:fullscreen')
}
handleTabMoveLeftButtonClick (e) {
{
const left = this.visibleTabs.scrollLeft
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= left
})
if (lastVisibleTab) {
const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling)
? lastVisibleTab.previousSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'start', block: 'start'})
}
}
}
handleTabMoveRightButtonClick (e) {
const left = this.visibleTabs.scrollLeft
const width = this.visibleTabs.offsetWidth
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= width + left
})
if (lastVisibleTab) {
const visiblePart = width + left - lastVisibleTab.offsetLeft
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling)
? lastVisibleTab.nextSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'end', block: 'end'})
}
}
handleTabPlusButtonClick (e) {
this.addSnippet()
}
@@ -262,9 +329,9 @@ class SnippetNoteDetail extends React.Component {
if (this.state.note.snippets[index].content.trim().length > 0) {
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a snippet',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
message: i18n.__('Delete a snippet'),
detail: i18n.__('This work cannot be undone.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (dialogIndex === 0) {
this.deleteSnippetByIndex(index)
@@ -285,6 +352,21 @@ class SnippetNoteDetail extends React.Component {
this.setState({ note, snippetIndex }, () => {
this.save()
this.refs['code-' + this.state.snippetIndex].reload()
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
console.log('no need for arrows')
this.moveTabBarBy(0)
} else {
const lastTab = this.allTabs.lastChild
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
console.log('need to scroll')
const width = this.visibleTabs.offsetWidth
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
} else {
this.setState(this.getArrowsState())
}
}
})
}
@@ -341,6 +423,7 @@ class SnippetNoteDetail extends React.Component {
handleKeyDown (e) {
switch (e.keyCode) {
// tab key
case 9:
if (e.ctrlKey && !e.shiftKey) {
e.preventDefault()
@@ -353,6 +436,7 @@ class SnippetNoteDetail extends React.Component {
this.focusEditor()
}
break
// L key
case 76:
{
const isSuper = global.process.platform === 'darwin'
@@ -364,6 +448,7 @@ class SnippetNoteDetail extends React.Component {
}
}
break
// T key
case 84:
{
const isSuper = global.process.platform === 'darwin'
@@ -455,6 +540,51 @@ class SnippetNoteDetail extends React.Component {
this.refs.description.focus()
}
moveToTab (tab) {
const easeOutCubic = t => (--t) * t * t + 1
const startScrollPosition = this.visibleTabs.scrollLeft
const animationTiming = 300
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
let scrollBy = (tab.offsetLeft - startScrollPosition)
if (tab.offsetLeft > startScrollPosition) {
// if tab is on the right side and we want to show the whole tab in visible area,
// we need to include width of the tab and visible area in the formula
// ___________________________________________
// |____|_______|________|________|_show_this_|
// ↑_____________________↑
// visible area
scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth)
}
let startTime = null
const scrollAnimation = time => {
startTime = startTime || time
const elapsed = (time - startTime) / animationTiming
this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
if (elapsed < 1) {
window.requestAnimationFrame(scrollAnimation)
} else {
this.setState(this.getArrowsState())
}
}
window.requestAnimationFrame(scrollAnimation)
}
getArrowsState () {
const allTabs = this.allTabs
const visibleTabs = this.visibleTabs
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
const enableRightArrow = visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
const enableLeftArrow = visibleTabs.scrollLeft !== 0
return {showArrows, enableRightArrow, enableLeftArrow}
}
addSnippet () {
const { note } = this.state
@@ -465,10 +595,16 @@ class SnippetNoteDetail extends React.Component {
}])
const snippetIndex = note.snippets.length - 1
this.setState({
this.setState(Object.assign({
note,
snippetIndex
}, () => {
}, this.getArrowsState()), () => {
if (this.state.showArrows) {
const tabs = this.allTabs.querySelectorAll('div')
if (tabs) {
this.moveToTab(tabs[snippetIndex])
}
}
this.refs['tab-' + snippetIndex].startRenaming()
})
}
@@ -502,9 +638,9 @@ class SnippetNoteDetail extends React.Component {
showWarning () {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.',
buttons: ['OK']
message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'),
buttons: [i18n.__('OK')]
})
}
@@ -567,6 +703,7 @@ class SnippetNoteDetail extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
/>
@@ -587,10 +724,7 @@ class SnippetNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -632,10 +766,10 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred}
/>
<button styleName='control-fullScreenButton' title='Fullscreen'
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')}
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -647,7 +781,7 @@ class SnippetNoteDetail extends React.Component {
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](${location.query.key})`}
noteLink={`[${note.title}](:note:${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
@@ -673,16 +807,32 @@ class SnippetNoteDetail extends React.Component {
fontSize: parseInt(config.preview.fontSize, 10)
}}
ref='description'
placeholder='Description...'
placeholder={i18n.__('Description...')}
value={this.state.note.description}
onChange={(e) => this.handleChange(e)}
/>
</div>
<div styleName='tabList'>
<div styleName='list'>
<button styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableLeftArrow}
onClick={(e) => this.handleTabMoveLeftButtonClick(e)}
>
<i className='fa fa-chevron-left' />
</button>
<div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
<div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}>
{tabList}
</div>
<button styleName='plusButton'
</div>
<button styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableRightArrow}
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
>
<i className='fa fa-chevron-right' />
</button>
<button styleName='tabButton'
onClick={(e) => this.handleTabPlusButtonClick(e)}
>
<i className='fa fa-plus' />
@@ -696,7 +846,7 @@ class SnippetNoteDetail extends React.Component {
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
>
{this.state.note.snippets[this.state.snippetIndex].mode == null
? 'Select Syntax...'
? i18n.__('Select Syntax...')
: this.state.note.snippets[this.state.snippetIndex].mode
}&nbsp;
<i className='fa fa-caret-down' />

View File

@@ -35,13 +35,26 @@
height 30px
display flex
background-color $ui-noteDetail-backgroundColor
overflow hidden
.tabList .list
flex 1
display flex
overflow hidden
overflow-x scroll
position relative
.tabList .plusButton
&::-webkit-scrollbar {
display: none;
}
.allTabs
display flex
position relative
overflow visible
left 0
transition left 0.1s
.tabList .tabButton
navWhiteButtonColor()
width 30px

View File

@@ -3,6 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StarButton.styl'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class StarButton extends React.Component {
constructor (props) {
@@ -53,7 +54,7 @@ class StarButton extends React.Component {
: '../resources/icon/icon-star.svg'
}
/>
<span styleName='tooltip'>Star</span>
<span styleName='tooltip'>{i18n.__('Star')}</span>
</button>
)
}

View File

@@ -4,6 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl'
import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class TagSelect extends React.Component {
constructor (props) {
@@ -137,7 +138,7 @@ class TagSelect extends React.Component {
<input styleName='newTag'
ref='newTag'
value={this.state.newTag}
placeholder='Add tag...'
placeholder={i18n.__('Add tag...')}
onChange={(e) => this.handleNewTagInputChange(e)}
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
onBlur={(e) => this.handleNewTagBlur(e)}

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleModeButton.styl'
import i18n from 'browser/lib/i18n'
const ToggleModeButton = ({
onClick, editorType
@@ -13,7 +14,7 @@ const ToggleModeButton = ({
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<span styleName='tooltip'>Toggle Mode</span>
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
</div>
)

View File

@@ -5,8 +5,8 @@
width 52px
display flex
align-items center
position absolute
right 165px
position: relative
top 2px
.active
background-color #1EC38B
width 33px

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const TrashButton = ({
onClick
@@ -10,7 +11,7 @@ const TrashButton = ({
onClick={(e) => onClick(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Trash</span>
<span styleName='tooltip'>{i18n.__('Trash')}</span>
</button>
)

View File

@@ -7,6 +7,7 @@ import MarkdownNoteDetail from './MarkdownNoteDetail'
import SnippetNoteDetail from './SnippetNoteDetail'
import ee from 'browser/main/lib/eventEmitter'
import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
@@ -40,9 +41,9 @@ class Detail extends React.Component {
const alertConfig = {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
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)
@@ -56,11 +57,8 @@ class Detail extends React.Component {
const { location, data, config } = this.props
let note = null
if (location.query.key != null) {
const splitted = location.query.key.split('-')
const storageKey = splitted.shift()
const noteKey = splitted.shift()
note = data.noteMap.get(storageKey + '-' + noteKey)
const noteKey = location.query.key
note = data.noteMap.get(noteKey)
}
if (note == null) {
@@ -70,7 +68,7 @@ class Detail extends React.Component {
tabIndex='0'
>
<div styleName='empty'>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new note</div>
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
</div>
<StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])}

View File

@@ -14,6 +14,7 @@ import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
import eventEmitter from 'browser/main/lib/eventEmitter'
import { hashHistory } from 'react-router'
import store from 'browser/main/store'
import i18n from 'browser/lib/i18n'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -148,6 +149,37 @@ class Main extends React.Component {
} 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') {
i18n.setLocale('es')
} else {
i18n.setLocale('en')
}
// Reload all data
dataApi.init()

View File

@@ -6,6 +6,7 @@ import _ from 'lodash'
import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal'
import eventEmitter from 'browser/main/lib/eventEmitter'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { dialog } = remote
@@ -56,9 +57,9 @@ class NewNoteButton extends React.Component {
}
}
if (storage == null) this.showMessageBox('No storage to create a note')
if (storage == null) this.showMessageBox(i18n.__('No storage to create a note'))
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
if (folder == null) this.showMessageBox('No folder to create a note')
if (folder == null) this.showMessageBox(i18n.__('No folder to create a note'))
return {
storage,
@@ -86,7 +87,7 @@ class NewNoteButton extends React.Component {
onClick={(e) => this.handleNewNoteButtonClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'>
Make a note {OSX ? '⌘' : 'Ctrl'} + N
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
</span>
</button>
</div>

View File

@@ -1,3 +1,4 @@
/* global electron */
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
@@ -13,10 +14,14 @@ import searchFromNotes from 'browser/lib/search'
import fs from 'fs'
import path from 'path'
import { hashHistory } from 'react-router'
import copy from 'copy-to-clipboard'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
function sortByCreatedAt (a, b) {
return new Date(b.createdAt) - new Date(a.createdAt)
@@ -31,7 +36,7 @@ function sortByUpdatedAt (a, b) {
}
function findNoteByKey (notes, noteKey) {
return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
return notes.find((note) => note.key === noteKey)
}
function findNotesByKeys (notes, noteKeys) {
@@ -39,7 +44,7 @@ function findNotesByKeys (notes, noteKeys) {
}
function getNoteKey (note) {
return `${note.storage}-${note.key}`
return note.key
}
class NoteList extends React.Component {
@@ -66,6 +71,11 @@ class NoteList extends React.Component {
this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this)
this.getNoteStorage = this.getNoteStorage.bind(this)
this.getNoteFolder = this.getNoteFolder.bind(this)
this.getViewType = this.getViewType.bind(this)
this.restoreNote = this.restoreNote.bind(this)
this.copyNoteLink = this.copyNoteLink.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = {
@@ -109,14 +119,27 @@ class NoteList extends React.Component {
componentDidUpdate (prevProps) {
const { location } = this.props
const { selectedNoteKeys } = this.state
const visibleNoteKeys = this.notes.map(note => note.key)
const note = this.notes[0]
const prevKey = prevProps.location.query.key
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
if (this.notes.length > 0 && location.query.key == null) {
if (note && location.query.key == null) {
const { router } = this.context
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
// A visible note is an active note
if (!selectedNoteKeys.includes(noteKey)) {
if (selectedNoteKeys.length === 1) selectedNoteKeys.pop()
selectedNoteKeys.push(noteKey)
ee.emit('list:moved')
}
router.replace({
pathname: location.pathname,
query: {
key: this.notes[0].storage + '-' + this.notes[0].key
key: noteKey
}
})
return
@@ -240,27 +263,38 @@ class NoteList extends React.Component {
handleNoteListKeyDown (e) {
if (e.metaKey || e.ctrlKey) return true
// A key
if (e.keyCode === 65 && !e.shiftKey) {
e.preventDefault()
ee.emit('top:new-note')
}
// D key
if (e.keyCode === 68) {
e.preventDefault()
this.deleteNote()
}
// E key
if (e.keyCode === 69) {
e.preventDefault()
ee.emit('detail:focus')
}
if (e.keyCode === 38) {
// F or S key
if (e.keyCode === 70 || e.keyCode === 83) {
e.preventDefault()
ee.emit('top:focus-search')
}
// UP or K key
if (e.keyCode === 38 || e.keyCode === 75) {
e.preventDefault()
this.selectPriorNote()
}
if (e.keyCode === 40) {
// DOWN or J key
if (e.keyCode === 40 || e.keyCode === 74) {
e.preventDefault()
this.selectNextNote()
}
@@ -411,9 +445,9 @@ class NoteList extends React.Component {
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.',
buttons: ['OK', 'Cancel']
message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'),
buttons: [i18n.__('OK'), i18n.__('Cancel')]
})
}
}
@@ -437,17 +471,30 @@ class NoteList extends React.Component {
this.handleNoteClick(e, uniqueKey)
}
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = 'Delete Note'
const cloneNote = 'Clone Note'
const pinLabel = note.isPinned ? i18n.__('Remove pin') : i18n.__('Pin to Top')
const deleteLabel = i18n.__('Delete Note')
const cloneNote = i18n.__('Clone Note')
const restoreNote = i18n.__('Restore Note')
const copyNoteLink = i18n.__('Copy Note Link')
const publishLabel = i18n.__('Publish Blog')
const updateLabel = i18n.__('Update Blog')
const openBlogLabel = i18n.__('Open Blog')
const menu = new Menu()
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
if (!location.pathname.match(/\/starred|\/trash/)) {
menu.append(new MenuItem({
label: pinLabel,
click: this.pinToTop
}))
}
if (location.pathname.match(/\/trash/)) {
menu.append(new MenuItem({
label: restoreNote,
click: this.restoreNote
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
@@ -456,18 +503,45 @@ class NoteList extends React.Component {
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.append(new MenuItem({
label: copyNoteLink,
click: this.copyNoteLink(note)
}))
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
menu.append(new MenuItem({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}))
menu.append(new MenuItem({
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
}))
} else {
menu.append(new MenuItem({
label: publishLabel,
click: this.publishMarkdown.bind(this)
}))
}
}
menu.popup()
}
pinToTop () {
updateSelectedNotes (updateFunc, cleanSelection = true) {
const { selectedNoteKeys } = this.state
const { dispatch } = this.props
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
if (!_.isFunction(updateFunc)) {
console.warn('Update function is not defined. No update will happen')
updateFunc = (note) => { return note }
}
Promise.all(
selectedNotes.map((note) => {
note.isPinned = !note.isPinned
note = updateFunc(note)
return dataApi
.updateNote(note.storage, note.key, note)
})
@@ -480,7 +554,24 @@ class NoteList extends React.Component {
})
})
})
this.setState({ selectedNoteKeys: [] })
if (cleanSelection) {
this.selectNextNote()
}
}
pinToTop () {
this.updateSelectedNotes((note) => {
note.isPinned = !note.isPinned
return note
})
}
restoreNote () {
this.updateSelectedNotes((note) => {
note.isTrashed = false
return note
})
}
deleteNote () {
@@ -494,17 +585,15 @@ class NoteList extends React.Component {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
message: i18n.__('Confirm note deletion'),
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
buttons: ['Confirm', 'Cancel']
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (dialogueButtonIndex === 1) return
Promise.all(
selectedNoteKeys.map((uniqueKey) => {
const storageKey = uniqueKey.split('-')[0]
const noteKey = uniqueKey.split('-')[1]
selectedNotes.map((note) => {
return dataApi
.deleteNote(storageKey, noteKey)
.deleteNote(note.storage, note.key)
})
)
.then((data) => {
@@ -565,23 +654,134 @@ class NoteList extends React.Component {
content: firstNote.content
})
.then((note) => {
const uniqueKey = note.storage + '-' + note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
this.setState({
selectedNoteKeys: [uniqueKey]
selectedNoteKeys: [note.key]
})
hashHistory.push({
pathname: location.pathname,
query: {key: uniqueKey}
query: {key: note.key}
})
})
}
copyNoteLink (note) {
const noteLink = `[${note.title}](:note:${note.key})`
return copy(noteLink)
}
save (note) {
const { dispatch } = this.props
dataApi
.updateNote(note.storage, note.key, note)
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
}
publishMarkdown () {
if (this.pendingPublish) {
clearTimeout(this.pendingPublish)
}
this.pendingPublish = setTimeout(() => {
this.publishMarkdownNow()
}, 1000)
}
publishMarkdownNow () {
const {selectedNoteKeys} = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const config = ConfigManager.get()
const {address, token, authMethod, username, password} = config.blog
let authToken = ''
if (authMethod === 'USER') {
authToken = `Basic ${window.btoa(`${username}:${password}`)}`
} else {
authToken = `Bearer ${token}`
}
const contentToRender = firstNote.content.replace(`# ${firstNote.title}`, '')
const markdown = new Markdown()
const data = {
title: firstNote.title,
content: markdown.render(contentToRender),
status: 'publish'
}
let url = ''
let method = ''
if (firstNote.blog && firstNote.blog.blogId) {
url = `${address}${WP_POST_PATH}/${firstNote.blog.blogId}`
method = 'PUT'
} else {
url = `${address}${WP_POST_PATH}`
method = 'POST'
}
// eslint-disable-next-line no-undef
fetch(url, {
method: method,
body: JSON.stringify(data),
headers: {
'Authorization': authToken,
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(response => {
if (_.isNil(response.link) || _.isNil(response.id)) {
return Promise.reject()
}
firstNote.blog = {
blogLink: response.link,
blogId: response.id
}
this.save(firstNote)
this.confirmPublish(firstNote)
})
.catch((error) => {
console.error(error)
this.confirmPublishError()
})
}
confirmPublishError () {
const { remote } = electron
const { dialog } = remote
const alertError = {
type: 'warning',
message: i18n.__('Publish Failed'),
detail: i18n.__('Check and update your blog setting and try again.'),
buttons: [i18n.__('Confirm')]
}
dialog.showMessageBox(remote.getCurrentWindow(), alertError)
}
confirmPublish (note) {
const buttonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Publish Succeeded'),
detail: `${note.title} is published at ${note.blog.blogLink}`,
buttons: [i18n.__('Confirm'), i18n.__('Open Blog')]
})
if (buttonIndex === 1) {
this.openBlog(note)
}
}
openBlog (note) {
const { shell } = electron
shell.openExternal(note.blog.blogLink)
}
importFromFile () {
const options = {
filters: [
@@ -674,10 +874,28 @@ class NoteList extends React.Component {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: message,
buttons: ['OK']
buttons: [i18n.__('OK')]
})
}
getNoteStorage (note) { // note.storage = storage key
return this.props.data.storageMap.toJS()[note.storage]
}
getNoteFolder (note) { // note.folder = folder key
return _.find(this.getNoteStorage(note).folders, ({ key }) => key === note.folder)
}
getViewType () {
const { pathname } = this.props.location
const folder = /\/folders\/[a-zA-Z0-9]+/.test(pathname)
const storage = /\/storages\/[a-zA-Z0-9]+/.test(pathname) && !folder
const allNotes = pathname === '/home'
if (allNotes) return 'ALL'
if (folder) return 'FOLDER'
if (storage) return 'STORAGE'
}
render () {
const { location, config } = this.props
let { notes } = this.props
@@ -687,7 +905,7 @@ class NoteList extends React.Component {
: config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical
: sortByUpdatedAt
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
const sortedNotes = location.pathname.match(/\/starred|\/trash/)
? this.getNotes().sort(sortFunc)
: this.sortByPin(this.getNotes().sort(sortFunc))
this.notes = notes = sortedNotes.filter((note) => {
@@ -714,6 +932,8 @@ class NoteList extends React.Component {
}
})
const viewType = this.getViewType()
const noteList = notes
.map(note => {
if (note == null) {
@@ -739,6 +959,9 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/>
)
}
@@ -752,6 +975,9 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/>
)
})
@@ -766,16 +992,17 @@ class NoteList extends React.Component {
<div styleName='control-sortBy'>
<i className='fa fa-angle-down' />
<select styleName='control-sortBy-select'
title={i18n.__('Select filter mode')}
value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)}
>
<option value='UPDATED_AT'>Updated</option>
<option value='CREATED_AT'>Created</option>
<option value='ALPHABETICAL'>Alphabetically</option>
<option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option>
<option title='Sort by create time' value='CREATED_AT'>{i18n.__('Created')}</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
</select>
</div>
<div styleName='control-button-area'>
<button styleName={config.listStyle === 'DEFAULT'
<button title={i18n.__('Default View')} styleName={config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
@@ -783,7 +1010,7 @@ class NoteList extends React.Component {
>
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
</button>
<button styleName={config.listStyle === 'SMALL'
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
import i18n from 'browser/lib/i18n'
const ListButton = ({
onClick, isTagActive
@@ -12,7 +13,7 @@ const ListButton = ({
: '../resources/icon/icon-list-active.svg'
}
/>
<span styleName='tooltip'>Notes</span>
<span styleName='tooltip'>{i18n.__('Notes')}</span>
</button>
)

View File

@@ -2,13 +2,14 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferenceButton.styl'
import i18n from 'browser/lib/i18n'
const PreferenceButton = ({
onClick
}) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>Preferences</span>
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
</button>
)

View File

@@ -9,6 +9,8 @@ import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
import dataApi from 'browser/main/lib/dataApi'
import StorageItemChild from 'browser/components/StorageItem'
import _ from 'lodash'
import { SortableElement } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { Menu, dialog } = remote
@@ -25,14 +27,14 @@ class StorageItem extends React.Component {
handleHeaderContextMenu (e) {
const menu = Menu.buildFromTemplate([
{
label: 'Add Folder',
label: i18n.__('Add Folder'),
click: (e) => this.handleAddFolderButtonClick(e)
},
{
type: 'separator'
},
{
label: 'Unlink Storage',
label: i18n.__('Unlink Storage'),
click: (e) => this.handleUnlinkStorageClick(e)
}
])
@@ -43,9 +45,9 @@ class StorageItem extends React.Component {
handleUnlinkStorageClick (e) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Unlink Storage',
detail: 'This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
buttons: ['Confirm', 'Cancel']
message: i18n.__('Unlink Storage'),
detail: i18n.__('This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (index === 0) {
@@ -92,21 +94,21 @@ class StorageItem extends React.Component {
handleFolderButtonContextMenu (e, folder) {
const menu = Menu.buildFromTemplate([
{
label: 'Rename Folder',
label: i18n.__('Rename Folder'),
click: (e) => this.handleRenameFolderClick(e, folder)
},
{
type: 'separator'
},
{
label: 'Export Folder',
label: i18n.__('Export Folder'),
submenu: [
{
label: 'Export as txt',
label: i18n.__('Export as txt'),
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
},
{
label: 'Export as md',
label: i18n.__('Export as md'),
click: (e) => this.handleExportFolderClick(e, folder, 'md')
}
]
@@ -115,7 +117,7 @@ class StorageItem extends React.Component {
type: 'separator'
},
{
label: 'Delete Folder',
label: i18n.__('Delete Folder'),
click: (e) => this.handleFolderDeleteClick(e, folder)
}
])
@@ -134,8 +136,8 @@ class StorageItem extends React.Component {
handleExportFolderClick (e, folder, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: 'Select directory',
title: 'Select a folder to export the files to',
buttonLabel: i18n.__('Select directory'),
title: i18n.__('Select a folder to export the files to'),
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
@@ -159,9 +161,9 @@ class StorageItem extends React.Component {
handleFolderDeleteClick (e, folder) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete Folder',
detail: 'This will delete all notes in the folder and can not be undone.',
buttons: ['Confirm', 'Cancel']
message: i18n.__('Delete Folder'),
detail: i18n.__('This will delete all notes in the folder and can not be undone.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (index === 0) {
@@ -191,33 +193,16 @@ class StorageItem extends React.Component {
dropNote (storage, folder, dispatch, location, noteData) {
noteData = noteData.filter((note) => folder.key !== note.folder)
if (noteData.length === 0) return
const newNoteData = noteData.map((note) => Object.assign({}, note, {storage: storage, folder: folder.key}))
Promise.all(
newNoteData.map((note) => dataApi.createNote(storage.key, note))
noteData.map((note) => dataApi.moveNote(note.storage, note.key, storage.key, folder.key))
)
.then((createdNoteData) => {
createdNoteData.forEach((note) => {
createdNoteData.forEach((newNote) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
})
.catch((err) => {
console.error(`error on create notes: ${err}`)
})
.then(() => {
return Promise.all(
noteData.map((note) => dataApi.deleteNote(note.storage, note.key))
)
})
.then((deletedNoteData) => {
deletedNoteData.forEach((note) => {
dispatch({
type: 'DELETE_NOTE',
storageKey: note.storageKey,
noteKey: note.noteKey
type: 'MOVE_NOTE',
originNote: noteData.find((note) => note.content === newNote.content),
note: newNote
})
})
})
@@ -236,7 +221,8 @@ class StorageItem extends React.Component {
render () {
const { storage, location, isFolded, data, dispatch } = this.props
const { folderNoteMap, trashedSet } = data
const folderList = storage.folders.map((folder) => {
const SortableStorageItemChild = SortableElement(StorageItemChild)
const folderList = storage.folders.map((folder, index) => {
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
@@ -250,8 +236,9 @@ class StorageItem extends React.Component {
noteCount = noteSet.size - trashedNoteCount
}
return (
<StorageItemChild
<SortableStorageItemChild
key={folder.key}
index={index}
isActive={isActive}
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
import i18n from 'browser/lib/i18n'
const TagButton = ({
onClick, isTagActive
@@ -12,7 +13,7 @@ const TagButton = ({
: '../resources/icon/icon-tag.svg'
}
/>
<span styleName='tooltip'>Tags</span>
<span styleName='tooltip'>{i18n.__('Tags')}</span>
</button>
)

View File

@@ -1,6 +1,9 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
const { remote } = require('electron')
const { Menu } = remote
import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl'
import { openModal } from 'browser/main/lib/modal'
import PreferencesModal from '../modals/PreferencesModal'
@@ -14,6 +17,8 @@ import EventEmitter from 'browser/main/lib/eventEmitter'
import PreferenceButton from './PreferenceButton'
import ListButton from './ListButton'
import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
@@ -65,6 +70,17 @@ class SideNav extends React.Component {
router.push('/alltags')
}
onSortEnd (storage) {
return ({oldIndex, newIndex}) => {
const { dispatch } = this.props
dataApi
.reorderFolder(storage.key, oldIndex, newIndex)
.then((data) => {
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
})
}
}
SideNavComponent (isFolded, storageList) {
const { location, data } = this.props
@@ -86,9 +102,10 @@ class SideNav extends React.Component {
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
counterTotalNote={data.noteMap._map.size}
counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size}
counterStarredNote={data.starredSet._set.size}
counterDelNote={data.trashedSet._set.size}
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
/>
<StorageList storageList={storageList} />
@@ -99,7 +116,7 @@ class SideNav extends React.Component {
component = (
<div styleName='tabBody'>
<div styleName='tag-title'>
<p>Tags</p>
<p>{i18n.__('Tags')}</p>
</div>
<div styleName='tagList'>
{this.tagListComponent(data)}
@@ -113,18 +130,21 @@ class SideNav extends React.Component {
tagListComponent () {
const { data, location } = this.props
const tagList = data.tagNoteMap.map((tag, key) => {
return key
})
const tagList = _.sortBy(data.tagNoteMap.map((tag, name) => {
return { name, size: tag.size }
}), ['name'])
return (
tagList.map(tag => {
return (
tagList.map(tag => (
<TagListItem
name={tag}
name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={this.getTagActive(location.pathname, tag)}
key={tag}
key={tag.name}
count={tag.size}
/>
))
)
})
)
}
@@ -139,19 +159,48 @@ class SideNav extends React.Component {
router.push(`/tags/${name}`)
}
emptyTrash (entries) {
const { dispatch } = this.props
const deletionPromises = entries.map((note) => {
return dataApi.deleteNote(note.storage, note.key)
})
Promise.all(deletionPromises)
.then((arrayOfStorageAndNoteKeys) => {
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
})
})
.catch((err) => {
console.error('Cannot Delete note: ' + err)
})
console.log('Trash emptied')
}
handleFilterButtonContextMenu (event) {
const { data } = this.props
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
const menu = Menu.buildFromTemplate([
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
])
menu.popup()
}
render () {
const { data, location, config, dispatch } = this.props
const isFolded = config.isSideNavFolded
const storageList = data.storageMap.map((storage, key) => {
return <StorageItem
const SortableStorageItem = SortableContainer(StorageItem)
return <SortableStorageItem
key={storage.key}
storage={storage}
data={data}
location={location}
isFolded={isFolded}
dispatch={dispatch}
onSortEnd={this.onSortEnd.bind(this)(storage)}
useDragHandle
/>
})
const style = {}

View File

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

View File

@@ -3,6 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StatusBar.styl'
import ZoomManager from 'browser/main/lib/ZoomManager'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { remote, ipcRenderer } = electron
@@ -14,9 +15,9 @@ class StatusBar extends React.Component {
updateApp () {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.',
buttons: ['Restart & Install', 'Not Now']
message: i18n.__('Update Boostnote'),
detail: i18n.__('New Boostnote is ready to be installed.'),
buttons: [i18n.__('Restart & Install'), i18n.__('Not Now')]
})
if (index === 0) {
@@ -62,7 +63,7 @@ class StatusBar extends React.Component {
{status.updateReady
? <button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
<i styleName='update-icon' className='fa fa-cloud-download' /> {i18n.__('Ready to Update!')}
</button>
: null
}

View File

@@ -40,6 +40,32 @@ $control-height = 34px
padding-bottom 2px
background-color $ui-noteList-backgroundColor
.control-search-input-clear
height 16px
width 16px
position absolute
right 40px
top 10px
z-index 300
border none
background-color transparent
color #999
&:hover .control-search-input-clear-tooltip
opacity 1
.control-search-input-clear-tooltip
tooltip()
position fixed
pointer-events none
top 50px
left 433px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.control-search-optionList
position fixed
z-index 200

View File

@@ -5,6 +5,7 @@ import styles from './TopBar.styl'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n'
class TopBar extends React.Component {
constructor (props) {
@@ -22,14 +23,29 @@ class TopBar extends React.Component {
this.focusSearchHandler = () => {
this.handleOnSearchFocus()
}
this.codeInitHandler = this.handleCodeInit.bind(this)
}
componentDidMount () {
ee.on('top:focus-search', this.focusSearchHandler)
ee.on('code:init', this.codeInitHandler)
}
componentWillUnmount () {
ee.off('top:focus-search', this.focusSearchHandler)
ee.off('code:init', this.codeInitHandler)
}
handleSearchClearButton (e) {
const { router } = this.context
this.setState({
search: '',
isSearching: false
})
this.refs.search.childNodes[0].blur
router.push('/searched')
e.preventDefault()
}
handleKeyDown (e) {
@@ -39,6 +55,23 @@ class TopBar extends React.Component {
isIME: false
})
// Clear search on ESC
if (e.keyCode === 27) {
return this.handleSearchClearButton(e)
}
// Next note on DOWN key
if (e.keyCode === 40) {
ee.emit('list:next')
e.preventDefault()
}
// Prev note on UP key
if (e.keyCode === 38) {
ee.emit('list:prior')
e.preventDefault()
}
// When the key is an alphabet, del, enter or ctr
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
this.setState({
@@ -73,14 +106,16 @@ class TopBar extends React.Component {
handleSearchChange (e) {
const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched')
} else {
e.preventDefault()
}
this.setState({
search: this.refs.searchInput.value
search: keyword
})
ee.emit('top:search', keyword)
}
handleSearchFocus (e) {
@@ -108,13 +143,19 @@ class TopBar extends React.Component {
}
handleOnSearchFocus () {
const el = this.refs.search.childNodes[0]
if (this.state.isSearching) {
this.refs.search.childNodes[0].blur()
el.blur()
} else {
this.refs.search.childNodes[0].focus()
el.focus()
el.setSelectionRange(0, el.value.length)
}
}
handleCodeInit () {
ee.emit('top:search', this.refs.searchInput.value)
}
render () {
const { config, style, location } = this.props
return (
@@ -136,19 +177,19 @@ class TopBar extends React.Component {
onChange={(e) => this.handleSearchChange(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
placeholder='Search'
placeholder={i18n.__('Search')}
type='text'
className='searchInput'
/>
</div>
{this.state.search > 0 &&
<button styleName='left-search-clearButton'
{this.state.search !== '' &&
<button styleName='control-search-input-clear'
onClick={(e) => this.handleSearchClearButton(e)}
>
<i className='fa fa-times' />
<i className='fa fa-fw fa-times' />
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>
</button>
}
</div>
</div>
</div>
{location.pathname === '/trashed' ? ''

View File

@@ -8,6 +8,7 @@ import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-rou
import { syncHistoryWithStore } from 'react-router-redux'
require('./lib/ipcClient')
require('../lib/customMeta')
import i18n from 'browser/lib/i18n'
const electron = require('electron')
@@ -46,9 +47,9 @@ function notify (...args) {
function updateApp () {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.',
buttons: ['Restart & Install', 'Not Now']
message: i18n.__('Update Boostnote'),
detail: i18n.__('New Boostnote is ready to be installed.'),
buttons: [i18n.__('Restart & Install'), i18n.__('Not Now')]
})
if (index === 0) {

View File

@@ -1,5 +1,6 @@
import _ from 'lodash'
import RcParser from 'browser/lib/RcParser'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
@@ -21,6 +22,7 @@ export const DEFAULT_CONFIG = {
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
},
ui: {
language: 'en',
theme: 'default',
showCopyNotification: true,
disableDirectWrite: false,
@@ -33,10 +35,13 @@ export const DEFAULT_CONFIG = {
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
indentType: 'space',
indentSize: '2',
enableRulers: false,
rulers: [80, 120],
displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false,
type: 'SPLIT'
type: 'SPLIT',
fetchUrlTitle: true
},
preview: {
fontSize: '14',
@@ -47,7 +52,17 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$',
latexBlockOpen: '$$',
latexBlockClose: '$$',
scrollPastEnd: false
scrollPastEnd: false,
smartQuotes: true,
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
},
blog: {
type: 'wordpress', // Available value: wordpress, add more types in the future plz
address: 'http://wordpress.com/wp-json',
authMethod: 'JWT', // Available value: JWT, USER
token: '',
username: '',
password: ''
}
}
@@ -123,6 +138,38 @@ function set (updates) {
document.body.setAttribute('data-theme', 'default')
}
if (newConfig.ui.language === 'sq') {
i18n.setLocale('sq')
} else if (newConfig.ui.language === 'zh-CN') {
i18n.setLocale('zh-CN')
} else if (newConfig.ui.language === 'zh-TW') {
i18n.setLocale('zh-TW')
} else if (newConfig.ui.language === 'da') {
i18n.setLocale('da')
} else if (newConfig.ui.language === 'fr') {
i18n.setLocale('fr')
} else if (newConfig.ui.language === 'de') {
i18n.setLocale('de')
} else if (newConfig.ui.language === 'hu') {
i18n.setLocale('hu')
} else if (newConfig.ui.language === 'ja') {
i18n.setLocale('ja')
} else if (newConfig.ui.language === 'ko') {
i18n.setLocale('ko')
} else if (newConfig.ui.language === 'no') {
i18n.setLocale('no')
} else if (newConfig.ui.language === 'pl') {
i18n.setLocale('pl')
} else if (newConfig.ui.language === 'pt') {
i18n.setLocale('pt')
} else if (newConfig.ui.language === 'ru') {
i18n.setLocale('ru')
} else if (newConfig.ui.language === 'es') {
i18n.setLocale('es')
} else {
i18n.setLocale('en')
}
let editorTheme = document.getElementById('editorTheme')
if (editorTheme == null) {
editorTheme = document.createElement('link')
@@ -150,6 +197,7 @@ function set (updates) {
function assignConfigValues (originalConfig, rcConfig) {
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
config.blog = Object.assign({}, DEFAULT_CONFIG.blog, originalConfig.blog, rcConfig.blog)
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)

View File

@@ -0,0 +1,31 @@
const fs = require('fs')
const path = require('path')
/**
* @description Copy a file from source to destination
* @param {String} srcPath
* @param {String} dstPath
* @return {Promise} an image path
*/
function copyFile (srcPath, dstPath) {
if (!path.extname(dstPath)) {
dstPath = path.join(dstPath, path.basename(srcPath))
}
return new Promise((resolve, reject) => {
const dstFolder = path.dirname(dstPath)
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
const input = fs.createReadStream(srcPath)
const output = fs.createWriteStream(dstPath)
output.on('error', reject)
input.on('error', reject)
input.on('end', () => {
resolve(dstPath)
})
input.pipe(output)
})
}
module.exports = copyFile

View File

@@ -3,19 +3,20 @@ const path = require('path')
const { findStorage } = require('browser/lib/findStorage')
/**
* @description To copy an image and return the path.
* @description Copy an image and return the path.
* @param {String} filePath
* @param {String} storageKey
* @return {String} an image path
* @param {Boolean} rename create new filename or leave the old one
* @return {Promise<any>} an image path
*/
function copyImage (filePath, storageKey) {
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 = Math.random().toString(36).slice(-16)
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)

View File

@@ -52,12 +52,12 @@ function createNote (storageKey, input) {
return storage
})
.then(function saveNote (storage) {
let key = keygen()
let key = keygen(true)
let isUnique = false
while (!isUnique) {
try {
sander.statSync(path.join(storage.path, 'notes', key + '.cson'))
key = keygen()
key = keygen(true)
} catch (err) {
if (err.code === 'ENOENT') {
isUnique = true

View File

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

View File

@@ -0,0 +1,112 @@
import copyFile from 'browser/main/lib/dataApi/copyFile'
import {findStorage} from 'browser/lib/findStorage'
import filenamify from 'filenamify'
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
*
* If images is stored in the storage, creates 'images' subfolder in target directory
* and copies images to it. Changes links to images in the content of the note
*
* @param {String} storageKey or storage path
* @param {String} noteContent Content to export
* @param {String} targetPath Path to exported file
* @param {function} outputFormatter
* @return {Promise.<*[]>}
*/
function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
const exportTasks = []
if (!storagePath) {
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})`
})
if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks)
}
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
.then(() => {
return saveToFile(exportedData, targetPath)
}).catch((err) => {
rollbackExport(tasks)
throw err
})
}
function prepareTasks (tasks, storagePath, targetPath) {
return tasks.map((task) => {
if (!path.isAbsolute(task.src)) {
task.src = path.join(storagePath, task.src)
}
if (!path.isAbsolute(task.dst)) {
task.dst = path.join(targetPath, task.dst)
}
return task
})
}
function saveToFile (data, filename) {
return new Promise((resolve, reject) => {
fs.writeFile(filename, data, (err) => {
if (err) return reject(err)
resolve(filename)
})
})
}
/**
* Remove exported files
* @param tasks Array of copy task objects. Object consists of two mandatory fields `src` and `dst`
*/
function rollbackExport (tasks) {
const folders = new Set()
tasks.forEach((task) => {
let fullpath = task.dst
if (!path.extname(task.dst)) {
fullpath = path.join(task.dst, path.basename(task.src))
}
if (fs.existsSync(fullpath)) {
fs.unlink(fullpath)
folders.add(path.dirname(fullpath))
}
})
folders.forEach((folder) => {
if (fs.readdirSync(folder).length === 0) {
fs.rmdir(folder)
}
})
}
export default exportNote

View File

@@ -1,10 +1,12 @@
const resolveStorageData = require('./resolveStorageData')
const _ = require('lodash')
const path = require('path')
const fs = require('fs')
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')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage
@@ -37,12 +39,12 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
return resolveStorageData(newStorage)
.then(function findNewNoteKey (_newStorage) {
newStorage = _newStorage
newNoteKey = keygen()
newNoteKey = keygen(true)
let isUnique = false
while (!isUnique) {
try {
sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson'))
newNoteKey = keygen()
newNoteKey = keygen(true)
} catch (err) {
if (err.code === 'ENOENT') {
isUnique = true
@@ -65,6 +67,27 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
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)
}
return Promise.all(moveTasks).then(() => noteData)
})
.then(function writeAndReturn (noteData) {
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
return noteData

View File

@@ -30,6 +30,9 @@ function validateInput (input) {
validatedInput.isPinned = !!input.isPinned
}
if (!_.isNil(input.blog)) {
validatedInput.blog = input.blog
}
validatedInput.type = input.type
switch (input.type) {
case 'MARKDOWN_NOTE':

View File

@@ -1,5 +1,4 @@
import ConfigManager from './ConfigManager'
import store from 'browser/main/store'
const nodeIpc = require('node-ipc')
const { remote, ipcRenderer } = require('electron')
@@ -18,7 +17,7 @@ nodeIpc.connectTo(
console.log(err)
})
nodeIpc.of.node.on('connect', function () {
console.log('Conncted successfully')
console.log('Connected successfully')
ipcRenderer.send('config-renew', {config: ConfigManager.get()})
})
nodeIpc.of.node.on('disconnect', function () {

View File

@@ -7,6 +7,7 @@ import store from 'browser/main/store'
import consts from 'browser/lib/consts'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class CreateFolderModal extends React.Component {
constructor (props) {
@@ -79,12 +80,12 @@ class CreateFolderModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>Create new folder</div>
<div styleName='title'>{i18n.__('Create new folder')}</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<div styleName='control-folder'>
<div styleName='control-folder-label'>Folder name</div>
<div styleName='control-folder-label'>{i18n.__('Folder name')}</div>
<input styleName='control-folder-input'
ref='name'
value={this.state.name}

View File

@@ -6,6 +6,7 @@ import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class NewNoteModal extends React.Component {
constructor (props) {
@@ -35,14 +36,16 @@ class NewNoteModal extends React.Component {
content: ''
})
.then((note) => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: note.storage + '-' + note.key}
query: {key: noteHash}
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
this.props.close()
})
@@ -73,14 +76,16 @@ class NewNoteModal extends React.Component {
}]
})
.then((note) => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: note.storage + '-' + note.key}
query: {key: noteHash}
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
this.props.close()
})
@@ -106,7 +111,7 @@ class NewNoteModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>Make a note</div>
<div styleName='title'>{i18n.__('Make a note')}</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
@@ -118,8 +123,8 @@ class NewNoteModal extends React.Component {
<i styleName='control-button-icon'
className='fa fa-file-text-o'
/><br />
<span styleName='control-button-label'>Markdown Note</span><br />
<span styleName='control-button-description'>This format is for creating text documents. Checklists, code blocks and Latex blocks are available.</span>
<span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br />
<span styleName='control-button-description'>{i18n.__('This format is for creating text documents. Checklists, code blocks and Latex blocks are available.')}</span>
</button>
<button styleName='control-button'
@@ -130,13 +135,13 @@ class NewNoteModal extends React.Component {
<i styleName='control-button-icon'
className='fa fa-code'
/><br />
<span styleName='control-button-label'>Snippet Note</span><br />
<span styleName='control-button-description'>This format is for creating code snippets. Multiple snippets can be grouped into a single note.
<span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br />
<span styleName='control-button-description'>{i18n.__('This format is for creating code snippets. Multiple snippets can be grouped into a single note.')}
</span>
</button>
</div>
<div styleName='description'><i className='fa fa-arrows-h' /> Tab to switch format</div>
<div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
</div>
)

View File

@@ -0,0 +1,199 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import PropTypes from 'prop-types'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell } = electron
const ipc = electron.ipcRenderer
class Blog extends React.Component {
constructor (props) {
super(props)
this.state = {
config: props.config,
BlogAlert: null
}
}
handleLinkClick (e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
clearMessage () {
_.debounce(() => {
this.setState({
BlogAlert: null
})
}, 2000)()
}
componentDidMount () {
this.handleSettingDone = () => {
this.setState({BlogAlert: {
type: 'success',
message: i18n.__('Successfully applied!')
}})
}
this.handleSettingError = (err) => {
this.setState({BlogAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('Error occurs!')
}})
}
this.oldBlog = this.state.config.blog
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
}
handleBlogChange (e) {
const { config } = this.state
config.blog = {
password: !_.isNil(this.refs.passwordInput) ? this.refs.passwordInput.value : config.blog.password,
username: !_.isNil(this.refs.usernameInput) ? this.refs.usernameInput.value : config.blog.username,
token: !_.isNil(this.refs.tokenInput) ? this.refs.tokenInput.value : config.blog.token,
authMethod: this.refs.authMethodDropdown.value,
address: this.refs.addressInput.value,
type: this.refs.typeDropdown.value
}
this.setState({
config
})
if (_.isEqual(this.oldBlog, config.blog)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'Blog',
type: 'warning',
message: i18n.__('You have to save!')
})
}
}
handleSaveButtonClick (e) {
const newConfig = {
blog: this.state.config.blog
}
ConfigManager.set(newConfig)
store.dispatch({
type: 'SET_UI',
config: newConfig
})
this.clearMessage()
this.props.haveToSave()
}
render () {
const {config, BlogAlert} = this.state
const blogAlertElement = BlogAlert != null
? <p className={`alert ${BlogAlert.type}`}>
{BlogAlert.message}
</p>
: null
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>{i18n.__('Blog')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Blog Type')}
</div>
<div styleName='group-section-control'>
<select
value={config.blog.type}
ref='typeDropdown'
onChange={(e) => this.handleBlogChange(e)}
>
<option value='wordpress' key='wordpress'>{i18n.__('wordpress')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Blog Address')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='addressInput'
value={config.blog.address}
type='text'
/>
</div>
</div>
<div styleName='group-control'>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
</button>
{blogAlertElement}
</div>
</div>
<div styleName='group-header2'>{i18n.__('Auth')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Authentication Method')}
</div>
<div styleName='group-section-control'>
<select
value={config.blog.authMethod}
ref='authMethodDropdown'
onChange={(e) => this.handleBlogChange(e)}
>
<option value='JWT' key='JWT'>{i18n.__('JWT')}</option>
<option value='USER' key='USER'>{i18n.__('USER')}</option>
</select>
</div>
</div>
{ config.blog.authMethod === 'JWT' &&
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Token')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='tokenInput'
value={config.blog.token}
type='text' />
</div>
</div>
}
{ config.blog.authMethod === 'USER' &&
<div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('UserName')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='usernameInput'
value={config.blog.username}
type='text' />
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Password')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='passwordInput'
value={config.blog.password}
type='password' />
</div>
</div>
</div>
}
</div>
)
}
}
Blog.propTypes = {
dispatch: PropTypes.func,
haveToSave: PropTypes.func
}
export default CSSModules(Blog, styles)

View File

@@ -1,6 +1,7 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './Crowdfunding.styl'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell } = electron
@@ -21,22 +22,22 @@ class Crowdfunding extends React.Component {
render () {
return (
<div styleName='root'>
<div styleName='header'>Crowdfunding</div>
<p>Dear everyone,</p>
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
<p>{i18n.__('Dear everyone,')}</p>
<br />
<p>Thank you for using Boostnote!</p>
<p>Boostnote is used in about 200 different countries and regions by an awesome community of developers.</p>
<p>{i18n.__('Thank you for using Boostnote!')}</p>
<p>{i18n.__('Boostnote is used in about 200 different countries and regions by an awesome community of developers.')}</p>
<br />
<p>To continue supporting this growth, and to satisfy community expectations,</p>
<p>we would like to invest more time and resources in this project.</p>
<p>{i18n.__('To continue supporting this growth, and to satisfy community expectations,')}</p>
<p>{i18n.__('we would like to invest more time and resources in this project.')}</p>
<br />
<p>If you like this project and see its potential, you can help by supporting us on OpenCollective!</p>
<p>{i18n.__('If you like this project and see its potential, you can help by supporting us on OpenCollective!')}</p>
<br />
<p>Thanks,</p>
<p>Boostnote maintainers</p>
<p>{i18n.__('Thanks,')}</p>
<p>{i18n.__('Boostnote maintainers')}</p>
<br />
<button styleName='cf-link'>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('Support via OpenCollective')}</a>
</button>
</div>
)

View File

@@ -7,6 +7,7 @@ import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { SketchPicker } from 'react-color'
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class FolderItem extends React.Component {
constructor (props) {
@@ -179,18 +180,18 @@ class FolderItem extends React.Component {
return (
<div styleName='folderItem'>
<div styleName='folderItem-left'>
Are you sure to <span styleName='folderItem-left-danger'>delete</span> this folder?
{i18n.__('Are you sure to ')} <span styleName='folderItem-left-danger'>{i18n.__(' delete')}</span> {i18n.__('this folder?')}
</div>
<div styleName='folderItem-right'>
<button styleName='folderItem-right-dangerButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
>
Confirm
{i18n.__('Confirm')}
</button>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleCancelButtonClick(e)}
>
Cancel
{i18n.__('Cancel')}
</button>
</div>
</div>
@@ -231,12 +232,12 @@ class FolderItem extends React.Component {
<button styleName='folderItem-right-button'
onClick={(e) => this.handleEditButtonClick(e)}
>
Edit
{i18n.__('Edit')}
</button>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleDeleteButtonClick(e)}
>
Delete
{i18n.__('Delete')}
</button>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import styles from './FolderList.styl'
import store from 'browser/main/store'
import FolderItem from './FolderItem'
import { SortableContainer } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class FolderList extends React.Component {
render () {
@@ -24,7 +25,7 @@ class FolderList extends React.Component {
<div styleName='folderList'>
{folderList.length > 0
? folderList
: <div styleName='folderList-empty'>No Folders</div>
: <div styleName='folderList-empty'>{i18n.__('No Folders')}</div>
}
</div>
)

View File

@@ -5,6 +5,7 @@ import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const ipc = electron.ipcRenderer
@@ -23,13 +24,13 @@ class HotkeyTab extends React.Component {
this.handleSettingDone = () => {
this.setState({keymapAlert: {
type: 'success',
message: 'Successfully applied!'
message: i18n.__('Successfully applied!')
}})
}
this.handleSettingError = (err) => {
this.setState({keymapAlert: {
type: 'error',
message: err.message != null ? err.message : 'Error occurs!'
message: err.message != null ? err.message : i18n.__('Error occurs!')
}})
}
this.oldHotkey = this.state.config.hotkey
@@ -77,7 +78,7 @@ class HotkeyTab extends React.Component {
this.props.haveToSave({
tab: 'Hotkey',
type: 'warning',
message: 'You have to save!'
message: i18n.__('You have to save!')
})
}
}
@@ -102,9 +103,9 @@ class HotkeyTab extends React.Component {
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>Hotkeys</div>
<div styleName='group-header'>{i18n.__('Hotkeys')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>Show/Hide Boostnote</div>
<div styleName='group-section-label'>{i18n.__('Show/Hide Boostnote')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}
@@ -124,7 +125,7 @@ class HotkeyTab extends React.Component {
}
</button>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>Save
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
</button>
{keymapAlertElement}
</div>

View File

@@ -5,6 +5,7 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell, remote } = electron
@@ -38,11 +39,11 @@ class InfoTab extends React.Component {
if (!newConfig.amaEnabled) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
this.setState({
amaMessage: 'We hope we will gain your trust'
amaMessage: i18n.__('We hope we will gain your trust')
})
} else {
this.setState({
amaMessage: 'Thank\'s for trust us'
amaMessage: i18n.__('Thank\'s for trusting us')
})
}
@@ -69,48 +70,48 @@ class InfoTab extends React.Component {
return (
<div styleName='root'>
<div styleName='header--sub'>Community</div>
<div styleName='header--sub'>{i18n.__('Community')}</div>
<div styleName='top'>
<ul styleName='list'>
<li>
<a href='https://boostnote.io/#subscribe'
onClick={(e) => this.handleLinkClick(e)}
>Subscribe to Newsletter</a>
>{i18n.__('Subscribe to Newsletter')}</a>
</li>
<li>
<a href='https://github.com/BoostIO/Boostnote/issues'
onClick={(e) => this.handleLinkClick(e)}
>GitHub</a>
>{i18n.__('GitHub')}</a>
</li>
<li>
<a href='https://medium.com/boostnote'
<a href='https://boostlog.io/@junp1234'
onClick={(e) => this.handleLinkClick(e)}
>Blog</a>
>{i18n.__('Blog')}</a>
</li>
<li>
<a href='https://www.facebook.com/groups/boostnote'
onClick={(e) => this.handleLinkClick(e)}
>Facebook Group</a>
>{i18n.__('Facebook Group')}</a>
</li>
<li>
<a href='https://twitter.com/boostnoteapp'
onClick={(e) => this.handleLinkClick(e)}
>Twitter</a>
>{i18n.__('Twitter')}</a>
</li>
</ul>
</div>
<hr />
<div styleName='header--sub'>About</div>
<div styleName='header--sub'>{i18n.__('About')}</div>
<div styleName='top'>
<div styleName='icon-space'>
<img styleName='icon' src='../resources/app.png' width='92' height='92' />
<div styleName='icon-right'>
<div styleName='appId'>Boostnote {appVersion}</div>
<div styleName='appId'>{i18n.__('Boostnote')} {appVersion}</div>
<div styleName='description'>
An open source note-taking app made for programmers just like you.
{i18n.__('An open source note-taking app made for programmers just like you.')}
</div>
</div>
</div>
@@ -120,35 +121,35 @@ class InfoTab extends React.Component {
<li>
<a href='https://boostnote.io'
onClick={(e) => this.handleLinkClick(e)}
>Website</a>
>{i18n.__('Website')}</a>
</li>
<li>
<a href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
onClick={(e) => this.handleLinkClick(e)}
>Development</a> : Development configurations for Boostnote.
>{i18n.__('Development')}</a>{i18n.__(' : Development configurations for Boostnote.')}
</li>
<li styleName='cc'>
Copyright (C) 2017 Maisin&Co.
{i18n.__('Copyright (C) 2017 - 2018 BoostIO')}
</li>
<li styleName='cc'>
License: GPL v3
{i18n.__('License: GPL v3')}
</li>
</ul>
<hr styleName='separate-line' />
<div styleName='policy'>Analytics</div>
<div>Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.</div>
<div>You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
<div styleName='policy'>{i18n.__('Analytics')}</div>
<div>{i18n.__('Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.')}</div>
<div>{i18n.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
<br />
<div>You can choose to enable or disable this option.</div>
<div>{i18n.__('You can choose to enable or disable this option.')}</div>
<input onChange={(e) => this.handleConfigChange(e)}
checked={this.state.config.amaEnabled}
ref='amaEnabled'
type='checkbox'
/>
Enable analytics to help improve Boostnote<br />
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
{i18n.__('Enable analytics to help improve Boostnote')}<br />
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}</button>
<br />
{this.infoMessage()}
</div>

View File

@@ -6,6 +6,7 @@ import consts from 'browser/lib/consts'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import FolderList from './FolderList'
import i18n from 'browser/lib/i18n'
const { shell, remote } = require('electron')
const { dialog } = remote
@@ -46,9 +47,9 @@ class StorageItem extends React.Component {
handleUnlinkButtonClick (e) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Unlink Storage',
detail: 'Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.',
buttons: ['Unlink', 'Cancel']
message: i18n.__('Unlink Storage'),
detail: i18n.__('Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.'),
buttons: [i18n.__('Unlink'), i18n.__('Cancel')]
})
if (index === 0) {
@@ -127,7 +128,7 @@ class StorageItem extends React.Component {
<i className='fa fa-plus' />
<span styleName='header-control-button-tooltip'
style={{left: -20}}
>Add Folder</span>
>{i18n.__('Add Folder')}</span>
</button>
<button styleName='header-control-button'
onClick={(e) => this.handleExternalButtonClick(e)}
@@ -135,7 +136,7 @@ class StorageItem extends React.Component {
<i className='fa fa-external-link' />
<span styleName='header-control-button-tooltip'
style={{left: -50}}
>Open Storage folder</span>
>{i18n.__('Open Storage folder')}</span>
</button>
<button styleName='header-control-button'
onClick={(e) => this.handleUnlinkButtonClick(e)}
@@ -143,7 +144,7 @@ class StorageItem extends React.Component {
<i className='fa fa-unlink' />
<span styleName='header-control-button-tooltip'
style={{left: -10}}
>Unlink</span>
>{i18n.__('Unlink')}</span>
</button>
</div>
</div>

View File

@@ -4,6 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './StoragesTab.styl'
import dataApi from 'browser/main/lib/dataApi'
import StorageItem from './StorageItem'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell, remote } = electron
@@ -14,7 +15,7 @@ function browseFolder () {
const defaultPath = remote.app.getPath('home')
return new Promise((resolve, reject) => {
dialog.showOpenDialog({
title: 'Select Directory',
title: i18n.__('Select Directory'),
defaultPath,
properties: ['openDirectory', 'createDirectory']
}, function (targetPaths) {
@@ -69,16 +70,16 @@ class StoragesTab extends React.Component {
})
return (
<div styleName='list'>
<div styleName='header'>Storages</div>
<div styleName='header'>{i18n.__('Storages')}</div>
{storageList.length > 0
? storageList
: <div styleName='list-empty'>No storage found.</div>
: <div styleName='list-empty'>{i18n.__('No storage found.')}</div>
}
<div styleName='list-control'>
<button styleName='list-control-addStorageButton'
onClick={(e) => this.handleAddStorageButton(e)}
>
<i className='fa fa-plus' /> Add Storage Location
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
</button>
</div>
</div>
@@ -140,13 +141,13 @@ class StoragesTab extends React.Component {
return (
<div styleName='addStorage'>
<div styleName='addStorage-header'>Add Storage</div>
<div styleName='addStorage-header'>{i18n.__('Add Storage')}</div>
<div styleName='addStorage-body'>
<div styleName='addStorage-body-section'>
<div styleName='addStorage-body-section-label'>
Name
{i18n.__('Name')}
</div>
<div styleName='addStorage-body-section-name'>
<input styleName='addStorage-body-section-name-input'
@@ -158,25 +159,25 @@ class StoragesTab extends React.Component {
</div>
<div styleName='addStorage-body-section'>
<div styleName='addStorage-body-section-label'>Type</div>
<div styleName='addStorage-body-section-label'>{i18n.__('Type')}</div>
<div styleName='addStorage-body-section-type'>
<select styleName='addStorage-body-section-type-select'
value={this.state.newStorage.type}
readOnly
>
<option value='FILESYSTEM'>File System</option>
<option value='FILESYSTEM'>{i18n.__('File System')}</option>
</select>
<div styleName='addStorage-body-section-type-description'>
Setting up 3rd-party cloud storage integration:{' '}
{i18n.__('Setting up 3rd-party cloud storage integration:')}{' '}
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
onClick={(e) => this.handleLinkClick(e)}
>Cloud-Syncing-and-Backup</a>
>{i18n.__('Cloud-Syncing-and-Backup')}</a>
</div>
</div>
</div>
<div styleName='addStorage-body-section'>
<div styleName='addStorage-body-section-label'>Location
<div styleName='addStorage-body-section-label'>{i18n.__('Location')}
</div>
<div styleName='addStorage-body-section-path'>
<input styleName='addStorage-body-section-path-input'
@@ -196,10 +197,10 @@ class StoragesTab extends React.Component {
<div styleName='addStorage-body-control'>
<button styleName='addStorage-body-control-createButton'
onClick={(e) => this.handleAddStorageCreateButton(e)}
>Add</button>
>{i18n.__('Add')}</button>
<button styleName='addStorage-body-control-cancelButton'
onClick={(e) => this.handleAddStorageCancelButton(e)}
>Cancel</button>
>{i18n.__('Cancel')}</button>
</div>
</div>

View File

@@ -9,6 +9,7 @@ import ReactCodeMirror from 'react-codemirror'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
@@ -29,13 +30,13 @@ class UiTab extends React.Component {
this.handleSettingDone = () => {
this.setState({UiAlert: {
type: 'success',
message: 'Successfully applied!'
message: i18n.__('Successfully applied!')
}})
}
this.handleSettingError = (err) => {
this.setState({UiAlert: {
type: 'error',
message: err.message != null ? err.message : 'Error occurs!'
message: err.message != null ? err.message : i18n.__('Error occurs!')
}})
}
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
@@ -61,6 +62,7 @@ class UiTab extends React.Component {
const newConfig = {
ui: {
theme: this.refs.uiTheme.value,
language: this.refs.uiLanguage.value,
showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked,
disableDirectWrite: this.refs.uiD2w != null
@@ -73,10 +75,13 @@ class UiTab extends React.Component {
fontFamily: this.refs.editorFontFamily.value,
indentType: this.refs.editorIndentType.value,
indentSize: this.refs.editorIndentSize.value,
enableRulers: this.refs.enableEditorRulers.value === 'true',
rulers: this.refs.editorRulers.value.replace(/[^0-9,]/g, '').split(','),
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value,
scrollPastEnd: this.refs.scrollPastEnd.checked
scrollPastEnd: this.refs.scrollPastEnd.checked,
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
},
preview: {
fontSize: this.refs.previewFontSize.value,
@@ -87,7 +92,9 @@ class UiTab extends React.Component {
latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
smartQuotes: this.refs.previewSmartQuotes.checked,
sanitize: this.refs.previewSanitize.value
}
}
@@ -105,7 +112,7 @@ class UiTab extends React.Component {
this.props.haveToSave({
tab: 'UI',
type: 'warning',
message: 'You have to save!'
message: i18n.__('You have to save!')
})
}
})
@@ -147,25 +154,53 @@ class UiTab extends React.Component {
const themes = consts.THEMES
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'
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>Interface</div>
<div styleName='group-header'>{i18n.__('Interface')}</div>
<div styleName='group-section'>
Interface Theme
{i18n.__('Interface Theme')}
<div styleName='group-section-control'>
<select value={config.ui.theme}
onChange={(e) => this.handleUIChange(e)}
ref='uiTheme'
>
<option value='default'>Default</option>
<option value='white'>White</option>
<option value='solarized-dark'>Solarized Dark</option>
<option value='dark'>Dark</option>
<option value='default'>{i18n.__('Default')}</option>
<option value='white'>{i18n.__('White')}</option>
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
<option value='dark'>{i18n.__('Dark')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
{i18n.__('Language')}
<div styleName='group-section-control'>
<select value={config.ui.language}
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>
</select>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
@@ -173,7 +208,7 @@ class UiTab extends React.Component {
ref='showCopyNotification'
type='checkbox'
/>&nbsp;
Show &quot;Saved to Clipboard&quot; notification when copying
{i18n.__('Show "Saved to Clipboard" notification when copying')}
</label>
</div>
<div styleName='group-checkBoxSection'>
@@ -183,7 +218,7 @@ class UiTab extends React.Component {
ref='confirmDeletion'
type='checkbox'
/>&nbsp;
Show a confirmation dialog when deleting notes
{i18n.__('Show a confirmation dialog when deleting notes')}
</label>
</div>
{
@@ -205,7 +240,7 @@ class UiTab extends React.Component {
<div styleName='group-section'>
<div styleName='group-section-label'>
Editor Theme
{i18n.__('Editor Theme')}
</div>
<div styleName='group-section-control'>
<select value={config.editor.theme}
@@ -225,7 +260,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Editor Font Size
{i18n.__('Editor Font Size')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -238,7 +273,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Editor Font Family
{i18n.__('Editor Font Family')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -251,7 +286,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Editor Indent Style
{i18n.__('Editor Indent Style')}
</div>
<div styleName='group-section-control'>
<select value={config.editor.indentSize}
@@ -267,41 +302,70 @@ class UiTab extends React.Component {
ref='editorIndentType'
onChange={(e) => this.handleUIChange(e)}
>
<option value='space'>Spaces</option>
<option value='tab'>Tabs</option>
<option value='space'>{i18n.__('Spaces')}</option>
<option value='tab'>{i18n.__('Tabs')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Switch to Preview
{i18n.__('Editor Rulers')}
</div>
<div styleName='group-section-control'>
<div>
<select value={config.editor.enableRulers}
ref='enableEditorRulers'
onChange={(e) => this.handleUIChange(e)}
>
<option value='true'>
{i18n.__('Enable')}
</option>
<option value='false'>
{i18n.__('Disable')}
</option>
</select>
</div>
<input styleName='group-section-control-input'
style={{ display: enableEditRulersStyle }}
ref='editorRulers'
value={config.editor.rulers}
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Switch to Preview')}
</div>
<div styleName='group-section-control'>
<select value={config.editor.switchPreview}
ref='editorSwitchPreview'
onChange={(e) => this.handleUIChange(e)}
>
<option value='BLUR'>When Editor Blurred</option>
<option value='RIGHTCLICK'>On Right Click</option>
<option value='BLUR'>{i18n.__('When Editor Blurred')}</option>
<option value='DBL_CLICK'>{i18n.__('When Editor Blurred, Edit On Double Click')}</option>
<option value='RIGHTCLICK'>{i18n.__('On Right Click')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Editor Keymap
{i18n.__('Editor Keymap')}
</div>
<div styleName='group-section-control'>
<select value={config.editor.keyMap}
ref='editorKeyMap'
onChange={(e) => this.handleUIChange(e)}
>
<option value='sublime'>default</option>
<option value='vim'>vim</option>
<option value='emacs'>emacs</option>
<option value='sublime'>{i18n.__('default')}</option>
<option value='vim'>{i18n.__('vim')}</option>
<option value='emacs'>{i18n.__('emacs')}</option>
</select>
<p styleName='note-for-keymap'> Please restart boostnote after you change the keymap</p>
<p styleName='note-for-keymap'>{i18n.__('⚠️ Please restart boostnote after you change the keymap')}</p>
</div>
</div>
@@ -312,7 +376,7 @@ class UiTab extends React.Component {
ref='editorDisplayLineNumbers'
type='checkbox'
/>&nbsp;
Show line numbers in the editor
{i18n.__('Show line numbers in the editor')}
</label>
</div>
@@ -323,14 +387,25 @@ class UiTab extends React.Component {
ref='scrollPastEnd'
type='checkbox'
/>&nbsp;
Allow editor to scroll past the last line
{i18n.__('Allow editor to scroll past the last line')}
</label>
</div>
<div styleName='group-header2'>Preview</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.fetchUrlTitle}
ref='editorFetchUrlTitle'
type='checkbox'
/>&nbsp;
{i18n.__('Bring in web page title when pasting URL on editor')}
</label>
</div>
<div styleName='group-header2'>{i18n.__('Preview')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Preview Font Size
{i18n.__('Preview Font Size')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -343,7 +418,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Preview Font Family
{i18n.__('Preview Font Family')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -355,7 +430,7 @@ class UiTab extends React.Component {
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>Code block Theme</div>
<div styleName='group-section-label'>{i18n.__('Code block Theme')}</div>
<div styleName='group-section-control'>
<select value={config.preview.codeBlockTheme}
ref='previewCodeBlockTheme'
@@ -376,7 +451,7 @@ class UiTab extends React.Component {
ref='previewScrollPastEnd'
type='checkbox'
/>&nbsp;
Allow preview to scroll past the last line
{i18n.__('Allow preview to scroll past the last line')}
</label>
</div>
<div styleName='group-checkBoxSection'>
@@ -386,12 +461,39 @@ class UiTab extends React.Component {
ref='previewLineNumber'
type='checkbox'
/>&nbsp;
Show line numbers for preview code blocks
{i18n.__('Show line numbers for preview code blocks')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.smartQuotes}
ref='previewSmartQuotes'
type='checkbox'
/>&nbsp;
Enable smart quotes
</label>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Sanitization')}
</div>
<div styleName='group-section-control'>
<select value={config.preview.sanitize}
ref='previewSanitize'
onChange={(e) => this.handleUIChange(e)}
>
<option value='STRICT'> {i18n.__('Only allow secure html tags (recommended)')}
</option>
<option value='ALLOW_STYLES'> {i18n.__('Allow styles')}</option>
<option value='NONE'> {i18n.__('Allow dangerous html tags')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
LaTeX Inline Open Delimiter
{i18n.__('LaTeX Inline Open Delimiter')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -404,7 +506,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
LaTeX Inline Close Delimiter
{i18n.__('LaTeX Inline Close Delimiter')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -417,7 +519,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
LaTeX Block Open Delimiter
{i18n.__('LaTeX Block Open Delimiter')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -430,7 +532,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
LaTeX Block Close Delimiter
{i18n.__('LaTeX Block Close Delimiter')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -444,7 +546,7 @@ class UiTab extends React.Component {
<div styleName='group-control'>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveUIClick(e)}>Save
onClick={(e) => this.handleSaveUIClick(e)}>{i18n.__('Save')}
</button>
{UiAlertElement}
</div>

View File

@@ -6,11 +6,13 @@ import UiTab from './UiTab'
import InfoTab from './InfoTab'
import Crowdfunding from './Crowdfunding'
import StoragesTab from './StoragesTab'
import Blog from './Blog'
import ModalEscButton from 'browser/components/ModalEscButton'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferencesModal.styl'
import RealtimeNotification from 'browser/components/RealtimeNotification'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class Preferences extends React.Component {
constructor (props) {
@@ -19,7 +21,8 @@ class Preferences extends React.Component {
this.state = {
currentTab: 'STORAGES',
UIAlert: '',
HotkeyAlert: ''
HotkeyAlert: '',
BlogAlert: ''
}
}
@@ -75,6 +78,14 @@ class Preferences extends React.Component {
return (
<Crowdfunding />
)
case 'BLOG':
return (
<Blog
dispatch={dispatch}
config={config}
haveToSave={alert => this.setState({BlogAlert: alert})}
/>
)
case 'STORAGES':
default:
return (
@@ -107,11 +118,12 @@ class Preferences extends React.Component {
const content = this.renderContent()
const tabs = [
{target: 'STORAGES', label: 'Storage'},
{target: 'HOTKEY', label: 'Hotkeys', Hotkey: this.state.HotkeyAlert},
{target: 'UI', label: 'Interface', UI: this.state.UIAlert},
{target: 'INFO', label: 'About'},
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
{target: 'STORAGES', label: i18n.__('Storage')},
{target: 'HOTKEY', label: i18n.__('Hotkeys'), Hotkey: this.state.HotkeyAlert},
{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}
]
const navButtons = tabs.map((tab) => {
@@ -140,7 +152,7 @@ class Preferences extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='top-bar'>
<p>Your preferences for Boostnote</p>
<p>{i18n.__('Your preferences for Boostnote')}</p>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleEscButtonClick(e)} />
<div styleName='nav'>

View File

@@ -5,6 +5,7 @@ import styles from './RenameFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'
class RenameFolderModal extends React.Component {
constructor (props) {
@@ -72,13 +73,13 @@ class RenameFolderModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>Rename Folder</div>
<div styleName='title'>{i18n.__('Rename Folder')}</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<input styleName='control-input'
placeholder='Folder Name'
placeholder={i18n.__('Folder Name')}
ref='name'
value={this.state.name}
onChange={(e) => this.handleChange(e)}

View File

@@ -27,7 +27,7 @@ function data (state = defaultDataMap(), action) {
action.notes.some((note) => {
if (note === undefined) return true
const uniqueKey = note.storage + '-' + note.key
const uniqueKey = note.key
const folderKey = note.storage + '-' + note.folder
state.noteMap.set(uniqueKey, note)
@@ -66,7 +66,7 @@ function data (state = defaultDataMap(), action) {
case 'UPDATE_NOTE':
{
const note = action.note
const uniqueKey = note.storage + '-' + note.key
const uniqueKey = note.key
const folderKey = note.storage + '-' + note.folder
const oldNote = state.noteMap.get(uniqueKey)
@@ -162,9 +162,9 @@ function data (state = defaultDataMap(), action) {
case 'MOVE_NOTE':
{
const originNote = action.originNote
const originKey = originNote.storage + '-' + originNote.key
const originKey = originNote.key
const note = action.note
const uniqueKey = note.storage + '-' + note.key
const uniqueKey = note.key
const folderKey = note.storage + '-' + note.folder
const oldNote = state.noteMap.get(uniqueKey)
@@ -297,7 +297,7 @@ function data (state = defaultDataMap(), action) {
}
case 'DELETE_NOTE':
{
const uniqueKey = action.storageKey + '-' + action.noteKey
const uniqueKey = action.noteKey
const targetNote = state.noteMap.get(uniqueKey)
state = Object.assign({}, state)
@@ -423,7 +423,7 @@ function data (state = defaultDataMap(), action) {
state.folderNoteMap = new Map(state.folderNoteMap)
state.tagNoteMap = new Map(state.tagNoteMap)
action.notes.forEach((note) => {
const uniqueKey = note.storage + '-' + note.key
const uniqueKey = note.key
const folderKey = note.storage + '-' + note.folder
state.noteMap.set(uniqueKey, note)
@@ -483,7 +483,7 @@ function data (state = defaultDataMap(), action) {
state.tagNoteMap = new Map(state.tagNoteMap)
state.starredSet = new Set(state.starredSet)
notes.forEach((note) => {
const noteKey = storage.key + '-' + note.key
const noteKey = note.key
state.noteMap.delete(noteKey)
state.starredSet.delete(noteKey)
note.tags.forEach((tag) => {

View File

@@ -5,7 +5,7 @@ $danger-color = #c9302c
$danger-lighten-color = lighten(#c9302c, 5%)
// Layouts
$statusBar-height = 0px
$statusBar-height = 22px
$sideNav-width = 200px
$sideNav--folded-width = 44px
$topBar-height = 60px

View File

@@ -9,7 +9,7 @@ Thank you for your help in advance.
### About copyright of Pull Request
If you make a pull request, It means you agree to transfer the copyright of the code changes to Maisin&Co.
If you make a pull request, It means you agree to transfer the copyright of the code changes to BoostIO.
It doesn't mean Boostnote will become a paid app. If we want to earn some money, We will try other way, which is some kind of cloud storage, Mobile app integration or some SPECIAL features.
Because GPL v3 is too strict to be compatible with any other License, We thought this is needed to replace the license with much freer one(like BSD, MIT) somewhen.
@@ -27,7 +27,7 @@ Because GPL v3 is too strict to be compatible with any other License, We thought
### Об авторских правах Pull Request
Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в Maisin&Co.
Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в BoostIO.
Это не означает, что Boostnote станет платным приложением. Если мы захотим заработать немного денег, мы найдем другой способ. Например, использование облачного хранилища, интеграцией мобильных приложений или другими специальными функциями.
Так как лицензия GPL v3 слишком строгая, чтобы быть совместимой с любой другой лицензией, мы думаем, что нужно заменить лицензию на более свободную (например, BSD, MIT).
@@ -45,7 +45,7 @@ Because GPL v3 is too strict to be compatible with any other License, We thought
### Pull Request의 저작권에 관하여
당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 Maisin&Co에 양도한다는 것에 동의한다는 의미입니다.
당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 BoostIO에 양도한다는 것에 동의한다는 의미입니다.
이것은 Boostnote가 유료화가 되는 것을 의미하는 건 아닙니다. 만약 우리가 자금이 필요하다면, 우리는 클라우드 연동, 모바일 앱 통합 혹은 특수한 기능 같은 것을 사용해 수입 창출을 시도할 것입니다.
GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무 엄격하므로, 우리는 BSD, MIT 라이센스와 같은 더 자유로운 라이센스로 교체하는 것을 생각하고 있습니다.
@@ -63,7 +63,7 @@ GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무
### Pull requestの著作権について
Pull requestをすることはその変化分のコードの著作権をMaisin&Co.に譲渡することに同意することになります。
Pull requestをすることはその変化分のコードの著作権をBoostIOに譲渡することに同意することになります。
アプリケーションのLicenseをいつでも変える選択肢を残したいと思うからです。
これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。
@@ -83,7 +83,7 @@ Pull requestをすることはその変化分のコードの著作権をMaisin&C
感谢您对我们的支持。
### 关于您提供的Pull Request的著作权版权问题
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给Maisin&Co
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给BoostIO
这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益我们会尝试一些其他的方法比如说云存储、绑定手机软件等。
因为GPLv3过于严格不能和其他的一些协议兼容所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议比如说BSD、MIT。

View File

@@ -1,5 +1,5 @@
# Build
Diese Seite ist auch verfügbar in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## Umgebungen
* npm: 4.x

View File

@@ -1,25 +1,31 @@
# How to debug Boostnote (Electron app)
Diese Seite ist auch verfügbar in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/add-german-documents/docs/de/debug.md).
Boostnote is eine Electron app, somit basiert sie auf Chromium; Entwickler können die `Developer Tools` verwenden, wie Google Chrome.
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
Boostnote ist eine Electron App und basiert auf Chromium.
Zum Entwicklen verwendest du am Besten die `Developer Tools` von Google Chrome verwenden. Diese kannst du ganz einfach im unter dem Menüpunkt `View` mit `Toggle Developer Tools` aktivieren:
Du kannst die `Developer Tools` so einschalten:
![how_to_toggle_devTools](https://cloud.githubusercontent.com/assets/11307908/24343585/162187e2-127c-11e7-9c01-23578db03ecf.png)
Die `Developer Tools` schauen dann ungefähr so aus:
Die Anzeige der `Developer Tools` sieht in etwa so aus:
![Developer_Tools](https://cloud.githubusercontent.com/assets/11307908/24343545/eff9f3a6-127b-11e7-94cf-cb67bfda634a.png)
Wenn Fehler vorkommen, werden die Fehlermeldungen in der `console` ausgegeben.
## Debugging
Zum Beispiel kannst du mit dem `debugger` Haltepunkte im Code setzen wie hier veranschaulicht:
Fehlermeldungen werden in der Regel in der `console` ausgegeben, die du über den gleichnamigen Reiter der `Developer Tools` anzeigen lassen kannst. Zum Debuggen kannst du beispielsweise über den `debugger` Haltepunkte im Code setzen.
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png)
Das ist ledigtlich ein Beispiel, du kannst die Art von Debugging verwenden die für dich am besten ist.
Du kannst aber natürlich auch die Art von Debugging verwenden mit der du am besten zurecht kommst.
## Referenz
* [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools)
---
Special thanks: Translated by [gino909](https://github.com/gino909)
Special thanks: Translated by [gino909](https://github.com/gino909), [mdeuerlein](https://github.com/mdeuerlein)

View File

@@ -1,5 +1,5 @@
# How to debug Boostnote (Electron app)
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/add-german-documents/docs/de/debug.md).
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.

View File

@@ -1,5 +1,5 @@
# Build
Cette page est également disponible en [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), et en [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md).
Cette page est également disponible en [Anglais](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md)
## Environnements
* npm: 4.x

View File

@@ -1,5 +1,5 @@
# Comment débugger Boostnote (Application Electron)
Cette page est également disponible en [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), et en [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md)
Cette page est également disponible en [Angalis](https://github.com/BoostIO/Boostnote/blob/master/docs/debug.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md)
Boostnote est une application Electron donc basée sur Chromium. Il est possible d'utiliser les `Developer Tools` comme dans Google Chrome.

86
docs/zh_TW/build.md Normal file
View File

@@ -0,0 +1,86 @@
# 編譯
此文件還提供下列的語言 [日文](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [韓文](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [俄文](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [簡體中文](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [法文](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [德文](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## 環境
* npm: 4.x
* node: 7.x
`$ grunt pre-build``npm v5.x` 有問題,所以只能用 `npm v4.x`
## 開發
我們使用 Webpack HMR 來開發 Boostnote。

在專案根目錄底下執行下列指令,將會以原始設置啟動 Boostnote。
**用 yarn 來安裝必要 packages**
```bash
$ yarn
```
**開始開發**
```
$ yarn run dev-start
```
上述指令同時運行了 `yarn run webpack``yarn run hot`,相當於將這兩個指令在不同的 terminal 中運行。
`webpack` 會同時監控修改過的程式碼,並
The `webpack` will watch for code changes and then apply them automatically.
If the following error occurs: `Failed to load resource: net::ERR_CONNECTION_REFUSED`, please reload Boostnote.
![net::ERR_CONNECTION_REFUSED](https://cloud.githubusercontent.com/assets/11307908/24343004/081e66ae-1279-11e7-8d9e-7f478043d835.png)
> ### Notice
> There are some cases where you have to refresh the app manually.
> 1. When editing a constructor method of a component
> 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.)
## Deploy
We use Grunt to automate deployment.
You can build the program by using `grunt`. However, we don't recommend this because the default task includes codesign and authenticode.
So, we've prepared a separate script which just makes an executable file.
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
```
grunt pre-build
```
You will find the executable in the `dist` directory. Note, the auto updater won't work because the app isn't signed.
If you find it necessary, you can use codesign or authenticode with this executable.
## Make own distribution packages (deb, rpm)
Distribution packages are created by exec `grunt build` on Linux platform (e.g. Ubuntu, Fedora).
> Note: You can create both `.deb` and `.rpm` in a single environment.
After installing the supported version of `node` and `npm`, install build dependency packages.
Ubuntu/Debian:
```
$ sudo apt-get install -y rpm fakeroot
```
Fedora:
```
$ sudo dnf install -y dpkg dpkg-dev rpm-build fakeroot
```
Then execute `grunt build`.
```
$ grunt build
```
You will find `.deb` and `.rpm` in the `dist` directory.

View File

@@ -0,0 +1,52 @@
(function (mod) {
if (typeof exports === 'object' && typeof module === 'object') { // Common JS
mod(require('../codemirror/lib/codemirror'))
} else if (typeof define === 'function' && define.amd) { // AMD
define(['../codemirror/lib/codemirror'], mod)
} else { // Plain browser env
mod(CodeMirror)
}
})(function (CodeMirror) {
'use strict'
var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/
var emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/
var unorderedListRE = /[*+-]\s/
CodeMirror.commands.boostNewLineAndIndentContinueMarkdownList = function (cm) {
if (cm.getOption('disableInput')) return CodeMirror.Pass
var ranges = cm.listSelections()
var replacements = []
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head
var eolState = cm.getStateAfter(pos.line)
var inList = eolState.list !== false
var inQuote = eolState.quote !== 0
var line = cm.getLine(pos.line)
var match = listRE.exec(line)
if (!ranges[i].empty() || (!inList && !inQuote) || !match || pos.ch < match[2].length - 1) {
cm.execCommand('newlineAndIndent')
return
}
if (emptyListRE.test(line)) {
if (!/>\s*$/.test(line)) {
cm.replaceRange('', {
line: pos.line, ch: 0
}, {
line: pos.line, ch: pos.ch + 1
})
}
replacements[i] = '\n'
} else {
var indent = match[1]
var after = match[5]
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf('>') >= 0
? match[2].replace('x', ' ')
: (parseInt(match[3], 10) + 1) + match[4]
replacements[i] = '\n' + indent + bullet + after
}
}
cm.replaceSelections(replacements)
}
})

View File

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

View File

@@ -69,6 +69,10 @@ ipc.on('update-app-confirm', function (event, msg) {
}
})
app.on('window-all-closed', function () {
app.quit()
})
app.on('ready', function () {
mainWindow = require('./main-window')

View File

@@ -1,6 +1,7 @@
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const shell = electron.shell
const ipc = electron.ipcMain
const mainWindow = require('./main-window')
const macOS = process.platform === 'darwin'
@@ -259,6 +260,23 @@ const view = {
]
}
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 (!editorFocused) {
mainWindow.webContents.send('top:focus-search')
event.preventDefault()
}
}
})
ipc.on('editor:focused', (event, isFocused) => {
editorFocused = isFocused
})
const window = {
label: 'Window',
submenu: [
@@ -267,6 +285,11 @@ const window = {
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Toggle Full Screen',
accelerator: 'Command+Control+F',
selector: 'toggleFullScreen:'
},
{
label: 'Close',
accelerator: 'Command+W',

View File

@@ -56,11 +56,8 @@ if (process.platform === 'darwin' || process.env.DESKTOP_SESSION === 'cinnamon')
app.on('before-quit', function (e) {
mainWindow.removeAllListeners()
})
} else {
app.on('window-all-closed', function () {
app.quit()
})
}
mainWindow.on('resize', _.throttle(storeWindowSize, 500))
function storeWindowSize () {

View File

@@ -57,6 +57,10 @@
opacity: 1 !important;
pointer-events: auto !important;
}
.CodeMirror-ruler {
border-left-color: rgba(142, 142, 142, 0.5);
mix-blend-mode: difference;
}
</style>
</head>
<body>
@@ -78,7 +82,7 @@
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
<script src="../node_modules/codemirror/addon/edit/continuelist.js"></script>
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
@@ -89,6 +93,7 @@
<script src="../node_modules/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="../node_modules/codemirror/addon/search/jump-to-line.js"></script>
<script src="../node_modules/codemirror/addon/dialog/dialog.js"></script>
<script src="../node_modules/codemirror/addon/display/rulers.js"></script>
<script src="../node_modules/raphael/raphael.min.js"></script>
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
@@ -97,7 +102,6 @@
</script>
<script src="../node_modules/js-sequence-diagrams/fucknpm/sequence-diagram-min.js"></script>
<script src="../node_modules/katex/dist/katex.min.js"></script>
<script src="../node_modules/react/dist/react.min.js"></script>
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script>

153
locales/da.json Normal file
View File

@@ -0,0 +1,153 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "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",
".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": "Delete",
"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",
"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",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"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.",
"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",
"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"
}

155
locales/de.json Normal file
View File

@@ -0,0 +1,155 @@
{
"Notes": "Notizen",
"Tags": "Tags",
"Preferences": "Einstellungen",
"Make a note": "Notiz erstellen",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "um eine neue Notiz zu erstellen",
"Toggle Mode": "Modus umschalten",
"Trash": "Papierkorb",
"MODIFICATION DATE": "ÄNDERUNGSDATUM",
"Words": "Wörter",
"Letters": "Buchstaben",
"STORAGE": "SPEICHERORT",
"FOLDER": "ORDNER",
"CREATION DATE": "ERSTELLUNGSDATUM",
"NOTE LINK": "NOTIZ LINK",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Deine Boostnote Einstellungen",
"Storages": "Speicherorte",
"Add Storage Location": "Speicherort Hinzufügen",
"Add Folder": "Ordner Hinzufügen",
"Open Storage folder": "Speicherort Öffnen",
"Unlink": "Verknüpfung aufheben",
"Edit": "Bearbeiten",
"Delete": "Löschen",
"Interface": "Interface",
"Interface Theme": "Interface-Thema",
"Default": "Standard",
"White": "Weiß",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Bestätigungsdialog beim Löschen von Notizen anzeigen",
"Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Schriftgröße",
"Editor Font Family": "Editor Schriftart",
"Editor Indent Style": "Editor Einrückestil",
"Spaces": "Leerzeichen",
"Tabs": "Tabs",
"Switch to Preview": "Zur Vorschau wechseln",
"When Editor Blurred": "Wenn Editor verschwommen ist",
"When Editor Blurred, Edit On Double Click": "Mit Doppelklick bearbeiten, wenn Editor verschwommen ist",
"On Right Click": "Mit Rechtsklcik",
"Editor Keymap": "Editor Tastenbelegung",
"default": "standard",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Boostnote nach Änderung der Tastenbelegung neu starten",
"Show line numbers in the editor": "Zeilennummern im Editor anzeigen",
"Allow editor to scroll past the last line": "Editor das Scrollen über das Ende hinaus erlauben",
"Bring in web page title when pasting URL on editor": "Websitetitel beim Einfügen in den Editor anzeigen",
"Preview": "Vorschau",
"Preview Font Size": "Vorschau Schriftgröße",
"Preview Font Family": "Vorschau Schriftart",
"Code block Theme": "Code block Theme",
"Allow preview to scroll past the last line": "Vorschau das Scrollen über das Ende hinaus erlauben",
"Show line numbers for preview code blocks": "Zeilennummern in Vorschau-Code-Blöcken anzeigen",
"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": "Newsletter abonnieren",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "Eine OpenSource-Notizapp für Programmierer wie du und ich.",
"Website": "Website",
"Development": "Entwicklung",
" : Development configurations for Boostnote.": " : Entwicklungseinstellungen für 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 sammelt anonyme Daten, um die App zu verbessern und nicht persönliche Informationen, wie z.B. der Inhalt deiner Notizen.",
"You can see how it works on ": "You can see how it works on ",
"You can choose to enable or disable this option.": "Du kannst wählen, ob du diese Option aktivierst oder daektivierst.",
"Enable analytics to help improve Boostnote": "Datenerhebung zur Verbesserung von Boostnote aktivieren",
"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 Betreuer",
"Support via OpenCollective": "Support per OpenCollective",
"Language": "Sprache",
"English": "Englisch",
"German": "Deutsch",
"French": "Französisch",
"Show \"Saved to Clipboard\" notification when copying": "\"Auf Clipboard gespeichert\" Benachrichtigungen beim kopieren anzeigen",
"All Notes": "Alle Notizen",
"Starred": "Starred",
"Are you sure to ": "Sind sie sicher ",
" delete": " zu löschen",
"this folder?": "diesen Ordner?",
"Confirm": "Bestätigen",
"Cancel": "Abbrechen",
"Markdown Note": "Markdown Notiz",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Dieses Format dient der Erstellung von Text Dokumenten. Checklisten, Code-Blöcke und Latex-Blöcke sind verfügbar.",
"Snippet Note": "Snippet Notiz",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Dieses Format dient der Erstellung von Code-Schnipslen. Mehrere snippets können zu einer Notiz zusammengefasst werden.",
"Tab to switch format": "Tab drücken, um das Format zu wechseln",
"Updated": "Bearbeitet",
"Created": "Erstellt",
"Alphabetically": "Alphabetisch",
"Default View": "Standardansicht",
"Compressed View": "Komprimierte Ansicht",
"Search": "Suchen",
"Blog Type": "Blog-Typ",
"Blog Address": "Blog Adresse",
"Save": "Speichern",
"Auth": "Authentifizierung",
"Authentication Method": "Authentifizierungsmethode",
"JWT": "JWT",
"USER": "BENUTZER",
"Token": "Token",
"Storage": "Speicherorte",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Boostnote anzeigen/verstecken",
"Restore": "Restore",
"Permanent Delete": "Dauerhaft Löschen",
"Confirm note deletion": "Löschen bestätigen",
"This will permanently remove this note.": "Notiz wird dauerhaft gelöscht",
"You have to save!": "Es muss gespeichert werden",
"Albanian": "Albanisch",
"Danish": "Dänisch",
"Japanese": "Japanisch",
"Korean": "Koreanisch",
"Norwegian": "Norwegisch",
"Polish": "Polnish",
"Portuguese": "Portugiesisch",
"Spanish": "Spanisch",
"Chinese (zh-CN)": "Chinesisch (zh-CN)",
"Chinese (zh-TW)": "Chinesisch (zh-TW)",
"Successfully applied!": "Erfolgreich angewendet!",
"UserName": "UserName",
"Password": "Password",
"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"
}

155
locales/en.json Normal file
View File

@@ -0,0 +1,155 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "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",
".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": "Delete",
"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",
"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",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"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.",
"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",
"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!",
"UserName": "UserName",
"Password": "Password",
"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"
}

153
locales/es.json Normal file
View File

@@ -0,0 +1,153 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "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",
".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": "Delete",
"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",
"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",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"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.",
"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",
"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"
}

153
locales/fr.json Normal file
View File

@@ -0,0 +1,153 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Préférences",
"Make a note": "Créer une note",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "pour créer une nouvelle note",
"Toggle Mode": "Toggle Mode",
"Trash": "Poubelle",
"MODIFICATION DATE": "DATE DE MODIFICATION",
"Words": "Mots",
"Letters": "Lettres",
"STORAGE": "STOCKAGE",
"FOLDER": "DOSSIER",
"CREATION DATE": "DATE DE CREATION",
"NOTE LINK": "LIEN DE LA NOTE",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Imprimer",
"Your preferences for Boostnote": "Vos préférences pour Boostnote",
"Storages": "Stockages",
"Add Storage Location": "Ajouter un espace de stockage",
"Add Folder": "Ajouter un dossier",
"Open Storage folder": "Ouvrir un dossier de stockage",
"Unlink": "Délier",
"Edit": "Editer",
"Delete": "Supprimer",
"Interface": "Interface",
"Interface Theme": "Thème d'interface",
"Default": "Effacer",
"White": "Blanc",
"Solarized Dark": "Foncé solarisé",
"Dark": "Foncé",
"Show a confirmation dialog when deleting notes": "Montrer une alerte de confirmation lors de la suppression de notes",
"Editor Theme": "Theme d'éditeur",
"Editor Font Size": "Taille de police de l'éditeur",
"Editor Font Family": "Police de l'éditeur",
"Editor Indent Style": "Style d'indentation de l'éditeur",
"Spaces": "Espaces",
"Tabs": "Tabulations",
"Switch to Preview": "Switcher vers l'aperçu",
"When Editor Blurred": "Quand l'éditeur n'est pas sélectionné",
"When Editor Blurred, Edit On Double Click": "Quand l'éditeur n'est pas sélectionné, éditer avec un double clic",
"On Right Click": "Avec un clic droit",
"Editor Keymap": "Keymap de l'éditeur",
"default": "Par défaut",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Redémarrez Boostnote après avoir changé la keymap",
"Show line numbers in the editor": "Montrer les numéros de lignes dans l'éditeur",
"Allow editor to scroll past the last line": "Contrôle si l'éditeur défile au-delà de la dernière ligne",
"Bring in web page title when pasting URL on editor": "Mettre le titre de la page lors d'un collé d'une URL dans l'éditeur",
"Preview": "Aperçu",
"Preview Font Size": "Taille de police de l'aperçu",
"Preview Font Family": "Police de l'aperçu",
"Code block Theme": "Thème des blocs de code",
"Show line numbers for preview code blocks": "Montrer les numéros de lignes dans les blocs de code dans l'aperçu",
"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": "Communauté",
"Subscribe to Newsletter": "Souscrire à la newsletter",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Groupe Facebook",
"Twitter": "Twitter",
"About": "A propos",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "Une appli de prise de notes open-source faite pour les développeurs comme vous.",
"Website": "Site web",
"Development": "Développement",
" : Development configurations for Boostnote.": " : Configurations de développement pour 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 collecte des données anonymisées dans le seul but d'améliorer l'application, et ne collecte aucune donnée personnelle telle que le contenu de vos notes.",
"You can see how it works on ": "Vous pouvez voir comment ça marche sur",
"You can choose to enable or disable this option.": "Vous pouvez choisir d'activer/désactiver cette option.",
"Enable analytics to help improve Boostnote": "Activer la collecte de données anonymisées pour améliorer Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Cher utilisateur,",
"Thank you for using Boostnote!": "Merci d'utiliser Boostnote !",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote est utilisé dans plus de 200 pays et régions par une impressionnante communauté de développeurs.",
"To continue supporting this growth, and to satisfy community expectations,": "Afin de continuer à grandir, et de satisfaire les attentes de la communauté,",
"we would like to invest more time and resources in this project.": "nous aimerions investir d'avantage de temps et de ressources dans ce proje.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si vous aimez ce projet et que vous en voyez tout le potentiel, vous pouvez aider par un support sur OpenCollective !",
"Thanks,": "Merci,",
"Boostnote maintainers": "Les mainteneurs de Boostnote",
"Support via OpenCollective": "Support via OpenCollective",
"Language": "Langues",
"English": "Anglais",
"German": "Allemand",
"French": "Français",
"Show \"Saved to Clipboard\" notification when copying": "Montrer la notification \"Sauvegardé dans le presse-papiers\" lors de la copie",
"All Notes": "Toutes les notes",
"Starred": "Favoris",
"Are you sure to ": "Etes-vous sûr de ",
" delete": " supprimer",
"this folder?": "ce dossier ?",
"Confirm": "Confimer",
"Cancel": "Annuler",
"Markdown Note": "Note Markdown",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Ce format est pour créer des documents texte. Checklists, blocks de code et blocks Latex sont disponibles.",
"Snippet Note": "Note Snippet",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Ce format est pour créer des snippets de code. Plusieurs snippets peuvent être groupés en une seule note.",
"Tab to switch format": "Tab pour changer de format",
"Updated": "Mis à jour",
"Created": "Créé",
"Alphabetically": "De manière alphabétique",
"Default View": "Vue par défaut",
"Compressed View": "Vue compressée",
"Search": "Chercher",
"Blog Type": "Blog Type",
"Blog Address": "Blog Address",
"Save": "Sauvegarder",
"Auth": "Auth",
"Authentication Method": "Méthode d'Authentification",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Stockage",
"Hotkeys": "Raccourcis",
"Show/Hide Boostnote": "Montrer/Cacher Boostnote",
"Restore": "Restaurer",
"Permanent Delete": "Supprimer définivitement",
"Confirm note deletion": "Confirmer la suppression de la note",
"This will permanently remove this note.": "Cela va supprimer cette note définitivement.",
"Successfully applied!": " Succès !",
"Albanian": "Albanais",
"Chinese (zh-CN)": "Chinois (zh-CN)",
"Chinese (zh-TW)": "Chinois (zh-TW)",
"Danish": "Danois",
"Japanese": "Japonais",
"Korean": "Coréen",
"Norwegian": "Norvégien",
"Polish": "Polonais",
"Portuguese": "Portugais",
"Spanish": "Espagnol",
"You have to save!": "Il faut sauvegarder !",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
"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"
}

159
locales/hu.json Normal file
View File

@@ -0,0 +1,159 @@
{
"Notes": "Jegyzetek",
"Tags": "Címkék",
"Preferences": "Beállítások",
"Make a note": "Új jegyzet",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "hogy létrehozz egy jegyzetet",
"Toggle Mode": "Mód Váltás",
"Trash": "Lomtár",
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
"Words": "Szó",
"Letters": "Betű",
"STORAGE": "TÁROLÓ",
"FOLDER": "KÖNYVTÁR",
"CREATION DATE": "LÉTREHOZÁS DÁTUMA",
"NOTE LINK": "JEGYZET LINKJE",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Nyomtatás",
"Your preferences for Boostnote": "Boostnote beállításaid",
"Storages": "Tárolók",
"Add Storage Location": "Tároló Hozzáadása",
"Add Folder": "Könyvtár Hozzáadása",
"Open Storage folder": "Tároló Megnyitása",
"Unlink": "Tároló Leválasztása",
"Edit": "Szerkesztés",
"Delete": "Törlés",
"Interface": "Felület",
"Interface Theme": "Felület Témája",
"Default": "Alapértelmezett",
"White": "Világos",
"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",
"Editor Theme": "Szerkesztő Témája",
"Editor Font Size": "Szerkesztő Betűmérete",
"Editor Font Family": "Szerkesztő Betűtípusa",
"Editor Indent Style": "Szerkesztő Behúzása",
"Spaces": "Szóközök",
"Tabs": "Tabulátor karakterek",
"Switch to Preview": "Váltás Megtekintésre",
"When Editor Blurred": "Szerkesztő Elhagyásakor",
"When Editor Blurred, Edit On Double Click": "Szerkesztő Elhagyásakor, Szerkesztő Megnyitása Dupla Kattintással",
"On Right Click": "Jobb Egérgombbal",
"Editor Keymap": "Szerkesztő Billentyűzetkiosztása",
"default": "alapértelmezett",
"vim": "vim",
"emacs": "emacs",
"⚠️ 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",
"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",
"Preview Font Family": "Megtekintés Betűtípusa",
"Code block Theme": "Kódblokk Témája",
"Allow preview to scroll past the last line": "Megtekintésben az utolsó sor alá is lehessen görgetni",
"Show line numbers for preview code blocks": "Mutatassa a sorszámokat a megtekintett kódblokkokban",
"LaTeX Inline Open Delimiter": "LaTeX Inline Nyitó Határolója",
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
"Community": "Közösség",
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Csoport",
"Twitter": "Twitter",
"About": "Névjegy",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "Nyílt forráskódú jegyzetkészítő program a hozzád hasonló programozóknak.",
"Website": "Weboldal",
"Development": "Fejlesztés",
" : Development configurations for Boostnote.": " : Információk a Boostnote fejlesztéséről.",
"Copyright (C) 2017 - 2018 BoostIO": "Szerzői jog (C) 2017 - 2018 BoostIO",
"License: GPL v3": "Licensz: GPL v3",
"Analytics": "Adatok elemzése",
"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.": "A Boostnote névtelen adatokat gyűjt össze az alkalmazás tökéletesítése céljából, és szigorúan nem gyűjt semmilyen személyes adatot, például a jegyzetek tartalmát.",
"You can see how it works on ": "A működéséről további információkat itt találsz: ",
"You can choose to enable or disable this option.": "Kiválaszthatod, hogy engedélyezed, vagy tiltod ezt az opciót.",
"Enable analytics to help improve Boostnote": "Adatok elemzésének engedélyezése a Boostnote tökéletesítésének céljából",
"Crowdfunding": "Közösségi finanszírozás",
"Dear everyone,": "Kedves felhasználók!",
"Thank you for using Boostnote!": "Köszönjük, hogy a Boostnote-ot használjátok!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "A Boostnote-ot több, mint 200 ország és régió fantasztikus fejlesztői használják.",
"To continue supporting this growth, and to satisfy community expectations,": "Hogy folytathassuk ezt a fejlődést és kielégíthessük a felhasználói elvárásokat,",
"we would like to invest more time and resources in this project.": "több időt és erőforrást szeretnénk a projektbe fektetni.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Ha tetszik a projekt és hasznosnak találod, te is segíthetsz ebben az OpenCollective-en keresztül küldött támogatásoddal.",
"Thanks,": "Köszönjük!",
"Boostnote maintainers": "A Boostnote csapata",
"Support via OpenCollective": "Támogatás Küldése",
"Language": "Nyelv",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "Mutassa a \"Vágólapra Másolva\" üzenetet másoláskor",
"All Notes": "Minden Jegyzet",
"Starred": "Kiemelt",
"Are you sure to ": "Biztos, hogy ",
" delete": " törölni",
"this folder?": "szeretnéd a könyvtárat?",
"Confirm": "Igen",
"Cancel": "Mégse",
"Markdown Note": "Markdown Jegyzet",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Ez a formátum szöveges dokumentumok készítésére használható. Jelölőnégyzeteket, kódblokkokat és Latex blokkokat is tartalmazhat.",
"Snippet Note": "Kód Jegyzet",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Ez a formátum kódrészletek készítésére használható. Több kódrészlet tárolására is alkalmas (pl. HTML + CSS).",
"Tab to switch format": "Formátum váltásához nyomd le a Tabulátor billentyűt!",
"Updated": "Módosítás",
"Created": "Létrehozás",
"Alphabetically": "Ábécé sorrendben",
"Default View": "Alapértelmezett Nézet",
"Compressed View": "Tömörített Nézet",
"Search": "Keresés",
"Blog Type": "Blog Típusa",
"Blog Address": "Blog Címe",
"Save": "Mentés",
"Auth": "Hitelesítés",
"Authentication Method": "Hitelesítési Módszer",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Tároló",
"Hotkeys": "Gyorsbillentyűk",
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
"Restore": "Visszaállítás",
"Permanent Delete": "Végleges Törlés",
"Confirm note deletion": "Törlés megerősítése",
"This will permanently remove this note.": "A jegyzet véglegesen törölve lesz.",
"Successfully applied!": "Sikeresen alkalmazva.",
"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!": "Mentened kell!",
"UserName": "FelhasznaloNev",
"Password": "Jelszo",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Hungarian": "Hungarian",
"Add Storage": "Tároló hozzáadása",
"Name": "Név",
"Type": "Típus",
"File System": "Fájlrendszer",
"Setting up 3rd-party cloud storage integration:": "Harmadik féltől származó felhőtárolási integráció beállítása:",
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "Hely",
"Add": "Hozzáadás",
"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."
}

153
locales/ja.json Normal file
View File

@@ -0,0 +1,153 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "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",
".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": "Delete",
"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",
"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",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"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.",
"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",
"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"
}

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