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

Compare commits

..

220 Commits

Author SHA1 Message Date
Junyoung Choi
ed427130a9 0.12.1-0 2019-07-26 11:51:51 +09:00
Junyoung Choi
807feae540 Merge pull request #3165 from BoostIO/discard-warnings
Discard warnings
2019-07-26 11:45:29 +09:00
Junyoung Choi
4b62e93257 Fix more style warnings 2019-07-26 11:38:05 +09:00
Junyoung Choi
e425417d68 Load bfm mode info script only once 2019-07-26 11:33:05 +09:00
Junyoung Choi
bc1e837466 Set alias to stylus mode inof 2019-07-26 11:32:41 +09:00
Junyoung Choi
95321e33a0 Fix style of FolderItem 2019-07-26 11:32:07 +09:00
Junyoung Choi
5720b313a3 Merge branch 'master' into discard-warnings 2019-07-26 09:12:18 +09:00
hikerpig
f3e2205e69 fix several propType errors raised by 'react.development.js'
some are caused by typo, some are caused by unused propType declarations
2019-07-26 09:11:00 +09:00
Junyoung Choi
410b611b14 Discard all style warnings 2019-07-26 09:03:32 +09:00
Junyoung Choi
1c8af47bac Fix warnings in ToggleModeButton 2019-07-26 08:50:12 +09:00
Junyoung Choi
c8a2baca3c Use default value prop rather than value prop 2019-07-26 08:49:52 +09:00
Junyoung Choi
ac5a323115 Discard unused props 2019-07-26 08:49:21 +09:00
Junyoung Choi
fa65e7feef Fix isTagActive 2019-07-26 08:49:07 +09:00
amedora
71d27d0e55 MarkdownEditor and MarkdownSplitEditor always wrap lines 2019-07-26 07:50:53 +09:00
hikerpig
972d053c83 fix: array access error when token.map is null, fix #3123 2019-07-26 07:49:59 +09:00
Matt Gabriel
7797661489 Fixed permissions 2019-07-26 07:40:18 +09:00
Junyoung Choi
9f9e036c68 0.12.0 2019-07-24 16:57:04 +09:00
Tobias Doll
e940253caf Using single quotes for empty string 2019-07-22 16:43:21 +09:00
Tobias Doll
2b4d20b94e Dont highlight search term if search field is emptied 2019-07-22 16:43:21 +09:00
nathan-castlehow
f88fc23e58 Removed Paste / Cut options from preview context menu as they are not relevant in preview mode 2019-07-22 16:42:57 +09:00
nathan-castlehow
caf1f92fef Removed SetTimeout on Markdown Preview Context menu 2019-07-22 16:42:57 +09:00
nathan-castlehow
f2a02a25a7 Fixed Test Fail Issue due to incorrect Require for context menu items 2019-07-22 16:42:57 +09:00
nathan-castlehow
e85767b4a0 feat: Added Context Menu for markdown preview mode and copy url when hyperlink 2019-07-22 16:42:57 +09:00
amedora
c83e5cc7d8 add locales 2019-07-22 16:42:08 +09:00
amedora
71f565f66b fix UI change handler 2019-07-22 16:42:08 +09:00
amedora
769407b3df add "Wrap Line" button 2019-07-22 16:42:08 +09:00
amedora
e7615ed6d7 add "Wrap Line" button handlers 2019-07-22 16:42:08 +09:00
amedora
fe508307b2 make lineWrapping configurable 2019-07-22 16:42:08 +09:00
Baptiste Augrain
4e30d4b8fb fix URLs by using the correct path separator ('/' for an url and not path.sep) 2019-07-20 00:58:51 +09:00
Baptiste Augrain
850561613b fix gallery on windows 2019-07-20 00:58:51 +09:00
AWolf81
782d71ddb0 path fix for MacOs 2019-07-17 17:25:13 +09:00
AWolf81
a0799d19f8 fix app path 2019-07-17 17:25:13 +09:00
AWolf81
ba6eb4f26f fix path Linux 2019-07-17 17:25:13 +09:00
AWolf81
38fcee35c2 fix test for Windows (fs.rmdir throws dir not empty error) 2019-07-17 17:25:13 +09:00
AWolf81
4af7106e01 change theme path usage and remove relative path 2019-07-17 17:25:13 +09:00
AWolf81
bd52226ae2 change process.env check 2019-07-17 17:25:13 +09:00
ehhc
cb7ac77c61 Update attachmentManagement.js
Deleting unneeded stupid log-message
2019-07-10 11:42:00 +09:00
ehhc
55a7ee1f91 Debounce deletion of un-referenced attachments --> don't fixes but mitigates the problems of #3103 2019-07-10 11:42:00 +09:00
amorist
d37210a0d0 fix chinese language new note tab style 2019-07-08 13:14:56 +09:00
amedora
0c7a1e8f17 apply theme color to slider in MarkdownSplitEditor 2019-07-08 13:14:30 +09:00
Kazumi Harada
3d7ab40674 [update] apply the method of fixLocalURLS before exporting to pdf 2019-07-08 13:14:10 +09:00
Kazumi Harada
b8de51be57 [update] adjust code style 2019-07-08 13:13:52 +09:00
Kazumi Harada
1ce72b91ca [update] by force, insert the default value to the customCSS config 2019-07-08 13:13:52 +09:00
Kazumi Harada
3f96587a70 [update] move default value of the customCSS field to ConfigManager 2019-07-08 13:13:52 +09:00
Huachao Mao
c012bbd54a Update lock/unlock icons in editor #1760 2019-07-08 13:13:30 +09:00
David Thomason
a50852306e Loading insert day hotkey triggers from config.hotkey 2019-07-08 13:13:06 +09:00
David Thomason
db396ec107 Added insert date & time info to HotkeyTab settings 2019-07-08 13:13:06 +09:00
David Thomason
6b868658aa Changed "insert date & time" hotkey on mac from Shift-Cmd-/ to Alt-Cmd-/ 2019-07-08 13:13:06 +09:00
JianXu
9fe9e1a1c4 Add menu item "About" to "Help" 2019-07-08 13:12:22 +09:00
Junyoung Choi
b4251a793b Merge pull request #3037 from AWolf81/fix-search-issue-unicode
Simplified search input & fixed chinese character input
2019-06-29 00:41:24 +09:00
roottool
6736a08240 Fixes that TOC hasn't id attribute when the title is all 2-byte characters (#2994)
* Fix: 2-byte character support of slug

* Fix: Decodes slug to display slug

* Fix: Removed a logic of replaceDiacritics

* Fix: Fixed slugify to pass tests

* Fix: Fixed not to remove underscore

* Adds the test for slugify.js

* Fix: Fix to jump to heading

* Added a comment

* Fix: Created click event only linking to heading

* Fix: Fix to use handleLinkClick(e)

* Fix: Changed the regex rule

* Fix: Changed the regex rule of extractId
2019-06-29 00:40:16 +09:00
KZ
49c75e3599 Merge pull request #3059 from BoostIO/update-funding-yml
Update FUNDING.yml
2019-06-06 10:44:34 +09:00
KZ
05765642d9 Update FUNDING.yml 2019-06-06 10:39:09 +09:00
amedora
f62eba9d7b Fix #2922 (#2998)
* update katex to 0.10.1

* Update snapshot for markdown-test
2019-06-03 13:57:16 +09:00
amedora
bc27fd0acc disable fuzzy link 2019-06-03 13:54:40 +09:00
Ryota Kusano
244a28c7d2 Adjust continuous quotation pattern 2019-05-29 22:55:05 +09:00
Ryota Kusano
edac4d3fed Fix to remove unnecessary escape charactor erorr on IDE 2019-05-29 22:55:05 +09:00
Ryota Kusano
e402929cca Fix regular expression related checkbox logic 2019-05-29 22:55:05 +09:00
AWolf81
639bfbe549 apply search term on enter key 2019-05-29 09:02:34 +02:00
AWolf81
60e841e5a2 fix focus loss by checking switchPrieview prop change 2019-05-29 07:52:22 +02:00
AWolf81
25d055e560 Merge branch 'master' into fix-search-issue-unicode
# Conflicts:
#	browser/main/TopBar/index.js
#	yarn.lock
2019-05-29 07:42:20 +02:00
Morten Lautrup
052fb3df5b Specify wich images should not be draggable
Make only images and spans inside buttons undraggable
2019-05-29 13:02:37 +09:00
Morten Lautrup
929f475354 Make buttons undraggable 2019-05-29 13:02:37 +09:00
AWolf81
1afa02bbb3 fix local link detection by creating a link tag to parse the input string 2019-05-29 12:20:49 +09:00
AWolf81
3c39dc3cec change redirecting to connected-react-router 2019-05-29 12:20:49 +09:00
AWolf81
7e8f46c4f3 remove commented imports 2019-05-29 12:20:49 +09:00
AWolf81
333b0584a4 address review comments - add production/dev main.html & remove comments 2019-05-29 12:20:49 +09:00
AWolf81
f7a648903e fix auto-scroll 2019-05-29 12:20:49 +09:00
AWolf81
6ec687ef15 add React & Redux devtools 2019-05-29 12:20:49 +09:00
AWolf81
b6212f4bfe Update dependencies & change to React-router v5 2019-05-29 12:20:49 +09:00
AWolf81
9794149fae Merge branch 'master' into fix-search-issue-unicode 2019-05-28 20:18:13 +02:00
AWolf81
edc9d8bd4d use composition input 2019-05-28 20:16:02 +02:00
Junyoung Choi
76335f78ac v0.11.17 2019-05-26 18:49:44 +09:00
Junyoung Choi
56192f0758 Merge pull request #3034 from AWolf81/enable-markdownlint-option
Enable Markdown lint option
2019-05-26 18:38:30 +09:00
Junyoung Choi
63eb8584e7 Hide markdown lint settings when markdown lint is disabled 2019-05-26 18:32:24 +09:00
Zubata SMRTKA
aa4d06fb1e formatting 2019-05-26 18:22:04 +09:00
AWolf81
c70cca2776 remove console.log 2019-05-26 10:07:43 +02:00
AWolf81
1a38771f1a remove console.logs & improve error handling 2019-05-26 09:57:40 +02:00
AWolf81
25728e51d2 simplified search input & fixed focus loss issue 2019-05-25 21:01:51 +02:00
AWolf81
02576c48b6 move enableMarkdownLint checkbox to customMarkdownLintConfig area (like at allow custom css) 2019-05-25 07:31:21 +02:00
AWolf81
4263309d89 Merge branch 'add-markdownlint-rules-form' of github.com:roottool/Boostnote into enable-markdownlint-option 2019-05-25 07:21:26 +02:00
AWolf81
b68b367b3c Toggle linting gutter with document.querySelector and style.display 2019-05-25 07:16:03 +02:00
tool root
2cffb50884 Fix: Changed height of form 2019-05-25 08:16:25 +09:00
tool root
3b473a5f7a Fix: Changed the function name 2019-05-25 08:04:40 +09:00
tool root
8b82c448af Fix: Changed the function name 2019-05-25 07:58:43 +09:00
AWolf81
270a015514 WIP: Add MarkdownLint enable setting. Gutter toggle not working as expected. 2019-05-24 19:06:15 +02:00
Junyoung Choi
f02125e411 Update FUNDING.yml 2019-05-24 10:10:29 +09:00
KZ
b0f2694745 Create FUNDING.yml 2019-05-24 10:09:49 +09:00
roottool
2497bdb124 Fix: Removed changes to debug the app 2019-05-22 22:45:09 +09:00
Junyoung Choi
d5d564f789 v0.11.16 2019-05-22 15:20:34 +09:00
Junyoung Choi
49e48f7adc Update yarn.lock 2019-05-22 12:23:28 +09:00
Darío Hereñú
079aaec21e Fixed typo on string 137
* plus some translations added
2019-05-13 14:50:53 +09:00
AWolf81
1601292db7 add react hooks section 2019-05-13 14:05:57 +09:00
roottool
c82dbddc74 Fix markdownlint result desplay works properly 2019-05-12 13:13:32 +09:00
David Dreher
9a6ee9d2ef change return handling of sortByAlphabetical 2019-05-09 15:45:54 +09:00
David Dreher
2cbbe7aeda Check if the float is quals (abs value is greather 0.01) and return the sub value when not. 2019-05-09 15:45:54 +09:00
David Dreher
19fc1fd674 rename const 2019-05-09 15:45:54 +09:00
David Dreher
5b63bedc0d fix issue #2894: sort alphabetical will now parse float values starting at all titles and compare these. 2019-05-09 15:45:54 +09:00
amedora
12229a1719 allow to expand snippets following $ character 2019-05-09 15:41:33 +09:00
roottool
0d6c952721 Added a newline 2019-05-09 05:30:46 +09:00
roottool
c42eb41fb3 Fix: Fixed that default rule was shifted by indent 2019-05-09 05:29:43 +09:00
roottool
2da4f1df32 Fix: Rewrote the code to inline 2019-05-09 04:11:05 +09:00
roottool
69a62d1b73 Fix: Changed variable name 2019-05-09 04:06:27 +09:00
roottool
61e054024b Fix: Removed unnecessary css code 2019-05-07 04:44:23 +09:00
roottool
0a5c4c092a Fix: Improved for the app not to need to reload 2019-05-07 04:22:03 +09:00
roottool
f1597f8e84 Fix: Poped up the lint tooptip 2019-05-07 03:26:47 +09:00
roottool
f3ca893aea Ajusted markdownlint config editor to code editor 2019-05-07 02:30:41 +09:00
roottool
ecfeedeff3 Fix: Removed unnecessary caution 2019-05-07 00:44:27 +09:00
roottool
a162bab591 Fix: Improved for the app not to need to reload 2019-05-07 00:42:55 +09:00
roottool
79a29c3461 Merge branch 'master' into add-markdownlint-rules-form 2019-05-06 20:53:59 +09:00
Junyoung Choi
377901606e Merge pull request #2846 from roottool/IntroduceMarkdownLint#864
Introduce markdownlint #864
2019-05-06 18:31:01 +09:00
Nguyễn Việt Hưng
8cd24a5734 fixed open empty link 2 2019-05-06 18:30:20 +09:00
Baptiste Augrain
ba913b77e7 use constants and fix cursor color 2019-05-06 18:28:57 +09:00
Baptiste Augrain
6012fc929e add Nord theme to CodeMirror 2019-05-06 18:28:57 +09:00
roottool
33d1700548 Change styleName of caution 2019-04-29 10:01:55 +09:00
roottool
8b3beb45c6 Added a caution of the custom markdownlint rules 2019-04-29 09:17:25 +09:00
roottool
4ba4e68833 Added markdownlint rules form 2019-04-29 08:54:28 +09:00
roottool
11bed72bed Introduced jsonlint-mod 2019-04-29 08:49:19 +09:00
roottool
7fb22f3f07 Translated from English to Japanese 2019-04-29 03:24:16 +09:00
roottool
788900e31a Added markdownlint rules form 2019-04-29 03:23:25 +09:00
roottool
03b1adca12 Merge master branch into this branch 2019-04-29 01:57:16 +09:00
Charles Wang
aecf2eb08d Bug fix check in. 2019-04-28 12:31:24 -04:00
Christian Dimas
895aee3e6c add default value to custom CSS editor 2019-04-28 12:30:38 -04:00
David Dreher
c667e1465d After select an other note now scroll to top 2019-04-28 12:30:13 -04:00
David Dreher
3c9d614e1f Change the save botton at the about tab to the same style as the 'See IssueHunt' button at the crowdfunding tab 2019-04-28 12:29:33 -04:00
David Dreher
971e0cb61e change missing font color for the themes at the preferences headlines 2019-04-28 12:29:33 -04:00
David Dreher
67d78f6603 Replace the width calc with a fixed value 2019-04-28 12:29:33 -04:00
David Dreher
ed0b370d49 Change paragraph headlines at the crowdfunding tab 2019-04-28 12:29:33 -04:00
David Dreher
e5b8f4d372 Use same styles for all preferences tabs 2019-04-28 12:29:33 -04:00
Junyoung Choi
2999e490ad Merge pull request #2509 from lvarado/mojave-dark-mode
enable dark mode support for macOS mojave
2019-04-28 12:26:28 -04:00
Nguyễn Việt Hưng
156a2c6521 added check empty for safety reason 2019-04-23 22:58:38 -04:00
Nguyễn Việt Hưng
48bb9d3242 fixed empty link point to main file 2019-04-23 22:58:38 -04:00
steve-o
b2ee4f0c66 update electron-packager to 12.2.0 2019-04-22 10:02:19 -05:00
steve-o
9b68f34a31 remove hashes from lockfile (except those present in master branch) 2019-04-22 10:00:46 -05:00
alvarado
f8944eb8b3 Merge branch 'master' into mojave-dark-mode 2019-04-22 09:45:16 -05:00
David Dreher
3634194e4d Fix for #2836, Initialize storage ignores now tags from skipped notes 2019-04-15 10:57:20 +09:00
Abner Soares Alves Junior
038b87cf70 Fix test moving from Ava to JEST 2019-04-15 10:56:35 +09:00
Abner Soares Alves Junior
5ca6bbea11 Fix some linter issues 2019-04-15 10:56:35 +09:00
Abner Soares Alves Junior
7b9b4d56a7 Fix the position of title mark when replacing 2019-04-15 10:56:35 +09:00
Abner Soares Alves Junior
d81e69bf00 Add parse and fetch pasted markdown titles with url 2019-04-15 10:56:35 +09:00
linxiuda
5e134f990e fix newNoteModal create note twice after double click quickly 2019-04-15 10:55:26 +09:00
David Dreher
1675e04f90 Fix for #2782: CodeEditor.handleChange will now update the toc if available when a headline was modified 2019-04-15 10:54:45 +09:00
Todt
0a72acd899 Modified style 2019-04-15 10:44:16 +09:00
amedora
aa6c9680da Increase arrow size on sidebar 2019-04-15 10:43:52 +09:00
milotodt
d41663143b Repush due to pipeline issues 2019-04-15 10:43:10 +09:00
milotodt
9db363865c Slight refactor 2019-04-15 10:43:10 +09:00
milotodt
9b03a32eec removed uneeded line of code 2019-04-15 10:43:10 +09:00
milotodt
eeec1b12b5 adjusted handleAttachmentDrop 2019-04-15 10:43:10 +09:00
Milo Todt
e655c2078b Update MarkdownPreview.js 2019-04-12 18:02:23 +09:00
Milo Todt
3426191e59 Update MarkdownPreview.js 2019-04-12 18:02:23 +09:00
Todt
b1c77ae59c Changes and refactor of HandleMouseDown 2019-04-12 18:02:23 +09:00
Jamie Luckett
d17ff4afba Update productDescription
Fixed grammatical error
2019-04-12 18:01:40 +09:00
Garfield Lee
0131bbbbed Update all locales copyright year (#2881)
* Update all locales copyright year

* Fix js-sequence-diagrams
2019-04-12 18:01:05 +09:00
Boris Pruessmann
7f55fc4b56 Removed erroneous parameter 2019-04-12 14:33:38 +09:00
amedora
aea9673b78 Add "Ctrl + /" and "Shift + Ctrl + /" to insert today's date and time (#1765) 2019-04-12 14:32:09 +09:00
anasasilva
89e9a84ea6 fixing package.json 2019-04-08 21:10:42 +09:00
anasasilva
fba9afd6f5 Shorter code 2019-04-08 21:10:42 +09:00
anasasilva
c474d972cb Supports relative and absolute path 2019-04-08 21:10:42 +09:00
anasasilva
3d6f670e8d regex fixed again 2019-04-08 21:10:42 +09:00
anasasilva
9d47f319a0 absolute path 2019-04-08 21:10:42 +09:00
anasasilva
7106f042da Regex fixed 2019-04-08 21:10:42 +09:00
anasasilva
ea6e56842f Error message when the image path is wrong 2019-04-08 21:10:42 +09:00
anasasilva
65926fea73 fixing identation and regex 2019-04-08 21:10:42 +09:00
anasasilva
85cb94d99d importAttachments 2019-04-08 21:10:42 +09:00
Ryo Shibayama
1be208d96b Fix locale json format, add some translations 2019-04-02 14:09:39 +09:00
Max Schmitt
22d494d3f1 README: removed mobile app mention (#2828) 2019-03-25 11:10:09 +09:00
Nguyen Viet Hung
5b99132f66 Adjust find function
Co-Authored-By: dredav <dredav@users.noreply.github.com>
2019-03-25 11:06:13 +09:00
David Dreher
91d04b99d1 change filter function to find, find will match the first note with requested key 2019-03-25 11:06:13 +09:00
David Dreher
70e57cf738 jumpNoteByHashHandler will now try to find the location for the note that will be selected 2019-03-25 11:06:13 +09:00
Ryo Shibayama
2f00cec52b Upgrade Travis CI node and npm versions 2019-03-25 11:04:05 +09:00
Junyoung Choi
fee966996f Fix wrong binding (#2940) 2019-03-22 15:33:29 +09:00
Nguyen Viet Hung
bff081a263 fixed copy button (#2914) 2019-03-21 01:27:51 +09:00
Nguyen Viet Hung
b5b56f7af1 Refactor code editor by moving the expand snippet out to a separate file (#2864)
* refactored CodeEditor by moving Snippet out

* fixed typo
2019-03-21 01:26:53 +09:00
Junyoung Choi
3f7f0e677d Merge pull request #2819 from ZeroX-DG/feature/edit-from-preview
[Feature] Edit from preview
2019-03-21 01:23:11 +09:00
Junyoung Choi
2b29d96d61 Merge pull request #2678 from AgentEpsilon/export-pdf
Export a Markdown note as PDF
2019-03-21 01:20:12 +09:00
Junyoung Choi
9f13645127 Merge pull request #2545 from Danmou/patch-1
Add ~ and _ to autoclosing brackets
2019-03-21 01:19:25 +09:00
Junyoung Choi
bbbbe9a121 Merge pull request #2493 from opw0011/zh-locale
Add missing Chinese translation in preference page
2019-03-21 01:18:36 +09:00
Junyoung Choi
46ecf0af88 Merge pull request #2868 from dredav/issue-2859
[Fix] Cloning a note will now clone more note properties
2019-03-21 01:18:09 +09:00
Evan Miller
2a0906d88e Rehid printout BrowserWindow 2019-03-18 10:02:20 -04:00
Daniel Mouritzen
116244384e Add ~ and _ to autoclosing brackets 2019-03-04 08:37:29 +01:00
Daniel Mouritzen
29775040d1 Merge branch 'master' into patch-1 2019-03-04 08:33:29 +01:00
Evan Miller
177888b159 disable webSecurity to render files to pdf 2019-02-11 11:15:08 -05:00
Evan Miller
bc24acd057 add targetDir parameter to outputFormatter 2019-02-11 11:15:08 -05:00
Evan Miller
4d727b0af7 destroy window after printing 2019-02-11 11:15:08 -05:00
Junyoung Choi
78c00b1722 v0.11.15 2019-02-08 20:33:20 +09:00
Junyoung Choi
2ff655d2dc Check update if the app is packaged 2019-02-08 19:30:46 +09:00
David Dreher
d144a5884a fix for issue #2859: Cloning a note will now also copy the properties description, snippets, tags and isStarred 2019-02-06 22:11:35 +01:00
roottool
c15cc2ecfc added to check Markdown Note is opening 2019-02-02 14:59:04 +09:00
roottool
59b441e524 removed markdown mode 2019-02-02 03:07:59 +09:00
roottool
b84ebfa7b3 Merge branch 'master' into IntroduceMarkdownLint#864 2019-01-31 12:44:07 +09:00
roottool
d5629651d1 modified from require to import 2019-01-29 21:26:57 +09:00
roottool
afbc8eec75 changed variable name 2019-01-29 21:00:47 +09:00
roottool
3a96164c27 display lint result on CodeEditor #864 2019-01-29 20:42:24 +09:00
roottool
e393b5a2ea Add markdownlint #864 2019-01-29 20:41:29 +09:00
Benny O
5920b3515e Merge branch 'master' into zh-locale 2019-01-27 23:04:27 +08:00
Nguyễn Việt Hưng
5d38937f34 scroll selected line to middle of the editor 2019-01-23 00:20:50 +07:00
Nguyễn Việt Hưng
a2e050b8c5 added jump to line on click preview 2019-01-19 17:03:59 +07:00
Nguyễn Việt Hưng
a7ad56be98 Merge branch 'master' of https://github.com/BoostIO/Boostnote 2019-01-19 16:28:36 +07:00
Nguyễn Việt Hưng
c6c5e33ee6 Merge branch 'master' of https://github.com/BoostIO/Boostnote 2019-01-11 10:36:59 +07:00
Nguyễn Việt Hưng
9ad0f58095 Merge branch 'master' of https://github.com/BoostIO/Boostnote 2019-01-06 09:35:01 +07:00
Nguyễn Việt Hưng
b5763ec89d Merge branch 'master' of https://github.com/BoostIO/Boostnote 2018-12-15 13:40:03 +07:00
Nguyễn Việt Hưng
a095e8b25c Merge branch 'master' of https://github.com/BoostIO/Boostnote 2018-12-10 11:56:13 +07:00
Evan Miller
b546b9cbe7 InfoPanel automatically adjusts its width 2018-12-03 17:04:04 -05:00
Evan Miller
ce9f76fa63 fixed code style error 2018-12-03 16:55:05 -05:00
Evan Miller
dceed7d84d Fixes exportFolder by making it actually wait for each exportNote 2018-12-03 16:51:59 -05:00
Evan Miller
660a27850f Generate PDF through an Electron BrowserWindow 2018-12-03 16:39:41 -05:00
Evan Miller
0a7fd0288c Use promises for outputFormatter 2018-12-03 16:14:28 -05:00
Evan Miller
33d0a9d3b3 extract html contentformatter 2018-12-03 15:49:41 -05:00
Evan Miller
c1deeaf5f7 Added PDF error to SnippetNoteDetail 2018-12-03 14:40:52 -05:00
Evan Miller
7c1cd50def Add structure for exporting PDFs 2018-12-03 13:12:22 -05:00
Nguyễn Việt Hưng
2ccb541b7f Merge branch 'master' of https://github.com/BoostIO/Boostnote 2018-11-30 10:04:25 +07:00
Nguyễn Việt Hưng
e4a6ff4c70 Merge branch 'master' of https://github.com/ZeroX-DG/Boostnote 2018-11-30 10:04:16 +07:00
Ivan Chen
0904c62a2d Update locales/zh-CN.json
Co-Authored-By: opw0011 <i.mpeople@gmail.com>
2018-11-11 11:19:38 +08:00
Ivan Chen
817b74cc7f Update locales/zh-TW.json
Co-Authored-By: opw0011 <i.mpeople@gmail.com>
2018-11-11 11:18:57 +08:00
Daniel Mouritzen
d6fe0df24f Add ~ and _ to autoclosing brackets 2018-10-27 18:22:03 +02:00
Daniel Mouritzen
7fb1a06e1e Add ~ and _ to autoclosing brackets 2018-10-27 18:18:51 +02:00
steve-o
b004247478 update electron-packager; enable dark mode support for macOS mojave 2018-10-19 12:16:27 -05:00
Nguyễn Việt Hưng
43bd0b0dd5 Merge branch 'master' of https://github.com/BoostIO/Boostnote 2018-10-18 12:07:40 +07:00
Benny O
28007a33a0 Add missing translation for zh-cn 2018-10-13 19:19:48 +08:00
Benny O
4242e0d329 Add missing translation for zh-tw 2018-10-13 19:18:47 +08:00
119 changed files with 2696 additions and 1207 deletions

View File

@@ -7,7 +7,7 @@
"test": {
"presets": ["env" ,"react", "es2015"],
"plugins": [
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
[ "babel-plugin-webpack-alias", { "config": "<rootDir>/webpack.config.js" } ]
]
}
}

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
issuehunt: BoostIo/Boostnote

View File

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

View File

@@ -14,18 +14,20 @@ import {
import TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
import crypto from 'crypto'
import consts from 'browser/lib/consts'
import { isMarkdownTitleURL } from 'browser/lib/utils'
import styles from '../components/CodeEditor.styl'
import fs from 'fs'
const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
import TurndownService from 'turndown'
import {
gfm
} from 'turndown-plugin-gfm'
import {languageMaps} from '../lib/CMLanguageList'
import snippetManager from '../lib/SnippetManager'
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
import markdownlint from 'markdownlint'
import Jsonlint from 'jsonlint-mod'
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -38,85 +40,6 @@ function translateHotkey (hotkey) {
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
}
const languageMaps = {
brainfuck: 'Brainfuck',
cpp: 'C++',
cs: 'C#',
clojure: 'Clojure',
'clojure-repl': 'ClojureScript',
cmake: 'CMake',
coffeescript: 'CoffeeScript',
crystal: 'Crystal',
css: 'CSS',
d: 'D',
dart: 'Dart',
delphi: 'Pascal',
diff: 'Diff',
django: 'Django',
dockerfile: 'Dockerfile',
ebnf: 'EBNF',
elm: 'Elm',
erlang: 'Erlang',
'erlang-repl': 'Erlang',
fortran: 'Fortran',
fsharp: 'F#',
gherkin: 'Gherkin',
go: 'Go',
groovy: 'Groovy',
haml: 'HAML',
haskell: 'Haskell',
haxe: 'Haxe',
http: 'HTTP',
ini: 'toml',
java: 'Java',
javascript: 'JavaScript',
json: 'JSON',
julia: 'Julia',
kotlin: 'Kotlin',
less: 'LESS',
livescript: 'LiveScript',
lua: 'Lua',
markdown: 'Markdown',
mathematica: 'Mathematica',
nginx: 'Nginx',
nsis: 'NSIS',
objectivec: 'Objective-C',
ocaml: 'Ocaml',
perl: 'Perl',
php: 'PHP',
powershell: 'PowerShell',
properties: 'Properties files',
protobuf: 'ProtoBuf',
python: 'Python',
puppet: 'Puppet',
q: 'Q',
r: 'R',
ruby: 'Ruby',
rust: 'Rust',
sas: 'SAS',
scala: 'Scala',
scheme: 'Scheme',
scss: 'SCSS',
shell: 'Shell',
smalltalk: 'Smalltalk',
sml: 'SML',
sql: 'SQL',
stylus: 'Stylus',
swift: 'Swift',
tcl: 'Tcl',
tex: 'LaTex',
typescript: 'TypeScript',
twig: 'Twig',
vbnet: 'VB.NET',
vbscript: 'VBScript',
verilog: 'Verilog',
vhdl: 'VHDL',
xml: 'HTML',
xquery: 'XQuery',
yaml: 'YAML',
elixir: 'Elixir'
}
export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
@@ -130,6 +53,7 @@ export default class CodeEditor extends React.Component {
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
const debouncedDeletionOfAttachments = _.debounce(attachmentManagement.deleteAttachmentsNotPresentInNote, 30000)
this.blurHandler = (editor, e) => {
ipcRenderer.send('editor:focused', false)
if (e == null) return null
@@ -141,16 +65,11 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
this.props.onBlur != null && this.props.onBlur(e)
const {
storageKey,
noteKey
} = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote(
this.editor.getValue(),
storageKey,
noteKey
)
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
}
this.pasteHandler = (editor, e) => {
e.preventDefault()
@@ -163,6 +82,8 @@ export default class CodeEditor extends React.Component {
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
this.scrollToLineHandeler = this.scrollToLine.bind(this)
this.getCodeEditorLintConfig = this.getCodeEditorLintConfig.bind(this)
this.validatorOfMarkdown = this.validatorOfMarkdown.bind(this)
this.formatTable = () => this.handleFormatTable()
@@ -228,7 +149,8 @@ export default class CodeEditor extends React.Component {
updateDefaultKeyMap () {
const { hotkey } = this.props
const expandSnippet = this.expandSnippet.bind(this)
const self = this
const expandSnippet = snippetManager.expandSnippet
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
Tab: function (cm) {
@@ -248,14 +170,16 @@ export default class CodeEditor extends React.Component {
}
cm.execCommand('goLineEnd')
} else if (
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
!charBeforeCursor.match(/\t|\s|\r|\n|\$/) &&
cursor.ch > 1
) {
// text expansion on tab key if the char before is alphabet
const snippets = JSON.parse(
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
const wordBeforeCursor = self.getWordBeforeCursor(
line,
cursor.line,
cursor.ch
)
if (expandSnippet(line, cursor, cm, snippets) === false) {
if (expandSnippet(wordBeforeCursor, cursor, cm) === false) {
if (tabs) {
cm.execCommand('insertTab')
} else {
@@ -277,6 +201,14 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) {
// Do nothing
},
[translateHotkey(hotkey.insertDate)]: function (cm) {
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString())
},
[translateHotkey(hotkey.insertDateTime)]: function (cm) {
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString())
},
Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
@@ -307,25 +239,10 @@ export default class CodeEditor extends React.Component {
}
componentDidMount () {
const { rulers, enableRulers } = this.props
const { rulers, enableRulers, enableMarkdownLint } = this.props
eventEmitter.on('line:jump', this.scrollToLineHandeler)
const defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
if (!fs.existsSync(consts.SNIPPET_FILE)) {
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(defaultSnippet, null, 4),
'utf8'
)
}
snippetManager.init()
this.updateDefaultKeyMap()
this.value = this.props.value
@@ -334,7 +251,7 @@ export default class CodeEditor extends React.Component {
value: this.props.value,
linesHighlighted: this.props.linesHighlighted,
lineNumbers: this.props.displayLineNumbers,
lineWrapping: true,
lineWrapping: this.props.lineWrapping,
theme: this.props.theme,
indentUnit: this.props.indentSize,
tabSize: this.props.indentSize,
@@ -344,7 +261,8 @@ export default class CodeEditor extends React.Component {
inputStyle: 'textarea',
dragDrop: false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
autoCloseBrackets: {
pairs: this.props.matchingPairs,
triples: this.props.matchingTriples,
@@ -354,6 +272,8 @@ export default class CodeEditor extends React.Component {
extraKeys: this.defaultKeyMap
})
document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none'
if (!this.props.mode && this.props.value && this.props.autoDetect) {
this.autoDetectLanguage(this.props.value)
} else {
@@ -520,61 +440,12 @@ export default class CodeEditor extends React.Component {
this.initialHighlighting()
}
expandSnippet (line, cursor, cm, snippets) {
const wordBeforeCursor = this.getWordBeforeCursor(
line,
cursor.line,
cursor.ch
)
const templateCursorString = ':{}'
for (let i = 0; i < snippets.length; i++) {
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
let cursorIndex
for (let j = 0; j < snippetLines.length; j++) {
cursorIndex = snippetLines[j].indexOf(templateCursorString)
if (cursorIndex !== -1) {
cursorLineNumber = j
cursorLinePosition = cursorIndex
break
}
}
cm.replaceRange(
snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
cm.setCursor({
line: cursor.line + cursorLineNumber,
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
})
} else {
cm.replaceRange(
snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
}
return true
}
}
return false
}
getWordBeforeCursor (line, lineNumber, cursorPosition) {
let wordBeforeCursor = ''
const originCursorPosition = cursorPosition
const emptyChars = /\t|\s|\r|\n/
const emptyChars = /\t|\s|\r|\n|\$/
// to prevent the word to expand is long that will crash the whole app
// to prevent the word is long that will crash the whole app
// the safeStop is there to stop user to expand words that longer than 20 chars
const safeStop = 20
@@ -584,7 +455,7 @@ export default class CodeEditor extends React.Component {
if (!emptyChars.test(currentChar)) {
wordBeforeCursor = currentChar + wordBeforeCursor
} else if (wordBeforeCursor.length >= safeStop) {
throw new Error('Your snippet trigger is too long !')
throw new Error('Stopped after 20 loops for safety reason !')
} else {
break
}
@@ -629,7 +500,9 @@ export default class CodeEditor extends React.Component {
let needRefresh = false
const {
rulers,
enableRulers
enableRulers,
enableMarkdownLint,
customMarkdownLintConfig
} = this.props
if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode)
@@ -647,6 +520,16 @@ export default class CodeEditor extends React.Component {
if (prevProps.keyMap !== this.props.keyMap) {
needRefresh = true
}
if (prevProps.enableMarkdownLint !== enableMarkdownLint || prevProps.customMarkdownLintConfig !== customMarkdownLintConfig) {
if (!enableMarkdownLint) {
this.editor.setOption('lint', {default: false})
document.querySelector('.CodeMirror-lint-markers').style.display = 'none'
} else {
this.editor.setOption('lint', this.getCodeEditorLintConfig())
document.querySelector('.CodeMirror-lint-markers').style.display = 'inline-block'
}
needRefresh = true
}
if (
prevProps.enableRulers !== enableRulers ||
@@ -667,6 +550,10 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
}
if (prevProps.lineWrapping !== this.props.lineWrapping) {
this.editor.setOption('lineWrapping', this.props.lineWrapping)
}
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
}
@@ -727,6 +614,56 @@ export default class CodeEditor extends React.Component {
}
}
getCodeEditorLintConfig () {
const { mode } = this.props
const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'
return checkMarkdownNoteIsOpen ? {
'getAnnotations': this.validatorOfMarkdown,
'async': true
} : false
}
validatorOfMarkdown (text, updateLinting) {
const { customMarkdownLintConfig } = this.props
let lintConfigJson
try {
Jsonlint.parse(customMarkdownLintConfig)
lintConfigJson = JSON.parse(customMarkdownLintConfig)
} catch (err) {
eventEmitter.emit('APP_SETTING_ERROR')
return
}
const lintOptions = {
'strings': {
'content': text
},
'config': lintConfigJson
}
return markdownlint(lintOptions, (err, result) => {
if (!err) {
const foundIssues = []
const splitText = text.split('\n')
result.content.map(item => {
let ruleNames = ''
item.ruleNames.map((ruleName, index) => {
ruleNames += ruleName
ruleNames += (index === item.ruleNames.length - 1) ? ': ' : '/'
})
const lineNumber = item.lineNumber - 1
foundIssues.push({
from: CodeMirror.Pos(lineNumber, 0),
to: CodeMirror.Pos(lineNumber, splitText[lineNumber].length),
message: ruleNames + item.ruleDescription,
severity: 'warning'
})
})
updateLinting(foundIssues)
}
})
}
setMode (mode) {
let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
@@ -738,6 +675,34 @@ export default class CodeEditor extends React.Component {
handleChange (editor, changeObject) {
spellcheck.handleChange(editor, changeObject)
// The current note contains an toc. We'll check for changes on headlines.
// origin is undefined when markdownTocGenerator replace the old tod
if (tocExistsInEditor(editor) && changeObject.origin !== undefined) {
let requireTocUpdate
// Check if one of the changed lines contains a headline
for (let line = 0; line < changeObject.text.length; line++) {
if (this.linePossibleContainsHeadline(editor.getLine(changeObject.from.line + line))) {
requireTocUpdate = true
break
}
}
if (!requireTocUpdate) {
// Check if one of the removed lines contains a headline
for (let line = 0; line < changeObject.removed.length; line++) {
if (this.linePossibleContainsHeadline(changeObject.removed[line])) {
requireTocUpdate = true
break
}
}
}
if (requireTocUpdate) {
generateInEditor(editor)
}
}
this.updateHighlight(editor, changeObject)
this.value = editor.getValue()
@@ -746,15 +711,21 @@ export default class CodeEditor extends React.Component {
}
}
linePossibleContainsHeadline (currentLine) {
// We can't check if the line start with # because when some write text before
// the # we also need to update the toc
return currentLine.includes('# ')
}
incrementLines (start, linesAdded, linesRemoved, editor) {
let highlightedLines = editor.options.linesHighlighted
const highlightedLines = editor.options.linesHighlighted
const totalHighlightedLines = highlightedLines.length
let offset = linesAdded - linesRemoved
const offset = linesAdded - linesRemoved
// Store new items to be added as we're changing the lines
let newLines = []
const newLines = []
let i = totalHighlightedLines
@@ -835,6 +806,9 @@ export default class CodeEditor extends React.Component {
ch: 1
}
this.editor.setCursor(cursor)
const top = this.editor.charCoords({line: num, ch: 0}, 'local').top
const middleHeight = this.editor.getScrollerElement().offsetHeight / 2
this.editor.scrollTo(null, top - middleHeight - 5)
}
focus () {
@@ -950,6 +924,8 @@ export default class CodeEditor extends React.Component {
if (isInFencedCodeBlock(editor)) {
this.handlePasteText(editor, pastedTxt)
} else if (fetchUrlTitle && isMarkdownTitleURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt)
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt)
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
@@ -991,7 +967,17 @@ export default class CodeEditor extends React.Component {
}
handlePasteUrl (editor, pastedTxt) {
const taggedUrl = `<${pastedTxt}>`
let taggedUrl = `<${pastedTxt}>`
let urlToFetch = pastedTxt
let titleMark = ''
if (isMarkdownTitleURL(pastedTxt)) {
const pastedTxtSplitted = pastedTxt.split(' ')
titleMark = `${pastedTxtSplitted[0]} `
urlToFetch = pastedTxtSplitted[1]
taggedUrl = `<${urlToFetch}>`
}
editor.replaceSelection(taggedUrl)
const isImageReponse = response => {
@@ -1003,22 +989,23 @@ export default class CodeEditor extends React.Component {
const replaceTaggedUrl = replacement => {
const value = editor.getValue()
const cursor = editor.getCursor()
const newValue = value.replace(taggedUrl, replacement)
const newValue = value.replace(taggedUrl, titleMark + replacement)
const newCursor = Object.assign({}, cursor, {
ch: cursor.ch + newValue.length - value.length
ch: cursor.ch + newValue.length - (value.length - titleMark.length)
})
editor.setValue(newValue)
editor.setCursor(newCursor)
}
fetch(pastedTxt, {
fetch(urlToFetch, {
method: 'get'
})
.then(response => {
if (isImageReponse(response)) {
return this.mapImageResponse(response, pastedTxt)
return this.mapImageResponse(response, urlToFetch)
} else {
return this.mapNormalResponse(response, pastedTxt)
return this.mapNormalResponse(response, urlToFetch)
}
})
.then(replacement => {
@@ -1138,13 +1125,11 @@ export default class CodeEditor extends React.Component {
}
ref='root'
tabIndex='-1'
style={
{
style={{
fontFamily,
fontSize: fontSize,
width: width
}
}
}}
onDrop={
e => this.handleDropImage(e)
}
@@ -1182,7 +1167,9 @@ CodeEditor.propTypes = {
onChange: PropTypes.func,
readOnly: PropTypes.bool,
autoDetect: PropTypes.bool,
spellCheck: PropTypes.bool
spellCheck: PropTypes.bool,
enableMarkdownLint: PropTypes.bool,
customMarkdownLintConfig: PropTypes.string
}
CodeEditor.defaultProps = {
@@ -1194,5 +1181,7 @@ CodeEditor.defaultProps = {
indentSize: 4,
indentType: 'space',
autoDetect: false,
spellCheck: false
spellCheck: false,
enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint,
customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig
}

View File

@@ -3,4 +3,3 @@
.spellcheck-select
border: none
text-decoration underline wavy red

View File

@@ -32,6 +32,7 @@ class MarkdownEditor extends React.Component {
componentDidMount () {
this.value = this.refs.code.value
eventEmitter.on('editor:lock', this.lockEditorCode)
eventEmitter.on('editor:focus', this.focusEditor.bind(this))
}
componentDidUpdate () {
@@ -47,6 +48,15 @@ class MarkdownEditor extends React.Component {
componentWillUnmount () {
this.cancelQueue()
eventEmitter.off('editor:lock', this.lockEditorCode)
eventEmitter.off('editor:focus', this.focusEditor.bind(this))
}
focusEditor () {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
})
}
queueRendering (value) {
@@ -149,10 +159,10 @@ class MarkdownEditor extends React.Component {
e.preventDefault()
e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
const checkReplace = /\[x\]/i
const uncheckReplace = /\[ \]/
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
const checkReplace = /\[x]/i
const uncheckReplace = /\[ ]/
if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value
@@ -294,6 +304,7 @@ class MarkdownEditor extends React.Component {
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
@@ -309,6 +320,8 @@ class MarkdownEditor extends React.Component {
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
/>
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview'

View File

@@ -8,7 +8,7 @@ import consts from 'browser/lib/consts'
import Raphael from 'raphael'
import flowchart from 'flowchart'
import mermaidRender from './render/MermaidRender'
import SequenceDiagram from 'js-sequence-diagrams'
import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
import Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
@@ -18,15 +18,13 @@ import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
import { escapeHtmlCharacters } from 'browser/lib/utils'
import yaml from 'js-yaml'
import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
import fs from 'fs'
import { render } from 'react-dom'
import Carousel from 'react-image-carousel'
import ConfigManager from '../main/lib/ConfigManager'
const { remote, shell } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
const { app } = remote
const path = require('path')
@@ -34,8 +32,6 @@ const fileUrl = require('file-url')
const dialog = remote.dialog
const uri2path = require('file-uri-to-path')
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = fileUrl(
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
@@ -45,6 +41,7 @@ const CSS_FILES = [
`${appPath}/node_modules/codemirror/lib/codemirror.css`,
`${appPath}/node_modules/react-image-carousel/lib/css/main.min.css`
]
const win = global.process.platform === 'win32'
function buildStyle (
fontFamily,
@@ -192,6 +189,19 @@ const defaultCodeBlockFontFamily = [
'source-code-pro',
'monospace'
]
// return the line number of the line that used to generate the specified element
// return -1 if the line is not found
function getSourceLineNumberByElement (element) {
let isHasLineNumber = element.dataset.line !== undefined
let parent = element
while (!isHasLineNumber && parent.parentElement !== null) {
parent = parent.parentElement
isHasLineNumber = parent.dataset.line !== undefined
}
return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1
}
export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
@@ -208,6 +218,7 @@ export default class MarkdownPreview extends React.Component {
this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd()
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.saveAsPdfHandler = () => this.handleSaveAsPdf()
this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handleLinkClick.bind(this)
@@ -235,30 +246,9 @@ export default class MarkdownPreview extends React.Component {
}
handleContextMenu (event) {
// If a contextMenu handler was passed to us, use it instead of the self-defined one -> return
if (_.isFunction(this.props.onContextMenu)) {
this.props.onContextMenu(event)
return
}
// No contextMenu was passed to us -> execute our own link-opener
if (event.target.tagName.toLowerCase() === 'a') {
const href = event.target.href
const isLocalFile = href.startsWith('file:')
if (isLocalFile) {
const absPath = uri2path(href)
try {
if (fs.lstatSync(absPath).isFile()) {
context.popup([
{
label: i18n.__('Show in explorer'),
click: (e) => shell.showItemInFolder(absPath)
}
])
}
} catch (e) {
console.log('Error while evaluating if the file is locally available', e)
}
}
const menu = buildMarkdownPreviewContextMenu(this, event)
if (menu != null) {
menu.popup(remote.getCurrentWindow())
}
}
@@ -268,17 +258,27 @@ export default class MarkdownPreview extends React.Component {
handleMouseDown (e) {
const config = ConfigManager.get()
const clickElement = e.target
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
}
if (e.target != null) {
switch (e.target.tagName) {
case 'A':
case 'INPUT':
return null
if (e.ctrlKey) {
if (config.editor.type === 'SPLIT') {
if (lineNumber !== -1) {
eventEmitter.emit('line:jump', lineNumber)
}
} else {
if (lineNumber !== -1) {
eventEmitter.emit('editor:focus')
eventEmitter.emit('line:jump', lineNumber)
}
}
}
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
}
handleMouseUp (e) {
@@ -297,58 +297,81 @@ export default class MarkdownPreview extends React.Component {
this.exportAsDocument('md')
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => {
const {
fontFamily,
fontSize,
codeBlockFontFamily,
lineNumber,
codeBlockTheme,
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
} = this.getStyleParams()
htmlContentFormatter (noteContent, exportTasks, targetDir) {
const {
fontFamily,
fontSize,
codeBlockFontFamily,
lineNumber,
codeBlockTheme,
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
} = this.getStyleParams()
const inlineStyles = buildStyle(
fontFamily,
fontSize,
codeBlockFontFamily,
lineNumber,
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
)
let body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach(file => {
if (global.process.platform === 'win32') {
file = file.replace('file:///', '')
} else {
file = file.replace('file://', '')
}
exportTasks.push({
src: file,
dst: 'css'
const inlineStyles = buildStyle(
fontFamily,
fontSize,
codeBlockFontFamily,
lineNumber,
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
)
let body = this.markdown.render(noteContent)
body = attachmentManagement.fixLocalURLS(
body,
this.props.storagePath
)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach(file => {
if (global.process.platform === 'win32') {
file = file.replace('file:///', '')
} else {
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>
<base href="file://${targetDir}/">
<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>`
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
}
handleSaveAsPdf () {
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false}})
printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
return new Promise((resolve, reject) => {
printout.webContents.on('did-finish-load', () => {
printout.webContents.printToPDF({}, (err, data) => {
if (err) reject(err)
else resolve(data)
printout.destroy()
})
})
})
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>`
})
}
@@ -490,6 +513,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
eventEmitter.on('export:save-pdf', this.saveAsPdfHandler)
eventEmitter.on('print', this.printHandler)
}
@@ -527,6 +551,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
eventEmitter.off('export:save-pdf', this.saveAsPdfHandler)
eventEmitter.off('print', this.printHandler)
}
@@ -625,14 +650,12 @@ export default class MarkdownPreview extends React.Component {
)
}
GetCodeThemeLink (theme) {
theme = consts.THEMES.some(_theme => _theme === theme) &&
theme !== 'default'
? theme
: 'elegant'
return theme.startsWith('solarized')
? `${appPath}/node_modules/codemirror/theme/solarized.css`
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
GetCodeThemeLink (name) {
const theme = consts.THEMES.find(theme => theme.name === name)
return theme
? (win ? theme.path : `${appPath}/${theme.path}`)
: `${appPath}/node_modules/codemirror/theme/elegant.css`
}
rewriteIframe () {
@@ -690,9 +713,9 @@ export default class MarkdownPreview extends React.Component {
}
)
codeBlockTheme = consts.THEMES.some(_theme => _theme === codeBlockTheme)
? codeBlockTheme
: 'default'
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
@@ -705,6 +728,8 @@ export default class MarkdownPreview extends React.Component {
copyIcon.innerHTML =
'<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
copyIcon.onclick = e => {
e.preventDefault()
e.stopPropagation()
copy(content)
if (showCopyNotification) {
this.notify('Saved to Clipboard!', {
@@ -713,14 +738,11 @@ export default class MarkdownPreview extends React.Component {
})
}
}
el.parentNode.appendChild(copyIcon)
el.innerHTML = ''
if (codeBlockTheme.indexOf('solarized') === 0) {
const [refThema, color] = codeBlockTheme.split(' ')
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
} else {
el.parentNode.className += ` cm-s-${codeBlockTheme}`
}
el.parentNode.className += ` ${codeBlockThemeClassName}`
CodeMirror.runMode(content, syntax.mime, el, {
tabSize: indentSize
})
@@ -835,78 +857,102 @@ export default class MarkdownPreview extends React.Component {
const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect()
const config = { attributes: true, subtree: true }
const imgObserver = new MutationObserver((mutationList) => {
for (const mu of mutationList) {
if (mu.target.className === 'carouselContent-enter-done') {
this.setImgOnClickEventHelper(mu.target, rect)
break
}
}
})
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
for (const img of imgList) {
img.onclick = () => {
const widthMagnification = document.body.clientWidth / img.width
const heightMagnification = document.body.clientHeight / img.height
const baseOnWidth = widthMagnification < heightMagnification
const magnification = baseOnWidth ? widthMagnification : heightMagnification
const parentEl = img.parentElement
this.setImgOnClickEventHelper(img, rect)
imgObserver.observe(parentEl, config)
}
const zoomImgWidth = img.width * magnification
const zoomImgHeight = img.height * magnification
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
const originalImgTop = img.y + rect.top
const originalImgLeft = img.x + rect.left
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
for (const a of aList) {
a.removeEventListener('click', this.linkClickHandler)
a.addEventListener('click', this.linkClickHandler)
}
}
const zoomImg = document.createElement('img')
zoomImg.src = img.src
setImgOnClickEventHelper (img, rect) {
img.onclick = () => {
const widthMagnification = document.body.clientWidth / img.width
const heightMagnification = document.body.clientHeight / img.height
const baseOnWidth = widthMagnification < heightMagnification
const magnification = baseOnWidth ? widthMagnification : heightMagnification
const zoomImgWidth = img.width * magnification
const zoomImgHeight = img.height * magnification
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
const originalImgTop = img.y + rect.top
const originalImgLeft = img.x + rect.left
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const zoomImg = document.createElement('img')
zoomImg.src = img.src
zoomImg.style = `
position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px;
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = `
position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px;
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
top: ${originalImgTop}px;
left: ${originalImgLeft}px;
width: ${img.width}px;
height: ${img.height}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = `
position: absolute;
top: ${originalImgTop}px;
left: ${originalImgLeft}px;
width: ${img.width}px;
height: ${img.height}px;
`
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
}
this.getWindow().scrollTo(0, 0)
}
focus () {
@@ -953,12 +999,18 @@ export default class MarkdownPreview extends React.Component {
e.preventDefault()
e.stopPropagation()
const href = e.target.href
const linkHash = href.split('/').pop()
const rawHref = e.target.getAttribute('href')
const parser = document.createElement('a')
parser.href = e.target.getAttribute('href')
const { href, hash } = parser
const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10
const regexNoteInternalLink = /main.html#(.+)/
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
const extractId = /(main.html)?#/
const regexNoteInternalLink = new RegExp(`${extractId.source}(.+)`)
if (regexNoteInternalLink.test(linkHash)) {
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
const targetId = mdurl.encode(linkHash.replace(extractId, ''))
const targetElement = this.refs.root.contentWindow.document.getElementById(
targetId
)

View File

@@ -78,10 +78,10 @@ class MarkdownSplitEditor extends React.Component {
e.preventDefault()
e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /^\s*[\+\-\*] \[x\]/i
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/
const checkReplace = /\[x\]/i
const uncheckReplace = /\[ \]/
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
const checkReplace = /\[x]/i
const uncheckReplace = /\[ ]/
if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value
@@ -150,7 +150,6 @@ class MarkdownSplitEditor extends React.Component {
onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)}>
<CodeEditor
styleName='codeEditor'
ref='code'
width={this.state.codeEditorWidthInPercent + '%'}
mode='Boost Flavored Markdown'
@@ -160,6 +159,7 @@ class MarkdownSplitEditor extends React.Component {
fontFamily={config.editor.fontFamily}
fontSize={editorFontSize}
displayLineNumbers={config.editor.displayLineNumbers}
lineWrapping
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
@@ -179,13 +179,14 @@ class MarkdownSplitEditor extends React.Component {
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
/>
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
<div styleName='slider-hitbox' />
</div>
<MarkdownPreview
style={previewStyle}
styleName='preview'
theme={config.ui.theme}
keyMap={config.editor.keyMap}
fontSize={config.preview.fontSize}

View File

@@ -8,9 +8,30 @@
top -2px
width 0
z-index 0
border-left 1px solid $ui-borderColor
.slider-hitbox
absolute top bottom left right
width 7px
left -3px
z-index 10
cursor col-resize
body[data-theme="dark"]
.root
.slider
border-left 1px solid $ui-dark-borderColor
body[data-theme="solarized-dark"]
.root
.slider
border-left 1px solid $ui-solarized-dark-borderColor
body[data-theme="monokai"]
.root
.slider
border-left 1px solid $ui-monokai-borderColor
body[data-theme="dracula"]
.root
.slider
border-left 1px solid $ui-dracula-borderColor

View File

@@ -8,7 +8,7 @@ const ModalEscButton = ({
}) => (
<button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>×</div>
<div styleName='esc-text'>esc</div>
<div>esc</div>
</button>
)

View File

@@ -16,8 +16,8 @@ const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
onClick={(e) => handleToggleButtonClick(e)}
>
{isFolded
? <i className='fa fa-angle-double-right' />
: <i className='fa fa-angle-double-left' />
? <i className='fa fa-angle-double-right fa-2x' />
: <i className='fa fa-angle-double-left fa-2x' />
}
</button>
)

View File

@@ -7,7 +7,7 @@
border-radius 16.5px
height 34px
width 34px
line-height 32px
line-height 100%
padding 0
&:hover
border: 1px solid #1EC38B;

View File

@@ -148,15 +148,14 @@ NoteItem.propTypes = {
tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired,
blog: {
blog: PropTypes.shape({
blogLink: PropTypes.string,
blogId: PropTypes.number
}
})
}),
handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired,
handleDragStart: PropTypes.func.isRequired,
handleDragEnd: PropTypes.func.isRequired
handleDragStart: PropTypes.func.isRequired
}
export default CSSModules(NoteItem, styles)

View File

@@ -74,7 +74,7 @@ SideNavFilter.propTypes = {
isStarredActive: PropTypes.bool.isRequired,
isTrashedActive: PropTypes.bool.isRequired,
handleStarredButtonClick: PropTypes.func.isRequired,
handleTrashdButtonClick: PropTypes.func.isRequired
handleTrashedButtonClick: PropTypes.func.isRequired
}
export default CSSModules(SideNavFilter, styles)

View File

@@ -114,7 +114,7 @@ class SnippetTab extends React.Component {
>
{snippet.name.trim().length > 0
? snippet.name
: <span styleName='button-unnamed'>
: <span>
{i18n.__('Unnamed')}
</span>
}

View File

@@ -25,10 +25,10 @@ const TodoProcess = ({
)
TodoProcess.propTypes = {
todoStatus: {
todoStatus: PropTypes.exact({
total: PropTypes.number.isRequired,
completed: PropTypes.number.isRequired
}
})
}
export default CSSModules(TodoProcess, styles)

View File

@@ -0,0 +1,78 @@
export const languageMaps = {
brainfuck: 'Brainfuck',
cpp: 'C++',
cs: 'C#',
clojure: 'Clojure',
'clojure-repl': 'ClojureScript',
cmake: 'CMake',
coffeescript: 'CoffeeScript',
crystal: 'Crystal',
css: 'CSS',
d: 'D',
dart: 'Dart',
delphi: 'Pascal',
diff: 'Diff',
django: 'Django',
dockerfile: 'Dockerfile',
ebnf: 'EBNF',
elm: 'Elm',
erlang: 'Erlang',
'erlang-repl': 'Erlang',
fortran: 'Fortran',
fsharp: 'F#',
gherkin: 'Gherkin',
go: 'Go',
groovy: 'Groovy',
haml: 'HAML',
haskell: 'Haskell',
haxe: 'Haxe',
http: 'HTTP',
ini: 'toml',
java: 'Java',
javascript: 'JavaScript',
json: 'JSON',
julia: 'Julia',
kotlin: 'Kotlin',
less: 'LESS',
livescript: 'LiveScript',
lua: 'Lua',
markdown: 'Markdown',
mathematica: 'Mathematica',
nginx: 'Nginx',
nsis: 'NSIS',
objectivec: 'Objective-C',
ocaml: 'Ocaml',
perl: 'Perl',
php: 'PHP',
powershell: 'PowerShell',
properties: 'Properties files',
protobuf: 'ProtoBuf',
python: 'Python',
puppet: 'Puppet',
q: 'Q',
r: 'R',
ruby: 'Ruby',
rust: 'Rust',
sas: 'SAS',
scala: 'Scala',
scheme: 'Scheme',
scss: 'SCSS',
shell: 'Shell',
smalltalk: 'Smalltalk',
sml: 'SML',
sql: 'SQL',
stylus: 'Stylus',
swift: 'Swift',
tcl: 'Tcl',
tex: 'LaTex',
typescript: 'TypeScript',
twig: 'Twig',
vbnet: 'VB.NET',
vbscript: 'VBScript',
verilog: 'Verilog',
vhdl: 'VHDL',
xml: 'HTML',
xquery: 'XQuery',
yaml: 'YAML',
elixir: 'Elixir'
}

View File

@@ -1,5 +1,5 @@
import CSSModules from 'react-css-modules'
export default function (component, styles) {
return CSSModules(component, styles, {errorWhenNotFound: false})
return CSSModules(component, styles, {handleNotFoundStyleName: 'log'})
}

View File

@@ -62,10 +62,12 @@ const languages = [
{
name: 'Spanish',
locale: 'es-ES'
}, {
},
{
name: 'Turkish',
locale: 'tr'
}, {
},
{
name: 'Thai',
locale: 'th'
}
@@ -82,4 +84,3 @@ module.exports = {
return languages
}
}

View File

@@ -0,0 +1,91 @@
import crypto from 'crypto'
import fs from 'fs'
import consts from './consts'
class SnippetManager {
constructor () {
this.defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
this.snippets = []
this.expandSnippet = this.expandSnippet.bind(this)
this.init = this.init.bind(this)
this.assignSnippets = this.assignSnippets.bind(this)
}
init () {
if (fs.existsSync(consts.SNIPPET_FILE)) {
try {
this.snippets = JSON.parse(
fs.readFileSync(consts.SNIPPET_FILE, { encoding: 'UTF-8' })
)
} catch (error) {
console.log('Error while parsing snippet file')
}
return
}
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(this.defaultSnippet, null, 4),
'utf8'
)
this.snippets = this.defaultSnippet
}
assignSnippets (snippets) {
this.snippets = snippets
}
expandSnippet (wordBeforeCursor, cursor, cm) {
const templateCursorString = ':{}'
for (let i = 0; i < this.snippets.length; i++) {
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
continue
}
if (this.snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = this.snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
let cursorIndex
for (let j = 0; j < snippetLines.length; j++) {
cursorIndex = snippetLines[j].indexOf(templateCursorString)
if (cursorIndex !== -1) {
cursorLineNumber = j
cursorLinePosition = cursorIndex
break
}
}
cm.replaceRange(
this.snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
cm.setCursor({
line: cursor.line + cursorLineNumber,
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
})
} else {
cm.replaceRange(
this.snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
}
return true
}
return false
}
}
const manager = new SnippetManager()
export default manager

View File

@@ -3,14 +3,44 @@ const fs = require('sander')
const { remote } = require('electron')
const { app } = remote
const themePath = process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './node_modules/codemirror/theme')
: require('path').resolve('./node_modules/codemirror/theme')
const themes = fs.readdirSync(themePath)
.map((themePath) => {
return themePath.substring(0, themePath.lastIndexOf('.'))
})
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
const isProduction = process.env.NODE_ENV === 'production'
const paths = [
isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
]
const themes = paths
.map(directory => fs.readdirSync(directory).map(file => {
const name = file.substring(0, file.lastIndexOf('.'))
return {
name,
path: path.join(directory.split(/\//g).slice(-3).join('/'), file),
className: `cm-s-${name}`
}
}))
.reduce((accumulator, value) => accumulator.concat(value), [])
.sort((a, b) => a.name.localeCompare(b.name))
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
name: 'solarized dark',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
className: `cm-s-solarized cm-s-dark`
}, {
name: 'solarized light',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
className: `cm-s-solarized cm-s-light`
})
themes.splice(0, 0, {
name: 'default',
path: `${CODEMIRROR_THEME_PATH}/elegant.css`,
className: `cm-s-default`
})
const snippetFile = process.env.NODE_ENV !== 'test'
? path.join(app.getPath('userData'), 'snippets.json')
@@ -35,7 +65,7 @@ const consts = {
'Dodger Blue',
'Violet Eggplant'
],
THEMES: ['default'].concat(themes),
THEMES: themes,
SNIPPET_FILE: snippetFile,
DEFAULT_EDITOR_FONT_FAMILY: [
'Monaco',

View File

@@ -1,6 +1,12 @@
import i18n from 'browser/lib/i18n'
import fs from 'fs'
const {remote} = require('electron')
const {Menu} = remote.require('electron')
const {clipboard} = remote.require('electron')
const {shell} = remote.require('electron')
const spellcheck = require('./spellcheck')
const uri2path = require('file-uri-to-path')
/**
* Creates the context menu that is shown when there is a right click in the editor of a (not-snippet) note.
@@ -62,4 +68,57 @@ const buildEditorContextMenu = function (editor, event) {
return Menu.buildFromTemplate(template)
}
module.exports = buildEditorContextMenu
/**
* Creates the context menu that is shown when there is a right click Markdown preview of a (not-snippet) note.
* @param {MarkdownPreview} markdownPreview
* @param {MouseEvent} event that has triggered the creation of the context menu
* @returns {Electron.Menu} The created electron context menu
*/
const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
if (markdownPreview == null || event == null || event.pageX == null || event.pageY == null) {
return null
}
// Default context menu inclusions
const template = [{
role: 'copy'
}, {
role: 'selectall'
}]
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
// Link opener for files on the local system pointed to by href
const href = event.target.href
const isLocalFile = href.startsWith('file:')
if (isLocalFile) {
const absPath = uri2path(href)
try {
if (fs.lstatSync(absPath).isFile()) {
template.push(
{
label: i18n.__('Show in explorer'),
click: (e) => shell.showItemInFolder(absPath)
}
)
}
} catch (e) {
console.log('Error while evaluating if the file is locally available', e)
}
}
// Add option to context menu to copy url
template.push(
{
label: i18n.__('Copy Url'),
click: (e) => clipboard.writeText(href)
}
)
}
return Menu.buildFromTemplate(template)
}
module.exports =
{
buildEditorContextMenu: buildEditorContextMenu,
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
}

View File

@@ -1,5 +1,10 @@
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
if (stylusCodeInfo == null) {
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
} else {
stylusCodeInfo.alias = ['styl']
}
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})

View File

@@ -4,11 +4,11 @@ export function getTodoStatus (content) {
let numberOfCompletedTodo = 0
splitted.forEach((line) => {
const trimmedLine = line.trim()
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./i)) {
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
numberOfTodo++
}
if (trimmedLine.match(/^[\+\-\*] \[x\] ./i)) {
if (trimmedLine.match(/^[+\-*] \[x] ./i)) {
numberOfCompletedTodo++
}
})

View File

@@ -21,13 +21,15 @@ function uniqueSlug (slug, slugs, opts) {
}
function linkify (token) {
token.content = mdlink(token.content, '#' + token.slug)
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
return token
}
const TOC_MARKER_START = '<!-- toc -->'
const TOC_MARKER_END = '<!-- tocstop -->'
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
/**
* Takes care of proper updating given editor with TOC.
* If TOC doesn't exit in the editor, it's inserted at current caret position.
@@ -35,12 +37,6 @@ const TOC_MARKER_END = '<!-- tocstop -->'
* @param editor CodeMirror editor to be updated with TOC
*/
export function generateInEditor (editor) {
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
function tocExistsInEditor () {
return tocRegex.test(editor.getValue())
}
function updateExistingToc () {
const toc = generate(editor.getValue())
const search = editor.getSearchCursor(tocRegex)
@@ -54,13 +50,17 @@ export function generateInEditor (editor) {
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
}
if (tocExistsInEditor()) {
if (tocExistsInEditor(editor)) {
updateExistingToc()
} else {
addTocAtCursorPosition()
}
}
export function tocExistsInEditor (editor) {
return tocRegex.test(editor.getValue())
}
/**
* Generates MD TOC based on MD document passed as string.
* @param markdownText MD document
@@ -94,5 +94,6 @@ function wrapTocWithEol (toc, editor) {
export default {
generate,
generateInEditor
generateInEditor,
tocExistsInEditor
}

View File

@@ -2,6 +2,7 @@ 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 mdurl from 'mdurl'
import smartArrows from 'markdown-it-smartarrows'
import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
@@ -32,6 +33,7 @@ class Markdown {
const updatedOptions = Object.assign(defaultOptions, options)
this.md = markdownit(updatedOptions)
this.md.linkify.set({ fuzzyLink: false })
if (updatedOptions.sanitize !== 'NONE') {
const allowedTags = ['iframe', 'input', 'b',
@@ -149,9 +151,9 @@ class Markdown {
const content = token.content.split('\n').slice(0, -1).map(line => {
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
if (match) {
return match[1]
return mdurl.encode(match[1])
} else {
return line
return mdurl.encode(line)
}
}).join('\n')
@@ -181,7 +183,7 @@ class Markdown {
})
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
this.md.use(require('markdown-it-plantuml'), {
generateSource: function (umlCode) {
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
@@ -287,7 +289,9 @@ class Markdown {
case 'list_item_open':
case 'paragraph_open':
case 'table_open':
token.attrPush(['data-line', token.map[0]])
if (token.map) {
token.attrPush(['data-line', token.map[0]])
}
}
})
const result = originalRender.call(this.md.renderer, tokens, options, env)

View File

@@ -1,7 +1,8 @@
import { hashHistory } from 'react-router'
import dataApi from 'browser/main/lib/dataApi'
import ee from 'browser/main/lib/eventEmitter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import queryString from 'query-string'
import { push } from 'connected-react-router'
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
@@ -28,10 +29,10 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
note: note
})
hashHistory.push({
dispatch(push({
pathname: location.pathname,
query: { key: noteHash }
})
search: queryString.stringify({ key: noteHash })
}))
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
@@ -70,10 +71,10 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
dispatch(push({
pathname: location.pathname,
query: { key: noteHash }
})
search: queryString.stringify({ key: noteHash })
}))
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})

View File

@@ -1,17 +1,11 @@
import diacritics from 'diacritics-map'
function replaceDiacritics (str) {
return str.replace(/[À-ž]/g, function (ch) {
return diacritics[ch] || ch
})
}
module.exports = function slugify (title) {
let slug = title.trim()
const slug = encodeURI(
title.trim()
.replace(/^\s+/, '')
.replace(/\s+$/, '')
.replace(/\s+/g, '-')
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
)
slug = replaceDiacritics(slug)
slug = slug.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
return encodeURI(slug).replace(/\-+$/, '')
return slug
}

View File

@@ -14,7 +14,7 @@ let self
function getAvailableDictionaries () {
return [
{label: i18n.__('Disabled'), value: SPELLCHECK_DISABLED},
{label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED},
{label: i18n.__('English'), value: 'en_GB'},
{label: i18n.__('German'), value: 'de_DE'},
{label: i18n.__('French'), value: 'fr_FR'}

View File

@@ -132,8 +132,13 @@ export function isObjectEqual (a, b) {
return true
}
export function isMarkdownTitleURL (str) {
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
}
export default {
lastFindInArray,
escapeHtmlCharacters,
isObjectEqual
isObjectEqual,
isMarkdownTitleURL
}

View File

@@ -11,7 +11,7 @@ const FullscreenButton = ({
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
return (
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<img src='../resources/icon/icon-full.svg' />
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
</button>
)

View File

@@ -14,7 +14,7 @@ class InfoPanel extends React.Component {
render () {
const {
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
} = this.props
return (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
@@ -60,7 +60,7 @@ class InfoPanel extends React.Component {
</div>
<div>
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
<input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} />
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<i className='fa fa-clipboard' />
</button>
@@ -85,6 +85,11 @@ class InfoPanel extends React.Component {
<p>{i18n.__('.html')}</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<i className='fa fa-file-pdf-o' />
<p>{i18n.__('.pdf')}</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
<i className='fa fa-print' />
<p>{i18n.__('Print')}</p>
@@ -104,6 +109,7 @@ InfoPanel.propTypes = {
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired,
exportAsPdf: PropTypes.func.isRequired,
wordCount: PropTypes.number,
letterCount: PropTypes.number,
type: PropTypes.string.isRequired,

View File

@@ -15,7 +15,7 @@
right 25px
position absolute
padding 20px 25px 0 25px
width 300px
// width 300px
overflow auto
background-color $ui-noteList-backgroundColor
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)

View File

@@ -5,7 +5,7 @@ import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
}) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div>
@@ -46,7 +46,7 @@ const InfoPanelTrashed = ({
<p>.html</p>
</button>
<button styleName='export--unable'>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<i className='fa fa-file-pdf-o' />
<p>.pdf</p>
</button>
@@ -61,7 +61,8 @@ InfoPanelTrashed.propTypes = {
createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired
exportAsHtml: PropTypes.func.isRequired,
exportAsPdf: PropTypes.func.isRequired
}
export default CSSModules(InfoPanelTrashed, styles)

View File

@@ -9,7 +9,6 @@ import StarButton from './StarButton'
import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import markdown from 'browser/lib/markdownTextHelper'
import StatusBar from '../StatusBar'
@@ -30,6 +29,8 @@ import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
import queryString from 'query-string'
import { replace } from 'connected-react-router'
class MarkdownNoteDetail extends React.Component {
constructor (props) {
@@ -66,9 +67,6 @@ class MarkdownNoteDetail extends React.Component {
})
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
ee.on('code:generate-toc', this.generateToc)
// Focus content if using blur or double click
if (this.state.switchPreview === 'BLUR' || this.state.switchPreview === 'DBL_CLICK') this.focus()
}
componentWillReceiveProps (nextProps) {
@@ -83,6 +81,20 @@ class MarkdownNoteDetail extends React.Component {
if (this.refs.tags) this.refs.tags.reset()
})
}
// Focus content if using blur or double click
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
const {switchPreview} = nextProps.config.editor
if (this.state.switchPreview !== switchPreview) {
this.setState({
switchPreview
})
if (switchPreview === 'BLUR' || switchPreview === 'DBL_CLICK') {
console.log('setting focus', switchPreview)
this.focus()
}
}
}
componentWillUnmount () {
@@ -140,6 +152,7 @@ class MarkdownNoteDetail extends React.Component {
}
handleFolderChange (e) {
const { dispatch } = this.props
const { note } = this.state
const value = this.refs.folder.value
const splitted = value.split('-')
@@ -159,12 +172,12 @@ class MarkdownNoteDetail extends React.Component {
originNote: note,
note: newNote
})
hashHistory.replace({
dispatch(replace({
pathname: location.pathname,
query: {
search: queryString.stringify({
key: newNote.key
}
})
})
}))
this.setState({
isMovingNote: false
})
@@ -201,6 +214,10 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('export:save-html')
}
exportAsPdf () {
ee.emit('export:save-pdf')
}
handleKeyDown (e) {
switch (e.keyCode) {
// tab key
@@ -294,7 +311,7 @@ class MarkdownNoteDetail extends React.Component {
}
getToggleLockButton () {
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
}
handleDeleteKeyDown (e) {
@@ -426,13 +443,14 @@ class MarkdownNoteDetail extends React.Component {
exportAsHtml={this.exportAsHtml}
exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt}
exportAsPdf={this.exportAsPdf}
/>
</div>
</div>
const detailTopBar = <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<div>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
@@ -466,7 +484,7 @@ class MarkdownNoteDetail extends React.Component {
onFocus={(e) => this.handleFocus(e)}
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
>
<img styleName='iconInfo' src={imgSrc} />
<img src={imgSrc} />
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
</button>
@@ -486,12 +504,13 @@ class MarkdownNoteDetail extends React.Component {
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`}
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt}
exportAsHtml={this.exportAsHtml}
exportAsPdf={this.exportAsPdf}
wordCount={note.content.split(' ').length}
letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type}

View File

@@ -80,4 +80,12 @@ body[data-theme="monokai"]
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
background-color $ui-dracula-noteDetail-backgroundColor
div
> button, div
-webkit-user-drag none
user-select none
> img, span
-webkit-user-drag none
user-select none

View File

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

View File

@@ -8,7 +8,6 @@ import StarButton from './StarButton'
import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi'
import {hashHistory} from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
@@ -18,7 +17,6 @@ import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import FullscreenButton from './FullscreenButton'
import TrashButton from './TrashButton'
@@ -31,6 +29,8 @@ import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
import queryString from 'query-string'
import { replace } from 'connected-react-router'
const electron = require('electron')
const { remote } = electron
@@ -166,12 +166,12 @@ class SnippetNoteDetail extends React.Component {
originNote: note,
note: newNote
})
hashHistory.replace({
dispatch(replace({
pathname: location.pathname,
query: {
search: queryString.stringify({
key: newNote.key
}
})
})
}))
this.setState({
isMovingNote: false
})
@@ -518,6 +518,19 @@ class SnippetNoteDetail extends React.Component {
])
}
handleWrapLineButtonClick (e) {
context.popup([
{
label: 'on',
click: (e) => this.handleWrapLineItemClick(e, true)
},
{
label: 'off',
click: (e) => this.handleWrapLineItemClick(e, false)
}
])
}
handleIndentSizeItemClick (e, indentSize) {
const { config, dispatch } = this.props
const editor = Object.assign({}, config.editor, {
@@ -550,6 +563,22 @@ class SnippetNoteDetail extends React.Component {
})
}
handleWrapLineItemClick (e, lineWrapping) {
const { config, dispatch } = this.props
const editor = Object.assign({}, config.editor, {
lineWrapping
})
ConfigManager.set({
editor
})
dispatch({
type: 'SET_CONFIG',
config: {
editor
}
})
}
focus () {
this.refs.description.focus()
}
@@ -657,6 +686,7 @@ class SnippetNoteDetail extends React.Component {
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print'
})[msg]
@@ -719,6 +749,7 @@ class SnippetNoteDetail extends React.Component {
mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)}
value={snippet.content}
linesHighlighted={snippet.linesHighlighted}
lineWrapping={config.editor.lineWrapping}
theme={config.editor.theme}
fontFamily={config.editor.fontFamily}
fontSize={editorFontSize}
@@ -770,13 +801,14 @@ class SnippetNoteDetail extends React.Component {
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
/>
</div>
</div>
const detailTopBar = <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<div>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
@@ -812,12 +844,13 @@ class SnippetNoteDetail extends React.Component {
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`}
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
type={note.type}
print={this.showWarning}
/>
@@ -896,6 +929,12 @@ class SnippetNoteDetail extends React.Component {
size: {config.editor.indentSize}&nbsp;
<i className='fa fa-caret-down' />
</button>
<button
onClick={(e) => this.handleWrapLineButtonClick(e)}
>
Wrap Line: {config.editor.lineWrapping ? 'on' : 'off'}&nbsp;
<i className='fa fa-caret-down' />
</button>
</div>
<StatusBar

View File

@@ -8,11 +8,11 @@ const ToggleModeButton = ({
onClick, editorType
}) => (
<div styleName='control-toggleModeButton'>
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
<div styleName={editorType === 'SPLIT' ? 'active' : undefined} onClick={() => onClick('SPLIT')}>
<img src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
</div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined} onClick={() => onClick('EDITOR_PREVIEW')}>
<img src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
</div>
@@ -20,7 +20,7 @@ const ToggleModeButton = ({
ToggleModeButton.propTypes = {
onClick: PropTypes.func.isRequired,
editorType: PropTypes.string.Required
editorType: PropTypes.string.isRequired
}
export default CSSModules(ToggleModeButton, styles)

View File

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

View File

@@ -10,6 +10,7 @@ import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n'
import debounceRender from 'react-debounce-render'
import searchFromNotes from 'browser/lib/search'
import queryString from 'query-string'
const OSX = global.process.platform === 'darwin'
@@ -36,11 +37,11 @@ class Detail extends React.Component {
}
render () {
const { location, data, params, config } = this.props
const { location, data, match: { params }, config } = this.props
const noteKey = location.search !== '' && queryString.parse(location.search).key
let note = null
if (location.query.key != null) {
const noteKey = location.query.key
if (location.search !== '') {
const allNotes = data.noteMap.map(note => note)
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
let displayedNotes = allNotes

View File

@@ -0,0 +1,16 @@
import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
const DevTools = createDevTools(
<DockMonitor
toggleVisibilityKey='ctrl-h'
changePositionKey='ctrl-q'
defaultIsVisible={false}
>
<LogMonitor theme='tomorrow' />
</DockMonitor>
)
export default DevTools

View File

@@ -0,0 +1,8 @@
/* eslint-disable no-undef */
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line global-require
module.exports = require('./index.dev').default
} else {
// eslint-disable-next-line global-require
module.exports = require('./index.prod').default
}

View File

@@ -0,0 +1,6 @@
import React from 'react'
const DevTools = () => <div />
DevTools.instrument = () => {}
export default DevTools

View File

@@ -12,11 +12,11 @@ import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
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 { store } from 'browser/main/store'
import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager'
import { push } from 'connected-react-router'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -132,7 +132,7 @@ class Main extends React.Component {
.then(() => data.storage)
})
.then(storage => {
hashHistory.push('/storages/' + storage.key)
store.dispatch(push('/storages/' + storage.key))
})
.catch(err => {
throw err
@@ -311,7 +311,7 @@ class Main extends React.Component {
onMouseUp={e => this.handleMouseUp(e)}
>
<SideNav
{..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
{..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
width={this.state.navWidth}
/>
{!config.isSideNavFolded &&
@@ -341,7 +341,7 @@ class Main extends React.Component {
'dispatch',
'config',
'data',
'params',
'match',
'location'
])}
/>
@@ -351,7 +351,7 @@ class Main extends React.Component {
'dispatch',
'data',
'config',
'params',
'match',
'location'
])}
/>
@@ -373,7 +373,7 @@ class Main extends React.Component {
'dispatch',
'data',
'config',
'params',
'match',
'location'
])}
ignorePreviewPointerEvents={this.state.isRightSliderFocused}

View File

@@ -21,23 +21,20 @@ class NewNoteButton extends React.Component {
this.state = {
}
this.newNoteHandler = () => {
this.handleNewNoteButtonClick()
}
this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
}
componentDidMount () {
eventEmitter.on('top:new-note', this.newNoteHandler)
eventEmitter.on('top:new-note', this.handleNewNoteButtonClick)
}
componentWillUnmount () {
eventEmitter.off('top:new-note', this.newNoteHandler)
eventEmitter.off('top:new-note', this.handleNewNoteButtonClick)
}
handleNewNoteButtonClick (e) {
const { location, params, dispatch, config } = this.props
const { location, dispatch, match: { params }, config } = this.props
const { storage, folder } = this.resolveTargetFolder()
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
@@ -55,9 +52,8 @@ class NewNoteButton extends React.Component {
}
resolveTargetFolder () {
const { data, params } = this.props
const { data, match: { params } } = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
if (storage == null) {
for (const kv of data.storageMap) {
@@ -93,8 +89,8 @@ class NewNoteButton extends React.Component {
>
<div styleName='control'>
<button styleName='control-newNoteButton'
onClick={(e) => this.handleNewNoteButtonClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
onClick={this.handleNewNoteButtonClick}>
<img src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'>
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
</span>

View File

@@ -14,23 +14,42 @@ import NoteItemSimple from 'browser/components/NoteItemSimple'
import searchFromNotes from 'browser/lib/search'
import fs from 'fs'
import path from 'path'
import { hashHistory } from 'react-router'
import { push, replace } from 'connected-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'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import context from 'browser/lib/context'
import queryString from 'query-string'
const { remote } = require('electron')
const { dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
const regexMatchStartingTitleNumber = new RegExp('^([0-9]*\.?[0-9]+).*$')
function sortByCreatedAt (a, b) {
return new Date(b.createdAt) - new Date(a.createdAt)
}
function sortByAlphabetical (a, b) {
const matchA = regexMatchStartingTitleNumber.exec(a.title)
const matchB = regexMatchStartingTitleNumber.exec(b.title)
if (matchA && matchA.length === 2 && matchB && matchB.length === 2) {
// Both note titles are starting with a float. We will compare it now.
const floatA = parseFloat(matchA[1])
const floatB = parseFloat(matchB[1])
const diff = floatA - floatB
if (diff !== 0) {
return diff
}
// The float values are equal. We will compare the full title.
}
return a.title.localeCompare(b.title)
}
@@ -127,15 +146,15 @@ class NoteList extends React.Component {
}
componentDidUpdate (prevProps) {
const { location } = this.props
const { dispatch, 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 visibleNoteKeys = this.notes && this.notes.map(note => note.key)
const note = this.notes && this.notes[0]
const key = location.search && queryString.parse(location.search).key
const prevKey = prevProps.location.search && queryString.parse(prevProps.location.search).key
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
if (note && location.query.key == null) {
const { router } = this.context
if (note && location.search === '') {
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
// A visible note is an active note
@@ -145,17 +164,17 @@ class NoteList extends React.Component {
ee.emit('list:moved')
}
router.replace({
dispatch(replace({ // was passed with context - we can use connected router here
pathname: location.pathname,
query: {
search: queryString.stringify({
key: noteKey
}
})
})
}))
return
}
// Auto scroll
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
if (_.isString(key) && prevKey === key) {
const targetIndex = this.getTargetIndex()
if (targetIndex > -1) {
const list = this.refs.list
@@ -176,18 +195,18 @@ class NoteList extends React.Component {
}
focusNote (selectedNoteKeys, noteKey, pathname) {
const { router } = this.context
const { dispatch } = this.props
this.setState({
selectedNoteKeys
})
router.push({
dispatch(push({
pathname,
query: {
search: queryString.stringify({
key: noteKey
}
})
})
}))
}
getNoteKeyFromTargetIndex (targetIndex) {
@@ -259,13 +278,22 @@ class NoteList extends React.Component {
}
jumpNoteByHashHandler (event, noteHash) {
const { data } = this.props
// first argument event isn't used.
if (this.notes === null || this.notes.length === 0) {
return
}
const selectedNoteKeys = [noteHash]
this.focusNote(selectedNoteKeys, noteHash, '/home')
let locationToSelect = '/home'
const noteByHash = data.noteMap.map((note) => note).find(note => note.key === noteHash)
if (noteByHash !== undefined) {
locationToSelect = '/storages/' + noteByHash.storage + '/folders/' + noteByHash.folder
}
this.focusNote(selectedNoteKeys, noteHash, locationToSelect)
ee.emit('list:moved')
}
@@ -321,8 +349,7 @@ class NoteList extends React.Component {
}
getNotes () {
const { data, params, location } = this.props
const { data, match: { params }, location } = this.props
if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
const allNotes = data.noteMap.map((note) => note)
this.contextNotes = allNotes
@@ -363,7 +390,7 @@ class NoteList extends React.Component {
// get notes in the current folder
getContextNotes () {
const { data, params } = this.props
const { data, match: { params } } = this.props
const storageKey = params.storageKey
const folderKey = params.folderKey
const storage = data.storageMap.get(storageKey)
@@ -403,8 +430,7 @@ class NoteList extends React.Component {
}
handleNoteClick (e, uniqueKey) {
const { router } = this.context
const { location } = this.props
const { dispatch, location } = this.props
let { selectedNoteKeys, prevShiftNoteIndex } = this.state
const { ctrlKeyDown, shiftKeyDown } = this.state
const hasSelectedNoteKey = selectedNoteKeys.length > 0
@@ -455,16 +481,16 @@ class NoteList extends React.Component {
prevShiftNoteIndex
})
router.push({
dispatch(push({
pathname: location.pathname,
query: {
search: queryString.stringify({
key: uniqueKey
}
})
})
}))
}
handleSortByChange (e) {
const { dispatch, params: { folderKey } } = this.props
const { dispatch, match: { params: { folderKey } } } = this.props
const config = {
[folderKey]: { sortBy: e.target.value }
@@ -496,6 +522,7 @@ class NoteList extends React.Component {
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print'
})[msg]
@@ -716,7 +743,11 @@ class NoteList extends React.Component {
folder: folder.key,
title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content,
linesHighlighted: firstNote.linesHighlighted
linesHighlighted: firstNote.linesHighlighted,
description: firstNote.description,
snippets: firstNote.snippets,
tags: firstNote.tags,
isStarred: firstNote.isStarred
})
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
@@ -732,10 +763,10 @@ class NoteList extends React.Component {
selectedNoteKeys: [note.key]
})
hashHistory.push({
dispatch(push({
pathname: location.pathname,
query: {key: note.key}
})
search: queryString.stringify({key: note.key})
}))
})
}
@@ -745,13 +776,13 @@ class NoteList extends React.Component {
}
navigate (sender, pathname) {
const { router } = this.context
router.push({
const { dispatch } = this.props
dispatch(push({
pathname,
query: {
search: queryString.stringify({
// key: noteKey
}
})
})
}))
}
save (note) {
@@ -881,7 +912,7 @@ class NoteList extends React.Component {
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
}
// Add notes to the current folder
// Add notes to the current folder
addNotesFromFiles (filepaths) {
const { dispatch, location } = this.props
const { storage, folder } = this.resolveTargetFolder()
@@ -905,13 +936,20 @@ class NoteList extends React.Component {
}
dataApi.createNote(storage.key, newNote)
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: getNoteKey(note)}
attachmentManagement.importAttachments(note.content, filepath, storage.key, note.key)
.then((newcontent) => {
note.content = newcontent
dataApi.updateNote(storage.key, note.key, note)
dispatch({
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
pathname: location.pathname,
search: queryString.stringify({key: getNoteKey(note)})
}))
})
})
})
@@ -921,14 +959,15 @@ class NoteList extends React.Component {
getTargetIndex () {
const { location } = this.props
const key = queryString.parse(location.search).key
const targetIndex = _.findIndex(this.notes, (note) => {
return getNoteKey(note) === location.query.key
return getNoteKey(note) === key
})
return targetIndex
}
resolveTargetFolder () {
const { data, params } = this.props
const { data, match: { params } } = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
@@ -976,7 +1015,7 @@ class NoteList extends React.Component {
}
render () {
const { location, config, params: { folderKey } } = this.props
const { location, config, match: { params: { folderKey } } } = this.props
let { notes } = this.props
const { selectedNoteKeys } = this.state
const sortBy = _.get(config, [folderKey, 'sortBy'], config.sortBy.default)
@@ -1099,7 +1138,7 @@ class NoteList extends React.Component {
}
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
>
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
<img src='../resources/icon/icon-column.svg' />
</button>
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL'
? 'control-button--active'
@@ -1107,7 +1146,7 @@ class NoteList extends React.Component {
}
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
>
<img styleName='iconTag' src='../resources/icon/icon-column-list.svg' />
<img src='../resources/icon/icon-column-list.svg' />
</button>
</div>
</div>

View File

@@ -8,7 +8,7 @@ const PreferenceButton = ({
onClick
}) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
<img src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
</button>
)

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageItem.styl'
import { hashHistory } from 'react-router'
import modal from 'browser/main/lib/modal'
import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
@@ -12,6 +11,7 @@ import _ from 'lodash'
import { SortableElement } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context'
import { push } from 'connected-react-router'
const { remote } = require('electron')
const { dialog } = remote
@@ -134,14 +134,14 @@ class StorageItem extends React.Component {
}
handleHeaderInfoClick (e) {
const { storage } = this.props
hashHistory.push('/storages/' + storage.key)
const { storage, dispatch } = this.props
dispatch(push('/storages/' + storage.key))
}
handleFolderButtonClick (folderKey) {
return (e) => {
const { storage } = this.props
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
const { storage, dispatch } = this.props
dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
}
}
@@ -362,14 +362,14 @@ class StorageItem extends React.Component {
<button styleName='header-addFolderButton'
onClick={(e) => this.handleAddFolderButtonClick(e)}
>
<img styleName='iconTag' src='../resources/icon/icon-plus.svg' />
<img src='../resources/icon/icon-plus.svg' />
</button>
}
<button styleName='header-info'
onClick={(e) => this.handleHeaderInfoClick(e)}
>
<span styleName='header-info-name'>
<span>
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
</span>
{isFolded &&
@@ -380,7 +380,7 @@ class StorageItem extends React.Component {
</button>
</div>
{this.state.isOpen &&
<div styleName='folderList' >
<div>
{folderList}
</div>
}

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import { push } from 'connected-react-router'
import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl'
@@ -62,7 +63,7 @@ class SideNav extends React.Component {
})
if (selectedButton === 0) {
const { data, dispatch, location, params } = this.props
const { data, dispatch, location, match: { params } } = this.props
const notes = data.noteMap
.map(note => note)
@@ -92,7 +93,7 @@ class SideNav extends React.Component {
if (index !== -1) {
tags.splice(index, 1)
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`)
dispatch(push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`))
}
}
})
@@ -104,13 +105,13 @@ class SideNav extends React.Component {
}
handleHomeButtonClick (e) {
const { router } = this.context
router.push('/home')
const { dispatch } = this.props
dispatch(push('/home'))
}
handleStarredButtonClick (e) {
const { router } = this.context
router.push('/starred')
const { dispatch } = this.props
dispatch(push('/starred'))
}
handleTagContextMenu (e, tag) {
@@ -190,18 +191,18 @@ class SideNav extends React.Component {
}
handleTrashedButtonClick (e) {
const { router } = this.context
router.push('/trashed')
const { dispatch } = this.props
dispatch(push('/trashed'))
}
handleSwitchFoldersButtonClick () {
const { router } = this.context
router.push('/home')
const { dispatch } = this.props
dispatch(push('/home'))
}
handleSwitchTagsButtonClick () {
const { router } = this.context
router.push('/alltags')
const { dispatch } = this.props
dispatch(push('/alltags'))
}
onSortEnd (storage) {
@@ -348,8 +349,8 @@ class SideNav extends React.Component {
}
handleClickTagListItem (name) {
const { router } = this.context
router.push(`/tags/${encodeURIComponent(name)}`)
const { dispatch } = this.props
dispatch(push(`/tags/${encodeURIComponent(name)}`))
}
handleSortTagsByChange (e) {
@@ -367,8 +368,7 @@ class SideNav extends React.Component {
}
handleClickNarrowToTag (tag) {
const { router } = this.context
const { location } = this.props
const { dispatch, location } = this.props
const listOfTags = this.getActiveTags(location.pathname)
const indexOfTag = listOfTags.indexOf(tag)
if (indexOfTag > -1) {
@@ -376,7 +376,7 @@ class SideNav extends React.Component {
} else {
listOfTags.push(tag)
}
router.push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`)
dispatch(push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`))
}
emptyTrash (entries) {
@@ -440,7 +440,7 @@ class SideNav extends React.Component {
const style = {}
if (!isFolded) style.width = this.props.width
const isTagActive = location.pathname.match(/tag/)
const isTagActive = /tag/.test(location.pathname)
return (
<div className='SideNav'
styleName={isFolded ? 'root--folded' : 'root'}

View File

@@ -7,6 +7,8 @@ import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n'
import debounce from 'lodash/debounce'
import CInput from 'react-composition-input'
import { push } from 'connected-react-router'
class TopBar extends React.Component {
constructor (props) {
@@ -15,26 +17,36 @@ class TopBar extends React.Component {
this.state = {
search: '',
searchOptions: [],
isSearching: false,
isAlphabet: false,
isIME: false,
isConfirmTranslation: false
isSearching: false
}
const { dispatch } = this.props
this.focusSearchHandler = () => {
this.handleOnSearchFocus()
}
this.codeInitHandler = this.handleCodeInit.bind(this)
this.handleKeyDown = this.handleKeyDown.bind(this)
this.handleSearchFocus = this.handleSearchFocus.bind(this)
this.handleSearchBlur = this.handleSearchBlur.bind(this)
this.handleSearchChange = this.handleSearchChange.bind(this)
this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
this.debouncedUpdateKeyword = debounce((keyword) => {
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
this.setState({
search: keyword
})
ee.emit('top:search', keyword)
}, 1000 / 60, {
maxWait: 1000 / 8
})
}
componentDidMount () {
const { params } = this.props
const searchWord = params.searchword
const { match: { params } } = this.props
const searchWord = params && params.searchword
if (searchWord !== undefined) {
this.setState({
search: searchWord,
@@ -51,22 +63,22 @@ class TopBar extends React.Component {
}
handleSearchClearButton (e) {
const { router } = this.context
const { dispatch } = this.props
this.setState({
search: '',
isSearching: false
})
this.refs.search.childNodes[0].blur
router.push('/searched')
dispatch(push('/searched'))
e.preventDefault()
this.debouncedUpdateKeyword('')
}
handleKeyDown (e) {
// reset states
this.setState({
isAlphabet: false,
isIME: false
})
// Re-apply search field on ENTER key
if (e.keyCode === 13) {
this.debouncedUpdateKeyword(e.target.value)
}
// Clear search on ESC
if (e.keyCode === 27) {
@@ -84,51 +96,11 @@ class TopBar extends React.Component {
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({
isAlphabet: true
})
// When the key is an IME input (Japanese, Chinese)
} else if (e.keyCode === 229) {
this.setState({
isIME: true
})
}
}
handleKeyUp (e) {
// reset states
this.setState({
isConfirmTranslation: false
})
// When the key is translation confirmation (Enter, Space)
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
this.setState({
isConfirmTranslation: true
})
const keyword = this.refs.searchInput.value
this.updateKeyword(keyword)
}
}
handleSearchChange (e) {
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
const keyword = this.refs.searchInput.value
this.updateKeyword(keyword)
} else {
e.preventDefault()
}
}
updateKeyword (keyword) {
this.context.router.push(`/searched/${encodeURIComponent(keyword)}`)
this.setState({
search: keyword
})
ee.emit('top:search', keyword)
const keyword = e.target.value
this.debouncedUpdateKeyword(keyword)
}
handleSearchFocus (e) {
@@ -136,6 +108,7 @@ class TopBar extends React.Component {
isSearching: true
})
}
handleSearchBlur (e) {
e.stopPropagation()
@@ -165,7 +138,7 @@ class TopBar extends React.Component {
}
handleCodeInit () {
ee.emit('top:search', this.refs.searchInput.value)
ee.emit('top:search', this.refs.searchInput.value || '')
}
render () {
@@ -178,24 +151,23 @@ class TopBar extends React.Component {
<div styleName='control'>
<div styleName='control-search'>
<div styleName='control-search-input'
onFocus={(e) => this.handleSearchFocus(e)}
onBlur={(e) => this.handleSearchBlur(e)}
onFocus={this.handleSearchFocus}
onBlur={this.handleSearchBlur}
tabIndex='-1'
ref='search'
>
<input
<CInput
ref='searchInput'
value={this.state.search}
onChange={(e) => this.handleSearchChange(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
onInputChange={this.handleSearchChange}
onKeyDown={this.handleKeyDown}
placeholder={i18n.__('Search')}
type='text'
className='searchInput'
/>
{this.state.search !== '' &&
<button styleName='control-search-input-clear'
onClick={(e) => this.handleSearchClearButton(e)}
onClick={this.handleSearchClearButton}
>
<i className='fa fa-fw fa-times' />
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>
@@ -210,8 +182,8 @@ class TopBar extends React.Component {
'dispatch',
'data',
'config',
'params',
'location'
'location',
'match'
])}
/>}
</div>

View File

@@ -1,11 +1,13 @@
import { Provider } from 'react-redux'
import Main from './Main'
import store from './store'
import React from 'react'
import { store, history } from './store'
import React, { Fragment } from 'react'
import ReactDOM from 'react-dom'
require('!!style!css!stylus?sourceMap!./global.styl')
import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import { Route, Switch, Redirect } from 'react-router-dom'
import { ConnectedRouter } from 'connected-react-router'
import DevTools from './DevTools'
require('./lib/ipcClient')
require('../lib/customMeta')
import i18n from 'browser/lib/i18n'
@@ -77,7 +79,6 @@ document.addEventListener('click', function (e) {
})
const el = document.getElementById('content')
const history = syncHistoryWithStore(hashHistory, store)
function notify (...args) {
return new window.Notification(...args)
@@ -98,29 +99,24 @@ function updateApp () {
ReactDOM.render((
<Provider store={store}>
<Router history={history}>
<Route path='/' component={Main}>
<IndexRedirect to='/home' />
<Route path='home' />
<Route path='starred' />
<Route path='searched'>
<Route path=':searchword' />
</Route>
<Route path='trashed' />
<Route path='alltags' />
<Route path='tags'>
<IndexRedirect to='/alltags' />
<Route path=':tagname' />
</Route>
<Route path='storages'>
<IndexRedirect to='/home' />
<Route path=':storageKey'>
<IndexRoute />
<Route path='folders/:folderKey' />
</Route>
</Route>
</Route>
</Router>
<ConnectedRouter history={history}>
<Fragment>
<Switch>
<Redirect path='/' to='/home' exact />
<Route path='/(home|alltags|starred|trashed)' component={Main} />
<Route path='/searched' component={Main} exact />
<Route path='/searched/:searchword' component={Main} />
<Redirect path='/tags' to='/alltags' exact />
<Route path='/tags/:tagname' component={Main} />
{/* storages */}
<Redirect path='/storages' to='/home' exact />
<Route path='/storages/:storageKey' component={Main} exact />
<Route path='/storages/:storageKey/folders/:folderKey' component={Main} />
</Switch>
<DevTools />
</Fragment>
</ConnectedRouter>
</Provider>
), el, function () {
const loadingCover = document.getElementById('loadingCover')

View File

@@ -11,6 +11,10 @@ const consts = require('browser/lib/consts')
let isInitialized = false
const DEFAULT_MARKDOWN_LINT_CONFIG = `{
"default": true
}`
export const DEFAULT_CONFIG = {
zoom: 1,
isSideNavFolded: false,
@@ -27,6 +31,8 @@ export const DEFAULT_CONFIG = {
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
insertDate: OSX ? 'Command + /' : 'Ctrl + /',
insertDateTime: OSX ? 'Command + Alt + /' : 'Ctrl + Shift + /',
toggleMenuBar: 'Alt'
},
ui: {
@@ -44,10 +50,11 @@ export const DEFAULT_CONFIG = {
fontFamily: win ? 'Consolas' : 'Monaco',
indentType: 'space',
indentSize: '2',
lineWrapping: true,
enableRulers: false,
rulers: [80, 120],
displayLineNumbers: true,
matchingPairs: '()[]{}\'\'""$$**``',
matchingPairs: '()[]{}\'\'""$$**``~~__',
matchingTriples: '```"""\'\'\'',
explodingPairs: '[]{}``$$',
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
@@ -59,7 +66,9 @@ export const DEFAULT_CONFIG = {
enableFrontMatterTitle: true,
frontMatterTitleField: 'title',
spellcheck: false,
enableSmartPaste: false
enableSmartPaste: false,
enableMarkdownLint: false,
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG
},
preview: {
fontSize: '14',
@@ -77,7 +86,7 @@ export const DEFAULT_CONFIG = {
breaks: true,
smartArrows: false,
allowCustomCSS: false,
customCSS: '',
customCSS: '/* Drop Your Custom CSS Code Here */',
sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
lineThroughCheckbox: true
},
@@ -102,7 +111,6 @@ function validate (config) {
}
function _save (config) {
console.log(config)
window.localStorage.setItem('config', JSON.stringify(config))
}
@@ -132,16 +140,12 @@ function get () {
document.head.appendChild(editorTheme)
}
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme)
? config.editor.theme
: 'default'
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
if (config.editor.theme !== 'default') {
if (config.editor.theme.startsWith('solarized')) {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
} else {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
}
if (theme) {
editorTheme.setAttribute('href', win ? theme.path : `../${theme.path}`)
} else {
config.editor.theme = 'default'
}
}
@@ -150,7 +154,13 @@ function get () {
function set (updates) {
const currentConfig = get()
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
const arrangedUpdates = updates
if (updates.preview !== undefined && updates.preview.customCSS === '') {
arrangedUpdates.preview.customCSS = DEFAULT_CONFIG.preview.customCSS
}
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, arrangedUpdates)
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
_save(newConfig)
@@ -177,16 +187,11 @@ function set (updates) {
editorTheme.setAttribute('rel', 'stylesheet')
document.head.appendChild(editorTheme)
}
const newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
? newConfig.editor.theme
: 'default'
if (newTheme !== 'default') {
if (newTheme.startsWith('solarized')) {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
} else {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
}
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
if (newTheme) {
editorTheme.setAttribute('href', win ? newTheme.path : `../${newTheme.path}`)
}
ipcRenderer.send('config-renew', {

View File

@@ -85,7 +85,7 @@ function getOrientation (file) {
return view.getUint16(offset + (i * 12) + 8, little)
}
}
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker.
break
} else {
offset += view.getUint16(offset, false)
@@ -241,6 +241,10 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
* @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
*/
function fixLocalURLS (renderedHTML, storagePath) {
const encodedWin32SeparatorRegex = /%5C/g
const storageRegex = new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g')
const storageUrl = 'file:///' + path.join(storagePath, DESTINATION_FOLDER).replace(/\\/g, '/')
/*
A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`.
@@ -250,8 +254,7 @@ function fixLocalURLS (renderedHTML, storagePath) {
- `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows.
*/
return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[-.\\w]+)+', 'g'), function (match) {
var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
return match.replace(encodedWin32SeparatorRegex, '/').replace(storageRegex, storageUrl)
})
}
@@ -278,27 +281,40 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
let promise
if (dropEvent.dataTransfer.files.length > 0) {
promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => {
if (file.type.startsWith('image')) {
if (file.type === 'image/gif' || file.type === 'image/svg+xml') {
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
const filePath = file.path
const fileType = file.type // EX) 'image/gif' or 'text/html'
if (fileType.startsWith('image')) {
if (fileType === 'image/gif' || fileType === 'image/svg+xml') {
return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
fileName,
title: path.basename(file.path),
title: path.basename(filePath),
isImage: true
}))
} else {
return fixRotate(file)
.then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey)
.then(fileName => ({
return getOrientation(file)
.then((orientation) => {
if (orientation === -1) { // The image rotation is correct and does not need adjustment
return copyAttachment(filePath, storageKey, noteKey)
} else {
return fixRotate(file).then(data => copyAttachment({
type: 'base64',
data: data,
sourceFilePath: filePath
}, storageKey, noteKey))
}
})
.then(fileName =>
({
fileName,
title: path.basename(file.path),
title: path.basename(filePath),
isImage: true
}))
})
)
}
} else {
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
fileName,
title: path.basename(file.path),
title: path.basename(filePath),
isImage: false
}))
}
@@ -325,13 +341,18 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
canvas.height = image.height
context.drawImage(image, 0, 0)
return copyAttachment({type: 'base64', data: canvas.toDataURL(), sourceFilePath: imageURL}, storageKey, noteKey)
return copyAttachment({
type: 'base64',
data: canvas.toDataURL(),
sourceFilePath: imageURL
}, storageKey, noteKey)
})
.then(fileName => ({
fileName,
title: imageURL,
isImage: true
}))])
}))
])
}
promise.then(files => {
@@ -449,6 +470,54 @@ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
return result
}
/**
* @description Copies the attachments to the storage folder and returns the mardown content it should be replaced with
* @param {String} markDownContent content in which the attachment paths should be found
* @param {String} filepath The path of the file with attachments to import
* @param {String} storageKey Storage key of the destination storage
* @param {String} noteKey Key of the current note. Will be used as subfolder in :storage
*/
function importAttachments (markDownContent, filepath, storageKey, noteKey) {
return new Promise((resolve, reject) => {
const nameRegex = /(!\[.*?]\()(.+?\..+?)(\))/g
let attachPath = nameRegex.exec(markDownContent)
const promiseArray = []
const attachmentPaths = []
const groupIndex = 2
while (attachPath) {
let attachmentPath = attachPath[groupIndex]
attachmentPaths.push(attachmentPath)
attachmentPath = path.isAbsolute(attachmentPath) ? attachmentPath : path.join(path.dirname(filepath), attachmentPath)
promiseArray.push(this.copyAttachment(attachmentPath, storageKey, noteKey))
attachPath = nameRegex.exec(markDownContent)
}
let numResolvedPromises = 0
if (promiseArray.length === 0) {
resolve(markDownContent)
}
for (let j = 0; j < promiseArray.length; j++) {
promiseArray[j]
.then((fileName) => {
const newPath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName)
markDownContent = markDownContent.replace(attachmentPaths[j], newPath)
})
.catch((e) => {
console.error('File does not exist in path: ' + attachmentPaths[j])
})
.finally(() => {
numResolvedPromises++
if (numResolvedPromises === promiseArray.length) {
resolve(markDownContent)
}
})
}
})
}
/**
* @description Moves the attachments of the current note to the new location.
* Returns a modified version of the given content so that the links to the attachments point to the new note key.
@@ -551,8 +620,6 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
}
})
})
} else {
console.info('Attachment folder ("' + attachmentFolder + '") did not exist..')
}
}
@@ -656,6 +723,7 @@ module.exports = {
handlePasteNativeImage,
getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent,
importAttachments,
removeStorageAndNoteReferences,
deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote,

View File

@@ -43,19 +43,18 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
.then(function exportNotes (data) {
const { storage, notes } = data
notes
return Promise.all(notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(note => {
.map(note => {
const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
exportNote(note.key, storage.path, note.content, notePath, null)
return exportNote(note.key, storage.path, note.content, notePath, null)
})
return {
).then(() => ({
storage,
folderKey,
fileType,
exportDir
}
}))
})
}

View File

@@ -43,14 +43,17 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
)
if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks)
exportedData = outputFormatter(exportedData, exportTasks, path.dirname(targetPath))
} else {
exportedData = Promise.resolve(exportedData)
}
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
.then(() => {
return saveToFile(exportedData, targetPath)
.then(() => exportedData)
.then(data => {
return saveToFile(data, targetPath)
}).catch((err) => {
rollbackExport(tasks)
throw err

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { Provider } from 'react-redux'
import ReactDOM from 'react-dom'
import store from '../store'
import { store } from '../store'
class ModalBase extends React.Component {
constructor (props) {

View File

@@ -3,7 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './CreateFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
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'

View File

@@ -4,11 +4,12 @@ import styles from './NewNoteModal.styl'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
import queryString from 'query-string'
class NewNoteModal extends React.Component {
constructor (props) {
super(props)
this.lock = false
this.state = {}
}
@@ -21,10 +22,14 @@ class NewNoteModal extends React.Component {
}
handleMarkdownNoteButtonClick (e) {
const { storage, folder, dispatch, location, params, config } = this.props
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
const { storage, folder, dispatch, location, config } = this.props
const params = location.search !== '' && queryString.parse(location.search)
if (!this.lock) {
this.lock = true
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
}
}
handleMarkdownNoteButtonKeyDown (e) {
@@ -35,10 +40,14 @@ class NewNoteModal extends React.Component {
}
handleSnippetNoteButtonClick (e) {
const { storage, folder, dispatch, location, params, config } = this.props
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
const { storage, folder, dispatch, location, config } = this.props
const params = location.search !== '' && queryString.parse(location.search)
if (!this.lock) {
this.lock = true
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
}
}
handleSnippetNoteButtonKeyDown (e) {

View File

@@ -19,6 +19,7 @@
.control
padding 25px 0px
text-align center
display: flex
.control-button
width 240px

View File

@@ -2,7 +2,7 @@ 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 { store } from 'browser/main/store'
import PropTypes from 'prop-types'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'

View File

@@ -18,6 +18,14 @@
margin-bottom 15px
margin-top 30px
.group-header--sub
@extend .group-header
margin-bottom 10px
.group-header2--sub
@extend .group-header2
margin-bottom 10px
.group-section
margin-bottom 20px
display flex
@@ -148,10 +156,12 @@ body[data-theme="dark"]
color $ui-dark-text-color
.group-header
.group-header--sub
color $ui-dark-text-color
border-color $ui-dark-borderColor
.group-header2
.group-header2--sub
color $ui-dark-text-color
.group-section-control-input
@@ -176,10 +186,12 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
.group-header
.group-header--sub
color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor
.group-header2
.group-header2--sub
color $ui-solarized-dark-text-color
.group-section-control-input
@@ -203,10 +215,12 @@ body[data-theme="monokai"]
color $ui-monokai-text-color
.group-header
.group-header--sub
color $ui-monokai-text-color
border-color $ui-monokai-borderColor
.group-header2
.group-header2--sub
color $ui-monokai-text-color
.group-section-control-input
@@ -230,10 +244,12 @@ body[data-theme="dracula"]
color $ui-dracula-text-color
.group-header
.group-header--sub
color $ui-dracula-text-color
border-color $ui-dracula-borderColor
.group-header2
.group-header2--sub
color $ui-dracula-text-color
.group-section-control-input

View File

@@ -22,18 +22,16 @@ class Crowdfunding extends React.Component {
render () {
return (
<div styleName='root'>
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
<div styleName='group-header'>{i18n.__('Crowdfunding')}</div>
<p>{i18n.__('Thank you for using Boostnote!')}</p>
<br />
<p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p>
<p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p>
<br />
<p>{i18n.__('### Sustainable Open Source Ecosystem')}</p>
<div styleName='group-header2--sub'>{i18n.__('Sustainable Open Source Ecosystem')}</div>
<p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p>
<p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. Weve got tons of Github stars and hundred of contributors in two years.')}</p>
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
<br />
<p>{i18n.__('### We believe Meritocracy')}</p>
<div styleName='group-header2--sub'>{i18n.__('We believe Meritocracy')}</div>
<p>{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}</p>
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
<p>{i18n.__('It sometimes looks like exploitation.')}</p>

View File

@@ -1,14 +1,8 @@
@import('./Tab')
@import('./ConfigTab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
p
font-size 16px
line-height 1.4
.cf-link
height 35px

View File

@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import ReactDOM from 'react-dom'
import styles from './FolderItem.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { store } from 'browser/main/store'
import { SketchPicker } from 'react-color'
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
@@ -225,7 +225,7 @@ class FolderItem extends React.Component {
<div styleName='folderItem-left'
style={{borderColor: folder.color}}
>
<span styleName='folderItem-left-name'>{folder.name}</span>
<span>{folder.name}</span>
<span styleName='folderItem-left-key'>({folder.key})</span>
</div>
<div styleName='folderItem-right'>
@@ -288,10 +288,10 @@ class Handle extends React.Component {
class SortableFolderItemComponent extends React.Component {
render () {
const StyledHandle = CSSModules(Handle, this.props.styles)
const StyledHandle = CSSModules(Handle, styles)
const DragHandle = SortableHandle(StyledHandle)
const StyledFolderItem = CSSModules(FolderItem, this.props.styles)
const StyledFolderItem = CSSModules(FolderItem, styles)
return (
<div>

View File

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

View File

@@ -3,7 +3,7 @@ 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 { store } from 'browser/main/store'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
@@ -173,6 +173,26 @@ class HotkeyTab extends React.Component {
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Insert Current Date')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={config.hotkey.insertDate}
type='text'
disabled='true'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Insert Current Date and Time')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={config.hotkey.insertDateTime}
type='text'
disabled='true'
/>
</div>
</div>
<div styleName='group-control'>
<button styleName='group-control-leftButton'
onClick={(e) => this.handleHintToggleButtonClick(e)}

View File

@@ -2,7 +2,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import { store } from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
@@ -69,8 +69,7 @@ class InfoTab extends React.Component {
render () {
return (
<div styleName='root'>
<div styleName='header--sub'>{i18n.__('Community')}</div>
<div styleName='group-header'>{i18n.__('Community')}</div>
<div styleName='top'>
<ul styleName='list'>
<li>
@@ -108,7 +107,7 @@ class InfoTab extends React.Component {
<hr />
<div styleName='header--sub'>{i18n.__('About')}</div>
<div styleName='group-header--sub'>{i18n.__('About')}</div>
<div styleName='top'>
<div styleName='icon-space'>
@@ -143,7 +142,7 @@ class InfoTab extends React.Component {
<hr styleName='separate-line' />
<div styleName='policy'>{i18n.__('Analytics')}</div>
<div styleName='group-header2--sub'>{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 />

View File

@@ -1,16 +1,4 @@
@import('./Tab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
.top
text-align left
margin-bottom 20px
@import('./ConfigTab.styl')
.icon-space
margin 20px 0
@@ -45,13 +33,21 @@
.separate-line
margin 40px 0
.policy
width 100%
font-size 20px
margin-bottom 10px
.policy-submit
margin-top 10px
height 35px
border-radius 2px
border none
background-color alpha(#1EC38B, 90%)
padding-left 20px
padding-right 20px
text-decoration none
color white
font-weight 600
font-size 16px
&:hover
background-color #1EC38B
transition 0.2s
.policy-confirm
margin-top 10px
@@ -60,11 +56,14 @@
body[data-theme="dark"]
.root
color alpha($tab--dark-text-color, 80%)
.appId
color $ui-dark-text-color
body[data-theme="solarized-dark"]
.root
color $ui-solarized-dark-text-color
.appId
color $ui-solarized-dark-text-color
.list
a
color $ui-solarized-dark-active-color
@@ -72,6 +71,8 @@ body[data-theme="solarized-dark"]
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
.appId
color $ui-monokai-text-color
.list
a
color $ui-monokai-active-color
@@ -79,6 +80,8 @@ body[data-theme="monokai"]
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
.appId
color $ui-dracula-text-color
.list
a
color $ui-dracula-active-color
color $ui-dracula-active-color

View File

@@ -4,6 +4,7 @@ import _ from 'lodash'
import styles from './SnippetTab.styl'
import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi'
import snippetManager from '../../../lib/SnippetManager'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
const buildCMRulers = (rulers, enableRulers) =>
@@ -64,7 +65,9 @@ class SnippetEditor extends React.Component {
}
saveSnippet () {
dataApi.updateSnippet(this.snippet).catch((err) => { throw err })
dataApi.updateSnippet(this.snippet)
.then(snippets => snippetManager.assignSnippets(snippets))
.catch((err) => { throw err })
}
render () {

View File

@@ -91,7 +91,7 @@ class SnippetTab extends React.Component {
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
return (
<div styleName='root'>
<div styleName='header'>{i18n.__('Snippets')}</div>
<div styleName='group-header'>{i18n.__('Snippets')}</div>
<SnippetList
onSnippetSelect={this.handleSnippetSelect.bind(this)}
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}

View File

@@ -1,14 +1,5 @@
@import('./Tab')
@import('./ConfigTab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
.group
margin-bottom 45px
@@ -127,7 +118,7 @@
background darken(#f5f5f5, 5)
.snippet-detail
width 70%
width 67%
height calc(100% - 200px)
position absolute
left 33%

View File

@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageItem.styl'
import consts from 'browser/lib/consts'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { store } from 'browser/main/store'
import FolderList from './FolderList'
import i18n from 'browser/lib/i18n'

View File

@@ -1,8 +1,4 @@
@import('./Tab')
.root
padding 15px
color $ui-text-color
@import('./ConfigTab')
.list
margin-bottom 15px

View File

@@ -3,7 +3,7 @@ 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 { store } from 'browser/main/store'
import consts from 'browser/lib/consts'
import ReactCodeMirror from 'react-codemirror'
import CodeMirror from 'codemirror'
@@ -14,6 +14,7 @@ import { getLanguages } from 'browser/lib/Languages'
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
const electron = require('electron')
const ipc = electron.ipcRenderer
@@ -30,7 +31,9 @@ class UiTab extends React.Component {
componentDidMount () {
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
CodeMirror.autoLoadMode(this.customMarkdownLintConfigCM.getCodeMirror(), 'javascript')
this.customCSSCM.getCodeMirror().setSize('400px', '400px')
this.customMarkdownLintConfigCM.getCodeMirror().setSize('400px', '200px')
this.handleSettingDone = () => {
this.setState({UiAlert: {
type: 'success',
@@ -89,6 +92,7 @@ class UiTab extends React.Component {
enableRulers: this.refs.enableEditorRulers.value === 'true',
rulers: this.refs.editorRulers.value.replace(/[^0-9,]/g, '').split(','),
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
lineWrapping: this.refs.editorLineWrapping.checked,
switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value,
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
@@ -101,7 +105,9 @@ class UiTab extends React.Component {
matchingTriples: this.refs.matchingTriples.value,
explodingPairs: this.refs.explodingPairs.value,
spellcheck: this.refs.spellcheck.checked,
enableSmartPaste: this.refs.enableSmartPaste.checked
enableSmartPaste: this.refs.enableSmartPaste.checked,
enableMarkdownLint: this.refs.enableMarkdownLint.checked,
customMarkdownLintConfig: this.customMarkdownLintConfigCM.getCodeMirror().getValue()
},
preview: {
fontSize: this.refs.previewFontSize.value,
@@ -128,8 +134,13 @@ class UiTab extends React.Component {
const newCodemirrorTheme = this.refs.editorTheme.value
if (newCodemirrorTheme !== codemirrorTheme) {
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
if (theme) {
checkHighLight.setAttribute('href', win ? theme.path : `../${theme.path}`)
}
}
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => {
const {ui, editor, preview} = this.props.config
this.currentConfig = {ui, editor, preview}
@@ -355,7 +366,7 @@ class UiTab extends React.Component {
>
{
themes.map((theme) => {
return (<option value={theme} key={theme}>{theme}</option>)
return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
})
}
</select>
@@ -492,7 +503,7 @@ class UiTab extends React.Component {
ref='editorSnippetDefaultLanguage'
onChange={(e) => this.handleUIChange(e)}
>
<option key='Auto Detect' value='Auto Detect'>Auto Detect</option>
<option key='Auto Detect' value='Auto Detect'>{i18n.__('Auto Detect')}</option>
{
_.sortBy(CodeMirror.modeInfo.map(mode => mode.name)).map(name => (<option key={name} value={name}>{name}</option>))
}
@@ -536,6 +547,17 @@ class UiTab extends React.Component {
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.lineWrapping}
ref='editorLineWrapping'
type='checkbox'
/>&nbsp;
{i18n.__('Wrap line in Snippet Note')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
@@ -632,6 +654,34 @@ class UiTab extends React.Component {
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Custom MarkdownLint Rules')}
</div>
<div styleName='group-section-control'>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.enableMarkdownLint}
ref='enableMarkdownLint'
type='checkbox'
/>&nbsp;
{i18n.__('Enable MarkdownLint')}
<div style={{fontFamily, display: this.state.config.editor.enableMarkdownLint ? 'block' : 'none'}}>
<ReactCodeMirror
width='400px'
height='200px'
onChange={e => this.handleUIChange(e)}
ref={e => (this.customMarkdownLintConfigCM = e)}
value={config.editor.customMarkdownLintConfig}
options={{
lineNumbers: true,
mode: 'application/json',
theme: codemirrorTheme,
lint: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers']
}} />
</div>
</div>
</div>
<div styleName='group-header2'>{i18n.__('Preview')}</div>
<div styleName='group-section'>
@@ -670,7 +720,7 @@ class UiTab extends React.Component {
>
{
themes.map((theme) => {
return (<option value={theme} key={theme}>{theme}</option>)
return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
})
}
</select>

View File

@@ -147,7 +147,7 @@ class Preferences extends React.Component {
key={tab.target}
onClick={(e) => this.handleNavButtonClick(tab.target)(e)}
>
<span styleName='nav-button-label'>
<span>
{tab.label}
</span>
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}

View File

@@ -3,7 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RenameFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { store } from 'browser/main/store'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'

View File

@@ -1,8 +1,10 @@
import { combineReducers, createStore } from 'redux'
import { routerReducer } from 'react-router-redux'
import { combineReducers, createStore, compose, applyMiddleware } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import { createHashHistory as createHistory } from 'history'
import ConfigManager from 'browser/main/lib/ConfigManager'
import { Map, Set } from 'browser/lib/Mutable'
import _ from 'lodash'
import DevTools from './DevTools'
function defaultDataMap () {
return {
@@ -44,7 +46,9 @@ function data (state = defaultDataMap(), action) {
const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
folderNoteSet.add(uniqueKey)
assignToTags(note.tags, state, uniqueKey)
if (!note.isTrashed) {
assignToTags(note.tags, state, uniqueKey)
}
})
return state
case 'UPDATE_NOTE':
@@ -463,13 +467,17 @@ function getOrInitItem (target, key) {
return results
}
const history = createHistory()
const reducer = combineReducers({
data,
config,
status,
routing: routerReducer
router: connectRouter(history)
})
const store = createStore(reducer)
const store = createStore(reducer, undefined, process.env.NODE_ENV === 'development'
? compose(applyMiddleware(routerMiddleware(history)), DevTools.instrument())
: applyMiddleware(routerMiddleware(history)))
export default store
export { store, history }

View File

@@ -79,4 +79,11 @@ class MyComponent extends React.Component {
// code goes here...
}
}
```
```
## React Hooks
Existing code will be kept class-based and will only be changed to functional components with hooks if it improves readability or makes things more reusable.
For new components it's OK to use hooks with functional components but don't mix hooks & class-based components within a feature - just for code style / readability reasons.
Read more about hooks in the [React hooks introduction](https://reactjs.org/docs/hooks-intro.html).

25
extra_scripts/codemirror/theme/nord.css vendored Normal file
View File

@@ -0,0 +1,25 @@
/* Theme: nord */
.cm-s-nord.CodeMirror { color: #d8dee9; }
.cm-s-nord.CodeMirror { background: #2e3440; }
.cm-s-nord .CodeMirror-cursor { color: #d8dee9; border-color: #d8dee9; }
.cm-s-nord .CodeMirror-activeline-background { background: #434c5e52 !important; }
.cm-s-nord .CodeMirror-selected { background: undefined; }
.cm-s-nord .cm-comment { color: #4c566a; }
.cm-s-nord .cm-string { color: #a3be8c; }
.cm-s-nord .cm-string-2 { color: #8fbcbb; }
.cm-s-nord .cm-property { color: #8fbcbb; }
.cm-s-nord .cm-qualifier { color: #8fbcbb; }
.cm-s-nord .cm-tag { color: #81a1c1; }
.cm-s-nord .cm-attribute { color: #8fbcbb; }
.cm-s-nord .cm-number { color: #b48ead; }
.cm-s-nord .cm-keyword { color: #81a1c1; }
.cm-s-nord .cm-operator { color: #81a1c1; }
.cm-s-nord .cm-error { background: #bf616a; color: #d8dee9; }
.cm-s-nord .cm-invalidchar { background: #bf616a; color: #d8dee9; }
.cm-s-nord .cm-variable { color: #d8dee9; }
.cm-s-nord .cm-variable-2 { color: #8fbcbb; }
.cm-s-nord .CodeMirror-gutters {
background: #3b4252;
color: #d8dee9;
}

View File

@@ -39,7 +39,7 @@ module.exports = function (grunt) {
name: 'boostnote',
productName: 'Boostnote',
genericName: 'Boostnote',
productDescription: 'The opensource note app for developer.',
productDescription: 'The opensource note app for developers.',
arch: 'amd64',
categories: [
'Development',
@@ -58,7 +58,7 @@ module.exports = function (grunt) {
name: 'boostnote',
productName: 'Boostnote',
genericName: 'Boostnote',
productDescription: 'The opensource note app for developer.',
productDescription: 'The opensource note app for developers.',
arch: 'x86_64',
categories: [
'Development',
@@ -149,6 +149,7 @@ module.exports = function (grunt) {
case 'osx':
Object.assign(opts, {
platform: 'darwin',
darwinDarkModeSupport: true,
icon: path.join(__dirname, 'resources/app.icns'),
'app-category-type': 'public.app-category.developer-tools'
})

View File

@@ -3,8 +3,9 @@ const app = electron.app
const Menu = electron.Menu
const ipc = electron.ipcMain
const GhReleases = require('electron-gh-releases')
const isDev = process.env.NODE_ENV !== 'production'
const { isPackaged } = app
// electron.crashReporter.start()
var ipcServer = null
var mainWindow = null
@@ -36,7 +37,7 @@ const updater = new GhReleases(ghReleasesOpts)
// Check for updates
// `status` returns true if there is a new update available
function checkUpdate () {
if (isDev) { // Prevents app from attempting to update when in dev mode.
if (!isPackaged) { // Prevents app from attempting to update when in dev mode.
console.log('Updates are disabled in Development mode, see main-app.js')
return true
}
@@ -99,12 +100,12 @@ app.on('ready', function () {
// Check update every day
setInterval(function () {
if (!isDev) checkUpdate()
if (isPackaged) checkUpdate()
}, 1000 * 60 * 60 * 24)
// Check update after 10 secs to prevent file locking of Windows
setTimeout(() => {
if (!isDev) checkUpdate()
if (isPackaged) checkUpdate()
ipc.on('update-check', function (event, msg) {
if (isUpdateReady) {

View File

@@ -3,6 +3,7 @@ const BrowserWindow = electron.BrowserWindow
const shell = electron.shell
const ipc = electron.ipcMain
const mainWindow = require('./main-window')
const os = require('os')
const macOS = process.platform === 'darwin'
// const WIN = process.platform === 'win32'
@@ -141,6 +142,13 @@ const file = {
mainWindow.webContents.send('list:isMarkdownNote', 'export-html')
mainWindow.webContents.send('export:save-html')
}
},
{
label: 'PDF (.pdf)',
click () {
mainWindow.webContents.send('list:isMarkdownNote', 'export-pdf')
mainWindow.webContents.send('export:save-pdf')
}
}
]
},
@@ -404,6 +412,28 @@ const help = {
click () { shell.openExternal('https://github.com/TobseF/boostnote-markdown-cheatsheet/blob/master/BOOSTNOTE_MARKDOWN_CHEAT_SHEET.md') }
}
]
},
{
type: 'separator'
},
{
label: 'About',
click () {
const version = electron.app.getVersion()
const electronVersion = process.versions.electron
const chromeVersion = process.versions.chrome
const nodeVersion = process.versions.node
const v8Version = process.versions.v8
const OSInfo = `${os.type()} ${os.arch()} ${os.release()}`
const detail = `Version: ${version}\nElectron: ${electronVersion}\nChrome: ${chromeVersion}\nNode.js: ${nodeVersion}\nV8: ${v8Version}\nOS: ${OSInfo}`
electron.dialog.showMessageBox(BrowserWindow.getFocusedWindow(),
{
title: 'BoostNote',
message: 'BoostNote',
type: 'info',
detail: `\n${detail}`
})
}
}
]
}

View File

@@ -6,6 +6,33 @@ const Config = require('electron-config')
const config = new Config()
const _ = require('lodash')
// set up some chrome extensions
if (process.env.NODE_ENV === 'development') {
const {
default: installExtension,
REACT_DEVELOPER_TOOLS,
REACT_PERF
} = require('electron-devtools-installer')
require('electron-debug')({ showDevTools: false })
const ChromeLens = {
// ID of the extension (https://chrome.google.com/webstore/detail/chromelens/idikgljglpfilbhaboonnpnnincjhjkd)
id: 'idikgljglpfilbhaboonnpnnincjhjkd',
electron: '>=1.2.1'
}
const extensions = [REACT_DEVELOPER_TOOLS, REACT_PERF, ChromeLens]
for (const extension of extensions) {
try {
installExtension(extension)
} catch (e) {
console.error(`[ELECTRON] Extension installation failed`, e)
}
}
}
const windowSize = config.get('windowsize') || {
x: null,
y: null,
@@ -27,8 +54,7 @@ const mainWindow = new BrowserWindow({
},
icon: path.resolve(__dirname, '../resources/app.png')
})
const url = path.resolve(__dirname, './main.html')
const url = path.resolve(__dirname, process.env.NODE_ENV === 'development' ? './main.development.html' : './main.production.html')
mainWindow.loadURL('file://' + url)
mainWindow.setMenuBarVisibility(false)

165
lib/main.development.html Normal file
View File

@@ -0,0 +1,165 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
<link rel="shortcut icon" href="../resources/favicon.ico">
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="../node_modules/katex/dist/katex.min.css">
<link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" href="../node_modules/codemirror/addon/lint/lint.css">
<link rel="stylesheet" href="../extra_scripts/codemirror/mode/bfm/bfm.css">
<title>Boostnote</title>
<style>
@font-face {
font-family: 'OpenSans';
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
font-style: normal;
font-weight: normal;
text-rendering: optimizeLegibility;
}
@font-face {
font-family: 'Lato';
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
font-style: normal;
font-weight: normal;
text-rendering: optimizeLegibility;
}
#loadingCover {
background-color: #f4f4f4;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
box-sizing: border-box;
padding: 65px 0;
font-family: sans-serif;
}
#loadingCover img {
display: block;
margin: 75px auto 5px;
width: 160px;
height: 160px;
}
#loadingCover .message {
font-size: 30px;
text-align: center;
line-height: 1.6;
font-weight: 100;
color: #888;
}
.CodeEditor {
opacity: 1 !important;
pointer-events: auto !important;
}
.CodeMirror-ruler {
border-left-color: rgba(142, 142, 142, 0.5);
mix-blend-mode: difference;
}
.CodeMirror-lint-tooltip {
z-index: 1003;
}
</style>
</head>
<body>
<div id="loadingCover">
<img src="../resources/app.png">
<div class='message'>
<i class="fa fa-spinner fa-spin" spin></i>
</div>
</div>
<div id="content"></div>
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
<script src="../node_modules/codemirror/mode/meta.js"></script>
<script src="../node_modules/codemirror-mode-elixir/dist/elixir.js"></script>
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
<script src="../node_modules/codemirror/addon/mode/multiplex.js"></script>
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
<script src="../node_modules/codemirror/keymap/vim.js"></script>
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
<script src="../node_modules/codemirror/addon/display/panel.js"></script>
<script src="../node_modules/codemirror/mode/xml/xml.js"></script>
<script src="../node_modules/codemirror/mode/markdown/markdown.js"></script>
<script src="../node_modules/codemirror/mode/gfm/gfm.js"></script>
<script src="../node_modules/codemirror/mode/yaml/yaml.js"></script>
<script src="../node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js"></script>
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
<script src="../node_modules/codemirror/addon/search/search.js"></script>
<script src="../node_modules/codemirror/addon/search/searchcursor.js"></script>
<script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="../node_modules/codemirror/addon/scroll/scrollpastend.js"></script>
<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/fold/brace-fold.js"></script>
<script src="../node_modules/codemirror/addon/fold/markdown-fold.js"></script>
<script src="../node_modules/codemirror/addon/fold/foldgutter.js"></script>
<script src="../node_modules/codemirror/addon/fold/foldcode.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/jsonlint-mod/lib/jsonlint.js"></script>
<script src="../node_modules/codemirror/addon/lint/lint.js"></script>
<script src="../node_modules/codemirror/addon/lint/json-lint.js"></script>
<script src="../node_modules/raphael/raphael.min.js"></script>
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
<script>
window._ = require('lodash')
</script>
<script src="../node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
<script src="../node_modules/react/umd/react.development.js"></script>
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script>
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
<script type='text/javascript'>
const electron = require('electron')
electron.webFrame.setVisualZoomLevelLimits(1, 1)
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
? 'http://localhost:8080/assets/main.js'
: '../compiled/main.js'
var scriptEl = document.createElement('script')
scriptEl.setAttribute('type', 'text/javascript')
scriptEl.setAttribute('src', scriptUrl)
document.body.appendChild(scriptEl)
</script>
<style>
.ace_search {
background-color: #d9d9d9;
}
</style>
</body>
</html>

View File

@@ -105,7 +105,6 @@
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
@@ -131,9 +130,9 @@
window._ = require('lodash')
</script>
<script src="../node_modules/js-sequence-diagrams/fucknpm/sequence-diagram-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/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
<script src="../node_modules/react/umd/react.production.min.js"></script>
<script src="../node_modules/react-dom/umd/react-dom.production.min.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script>
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
<script type='text/javascript'>

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations",
@@ -75,7 +76,7 @@
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 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.",
@@ -153,5 +154,8 @@
"Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Drucken",
"Your preferences for Boostnote": "Boostnote Einstellungen",
"Storage Locations": "Speicherverwaltung",
@@ -75,7 +76,7 @@
"Website": "Website",
"Development": "Entwicklung",
" : Development configurations for Boostnote.": " : Entwicklungseinstellungen für Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 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. Persönliche Informationen, wie z.B. der Inhalt deiner Notizen, werden dabei nicht erfasst.",
@@ -209,5 +210,8 @@
"No tags": "Keine Tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -19,6 +19,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Help": "Help",
@@ -82,7 +83,7 @@
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 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.",
@@ -136,7 +137,7 @@
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote",
"Toggle Editor Mode": "Toggle Editor Mode",
"Delete Note": "Delete Note",
"Delete Note": "Delete Note",
"Restore": "Restore",
"Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion",
@@ -179,10 +180,13 @@
"Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled",
"Spellcheck disabled": "Spellcheck disabled",
"Save tags of a note in alphabetical order": "Save tags of a note in alphabetical order",
"Enable live count of notes": "Enable live count of notes",
"Enable smart table editor": "Enable smart table editor",
"Snippet Default Language": "Snippet Default Language",
"New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags"
"New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir",
"Your preferences for Boostnote": "Tus preferencias para Boostnote",
"Storage Locations": "Almacenamientos",
@@ -75,7 +76,7 @@
"Website": "Página web",
"Development": "Desarrollo",
" : Development configurations for Boostnote.": " : Configuraciones de desarrollo para Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licencia: GPL v3",
"Analytics": "Analítica",
"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 recopila datos anónimos con el único propósito de mejorar la aplicación, y de ninguna manera recopila información personal como el contenido de sus notas.",
@@ -133,7 +134,7 @@
"Successfully applied!": "¡Aplicado con éxito!",
"Albanian": "Albanés",
"Chinese (zh-CN)": "Chino - China",
"Chinese (zh-TW)": "Chino - Taiwan",
"Chinese (zh-TW)": "Chino - Taiwán",
"Danish": "Danés",
"Japanese": "Japonés",
"Korean": "Coreano",
@@ -154,6 +155,9 @@
"Allow dangerous html tags": "Permitir etiquetas html peligrosas",
"⚠ You have pasted a link referring an attachment that could not be found in the location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Ha pegado un enlace a un archivo adjunto que no se puede encontrar en el almacenamiento de esta nota. Pegar enlaces a archivos adjuntos solo está soportado si el origen y el destino son el mismo almacenamiento. ¡Por favor, mejor arrastre el archivo! ⚠",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir flechas textuales a símbolos bonitos. ⚠ Esto interferirá cuando use comentarios HTML en Markdown.",
"Disabled": "Disabled",
"Snippet Default Language": "Lenguaje por defecto de los fragmentos de código"
"Spellcheck disabled": "Deshabilitar corrector ortográfico",
"Show menu bar": "Mostrar barra del menú",
"Auto Detect": "Detección automática",
"Snippet Default Language": "Lenguaje por defecto de los fragmentos de código",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

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

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Imprimer",
"Your preferences for Boostnote": "Vos préférences pour Boostnote",
"Storage Locations": "Stockages",
@@ -76,7 +77,7 @@
"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",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 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.",
@@ -163,11 +164,14 @@
"Enable live count of notes": "Activer le comptage live des notes",
"Enable smart table editor": "Activer l'intelligent éditeur de tableaux",
"Snippet Default Language": "Langage par défaut d'un snippet",
"Disabled": "Disabled",
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"New Snippet": "Nouveau snippet",
"Custom CSS": "CSS personnalisé",
"Snippet name": "Nom du snippet",
"Snippet prefix": "Préfixe du snippet",
"Delete Note": "Supprimer la note",
"New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage"
"Delete Note": "Supprimer la note",
"New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -19,6 +19,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Nyomtatás",
"Your preferences for Boostnote": "Boostnote beállításaid",
"Help": "Súgó",
@@ -26,7 +27,6 @@
"Storage Locations": "Tárolók",
"Add Storage Location": "Tároló Hozzáadása",
"Add Folder": "Könyvtár Hozzáadása",
"Select Folder": "Könyvtár Kiválasztása",
"Open Storage folder": "Tároló Megnyitása",
"Unlink": "Tároló Leválasztása",
"Edit": "Szerkesztés",
@@ -82,7 +82,7 @@
"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",
"Copyright (C) 2017 - 2019 BoostIO": "Szerzői jog (C) 2017 - 2019 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.",
@@ -178,5 +178,8 @@
"Allow dangerous html tags": "Veszélyes html tag-ek engedélyezése",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Stampa",
"Your preferences for Boostnote": "Le tue preferenze per Boostnote",
"Storage Locations": "Posizioni",
@@ -75,7 +76,7 @@
"Website": "Sito Web",
"Development": "Sviluppo",
" : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licenza: GPL v3",
"Analytics": "Statistiche",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.",
@@ -157,5 +158,8 @@
"Allow dangerous html tags": "Consenti tag HTML pericolosi",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -8,10 +8,10 @@
"to create a new note": "ノートを新規に作成",
"Toggle Mode": "モード切替",
"Add tag...": "タグを追加...",
"Star": "お気に入り",
"Fullscreen": "全画面",
"Star": "お気に入り",
"Fullscreen": "全画面",
"Trash": "ゴミ箱",
"Info": "情報",
"Info": "情報",
"MODIFICATION DATE": "修正日",
"Words": "ワード",
"Letters": "文字",
@@ -22,6 +22,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "印刷",
"Your preferences for Boostnote": "Boostnoteの個人設定",
"Help": "ヘルプ",
@@ -46,7 +47,7 @@
"Default New Note": "新規ノートの形式",
"Always Ask": "作成時に聞く",
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
"Disable Direct Write (It will be applied after restarting)": "直接編集を無効にする(設定反映には再起動が必要です)",
"Save tags of a note in alphabetical order": "ノートのタグをアルファベット順に保存する",
"Show tags of a note in alphabetical order": "ノートのタグをアルファベット順に表示する",
"Show only related tags": "関連するタグのみ表示する",
@@ -78,6 +79,7 @@
"Matching character pairs": "自動補完する括弧ペアの列記",
"Matching character triples": "自動補完する3文字括弧の列記",
"Exploding character pairs": "改行時に空行を挿入する括弧ペアの列記",
"Custom MarkdownLint Rules": "カスタムMarkdownLintルール",
"Preview": "プレビュー",
"Preview Font Size": "プレビュー時フォントサイズ",
"Preview Font Family": "プレビュー時フォント",
@@ -105,7 +107,7 @@
"Website": "ウェブサイト",
"Development": "開発",
" : Development configurations for Boostnote.": " : Boostnote の開発構成",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "ライセンス: GPL v3",
"Analytics": "解析",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。",
@@ -199,7 +201,7 @@
"Type": "種類",
"File System": "ファイルシステム",
"Setting up 3rd-party cloud storage integration:": "サードパーティのクラウドストレージとの統合を設定する:",
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Cloud-Syncing-and-Backup": "クラウド同期とバックアップ",
"Location": "ロケーション",
"Add": "追加",
"Export Storage": "ストレージの書き出し",
@@ -215,5 +217,8 @@
"Allow dangerous html tags": "安全でないHTMLタグの利用を許可する",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "テキストの矢印を綺麗な記号に変換する ⚠ この設定はMarkdown内でのHTMLコメントに干渉します。",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ このノートのストレージに存在しない添付ファイルへのリンクを貼り付けました。添付ファイルへのリンクの貼り付けは同一ストレージ内でのみサポートされています。代わりに添付ファイルをドラッグアンドドロップしてください! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "スペルチェック無効",
"Show menu bar": "メニューバーを表示",
"Auto Detect": "自動検出",
"Wrap line in Snippet Note": "行を右端で折り返すSnippet Note"
}

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "인쇄",
"Your preferences for Boostnote": "Boostnote 설정",
"Storage Locations": "저장소",
@@ -75,7 +76,7 @@
"Website": "웹사이트",
"Development": "개발",
" : Development configurations for Boostnote.": " : Boostnote 개발을 위한 설정들.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "사용 통계/분석",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote는 서비스개선을 위해 익명으로 데이터를 수집하며 노트의 내용과같은 일체의 개인정보는 수집하지 않습니다.",
@@ -160,5 +161,8 @@
"Allow dangerous html tags": "모든 위험한 태그 허용",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations",
@@ -75,7 +76,7 @@
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 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.",
@@ -153,5 +154,8 @@
"Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -14,13 +14,14 @@
"STORAGE": "MIEJSCE ZAPISU",
"FOLDER": "FOLDER",
"CREATION DATE": "DATA UTWORZENIA",
"NOTE LINK": "LINK NOTATKI",
"Toggle Editor Mode": "Przełączanie trybu edytora",
"NOTE LINK": "LINK NOTATKI",
"Toggle Editor Mode": "Przełączanie trybu edytora",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Drukuj",
"Help": "Pomoc",
".pdf": ".pdf",
"Print": "Drukuj",
"Help": "Pomoc",
"Your preferences for Boostnote": "Twoje ustawienia dla Boostnote",
"Storage Locations": "Storage Locations",
"Add Storage Location": "Dodaj miejsce zapisu",
@@ -35,12 +36,12 @@
"White": "Biały",
"Solarized Dark": "Solarized Dark",
"Dark": "Ciemny",
"Show a confirmation dialog when deleting notes": "Zatwierdzaj usunięcie notatek",
"Show only related tags": "Pokazuj tylko powiązane tagi",
"Show a confirmation dialog when deleting notes": "Zatwierdzaj usunięcie notatek",
"Show only related tags": "Pokazuj tylko powiązane tagi",
"Editor Theme": "Wygląd edytora",
"Editor Font Size": "Rozmiar czcionki",
"Editor Font Family": "Czcionka",
"Snippet Default Language": "Domyślny język snippetów kodu",
"Editor Font Family": "Czcionka",
"Snippet Default Language": "Domyślny język snippetów kodu",
"Editor Indent Style": "Rodzaj wcięć",
"Spaces": "Spacje",
"Tabs": "Tabulatory",
@@ -57,9 +58,9 @@
"Allow editor to scroll past the last line": "Pozwalaj edytorowi na przewijanie poza końcową linię",
"Bring in web page title when pasting URL on editor": "Wprowadź tytuł wklejanej strony WWW do edytora",
"Preview": "Podgląd",
"Preview Font Size": "Rozmiar czcionki",
"Enable smart quotes": "Włącz inteligentne cytowanie",
"Render newlines in Markdown paragraphs as <br>": "Dodawaj nowe linie w notatce jako znacznik <br>",
"Preview Font Size": "Rozmiar czcionki",
"Enable smart quotes": "Włącz inteligentne cytowanie",
"Render newlines in Markdown paragraphs as <br>": "Dodawaj nowe linie w notatce jako znacznik <br>",
"Preview Font Family": "Czcionka",
"Code Block Theme": "Styl bloku kodu",
"Allow preview to scroll past the last line": "Pozwalaj podglądowi na przewijanie poza końcową linię",
@@ -81,7 +82,7 @@
"Website": "Strona WWW",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licencja: GPL v3",
"Analytics": "Statystyki",
"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 zbiera anonimowe dane wyłącznie w celu poprawy działania aplikacji, lecz nigdy nie pobiera prywatnych danych z Twoich notatek.",
@@ -159,8 +160,11 @@
"Allow dangerous html tags": "Zezwól na niebezpieczne tagi HTML",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Konwertuje tekstowe strzałki na znaki. ⚠ Wpłynie to na używanie komentarzy HTML w Twojej notatce.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Wkleiłes link odnoszący się do załącznika, ktory nie może zostać znaleziony. Wklejanie linków do załączników jest możliwe tylko gdy notatka i załącznik są w tym samym folderze. Używaj opcji 'Przeciągaj i upuść' załącznik! ⚠",
"Star": "Oznacz",
"Fullscreen": "Pełen ekran",
"Add tag...": "Dodaj tag...",
"Disabled": "Disabled"
"Star": "Oznacz",
"Fullscreen": "Pełen ekran",
"Add tag...": "Dodaj tag...",
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir",
"Your preferences for Boostnote": "Suas preferências para o Boostnote",
"Storage Locations": "Armazenamentos",
@@ -75,7 +76,7 @@
"Website": "Website",
"Development": "Desenvolvimento",
" : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Direitos Autorais (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Direitos Autorais (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licença: GPL v3",
"Analytics": "Técnicas analíticas",
"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.": "O Boostnote coleta dados anônimos com o único propósito de melhorar o aplicativo e de modo algum coleta qualquer informação pessoal, bem como o conteúdo de suas anotações.",
@@ -153,5 +154,8 @@
"Allow dangerous html tags": "Permitir tags html perigosas",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

View File

@@ -18,6 +18,7 @@
".md": ".md",
".txt": ".txt",
".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir",
"Your preferences for Boostnote": "As tuas definiçōes para Boostnote",
"Storage Locations": "Locais de Armazenamento",
@@ -75,7 +76,7 @@
"Website": "Website",
"Development": "Desenvolvimento",
" : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Direitos de Autor (C) 2017 - 2018 BoostIO",
"Copyright (C) 2017 - 2019 BoostIO": "Direitos de Autor (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licença: GPL v3",
"Analytics": "Analíse de Data",
"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.": "O Boostnote coleta dados anônimos com o único propósito de melhorar a aplicação e não adquire informação pessoal ou conteúdo das tuas notas.",
@@ -130,7 +131,7 @@
"Permanent Delete": "Apagar Permanentemente",
"Confirm note deletion": "Confirmar o apagamento da nota",
"This will permanently remove this note.": "Isto irá remover permanentemente esta nota.",
"Successfully applied!": "Aplicado com Sucesso!",
"Successfully applied!": "Aplicado com Sucesso!",
"Albanian": "Albanês",
"Chinese (zh-CN)": "Chinês (zh-CN)",
"Chinese (zh-TW)": "Chinês (zh-TW)",
@@ -152,5 +153,8 @@
"Allow dangerous html tags": "Permitir tags html perigosas",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Converter setas de texto em simbolos. ⚠ Isto irá interferir no use de comentários em HTML em Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Você colou um link referente a um anexo que não pôde ser encontrado no local de armazenamento desta nota. A vinculação de anexos de referência de links só é suportada se o local de origem e de destino for o mesmo de armazenamento. Por favor, arraste e solte o anexo na nota! ⚠",
"Disabled": "Disabled"
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"Wrap line in Snippet Note": "Wrap line in Snippet Note"
}

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