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

Compare commits

..

565 Commits

Author SHA1 Message Date
Junyoung Choi
6cad2ab4df v0.11.8 2018-07-19 15:21:35 +09:00
Junyoung Choi (Sai)
856d52891c Merge pull request #2205 from saaguero/fix-attachment
Fix attachment interoperability between win and *nix
2018-07-17 16:23:16 +09:00
Junyoung Choi
8de3b3bd8d Update yarn.lock 2018-07-17 15:55:03 +09:00
Junyoung Choi (Sai)
0414483be2 Merge pull request #2178 from enyaxu/feature/2165
Hotkey for toggle editor fullscreen
2018-07-17 14:49:03 +09:00
Junyoung Choi (Sai)
22939aa472 Merge pull request #2145 from amedora/table-formatter
Markdown Table Formatter
2018-07-17 13:50:00 +09:00
Junyoung Choi (Sai)
0cb7c44985 Merge pull request #2168 from enyaxu/bug/2018
Fixed 'Focus Search' shortcut
2018-07-17 13:35:44 +09:00
Junyoung Choi (Sai)
b18a09e5eb Merge pull request #1935 from ZeroX-DG/allow-no-html-escape
Allow customizing html escape when export note
2018-07-17 12:38:14 +09:00
Junyoung Choi (Sai)
ef3649b1d6 Merge pull request #2172 from enyaxu/feature/862
Add chartjs support
2018-07-17 12:21:31 +09:00
Junyoung Choi (Sai)
ac70a0d94d Merge branch 'master' into feature/862 2018-07-17 12:21:17 +09:00
Junyoung Choi (Sai)
3b91f9b88b Merge pull request #2173 from enyaxu/feature/389
Add mermaid support
2018-07-17 12:15:36 +09:00
Santiago Agüero
c37b780ca4 Use markdown content for migrateAttachments 2018-07-17 00:13:13 -03:00
Junyoung Choi (Sai)
20061d2c65 Merge pull request #2183 from max-buranbaev/flickering-on-create-note
Flickering on create note
2018-07-17 11:54:33 +09:00
Junyoung Choi (Sai)
f18fa77c1c Merge pull request #2187 from ivanovserge/issue-2156
Issue 2156
2018-07-17 11:49:39 +09:00
Junyoung Choi (Sai)
a4c6869d4d Merge pull request #2192 from enyaxu/improvement/2133
Replace shortcut for 'Next Note' and 'Previous Note'
2018-07-17 11:46:08 +09:00
Junyoung Choi (Sai)
f9a0070c82 Merge pull request #2197 from chang/improve-bracket-completion
Improve bracket autoclosing
2018-07-17 11:20:27 +09:00
Santiago Agüero
5cc52f91cb Fix lint errors 2018-07-15 12:07:27 -03:00
Santiago Agüero
a46b9fb2be Fix attachment interop between win and nix 2018-07-15 01:37:47 -03:00
Eric
933e38eca9 improve bracket autoclosing 2018-07-09 18:37:54 -05:00
JianXu
e182390480 Replace shortcut for 'Next Note' and 'Previous Note' 2018-07-07 23:02:17 +08:00
Nguyễn Việt Hưng
563fdcba94 added tests escape html function 2018-07-07 00:07:17 +07:00
Nguyễn Việt Hưng
bc640834cd allow detect code block or not in escapeHtml function 2018-07-06 23:45:18 +07:00
Junyoung Choi (Sai)
0e9e7d644a Merge pull request #2191 from BoostIO/fix-codefence-xss
Add sanitization for code fence
2018-07-07 01:23:23 +09:00
Junyoung Choi
1d9b3ac2b5 Add sanitization for code fence 2018-07-07 01:22:11 +09:00
Junyoung Choi (Sai)
aebed4a644 Merge pull request #2033 from ZeroX-DG/fix-scrollbar-disappear
fixed disappearing scroll bar
2018-07-07 00:47:14 +09:00
Junyoung Choi
7bfb094a40 use lighter color for scroll bar 2018-07-06 22:07:06 +09:00
Kazz Yokomizo
f90a44c1d0 Merge pull request #2189 from BoostIO/update-readme
Update readme
2018-07-06 15:55:25 +09:00
Kazz Yokomizo
dfcf6d2729 Update readme 2018-07-06 15:37:10 +09:00
Сергей Иванов
806a5daa86 Processing all location's pathnames 2018-07-05 11:23:57 +03:00
Сергей Иванов
4a3602099a Difference home and searched notes from trashed units 2018-07-04 16:09:49 +03:00
Сергей Иванов
c69be54655 Fixing empty string searching 2018-07-04 14:02:26 +03:00
Сергей Иванов
680eaa1d4a Filtering displayed notes in Detail component 2018-07-04 13:33:47 +03:00
Nguyễn Việt Hưng
9cc7b8bcc6 fixed redundant code 2018-07-04 15:35:46 +07:00
Nguyễn Việt Hưng
55d86d853a improved escape function 2018-07-04 15:27:30 +07:00
Nguyễn Việt Hưng
7d9f309e04 Merge remote-tracking branch 'upstream/master' into allow-no-html-escape 2018-07-04 13:50:10 +07:00
Nguyễn Việt Hưng
c2f0147cff updated new escape html function 2018-07-04 13:50:05 +07:00
JianXu
05488e66ae Add tooltip(CommandOrCtrl+B) for fullscreen 2018-07-04 11:38:43 +08:00
JianXu
09eac89086 Hotkey for toggle editor fullscreen 2018-07-04 09:59:06 +08:00
JianXu
866a0e7534 Add mermaid support 2018-07-03 15:10:08 +08:00
Max Buranbaev
3c8337cf54 adding timeout one creating a snippet 2018-07-03 10:29:22 +05:00
Max Buranbaev
883b4c4c26 adding timeout on creating a note 2018-07-03 10:27:13 +05:00
JianXu
6bc42c564d Add chartjs 2018-07-03 12:58:45 +08:00
JianXu
d2b2e76a6a Fixed 'Focus Search' shortcut 2018-07-02 21:43:46 +08:00
Sosuke Suzuki
9d9109e9e5 Merge pull request #2158 from BoostIO/fix-contextmenu-bug
Fix contextmenu bug
2018-07-02 11:27:28 +09:00
Kazz Yokomizo
18efb89b9a Merge pull request #2163 from BoostIO/update-slack
Update slack invitation url
2018-07-01 22:02:52 +09:00
Kazz Yokomizo
cefe883025 Update slack invitation url 2018-07-01 21:59:08 +09:00
Sosuke Suzuki
0ffa0b96d3 Merge pull request #2135 from BoostIO/improve-snippets-ui
Improve snippets ui
2018-06-30 20:11:33 +09:00
Sosuke Suzuki
0429acfa1b Merge pull request #2138 from BoostIO/improve-uitab-editor
Improve uitab editor
2018-06-30 20:11:20 +09:00
Sosuke Suzuki
827e3c1829 Merge pull request #2144 from kelvin-wong/retain-collapsed-storage-state
Add collapsed state for storage
2018-06-30 20:11:01 +09:00
Sosuke Suzuki
aa756ef194 Merge pull request #2146 from BoostIO/update-codemirror
update codemirror
2018-06-30 20:10:13 +09:00
Sosuke Suzuki
d8aad65b24 fix from eslint 2018-06-30 16:28:17 +09:00
Sosuke Suzuki
1038e86196 use context.popup on StatusBar 2018-06-30 16:25:44 +09:00
Sosuke Suzuki
47845fd4e3 use context.popup on SideNav 2018-06-30 16:23:51 +09:00
Sosuke Suzuki
294c3f10ab use context.popup on StorageItem 2018-06-30 16:21:35 +09:00
Sosuke Suzuki
f6afc756dc use context.popup on SnippetList 2018-06-30 16:16:18 +09:00
Sosuke Suzuki
64407e5ca6 use context.popup on SnippetNoteDetai; 2018-06-30 16:12:23 +09:00
Sosuke Suzuki
0a42b0f61f use context.popup on NoteList 2018-06-30 16:08:13 +09:00
Junyoung Choi
ae493cbd0e v0.11.7 2018-06-30 00:51:15 +09:00
Kelvin Wong
ddd339851b Fix code style 2018-06-29 10:58:11 +08:00
amedora
82db986bd7 add 'Format Table' in the File menu 2018-06-28 18:16:38 +09:00
amedora
7bacd6f8f0 CodeEditor can handle 'code:format-table' event 2018-06-28 18:16:38 +09:00
amedora
f0941f47dd create TableEditor when CodeEditor mounted 2018-06-28 18:09:06 +09:00
amedora
c9d05b1117 CodeEditor use TableEditor 2018-06-28 18:09:06 +09:00
amedora
7ee12752ec CodeEditor use TextEditorInterface 2018-06-28 18:09:06 +09:00
amedora
b44772441d implement TextEditorInterface 2018-06-28 18:09:06 +09:00
Sosuke Suzuki
72e3784fa5 update codemirror 2018-06-28 17:37:36 +09:00
Kelvin Wong
8a6c86bf65 Add collapsed state for storage
The root cause of this issue is that when the folder is clicked,
the router pushed the path and the StorageItem component has been
refreshed and isOpen has been reset

- Add storing collapse state for storage
- Add tests
- Default as collapsed for fallback

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

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

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

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

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

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

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

fixed snippet content empty on changed from list

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

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

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

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

NOTE: Tags are joined with `&` character (`#` not works) in
`location.pathname` thus it will make the tags with this character
unavailable. Any suggestion to pass multiple values via pathname?
2018-04-26 15:38:47 +02:00
Kazz Yokomizo
25440a26ee Merge pull request #1805 from frankkanis/move-image-note-fix
Fixed moving notes with images
2018-04-26 22:32:21 +09:00
Kazz Yokomizo
ab393b1f6d Merge pull request #1818 from ZeroX-DG/fix-centralize-language
Centralized languages into 1 files
2018-04-26 22:27:35 +09:00
Kazz Yokomizo
e643147b69 Merge pull request #1831 from lboullo0/patch-1
Fix small typo
2018-04-26 22:21:56 +09:00
Kazz Yokomizo
6c4aa71cbc Merge pull request #1834 from ralphchung/edit-locale-zh_TW
Edit the localization file of zh-TW
2018-04-26 22:18:56 +09:00
Kazz Yokomizo
9930ba8748 Merge pull request #1836 from bah2830/TAG_COUNT_FIX
Updated tag note count to float to the right like the storage list
2018-04-26 22:16:35 +09:00
Frank Kanis
fbb8b4687b Reduced nests 2018-04-25 17:53:18 +02:00
cJack1913
01b1c49738 Fix font color in code block(markdown preview) 2018-04-25 17:23:09 +08:00
yosmoc
c9c28eda1b support internal note link 2018-04-24 23:40:59 +02:00
Chen Shenghan
744bcba599 Fix #1780: Use black text with white background for printing and export in all theme 2018-04-25 03:31:36 +12:00
Hung Nguyen
d76db726c4 refactored code according to review 2018-04-24 18:06:10 +07:00
Peter Dave Hello
71ec528a87 Optimize PNG images losslessly via Google's zopflipng 2018-04-24 17:39:18 +08:00
Junyoung Choi (Sai)
fbbc93900e Merge pull request #1858 from zzdjk6/master
Fix #1797: In preview window, open external link in external browser.
2018-04-24 04:58:25 +09:00
Junyoung Choi (Sai)
33b45737c9 Merge pull request #1859 from yosmoc/moment_updateLocale_warning
fix deprecation warning
2018-04-24 04:56:52 +09:00
yosmoc
4a55f78a48 fix deprecation warning
moment.updateLocale will properly replace properties on an existing locale.
2018-04-23 21:08:10 +02:00
Ralph Chung
a82a79e25c Edit the localization file of zh-TW
I've edited the zh-TW.json based on the original version.
The use of words in this document is more in keeping with the habits of how Taiwanese use Chinese.
2018-04-24 00:41:27 +08:00
Chen Shenghan
6ec2124a9c Fix #1797: In preview window, open external link in external browser. 2018-04-24 02:51:58 +12:00
Kazz Yokomizo
1f1ef1440e Merge pull request #1855 from mkal1375/master
Add fa.json in `locales` directory
2018-04-22 14:10:05 +09:00
Hung Nguyen
a7d0a4bdac clean up some redundant code, changed to path.sep, remove some default snippet 2018-04-22 09:27:47 +07:00
Mahdi Kalhor
d2129ffac6 edit some errors in translations. 2018-04-21 19:20:14 +04:30
Mahdi Kalhor
a4782f0663 Add fa.json for persian(farsi) language. 2018-04-21 19:15:10 +04:30
ehhc
16794b9d78 Fixes #1822 -> fix for the broken tests 2018-04-21 16:32:27 +02:00
ehhc
a76aed2d4e Fixes #1822 2018-04-21 14:49:43 +02:00
ehhc
d2163dacf9 Fixes #1825 Refactoring of the attachment/image management 2018-04-21 14:32:24 +02:00
Kazz Yokomizo
158305346f Merge pull request #1850 from BoostIO/slack-url
Remake slack url
2018-04-21 15:56:27 +09:00
Kazz Yokomizo
e692432242 Remake slack url 2018-04-21 15:55:54 +09:00
Hung Nguyen
a7b85b123e fixed get appdata path error 2018-04-21 09:40:32 +07:00
Hung Nguyen
ddcd722598 fix eslint error 2018-04-21 09:07:49 +07:00
Hung Nguyen
358458a937 Fixed appdata path error 2018-04-21 09:04:30 +07:00
Hung Nguyen
8925f7c381 fixed eslint error 2018-04-20 23:58:52 +07:00
Hung Nguyen
ff2e39901a added delete snippet, update snippet, create snippet and save on snippet change 2018-04-20 23:15:17 +07:00
Junyoung Choi (Sai)
90ff0f43ea Merge pull request #1754 from yougotwill/crossplatform_fullscreen_shortcuts
Fullscreen shortcut for non macOS
2018-04-20 13:02:33 +09:00
Junyoung Choi (Sai)
442c352c8d Merge pull request #1842 from davix3f/patch-1
Italian translation
2018-04-20 13:00:50 +09:00
Davide Fiorito
e91b7fb082 Italian translation 2018-04-19 17:54:14 +02:00
Hung Nguyen
88de66a31f fix the same error when drop image 2018-04-19 19:17:10 +07:00
Hung Nguyen
d3b3e45800 improved style for snippet list 2018-04-19 19:05:46 +07:00
Hung Nguyen
50d2f90621 added snippet config in setting 2018-04-19 13:40:33 +07:00
Brent Hughes
8ccf6cb8a3 Updated tag note count to float to the right like the storage list 2018-04-18 10:55:01 -05:00
Hung Nguyen
2e9b478824 added text expansion support 2018-04-18 19:31:10 +07:00
Kazz Yokomizo
89b2d54725 Merge pull request #1832 from dotnsf/translate_ja
locales/ja.json translated in Japanese
2018-04-18 14:33:42 +09:00
K.Kimura
3d0af2d8ca locales/ja.json translated in Japanese 2018-04-18 13:20:20 +09:00
Lucas
813b433f4d Fix small typo 2018-04-17 18:07:27 -03:00
William Grant
0bce96b0c6 Merge branch 'master' into crossplatform_fullscreen_shortcuts 2018-04-17 18:24:35 +02:00
William Grant
1d4f1764fc Changed top:focus-search shortcut 2018-04-17 18:22:54 +02:00
Hung Nguyen
2994420160 Fixed image not displaying after paste 2018-04-17 20:47:13 +07:00
Hung Nguyen
65d8d7282f fetched lastest changes 2018-04-17 10:06:39 +07:00
Hung Nguyen
47af3f09fc Centralized languages into 1 file 2018-04-17 09:59:30 +07:00
Junyoung Choi (Sai)
f4024f4683 Merge pull request #1749 from yougotwill/delete_dialog
Show confirmation dialog when deleting notes from the context menu
2018-04-16 22:03:02 +09:00
Junyoung Choi (Sai)
ee0ed6df7a Merge pull request #1802 from frankkanis/languages-fix
Fixed change some languages
2018-04-16 21:56:32 +09:00
Junyoung Choi (Sai)
d3fbba3572 Merge pull request #1809 from ZeroX-DG/fix-menu-popup-alt
fixed menu popup on alt key pressed
2018-04-16 21:45:03 +09:00
Hung Nguyen
4a6b22f5b7 removed console.log used for debuging 2018-04-15 09:18:12 +07:00
William Grant
d070305002 Merge branch 'master' into delete_dialog 2018-04-14 20:20:17 +02:00
Hung Nguyen
a8500150b0 fixed menu popup on alt 2018-04-14 21:43:19 +07:00
Junyoung Choi (Sai)
02fb1d01ad Merge pull request #1804 from romainwn/feature/add-issue-template
add issue template
2018-04-14 15:20:53 +09:00
Kazz Yokomizo
497dee038f Merge pull request #1807 from ZeroX-DG/master
Edited theme and language init code to avoid code repetition
2018-04-14 14:11:24 +09:00
Hung Nguyen
a4af77f91e fixed eslint error 2018-04-13 21:46:19 +07:00
Hung Nguyen
f2a4e1d230 fixed typo in variable name 2018-04-13 21:00:01 +07:00
Hung Nguyen
8560901f80 Edited theme and language init code to avoid code repetition 2018-04-13 20:39:17 +07:00
Frank Kanis
daea604c60 Added spaces 2018-04-12 21:51:01 +02:00
Frank Kanis
7aedb59f26 Fixed moving notes with images 2018-04-12 21:20:16 +02:00
Unknown
0be1c2f464 remove unnecessary lines 2018-04-12 19:47:02 +02:00
Romain Le Quellec
0dbfaf0e79 fix checkbox 2018-04-12 13:34:26 +02:00
Romain Le Quellec
4147399cda add issue template 2018-04-12 13:25:37 +02:00
Frank Kanis
cc667ac738 Added spacing 2018-04-12 00:27:20 +02:00
Frank Kanis
022915ffc9 Fixed change some languages 2018-04-12 00:10:22 +02:00
Junyoung Choi
4b9cf775ff v0.11.4 2018-04-11 08:16:20 +09:00
Junyoung Choi (Sai)
56879924bd Merge pull request #1701 from bimlas/fix-filter-by-tag-in-search-entry
Fix tag filtering in search entry
2018-04-10 19:59:20 +09:00
Junyoung Choi (Sai)
6142f2d641 Merge pull request #1724 from ehhc/Issue_1723
Fixes #1723
2018-04-10 19:16:05 +09:00
Sosuke Suzuki
0d53f799b7 to short an arrow function 2018-04-10 18:40:57 +09:00
Junyoung Choi (Sai)
65e77e9669 Merge pull request #1742 from bimlas/order-of-tags
Add selector to sort tags by counter or alphabetically
2018-04-10 17:40:31 +09:00
Sosuke Suzuki
2c8f3b56ae use arrow functions 2018-04-10 17:14:25 +09:00
Junyoung Choi (Sai)
8ab190affc Merge pull request #1752 from nlopin/add-jest
Add jest and simple test
2018-04-10 16:50:49 +09:00
Sosuke Suzuki
eafccc4fc4 remove confirmDeletion function 2018-04-10 16:44:04 +09:00
Sosuke Suzuki
ce440351a5 use extracted confirmDeleteNote function 2018-04-10 16:28:05 +09:00
Sosuke Suzuki
be94edde0f extract confirmDeleteNote function 2018-04-10 16:26:50 +09:00
Sosuke Suzuki
4fcc9af933 add jest-localstorage-mock 2018-04-10 15:47:58 +09:00
Kazz Yokomizo
e2b4ac6ee8 Merge pull request #1778 from BoostIO/fix-folded-stragelist-bug
fix folded storagelist width
2018-04-04 22:28:37 +09:00
Sosuke Suzuki
c151049cc2 fix folded storagelist width 2018-04-04 21:17:07 +09:00
Junyoung Choi (Sai)
ac778d3f65 Merge pull request #1764 from frankkanis/languages-production
Fixed change language in production mode
2018-04-03 17:44:14 +09:00
Junyoung Choi (Sai)
1aed0cb4b9 Merge pull request #1774 from li1xu1bin/master
Translate into Chinese
2018-04-03 02:06:12 +09:00
Junyoung Choi (Sai)
5836b65aad Merge pull request #1744 from azu/search-routing
fix reload crash on /searched
2018-04-01 07:51:38 +09:00
Junyoung Choi (Sai)
46f750efba Merge pull request #1745 from frankkanis/trash-context-menu
Clean up trash context menu
2018-04-01 07:51:07 +09:00
Junyoung Choi (Sai)
b33c9e23ce Merge pull request #1737 from yosmoc/noteList_error_handling
remove broken note from note list
2018-04-01 07:48:38 +09:00
Junyoung Choi (Sai)
14694f1cb0 Merge pull request #1751 from gediminasml/folding
Add folding to CodeEditor
2018-04-01 07:48:25 +09:00
Junyoung Choi (Sai)
75575348cd Merge pull request #1757 from hidaiy/fix-vim-mode
Fix #1003 Add CodeMirror addon
2018-04-01 07:48:06 +09:00
Junyoung Choi (Sai)
f6ad0a235c Merge pull request #1759 from bimlas/fix-highlighting-of-active-tag
Fix highlighting of active tag
2018-04-01 07:47:49 +09:00
“li1xu1bin”
bbf6c60888 Chinesization 2018-03-31 18:58:21 +08:00
Junyoung Choi (Sai)
f5915f3e10 Merge pull request #1753 from nlopin/encode_content_in_html_export
Escape html characters before convert to HTML
2018-03-31 17:57:24 +09:00
William Grant
a32cfc8aff Revert "updated for separate methods for full screen depending on the platform"
This reverts commit b46b958105.
2018-03-30 13:01:18 +02:00
ehhc
c90a878c9e Merge branch 'master' into Issue_1723 2018-03-30 12:25:35 +02:00
William Grant
b46b958105 updated for separate methods for full screen depending on the platform 2018-03-30 11:05:20 +02:00
Frank Kanis
6943b06a88 Added missing space 2018-03-28 22:06:50 +02:00
Frank Kanis
27a9def88c Fixed change language in production 2018-03-28 21:55:22 +02:00
William Grant
11f8cfe0e6 fixed label key-spacing 2018-03-28 19:21:36 +02:00
William Grant
e1e3cc7999 moved label to a better position 2018-03-28 19:17:40 +02:00
bimlas
254c8816f1 Fix highlighting of active tag
The currently selected tag was not highlighted in the list.

Before:

![screencast](https://i.imgur.com/6JsjYk7.gif)

After:

![screencast](https://i.imgur.com/xD6fc0c.gif)
2018-03-27 22:14:47 +02:00
William Grant
9a445e34fd fixed trailing spaces 2018-03-27 21:06:59 +02:00
William Grant
ee78e113de Moved 'Toggle Fullscreen' to the View label used non-OS specific fullscreen 2018-03-27 20:52:58 +02:00
hidai
0dfb14962a Add CodeMirror addon 2018-03-27 18:37:20 +09:00
bimlas
d493df4295 Move selector next to title 2018-03-27 07:34:11 +02:00
William Grant
f0144233f9 Fullscreen shortcut for non macOS is now F11 2018-03-25 23:04:16 +02:00
Nikolay Lopin
90f21f4ed1 Escape html characters before convert to HTML 2018-03-25 23:47:17 +03:00
Nikolay Lopin
646ebe592e Add setup files and localstorage mock 2018-03-25 21:02:35 +03:00
Nikolay Lopin
b098a15e9c Add jest and simple test 2018-03-25 20:02:06 +03:00
William Grant
4f98995fe4 ... that second equals 2018-03-25 18:47:21 +02:00
William Grant
56231edc3a fix delete confirmation method 2018-03-25 18:43:54 +02:00
William Grant
871ab428c2 Revert "updated node version in travis.yml"
This reverts commit a9b75f752e.
2018-03-25 18:42:48 +02:00
William Grant
a9b75f752e updated node version in travis.yml 2018-03-25 18:28:02 +02:00
Junyoung Choi (Sai)
9590559b81 Merge pull request #1741 from wAuner/patch-1
fix incorrect app closing for linux mint cinnamon
2018-03-26 00:51:30 +09:00
Junyoung Choi (Sai)
24bd2eddf1 Merge pull request #1748 from davidmigloz/master
Add Spanish (es-ES) locale
2018-03-26 00:50:51 +09:00
Junyoung Choi (Sai)
f4ba06c401 Merge pull request #1747 from lfromanini/master
Brazilian Portuguese localization
2018-03-26 00:49:31 +09:00
Gediminas Petrikas
cd405d1df9 Add cursor pointer when hovering fold gutter 2018-03-25 14:55:33 +03:00
Gediminas Petrikas
9d6dbc1a6f Remove folding keyboard shortcut 2018-03-25 14:55:06 +03:00
Gediminas Petrikas
6d57712fca Add fold gutters 2018-03-25 14:13:19 +03:00
William Grant
191f2cacbf Show confirmation dialog when deleting notes from the context menu if set in preferences 2018-03-25 12:27:04 +02:00
David Miguel
080448af3a Add Spanish (es-ES) locale 2018-03-25 02:09:04 +01:00
Luiz
6e2272d043 Brazilian Portuguese localization 2018-03-24 18:04:45 -03:00
Luiz
71078dea4f Brazilian Portuguese localization 2018-03-24 17:41:48 -03:00
Frank Kanis
333f0be879 Clean up trash context menu 2018-03-24 20:49:04 +01:00
azu
3b0f664a3b fix: fix crash on /searched
Add routing for search word:

- `/searched/:searchword`

Restore the state from the `:searchword` params.
2018-03-25 00:33:57 +09:00
bimlas
2a23d19321 Add selector to sort tags by counter or alphabetically
![screencast](https://i.imgur.com/XUkTyRe.gif)
2018-03-24 15:46:49 +01:00
wAuner
5ee4237510 fix incorrect app closing for linux mint cinnamon
excluded the cinnamon desktop for the macOS-like behavior which closes the window, but does not shutdown the app processes
2018-03-24 14:48:34 +01:00
yosmoc
bdb906c26d remove broken note from note list
When .cson is broken and catch error in processing this file,  undefined is collected in notes.

remved broken note(s) from notes list.
2018-03-23 22:24:53 +01:00
Junyoung Choi (Sai)
02095ac155 Merge pull request #1736 from rayou/fix-typo-in-locales
Fixed typo in locales
2018-03-24 02:47:26 +09:00
Junyoung Choi (Sai)
80d1ca81ac Merge pull request #1725 from frankkanis/master
Updated German translation
2018-03-24 02:33:02 +09:00
Yu-Hung Ou
3bbabbc80b fixed typo in locales 2018-03-23 23:22:49 +11:00
Frank Kanis
f8ff3d4bf5 Update German translation 2018-03-22 21:21:15 +01:00
Frank Kanis
3a40f9ebd6 Prepared text for translation 2018-03-22 21:20:13 +01:00
ceh
cf776088e6 Fixes #1723 2018-03-22 17:26:59 +01:00
Junyoung Choi
7ab81608e8 v0.11.3 2018-03-22 23:07:28 +09:00
Junyoung Choi (Sai)
017e40fcb9 Merge pull request #1722 from BoostIO/disable-i18n-dev-mode
Disable devMode by default
2018-03-22 23:01:49 +09:00
Junyoung Choi
29309fbaa3 Disable devMode by default 2018-03-22 23:01:20 +09:00
Junyoung Choi (Sai)
b766b08bf5 Merge pull request #1709 from rayou/add-editor-rulers
Added editor rulers
2018-03-22 22:54:55 +09:00
Junyoung Choi
e796e00963 Merge branch 'master' into add-editor-rulers 2018-03-22 22:30:03 +09:00
Junyoung Choi (Sai)
10136df977 Merge pull request #1713 from frankkanis/master
Update text for internationalization
2018-03-22 22:09:15 +09:00
Junyoung Choi (Sai)
44ec107ce8 Merge pull request #1715 from BoostIO/sanitize-options
Make sanitization configurable
2018-03-22 21:57:37 +09:00
Junyoung Choi (Sai)
c7fb5b0475 Merge pull request #1718 from rayou/fix-1705-wrap-long-latex
Wrap long LaTeX formula in Preview
2018-03-22 21:57:21 +09:00
Junyoung Choi (Sai)
785735ccf5 Merge pull request #1721 from BoostIO/fix-quit
Always quit app when all window closed
2018-03-22 21:54:13 +09:00
Junyoung Choi (Sai)
f0736ccf8d Merge pull request #1720 from bimlas/i18n-hu-hungarian
i18n: Add Hungarian (HU) translations
2018-03-22 21:50:23 +09:00
Junyoung Choi
a24a7e03be Quit app when all window closed 2018-03-22 20:59:19 +09:00
bimlas
be235e5204 i18n: Add HU to locales 2018-03-22 12:35:42 +01:00
bimlas
57ec598672 i18n: Add Hungarian (HU) translations 2018-03-22 12:35:42 +01:00
Yu-Hung Ou
2627c09cda added new local strings to all locals 2018-03-22 21:44:26 +11:00
Yu-Hung Ou
36e63bb8a9 wrap long LaTeX formula in Preview 2018-03-22 21:33:32 +11:00
Junyoung Choi
5ea24f650b Make sanitization configurable 2018-03-22 12:01:16 +09:00
Junyoung Choi (Sai)
8c792ce7a1 Merge pull request #1714 from BoostIO/fix-lint-errors
Fix lint errors
2018-03-22 11:10:52 +09:00
Frank Kanis
055969f5c6 Update text for internationalization 2018-03-21 22:17:34 +01:00
bimlas
145ae10a79 Add test: find tags without hash symbol
Searching has to find tags without hash symbol too (`tag` instead of
`#tag`).
2018-03-21 20:07:21 +01:00
Yu-Hung Ou
bdb9349b52 updated en local 2018-03-21 22:32:05 +11:00
Yu-Hung Ou
8b11b57ec5 allow users to enable/disable editor rulers. default: disable 2018-03-21 22:31:46 +11:00
Yu-Hung Ou
29888c89ad added CSS style for rulers to make it fit most of the themes 2018-03-21 21:11:26 +11:00
Yu-Hung Ou
281fb2afd3 added CodeMirror ruler support 2018-03-21 21:11:26 +11:00
Yu-Hung Ou
fbb7839f83 added Editor Rulers field in Preferences 2018-03-21 21:11:26 +11:00
Junyoung Choi (Sai)
d3f9c170ac Merge pull request #1703 from Joel-Costamagna/patch-1
typo
2018-03-21 15:28:38 +09:00
bimlas
4f9a0b0040 Merge findByTag() and findByWord() into one
Both looked for word in tags and content too, the only difference is
when searched for `#tag`, the prefix (`#`) was truncated before compared
with list of tags.
2018-03-20 19:30:57 +01:00
bimlas
aae584106a Look for tags in context too
The previous commit broke this behaviour.

Looking for a tag means the union of **tags** AND **tag in content**, so
it has to search in the in currently found notes separetely, thus it has
to clone the list first (`.slice(0)`).
2018-03-20 17:11:08 +01:00
Joël COSTAMAGNA
2a784deb4b typo 2018-03-20 11:46:33 -04:00
bimlas
5f5a7880a6 Fix tag filtering in search entry
Some issues introduced in #954
(https://github.com/BoostIO/Boostnote/pull/954#issuecomment-336344915):

- search by `#tag1 #tag2` returns the results of only `#tag2`
- search by `#tag1 content` is as same as AND search by `#tag1 content` (and
  `#tag1` is regarded a word, not a tag)
- search by `content #tag1` returns the results of only `#tag1`

This commit fixing these:

- search by `#tag1 #tag2` returns the results of only `#tag2`

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

- search by `#tag1 content` is as same as AND search by `#tag1 content` (and
  `#tag1` is regarded a word, not a tag)

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

- search by `content #tag1` returns the results of only `#tag1`

  ![screencast](https://i.imgur.com/5MrMbE6.gif)

NOTE: the examples works without `#` character too, because
`findByWord()` checks the tags too.
2018-03-20 09:01:11 +01:00
Junyoung Choi (Sai)
ab8b6d806d Merge pull request #1700 from BoostIO/add-korean
Add korean
2018-03-20 16:36:54 +09:00
Junyoung Choi
6c4206a2dd Fix lint errors 2018-03-20 16:18:18 +09:00
Junyoung Choi
0d61d9cee4 Add missing locale 2018-03-20 15:10:55 +09:00
Junyoung Choi
48bdb9e818 Do not use devMode on production 2018-03-20 15:10:35 +09:00
Junyoung Choi
cfe447a403 Add Korean 2018-03-20 15:04:56 +09:00
Junyoung Choi (Sai)
2c30f0e487 Merge pull request #1684 from kawmra/prevent-fetching-title-in-link-tag
Prevent generating a link tag when pasting URL to inside of a link tag.
2018-03-20 10:32:59 +09:00
Junyoung Choi (Sai)
147211478c Merge pull request #1698 from nlopin/fix_export_folder
Fix filenames with forbidden symbols during export #1613
2018-03-19 23:23:47 +09:00
Junyoung Choi (Sai)
35b7674e69 Merge pull request #1696 from frankkanis/master
Fix Copy Note Link in Context Menu
2018-03-19 22:58:38 +09:00
Junyoung Choi (Sai)
55075b2a96 Merge pull request #1566 from nlopin/new-snippet-tabbar
New snippet tabbar
2018-03-19 21:27:37 +09:00
Nikolay Lopin
6577599959 Replace forbidden filename symbols to _ for image export 2018-03-19 00:33:23 +03:00
Nikolay Lopin
0c4e72e507 Replace forbidden filename symbols to _ 2018-03-19 00:28:36 +03:00
Nikolay Lopin
9bf96e943d Add filenamify dependency 2018-03-19 00:22:07 +03:00
Nikolay Lopin
2811843e70 Merge branch 'master' into new-snippet-tabbar
# Conflicts:
#	browser/main/Detail/SnippetNoteDetail.js
2018-03-18 21:26:12 +03:00
Frank Kanis
842ece2a8a Fix Copy Note Link in Context Menu 2018-03-18 11:14:22 +01:00
Junyoung Choi (Sai)
222de03ea3 Merge pull request #1678 from rayou/add-unit-tests-for-lib-markdown
Added unit tests for lib/markdown
2018-03-18 15:24:48 +09:00
Junyoung Choi (Sai)
d31ae9f3fc Merge pull request #1692 from romainwn/master
add missing i18n preview word + fr translation
2018-03-18 15:23:55 +09:00
Yu-Hung Ou
1dd7644e12 added smart quotes unit test for lib/markdown 2018-03-18 15:36:57 +11:00
Romain Le Quellec
2cee54f70a fix missing import 2018-03-17 10:23:44 +01:00
Romain Le Quellec
a8431fae96 add missing i18n preview word + fr translation 2018-03-17 10:16:51 +01:00
Yu-Hung Ou
03d11b7b58 Merge remote-tracking branch 'upstream/master' into add-unit-tests-for-lib-markdown 2018-03-17 18:54:18 +11:00
Junyoung Choi (Sai)
316e2eeefb Merge pull request #1640 from SiSchae/localization
Localization
2018-03-17 11:13:52 +09:00
Junyoung Choi (Sai)
c21e19337a Merge pull request #1691 from rayou/fix-smart-quotes-post-to-blog
Fixed smart quotes when posting a note to blog
2018-03-17 01:04:50 +09:00
Yu-Hung Ou
b9cab0dae8 Init markdown lib only when it's needed when posting a note to blog 2018-03-16 23:33:40 +11:00
Yu-Hung Ou
33b3299ca2 changed the default options of markdown lib to follow config 2018-03-16 23:32:18 +11:00
Yu-Hung Ou
00ba38beba reload config every time when markdown lib initiated 2018-03-16 23:30:57 +11:00
Yu-Hung Ou
433fce286e added electron as global variable to pass eslint check 2018-03-16 23:11:43 +11:00
kawmra
b36322bba4 Prevent generating a link tag when pasting URL to inside of a link tag. 2018-03-16 00:11:47 +09:00
Yu-Hung Ou
c147e0a789 mocked electron in ava unit tests 2018-03-15 22:14:21 +11:00
Yu-Hung Ou
8bf5d02624 Revert "updated lib/consts to access electron remote.app only when in production mode"
This reverts commit bbcd674516.
2018-03-15 22:12:22 +11:00
Simon
35616c1ccd Merge pull request #1 from nlopin/patch-1
Add russian translation
2018-03-15 09:47:09 +01:00
Nikolai Lopin
7c8939ecb8 Add russian translation 2018-03-15 02:23:15 +03:00
Simon
ccb0302d3f Resolved Merge issue; Added Russian; 2018-03-14 13:13:47 +01:00
Simon
0cfd048013 Merge branch 'master' into localization 2018-03-14 12:49:40 +01:00
Yu-Hung Ou
f72b4f0249 added unit test for checkbox markdown syntax 2018-03-14 21:47:07 +11:00
Yu-Hung Ou
b5cb209f14 added browser testing support to ava 2018-03-14 21:46:20 +11:00
Yu-Hung Ou
1af374439d added unit tests for lib/markdown 2018-03-14 20:37:25 +11:00
Yu-Hung Ou
bbcd674516 updated lib/consts to access electron remote.app only when in production mode 2018-03-14 20:37:25 +11:00
Yu-Hung Ou
3cba71b7a8 upgraded ava to 0.25.0 2018-03-14 20:37:25 +11:00
Yu-Hung Ou
6a1e9c5818 removed katex script tag from main.html, use import instead 2018-03-14 20:37:25 +11:00
Junyoung Choi (Sai)
ede41d01b4 Merge pull request #1676 from lijinglue/hotfix/markdown-render-in-blog-post
hotfix: update markdown render in blog post feature
2018-03-14 13:56:18 +09:00
Junyoung Choi (Sai)
826a67b550 Merge pull request #1677 from BoostIO/allow-more
Allow b tag and style attribute
2018-03-14 13:09:38 +09:00
Junyoung Choi
f3d59a9b61 Allow b tag and style attribute 2018-03-14 13:03:57 +09:00
lijinglue
65434453b8 hotfix: update markdown render in blog post feature 2018-03-14 00:30:29 +08:00
Junyoung Choi
b8658abbad v0.11.2 2018-03-13 23:12:32 +09:00
Junyoung Choi (Sai)
0387b33f6c Merge pull request #1673 from BoostIO/update-whitelist
Use same settings of github
2018-03-13 23:02:52 +09:00
Junyoung Choi (Sai)
41042fe64e Merge pull request #1664 from mirsch/fix-copy-note-link-from-list
fix "copy note link from note list" link format
2018-03-13 23:02:44 +09:00
Junyoung Choi (Sai)
1efc3bb15d Merge pull request #1663 from BoostIO/note-list-ui
Fix note list UI
2018-03-13 22:45:51 +09:00
Junyoung Choi
0816a9410b Use same settings of github 2018-03-13 22:41:17 +09:00
Junyoung Choi (Sai)
cb823eaaa6 Merge pull request #1670 from rayou/fix-1661-move-folder-cursor-style
Added draggable folder handle #1661
2018-03-13 22:04:53 +09:00
Junyoung Choi (Sai)
44f027bf18 Merge pull request #1671 from BoostIO/more-whitelist
Add table and details stuff to whitelist
2018-03-13 22:02:06 +09:00
Junyoung Choi
571d159a1f Add table and details stuff to whitelist 2018-03-13 22:00:07 +09:00
Yu-Hung Ou
a30d977727 added draggable folder handle 2018-03-13 23:42:14 +11:00
Simon
847ad2d781 Merge branch 'master' into localization 2018-03-13 09:24:53 +01:00
mirsch
da9067a672 fix "copy note link from note list" introduced in #1583 to reflect changes from #1636 2018-03-12 15:23:37 +01:00
Kazu Yokomizo
5ed5950ed5 Fix note list UI 2018-03-12 22:14:06 +09:00
Junyoung Choi
f53c182cfb Fix version 2018-03-12 18:50:22 +09:00
Junyoung Choi
bb2978b370 v0.11.1 2018-03-12 17:53:16 +09:00
Junyoung Choi (Sai)
66ae4a5163 Merge pull request #1660 from JKetelaar/bugfix/grammar-change
Fixed English typo & grammar
2018-03-12 17:44:40 +09:00
Jeroen Ketelaar
f435595ad8 Fixed English typo & grammar 2018-03-12 09:42:44 +01:00
Junyoung Choi (Sai)
4bec4596fe Merge pull request #1659 from BoostIO/fix-checkbox
Allow checkbox
2018-03-12 17:41:29 +09:00
Junyoung Choi
10b86aec49 Allow checkbox 2018-03-12 17:40:27 +09:00
Simon
ecabd37cbb Setting default language in DEFAULT_CONFIG 2018-03-12 07:26:19 +01:00
Junyoung Choi (Sai)
f3cabfcb4d Merge pull request #1656 from BoostIO/feature-v0-11-0
v0.11.0
2018-03-12 11:45:12 +09:00
Simon
4f6c35713e Added languages; better German translation 2018-03-09 17:30:11 +01:00
Simon
4d41c7f37f Changed default language to en 2018-03-09 07:39:08 +01:00
Nikolay Lopin
a222fd0786 Add animation for scrolling with buttons 2018-03-08 01:02:49 +03:00
Simon
40b5472866 Adjustments for Travis CI 2018-03-07 08:37:55 +01:00
Simon
a413e273ca Added strings in notifications 2018-03-07 08:20:05 +01:00
Simon
0f82085cae Adjustments for Travis CI 2018-03-07 07:10:49 +01:00
Nikolay Lopin
7472019422 Change property responsible for scrolling from "offsetLeft" to "scrollLeft" 2018-03-07 01:55:04 +03:00
Simon
aa3597881a added more strings 2018-03-06 15:56:48 +01:00
Simon
a36841e501 Implemented language switch in Interface Config Tab 2018-03-06 15:04:04 +01:00
Simon
fe9afc8952 extracted all strings from html lines 2018-03-06 14:07:04 +01:00
Simon
63d39a81aa Merge branch 'localization' of github.com:SiSchae/Boostnote into localization
hoffe es läuft
2018-03-06 10:02:29 +01:00
Simon
b11dc2ca20 first testing of translation 2018-03-06 09:54:37 +01:00
Nikolay Lopin
a35f876f4f Merge branch 'master' into new-snippet-tabbar 2018-03-05 00:06:57 +03:00
Yu-Hung Ou
8ae7d96cc7 updated Travis CI node version 2018-02-27 23:59:32 +11:00
Nikolay Lopin
a3ec6f470a Add horizontal scroll for touch devices 2018-02-24 01:03:25 +03:00
Nikolay Lopin
8e85a33dac Show Arrows at snippets tab bar only when needed 2018-02-24 00:49:21 +03:00
Nikolay Lopin
1769dd959e Revert spaces 2018-02-21 03:21:40 +03:00
Nikolay Lopin
1253b81a01 Introduce tabs with minimal width 2018-02-21 03:08:40 +03:00
197 changed files with 12390 additions and 2573 deletions

View File

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

View File

@@ -23,6 +23,7 @@
"lineNumber": true
},
"sortBy": "UPDATED_AT",
"sortTagsBy": "ALPHABETICAL",
"ui": {
"defaultNote": "ALWAYS_ASK",
"disableDirectWrite": false,

View File

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

View File

@@ -3,7 +3,9 @@
"plugins": ["react"],
"rules": {
"no-useless-escape": 0,
"prefer-const": "warn",
"prefer-const": ["warn", {
"destructuring": "all"
}],
"no-unused-vars": "warn",
"no-undef": "warn",
"no-lone-blocks": "warn",
@@ -17,5 +19,8 @@
"FileReader": true,
"localStorage": true,
"fetch": true
},
"env": {
"jest": true
}
}

View File

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

View File

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

7
__mocks__/electron.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
require: jest.genMockFunction(),
match: jest.genMockFunction(),
app: jest.genMockFunction(),
remote: jest.genMockFunction(),
dialog: jest.genMockFunction()
}

View File

@@ -3,32 +3,22 @@ import React from 'react'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import { options, TableEditor } from '@susisu/mte-kernel'
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 fs from 'fs'
const { ipcRenderer } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({column: ruler})) : []
export default class CodeEditor extends React.Component {
constructor (props) {
@@ -50,6 +40,9 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
this.props.onBlur != null && this.props.onBlur(e)
const {storageKey, noteKey} = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
}
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
this.loadStyleHandler = (e) => {
@@ -57,6 +50,8 @@ export default class CodeEditor extends React.Component {
}
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
this.formatTable = () => this.handleFormatTable()
}
handleSearch (msg) {
@@ -90,10 +85,29 @@ export default class CodeEditor extends React.Component {
})
}
componentDidMount () {
this.value = this.props.value
handleFormatTable () {
this.tableEditor.formatAll(options({textWidthOptions: {}}))
}
componentDidMount () {
const { rulers, enableRulers } = this.props
const expandSnippet = this.expandSnippet.bind(this)
const defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
if (!fs.existsSync(consts.SNIPPET_FILE)) {
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
}
this.value = this.props.value
this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value,
lineNumbers: this.props.displayLineNumbers,
lineWrapping: true,
@@ -105,11 +119,20 @@ export default class CodeEditor extends React.Component {
scrollPastEnd: this.props.scrollPastEnd,
inputStyle: 'textarea',
dragDrop: false,
autoCloseBrackets: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: {
pairs: '()[]{}\'\'""$$**``',
triples: '```"""\'\'\'',
explode: '[]{}``$$',
override: true
},
extraKeys: {
Tab: function (cm) {
const cursor = cm.getCursor()
const line = cm.getLine(cursor.line)
const cursorPosition = cursor.ch
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
if (cm.somethingSelected()) cm.indentSelection('add')
else {
const tabs = cm.getOption('indentWithTabs')
@@ -121,6 +144,16 @@ export default class CodeEditor extends React.Component {
cm.execCommand('insertSoftTab')
}
cm.execCommand('goLineEnd')
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
// text expansion on tab key if the char before is alphabet
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
if (expandSnippet(line, cursor, cm, snippets) === false) {
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
}
} else {
if (tabs) {
cm.execCommand('insertTab')
@@ -162,6 +195,76 @@ export default class CodeEditor extends React.Component {
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
CodeMirror.Vim.map('ZZ', ':q', 'normal')
this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
eventEmitter.on('code:format-table', this.formatTable)
}
expandSnippet (line, cursor, cm, snippets) {
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
const templateCursorString = ':{}'
for (let i = 0; i < snippets.length; i++) {
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
for (let j = 0; j < snippetLines.length; j++) {
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
if (cursorIndex !== -1) {
cursorLineNumber = j
cursorLinePosition = cursorIndex
cm.replaceRange(
snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
}
}
} else {
cm.replaceRange(
snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
}
return true
}
}
return false
}
getWordBeforeCursor (line, lineNumber, cursorPosition) {
let wordBeforeCursor = ''
const originCursorPosition = cursorPosition
const emptyChars = /\t|\s|\r|\n/
// to prevent the word to expand is long that will crash the whole app
// the safeStop is there to stop user to expand words that longer than 20 chars
const safeStop = 20
while (cursorPosition > 0) {
const currentChar = line.substr(cursorPosition - 1, 1)
// if char is not an empty char
if (!emptyChars.test(currentChar)) {
wordBeforeCursor = currentChar + wordBeforeCursor
} else if (wordBeforeCursor.length >= safeStop) {
throw new Error('Your snippet trigger is too long !')
} else {
break
}
cursorPosition--
}
return {
text: wordBeforeCursor,
range: {
from: {line: lineNumber, ch: originCursorPosition},
to: {line: lineNumber, ch: cursorPosition}
}
}
}
quitEditor () {
@@ -177,10 +280,13 @@ export default class CodeEditor extends React.Component {
this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)
eventEmitter.off('code:format-table', this.formatTable)
}
componentDidUpdate (prevProps, prevState) {
let needRefresh = false
const {rulers, enableRulers} = this.props
if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode)
}
@@ -198,6 +304,10 @@ export default class CodeEditor extends React.Component {
needRefresh = true
}
if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) {
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
}
if (prevProps.indentSize !== this.props.indentSize) {
this.editor.setOption('indentUnit', this.props.indentSize)
this.editor.setOption('tabSize', this.props.indentSize)
@@ -220,7 +330,7 @@ export default class CodeEditor extends React.Component {
}
setMode (mode) {
let syntax = CodeMirror.findModeByName(pass(mode))
let syntax = CodeMirror.findModeByName(convertModeName(mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime)
@@ -264,51 +374,50 @@ export default class CodeEditor extends React.Component {
this.editor.setCursor(cursor)
}
handleDropImage (e) {
e.preventDefault()
const imagePath = e.dataTransfer.files[0].path
const filename = path.basename(imagePath)
copyImage(imagePath, this.props.storageKey).then((imagePath) => {
const imageMd = `![${filename}](${path.join('/:storage', imagePath)})`
this.insertImageMd(imageMd)
})
handleDropImage (dropEvent) {
dropEvent.preventDefault()
const {storageKey, noteKey} = this.props
attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent)
}
insertImageMd (imageMd) {
insertAttachmentMd (imageMd) {
this.editor.replaceSelection(imageMd)
}
handlePaste (editor, e) {
const clipboardData = e.clipboardData
const {storageKey, noteKey} = this.props
const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text')
const isURL = (str) => {
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
return matcher.test(str)
}
const isInLinkTag = (editor) => {
const startCursor = editor.getCursor('start')
const prevChar = editor.getRange(
{line: startCursor.line, ch: startCursor.ch - 2},
{line: startCursor.line, ch: startCursor.ch}
)
const endCursor = editor.getCursor('end')
const nextChar = editor.getRange(
{line: endCursor.line, ch: endCursor.ch},
{line: endCursor.line, ch: endCursor.ch + 1}
)
return prevChar === '](' && nextChar === ')'
}
if (dataTransferItem.type.match('image')) {
const blob = dataTransferItem.getAsFile()
const reader = new FileReader()
let base64data
reader.readAsDataURL(blob)
reader.onloadend = () => {
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
const imageName = Math.random().toString(36).slice(-16)
const storagePath = findStorage(this.props.storageKey).path
const imageDir = path.join(storagePath, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
const imagePath = path.join(imageDir, `${imageName}.png`)
fs.writeFile(imagePath, binaryData, 'binary')
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
} else if (this.props.fetchUrlTitle && isURL(pastedTxt)) {
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(e, editor, pastedTxt)
}
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
.then((modifiedText) => {
this.editor.replaceSelection(modifiedText)
})
e.preventDefault()
}
}
handleScroll (e) {
@@ -322,24 +431,58 @@ export default class CodeEditor extends React.Component {
const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl)
const isImageReponse = (response) => {
return response.headers.has('content-type') &&
response.headers.get('content-type').match(/^image\/.+$/)
}
const replaceTaggedUrl = (replacement) => {
const value = editor.getValue()
const cursor = editor.getCursor()
const newValue = value.replace(taggedUrl, replacement)
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
editor.setValue(newValue)
editor.setCursor(newCursor)
}
fetch(pastedTxt, {
method: 'get'
}).then((response) => {
return this.decodeResponse(response)
}).then((response) => {
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
const value = editor.getValue()
const cursor = editor.getCursor()
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
const newValue = value.replace(taggedUrl, LinkWithTitle)
editor.setValue(newValue)
editor.setCursor(cursor)
if (isImageReponse(response)) {
return this.mapImageResponse(response, pastedTxt)
} else {
return this.mapNormalResponse(response, pastedTxt)
}
}).then((replacement) => {
replaceTaggedUrl(replacement)
}).catch((e) => {
const value = editor.getValue()
const newValue = value.replace(taggedUrl, pastedTxt)
const cursor = editor.getCursor()
editor.setValue(newValue)
editor.setCursor(cursor)
replaceTaggedUrl(pastedTxt)
})
}
mapNormalResponse (response, pastedTxt) {
return this.decodeResponse(response).then((body) => {
return new Promise((resolve, reject) => {
try {
const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html')
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
resolve(linkWithTitle)
} catch (e) {
reject(e)
}
})
})
}
mapImageResponse (response, pastedTxt) {
return new Promise((resolve, reject) => {
try {
const url = response.url
const name = url.substring(url.lastIndexOf('/') + 1)
const imageLinkWithName = `![${name}](${pastedTxt})`
resolve(imageLinkWithName)
} catch (e) {
reject(e)
}
})
}
@@ -369,11 +512,9 @@ export default class CodeEditor extends React.Component {
}
render () {
const { className, fontSize } = this.props
let fontFamily = this.props.fontFamily
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily
const {className, fontSize} = this.props
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
const width = this.props.width
return (
<div
className={className == null
@@ -383,8 +524,9 @@ export default class CodeEditor extends React.Component {
ref='root'
tabIndex='-1'
style={{
fontFamily: fontFamily.join(', '),
fontSize: fontSize
fontFamily,
fontSize: fontSize,
width: width
}}
onDrop={(e) => this.handleDropImage(e)}
/>
@@ -394,6 +536,8 @@ export default class CodeEditor extends React.Component {
CodeEditor.propTypes = {
value: PropTypes.string,
enableRulers: PropTypes.bool,
rulers: PropTypes.arrayOf(Number),
mode: PropTypes.string,
className: PropTypes.string,
onBlur: PropTypes.func,

View File

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

View File

@@ -7,20 +7,28 @@ import 'codemirror-mode-elixir'
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 Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
import { escapeHtmlCharacters } from 'browser/lib/utils'
const { remote } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const { app } = remote
const path = require('path')
const fileUrl = require('file-url')
const dialog = remote.dialog
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
const appPath = fileUrl(process.env.NODE_ENV === 'production'
? app.getAppPath()
: path.resolve())
const CSS_FILES = [
@@ -28,7 +36,7 @@ const CSS_FILES = [
`${appPath}/node_modules/codemirror/lib/codemirror.css`
]
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
return `
@font-face {
font-family: 'Lato';
@@ -48,7 +56,19 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scro
font-weight: 700;
text-rendering: optimizeLegibility;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
}
${allowCustomCSS ? customCSS : ''}
${markdownStyle}
body {
font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px;
@@ -100,9 +120,38 @@ h2 {
body p {
white-space: normal;
}
@media print {
body[data-theme="${theme}"] {
color: #000;
background-color: #fff;
}
.clipboardButton {
display: none
}
}
`
}
const scrollBarStyle = `
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.15);
}
`
const scrollBarDarkStyle = `
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.3);
}
`
const { shell } = require('electron')
const OSX = global.process.platform === 'darwin'
@@ -112,7 +161,6 @@ if (!OSX) {
defaultFontFamily.unshift('meiryo')
}
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
@@ -122,7 +170,6 @@ export default class MarkdownPreview extends React.Component {
this.mouseUpHandler = (e) => this.handleMouseUp(e)
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd()
@@ -135,24 +182,12 @@ export default class MarkdownPreview extends React.Component {
}
initMarkdown () {
const { smartQuotes } = this.props
this.markdown = new Markdown({ typographer: smartQuotes })
}
handlePreviewAnchorClick (e) {
e.preventDefault()
e.stopPropagation()
const anchor = e.target.closest('a')
const href = anchor.getAttribute('href')
if (_.isString(href) && href.match(/^#/)) {
const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
if (targetElement != null) {
this.getWindow().scrollTo(0, targetElement.offsetTop)
}
} else {
shell.openExternal(href)
}
const { smartQuotes, sanitize, breaks } = this.props
this.markdown = new Markdown({
typographer: smartQuotes,
sanitize,
breaks
})
}
handleCheckboxClick (e) {
@@ -202,11 +237,13 @@ export default class MarkdownPreview extends React.Component {
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
let body = this.markdown.render(escapeHtmlCharacters(noteContent, { detectCodeBlock: true }))
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
const body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
files.forEach((file) => {
file = file.replace('file://', '')
@@ -215,6 +252,13 @@ export default class MarkdownPreview extends React.Component {
dst: 'css'
})
})
attachmentsAbsolutePaths.forEach((attachment) => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
let styles = ''
files.forEach((file) => {
@@ -272,6 +316,21 @@ export default class MarkdownPreview extends React.Component {
}
}
getScrollBarStyle () {
const {
theme
} = this.props
switch (theme) {
case 'dark':
case 'solarized-dark':
case 'monokai':
return scrollBarDarkStyle
default:
return scrollBarStyle
}
}
componentDidMount () {
this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
@@ -280,6 +339,9 @@ export default class MarkdownPreview extends React.Component {
<style id='style'></style>
<link rel="stylesheet" id="codeTheme">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
${this.getScrollBarStyle()}
</style>
`
CSS_FILES.forEach((file) => {
@@ -318,7 +380,10 @@ export default class MarkdownPreview extends React.Component {
componentDidUpdate (prevProps) {
if (prevProps.value !== this.props.value) this.rewriteIframe()
if (prevProps.smartQuotes !== this.props.smartQuotes) {
if (prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize ||
prevProps.smartArrows !== this.props.smartArrows ||
prevProps.breaks !== this.props.breaks) {
this.initMarkdown()
this.rewriteIframe()
}
@@ -329,14 +394,16 @@ export default class MarkdownPreview extends React.Component {
prevProps.lineNumber !== this.props.lineNumber ||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
prevProps.theme !== this.props.theme ||
prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
prevProps.customCSS !== this.props.customCSS) {
this.applyStyle()
this.rewriteIframe()
}
}
getStyleParams () {
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
@@ -345,14 +412,14 @@ export default class MarkdownPreview extends React.Component {
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
}
applyStyle () {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams()
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
}
GetCodeThemeLink (theme) {
@@ -365,9 +432,6 @@ export default class MarkdownPreview extends React.Component {
}
rewriteIframe () {
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
el.removeEventListener('click', this.anchorClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
el.removeEventListener('click', this.checkboxClickHandler)
})
@@ -376,7 +440,7 @@ export default class MarkdownPreview extends React.Component {
el.removeEventListener('click', this.linkClickHandler)
})
const { theme, indentSize, showCopyNotification, storagePath } = this.props
const { theme, indentSize, showCopyNotification, storagePath, noteKey } = this.props
let { value, codeBlockTheme } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
@@ -387,33 +451,25 @@ export default class MarkdownPreview extends React.Component {
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
})
}
this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
el.addEventListener('click', this.anchorClickHandler)
})
const renderedHTML = this.markdown.render(value)
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
el.addEventListener('click', this.checkboxClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
el.addEventListener('click', this.linkClickHandler)
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
el.src = this.markdown.normalizeLinkText(el.src)
if (!/\/:storage/.test(el.src)) return
el.src = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
})
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
? codeBlockTheme
: 'default'
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
let syntax = CodeMirror.findModeByName(el.className)
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => {
const content = htmlTextHelper.decodeEntities(el.innerHTML)
@@ -455,7 +511,7 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = ''
diagram.drawSVG(el, opts)
_.forEach(el.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.anchorClickHandler)
el.addEventListener('click', this.linkClickHandler)
})
} catch (e) {
console.error(e)
@@ -471,7 +527,7 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = ''
diagram.drawSVG(el, {theme: 'simple'})
_.forEach(el.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.anchorClickHandler)
el.addEventListener('click', this.linkClickHandler)
})
} catch (e) {
console.error(e)
@@ -479,6 +535,30 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = 'Sequence diagram parse error: ' + e.message
}
})
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.chart'),
(el) => {
try {
const chartConfig = JSON.parse(el.innerHTML)
el.innerHTML = ''
var canvas = document.createElement('canvas')
el.appendChild(canvas)
/* eslint-disable no-new */
new Chart(canvas, chartConfig)
} catch (e) {
console.error(e)
el.className = 'chart-error'
el.innerHTML = 'chartjs diagram parse error: ' + e.message
}
}
)
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
(el) => {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML))
}
)
}
focus () {
@@ -516,22 +596,44 @@ export default class MarkdownPreview extends React.Component {
}
handlelinkClick (e) {
const noteHash = e.target.href.split('/').pop()
e.preventDefault()
e.stopPropagation()
const href = e.target.href
const linkHash = href.split('/').pop()
const regexNoteInternalLink = /main.html#(.+)/
if (regexNoteInternalLink.test(linkHash)) {
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
if (targetElement != null) {
this.getWindow().scrollTo(0, targetElement.offsetTop)
}
return
}
// this will match the new uuid v4 hash and the old hash
// e.g.
// :note:1c211eb7dcb463de6490 and
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
if (regexIsNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
if (regexIsNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
return
}
// this will match the old link format storage.key-note.key
// e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
if (regexIsLegacyNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.split('-')[1])
if (regexIsLegacyNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', linkHash.split('-')[1])
return
}
// other case
shell.openExternal(href)
}
render () {
@@ -554,9 +656,12 @@ MarkdownPreview.propTypes = {
onDoubleClick: PropTypes.func,
onMouseUp: PropTypes.func,
onMouseDown: PropTypes.func,
onContextMenu: PropTypes.func,
className: PropTypes.string,
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string,
smartQuotes: PropTypes.bool
smartQuotes: PropTypes.bool,
smartArrows: PropTypes.bool,
breaks: PropTypes.bool
}

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl'
import TodoProcess from './TodoProcess'
import i18n from 'browser/lib/i18n'
/**
* @description Tag element component.
@@ -76,7 +77,7 @@ const NoteItem = ({
<div styleName='item-title'>
{note.title.trim().length > 0
? note.title
: <span styleName='item-title-empty'>Empty</span>
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
}
</div>
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
@@ -93,14 +94,14 @@ const NoteItem = ({
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags)
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>No tags</span>
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>{i18n.__('No tags')}</span>
}
</div>
<div>
{note.isStarred
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
}
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
}
{note.type === 'MARKDOWN_NOTE'

View File

@@ -144,18 +144,18 @@ $control-height = 30px
padding-bottom 2px
.item-star
position relative
width 16px
height 16px
position absolute
right 2px
top 5px
color alpha($ui-favorite-star-button-color, 60%)
font-size 12px
padding 0
border-radius 17px
.item-pin
position relative
width 34px
height 34px
position absolute
right 25px
top 7px
color #E54D42
font-size 14px
padding 0
@@ -321,3 +321,76 @@ body[data-theme="solarized-dark"]
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle
body[data-theme="monokai"]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
.item
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
&:active
transition 0.15s
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
color $ui-monokai-text-color
.item-wrapper
border-color alpha($ui-monokai-button-backgroundColor, 60%)
.item--active
border-color $ui-monokai-borderColor
background-color $ui-monokai-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-monokai-text-color
.item-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
&:hover
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
color #c0392b
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-title
color $ui-inactive-text-color
.item-title-icon
color $ui-inactive-text-color
.item-title-empty
color $ui-inactive-text-color
.item-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle

View File

@@ -5,6 +5,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './NoteItemSimple.styl'
import i18n from 'browser/lib/i18n'
/**
* @description Note item component when using simple display mode.
@@ -45,7 +46,7 @@ const NoteItemSimple = ({
}
{note.title.trim().length > 0
? note.title
: <span styleName='item-simple-title-empty'>Empty</span>
: <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
}
{isAllNotesView && <div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SnippetTab.styl'
import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
class SnippetTab extends React.Component {
constructor (props) {
@@ -28,7 +29,7 @@ class SnippetTab extends React.Component {
handleContextMenu (e) {
context.popup([
{
label: 'Rename',
label: i18n.__('Rename'),
click: (e) => this.handleRenameClick(e)
}
])
@@ -54,10 +55,10 @@ class SnippetTab extends React.Component {
this.handleRename()
break
case 27:
this.setState({
name: this.props.snippet.name,
this.setState((prevState, props) => ({
name: props.snippet.name,
isRenaming: false
})
}))
break
}
}
@@ -114,7 +115,7 @@ class SnippetTab extends React.Component {
{snippet.name.trim().length > 0
? snippet.name
: <span styleName='button-unnamed'>
Unnamed
{i18n.__('Unnamed')}
</span>
}
</button>

View File

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

View File

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

View File

@@ -13,6 +13,7 @@
border none
overflow ellipsis
font-size 14px
align-items: center
&:first-child
margin-top 0
&:hover
@@ -22,7 +23,7 @@
&:active
color $$ui-button-default-color
background-color alpha($ui-button-default--active-backgroundColor, 20%)
.folderList-item--active
@extend .folderList-item
color #1EC38B
@@ -34,9 +35,7 @@
.folderList-item-name
display block
flex 1
padding 0 12px
height 26px
line-height 26px
padding-right: 10px
border-width 0 0 0 2px
border-style solid
border-color transparent
@@ -59,8 +58,8 @@
opacity 0
border-top-right-radius 2px
border-bottom-right-radius 2px
height 26px
line-height 26px
height 34px
line-height 32px
.folderList-item:hover, .folderList-item--active:hover
.folderList-item-tooltip
@@ -69,9 +68,20 @@
.folderList-item-name--folded
@extend .folderList-item-name
padding-left 7px
text
.folderList-item-icon
font-size 9px
.folderList-item-icon
padding-right: 10px
.folderList-item-reorder
font-size: 9px
padding: 10px 8px 10px 9px;
color: rgba(147, 147, 149, 0.3)
cursor: ns-resize
&:before
content: "\f142 \f142"
body[data-theme="white"]
.folderList-item
color $ui-inactive-text-color
@@ -127,4 +137,23 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
background-color $ui-solarized-dark-button-backgroundColor
body[data-theme="monokai"]
.folderList-item
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
&:active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
&:active
background-color $ui-monokai-button-backgroundColor
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor

View File

@@ -10,8 +10,8 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {Array} storgaeList
*/
const StorageList = ({storageList}) => (
<div styleName='storageList'>
const StorageList = ({storageList, isFolded}) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
{storageList.length > 0 ? storageList : (
<div styleName='storgaeList-empty'>No storage mount.</div>
)}

View File

@@ -4,6 +4,10 @@
top 180px
overflow-y auto
.storageList-folded
@extend .storageList
width 44px
.storageList-empty
padding 0 10px
margin-top 15px

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
import mermaidAPI from 'mermaid'
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
function getId () {
var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var id = 'm-'
for (var i = 0; i < 7; i++) {
id += pool[getRandomInt(0, 16)]
}
return id
}
function render (element, content) {
try {
mermaidAPI.render(getId(), content, (svgGraph) => {
element.innerHTML = svgGraph
})
} catch (e) {
console.error(e)
element.className = 'mermaid-error'
element.innerHTML = 'mermaid diagram parse error: ' + e.message
}
}
export default render

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

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

View File

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

View File

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

View File

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

View File

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

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

@@ -0,0 +1,17 @@
const path = require('path')
const { remote } = require('electron')
const { app } = remote
const { getLocales } = require('./Languages.js')
// load package for localization
const i18n = new (require('i18n-2'))({
// setup some locales - other locales default to the first locale
locales: getLocales(),
extension: '.json',
directory: process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './locales')
: path.resolve('./locales'),
devMode: false
})
export default i18n

View File

@@ -10,6 +10,9 @@ module.exports = function sanitizePlugin (md, options) {
if (state.tokens[tokenIdx].type === 'html_block') {
state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
}
if (state.tokens[tokenIdx].type === 'fence') {
state.tokens[tokenIdx].content = state.tokens[tokenIdx].content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}
if (state.tokens[tokenIdx].type === 'inline') {
const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {

View File

@@ -2,13 +2,11 @@ import markdownit from 'markdown-it'
import sanitize from './markdown-it-sanitize-html'
import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math'
import smartArrows from 'markdown-it-smartarrows'
import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
import {lastFindInArray} from './utils'
// FIXME We should not depend on global variable.
const katex = window.katex
const config = ConfigManager.get()
import katex from 'katex'
import { lastFindInArray } from './utils'
function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
@@ -22,12 +20,13 @@ function createGutter (str, firstLineNumber) {
class Markdown {
constructor (options = {}) {
const config = ConfigManager.get()
const defaultOptions = {
typographer: true,
typographer: config.preview.smartQuotes,
linkify: true,
html: true,
xhtmlOut: true,
breaks: true,
breaks: config.preview.breaks,
highlight: function (str, lang) {
const delimiter = ':'
const langInfo = lang.split(delimiter)
@@ -41,28 +40,74 @@ class Markdown {
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
if (langType === 'chart') {
return `<pre class="chart">${str}</pre>`
}
if (langType === 'mermaid') {
return `<pre class="mermaid">${str}</pre>`
}
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
}
},
sanitize: 'STRICT'
}
const updatedOptions = Object.assign(defaultOptions, options)
this.md = markdownit(updatedOptions)
// Sanitize use rinput before other plugins
this.md.use(sanitize, {
allowedTags: ['img', 'iframe'],
allowedAttributes: {
'*': ['alt', 'style'],
'img': ['src', 'width', 'height'],
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen']
},
allowedIframeHostnames: ['www.youtube.com']
})
if (updatedOptions.sanitize !== 'NONE') {
const allowedTags = ['iframe', 'input', 'b',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
]
const allowedAttributes = [
'abbr', 'accept', 'accept-charset',
'accesskey', 'action', 'align', 'alt', 'axis',
'border', 'cellpadding', 'cellspacing', 'char',
'charoff', 'charset', 'checked',
'clear', 'cols', 'colspan', 'color',
'compact', 'coords', 'datetime', 'dir',
'disabled', 'enctype', 'for', 'frame',
'headers', 'height', 'hreflang',
'hspace', 'ismap', 'label', 'lang',
'maxlength', 'media', 'method',
'multiple', 'name', 'nohref', 'noshade',
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
'rows', 'rowspan', 'rules', 'scope',
'selected', 'shape', 'size', 'span',
'start', 'summary', 'tabindex', 'target',
'title', 'type', 'usemap', 'valign', 'value',
'vspace', 'width', 'itemprop'
]
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
allowedTags.push('style')
allowedAttributes.push('style')
}
// Sanitize use rinput before other plugins
this.md.use(sanitize, {
allowedTags,
allowedAttributes: {
'*': allowedAttributes,
'a': ['href'],
'div': ['itemscope', 'itemtype'],
'blockquote': ['cite'],
'del': ['cite'],
'ins': ['cite'],
'q': ['cite'],
'img': ['src', 'width', 'height'],
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
'input': ['type', 'id', 'checked']
},
allowedIframeHostnames: ['www.youtube.com']
})
}
this.md.use(emoji, {
shortcuts: {}
@@ -103,15 +148,18 @@ class Markdown {
}
})
this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'))
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
return `${serverAddress}/${zippedCode}`
}
})
@@ -173,6 +221,10 @@ class Markdown {
return true
})
if (config.preview.smartArrows) {
this.md.use(smartArrows)
}
// Add line number attribute for scrolling
const originalRender = this.md.renderer.render
this.md.renderer.render = (tokens, options, env) => {
@@ -196,11 +248,6 @@ class Markdown {
if (!_.isString(content)) content = ''
return this.md.render(content)
}
normalizeLinkText (linkText) {
return this.md.normalizeLinkText(linkText)
}
}
export default Markdown

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

View File

@@ -0,0 +1,9 @@
import consts from 'browser/lib/consts'
import isString from 'lodash/isString'
export default function normalizeEditorFontFamily (fontFamily) {
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
return isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
: defaultEditorFontFamily.join(', ')
}

View File

@@ -4,39 +4,28 @@ export default function searchFromNotes (notes, search) {
if (search.trim().length === 0) return []
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
let foundNotes = findByWord(notes, searchBlocks[0])
let foundNotes = notes
searchBlocks.forEach((block) => {
foundNotes = findByWord(foundNotes, block)
if (block.match(/^#.+/)) {
foundNotes = foundNotes.concat(findByTag(notes, block))
}
foundNotes = findByWordOrTag(foundNotes, block)
})
return foundNotes
}
function findByTag (notes, block) {
const tag = block.match(/#(.+)/)[1]
const regExp = new RegExp(_.escapeRegExp(tag), 'i')
function findByWordOrTag (notes, block) {
let tag = block
if (tag.match(/^#.+/)) {
tag = tag.match(/#(.+)/)[1]
}
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => {
return _tag.match(regExp)
})
})
}
function findByWord (notes, block) {
const regExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => {
return _tag.match(regExp)
})) {
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
return true
}
if (note.type === 'SNIPPET_NOTE') {
return note.description.match(regExp)
return note.description.match(wordRegExp)
} else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(regExp)
return note.content.match(wordRegExp)
}
return false
})

View File

@@ -6,6 +6,85 @@ export function lastFindInArray (array, callback) {
}
}
export default {
lastFindInArray
export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) {
const matchHtmlRegExp = /["'&<>]/g
const escapes = ['&quot;', '&amp;', '&#39;', '&lt;', '&gt;']
let match = null
const replaceAt = (str, index, replace) =>
str.substr(0, index) +
replace +
str.substr(index + replace.length - (replace.length - 1))
// detecting code block
while ((match = matchHtmlRegExp.exec(html)) != null) {
const current = { char: match[0], index: match.index }
if (opt.detectCodeBlock) {
// position of the nearest line start
let previousLineEnd = current.index - 1
while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
previousLineEnd--
}
// 4 spaces means this character is in a code block
if (
html[previousLineEnd + 1] === ' ' &&
html[previousLineEnd + 2] === ' ' &&
html[previousLineEnd + 3] === ' ' &&
html[previousLineEnd + 4] === ' '
) {
// so skip it
continue
}
}
// otherwise, escape it !!!
if (current.char === '&') {
let nextStr = ''
let nextIndex = current.index
let escapedStr = false
// maximum length of an escape string is 5. For example ('&quot;')
while (nextStr.length <= 5) {
nextStr += html[nextIndex]
nextIndex++
if (escapes.indexOf(nextStr) !== -1) {
escapedStr = true
break
}
}
if (!escapedStr) {
// this & char is not a part of an escaped string
html = replaceAt(html, current.index, '&amp;')
}
} else if (current.char === '"') {
html = replaceAt(html, current.index, '&quot;')
} else if (current.char === "'") {
html = replaceAt(html, current.index, '&#39;')
} else if (current.char === '<') {
html = replaceAt(html, current.index, '&lt;')
} else if (current.char === '>') {
html = replaceAt(html, current.index, '&gt;')
}
}
return html
}
export function isObjectEqual (a, b) {
const aProps = Object.getOwnPropertyNames(a)
const bProps = Object.getOwnPropertyNames(b)
if (aProps.length !== bProps.length) {
return false
}
for (var i = 0; i < aProps.length; i++) {
const propName = aProps[i]
if (a[propName] !== b[propName]) {
return false
}
}
return true
}
export default {
lastFindInArray,
escapeHtmlCharacters,
isObjectEqual
}

View File

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

View File

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

View File

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

View File

@@ -2,13 +2,16 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
const FullscreenButton = ({
onClick
}) => (
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
</button>
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RestoreButton.styl'
import i18n from 'browser/lib/i18n'
const RestoreButton = ({
onClick
@@ -10,7 +11,7 @@ const RestoreButton = ({
onClick={onClick}
>
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
<span styleName='tooltip'>Restore</span>
<span styleName='tooltip'>{i18n.__('Restore')}</span>
</button>
)

View File

@@ -8,7 +8,7 @@ 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 {hashHistory} from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
@@ -17,7 +17,8 @@ import StatusBar from '../StatusBar'
import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
@@ -26,25 +27,12 @@ import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
const electron = require('electron')
const { remote } = electron
const { Menu, MenuItem, dialog } = remote
const { dialog } = remote
class SnippetNoteDetail extends React.Component {
constructor (props) {
@@ -53,16 +41,34 @@ class SnippetNoteDetail extends React.Component {
this.state = {
isMovingNote: false,
snippetIndex: 0,
showArrows: false,
enableLeftArrow: false,
enableRightArrow: false,
note: Object.assign({
description: ''
}, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
})
}
this.scrollToNextTabThreshold = 0.7
}
componentDidMount () {
const visibleTabs = this.visibleTabs
const allTabs = this.allTabs
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
this.setState({
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
const nextNote = Object.assign({
description: ''
@@ -78,6 +84,7 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + index].reload()
})
if (this.refs.tags) this.refs.tags.reset()
this.setState(this.getArrowsState())
})
}
}
@@ -177,10 +184,10 @@ class SnippetNoteDetail extends React.Component {
handleTrashButtonClick (e) {
const { note } = this.state
const { isTrashed } = note
const { confirmDeletion } = this.props
const { confirmDeletion } = this.props.config.ui
if (isTrashed) {
if (confirmDeletion(true)) {
if (confirmDeleteNote(confirmDeletion, true)) {
const {note, dispatch} = this.props
dataApi
.deleteNote(note.storage, note.key)
@@ -197,7 +204,7 @@ class SnippetNoteDetail extends React.Component {
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true
this.setState({
@@ -228,6 +235,51 @@ class SnippetNoteDetail extends React.Component {
ee.emit('editor:fullscreen')
}
handleTabMoveLeftButtonClick (e) {
{
const left = this.visibleTabs.scrollLeft
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= left
})
if (lastVisibleTab) {
const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling)
? lastVisibleTab.previousSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'start', block: 'start'})
}
}
}
handleTabMoveRightButtonClick (e) {
const left = this.visibleTabs.scrollLeft
const width = this.visibleTabs.offsetWidth
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= width + left
})
if (lastVisibleTab) {
const visiblePart = width + left - lastVisibleTab.offsetLeft
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling)
? lastVisibleTab.nextSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'end', block: 'end'})
}
}
handleTabPlusButtonClick (e) {
this.addSnippet()
}
@@ -264,9 +316,9 @@ class SnippetNoteDetail extends React.Component {
if (this.state.note.snippets[index].content.trim().length > 0) {
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a snippet',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
message: i18n.__('Delete a snippet'),
detail: i18n.__('This work cannot be undone.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (dialogIndex === 0) {
this.deleteSnippetByIndex(index)
@@ -287,6 +339,21 @@ class SnippetNoteDetail extends React.Component {
this.setState({ note, snippetIndex }, () => {
this.save()
this.refs['code-' + this.state.snippetIndex].reload()
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
console.log('no need for arrows')
this.moveTabBarBy(0)
} else {
const lastTab = this.allTabs.lastChild
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
console.log('need to scroll')
const width = this.visibleTabs.offsetWidth
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
} else {
this.setState(this.getArrowsState())
}
}
})
}
@@ -301,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
name: mode
})
}
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -314,11 +381,11 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].mode = name
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
@@ -332,10 +399,10 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -384,14 +451,14 @@ class SnippetNoteDetail extends React.Component {
}
handleModeButtonClick (e, index) {
const menu = new Menu()
const templetes = []
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
menu.append(new MenuItem({
templetes.push({
label: mode.name,
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
}))
})
})
menu.popup(remote.getCurrentWindow())
context.popup(templetes)
}
handleIndentTypeButtonClick (e) {
@@ -460,6 +527,51 @@ class SnippetNoteDetail extends React.Component {
this.refs.description.focus()
}
moveToTab (tab) {
const easeOutCubic = t => (--t) * t * t + 1
const startScrollPosition = this.visibleTabs.scrollLeft
const animationTiming = 300
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
let scrollBy = (tab.offsetLeft - startScrollPosition)
if (tab.offsetLeft > startScrollPosition) {
// if tab is on the right side and we want to show the whole tab in visible area,
// we need to include width of the tab and visible area in the formula
// ___________________________________________
// |____|_______|________|________|_show_this_|
// ↑_____________________↑
// visible area
scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth)
}
let startTime = null
const scrollAnimation = time => {
startTime = startTime || time
const elapsed = (time - startTime) / animationTiming
this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
if (elapsed < 1) {
window.requestAnimationFrame(scrollAnimation)
} else {
this.setState(this.getArrowsState())
}
}
window.requestAnimationFrame(scrollAnimation)
}
getArrowsState () {
const allTabs = this.allTabs
const visibleTabs = this.visibleTabs
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
const enableRightArrow = visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
const enableLeftArrow = visibleTabs.scrollLeft !== 0
return {showArrows, enableRightArrow, enableLeftArrow}
}
addSnippet () {
const { note } = this.state
@@ -470,26 +582,32 @@ class SnippetNoteDetail extends React.Component {
}])
const snippetIndex = note.snippets.length - 1
this.setState({
this.setState(Object.assign({
note,
snippetIndex
}, () => {
}, this.getArrowsState()), () => {
if (this.state.showArrows) {
const tabs = this.allTabs.querySelectorAll('div')
if (tabs) {
this.moveToTab(tabs[snippetIndex])
}
}
this.refs['tab-' + snippetIndex].startRenaming()
})
}
jumpNextTab () {
this.setState({
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
jumpPrevTab () {
this.setState({
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
@@ -507,9 +625,9 @@ class SnippetNoteDetail extends React.Component {
showWarning () {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.',
buttons: ['OK']
message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'),
buttons: [i18n.__('OK')]
})
}
@@ -545,7 +663,7 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return <div styleName='tabView'
@@ -635,10 +753,10 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred}
/>
<button styleName='control-fullScreenButton' title='Fullscreen'
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')}
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -676,16 +794,32 @@ class SnippetNoteDetail extends React.Component {
fontSize: parseInt(config.preview.fontSize, 10)
}}
ref='description'
placeholder='Description...'
placeholder={i18n.__('Description...')}
value={this.state.note.description}
onChange={(e) => this.handleChange(e)}
/>
</div>
<div styleName='tabList'>
<div styleName='list'>
{tabList}
<button styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableLeftArrow}
onClick={(e) => this.handleTabMoveLeftButtonClick(e)}
>
<i className='fa fa-chevron-left' />
</button>
<div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
<div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}>
{tabList}
</div>
</div>
<button styleName='plusButton'
<button styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableRightArrow}
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
>
<i className='fa fa-chevron-right' />
</button>
<button styleName='tabButton'
onClick={(e) => this.handleTabPlusButtonClick(e)}
>
<i className='fa fa-plus' />
@@ -699,7 +833,7 @@ class SnippetNoteDetail extends React.Component {
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
>
{this.state.note.snippets[this.state.snippetIndex].mode == null
? 'Select Syntax...'
? i18n.__('Select Syntax...')
: this.state.note.snippets[this.state.snippetIndex].mode
}&nbsp;
<i className='fa fa-caret-down' />
@@ -736,8 +870,7 @@ SnippetNoteDetail.propTypes = {
style: PropTypes.shape({
left: PropTypes.number
}),
ignorePreviewPointerEvents: PropTypes.bool,
confirmDeletion: PropTypes.bool.isRequired
ignorePreviewPointerEvents: PropTypes.bool
}
export default CSSModules(SnippetNoteDetail, styles)

View File

@@ -35,13 +35,26 @@
height 30px
display flex
background-color $ui-noteDetail-backgroundColor
overflow hidden
.tabList .list
flex 1
display flex
overflow hidden
overflow-x scroll
position relative
.tabList .plusButton
&::-webkit-scrollbar {
display: none;
}
.allTabs
display flex
position relative
overflow visible
left 0
transition left 0.1s
.tabList .tabButton
navWhiteButtonColor()
width 30px
@@ -139,4 +152,21 @@ body[data-theme="solarized-dark"]
.tabList
background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color
color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root
border-left 1px solid $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor
.body
background-color $ui-monokai-noteDetail-backgroundColor
.body .description textarea
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
border 1px solid $ui-monokai-borderColor
.tabList
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color

View File

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

View File

@@ -4,6 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl'
import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class TagSelect extends React.Component {
constructor (props) {
@@ -43,16 +44,9 @@ class TagSelect extends React.Component {
}
removeLastTag () {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
value.pop()
value = _.uniq(value)
this.value = value
this.props.onChange()
this.removeTagByCallback((value) => {
value.pop()
})
}
reset () {
@@ -95,15 +89,22 @@ class TagSelect extends React.Component {
}
handleTagRemoveButtonClick (tag) {
return (e) => {
let { value } = this.props
this.removeTagByCallback((value, tag) => {
value.splice(value.indexOf(tag), 1)
value = _.uniq(value)
}, tag)
}
this.value = value
this.props.onChange()
}
removeTagByCallback (callback, tag = null) {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
callback(value, tag)
value = _.uniq(value)
this.value = value
this.props.onChange()
}
render () {
@@ -117,7 +118,7 @@ class TagSelect extends React.Component {
>
<span styleName='tag-label'>#{tag}</span>
<button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
>
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
</button>
@@ -137,7 +138,7 @@ class TagSelect extends React.Component {
<input styleName='newTag'
ref='newTag'
value={this.state.newTag}
placeholder='Add tag...'
placeholder={i18n.__('Add tag...')}
onChange={(e) => this.handleNewTagInputChange(e)}
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
onBlur={(e) => this.handleNewTagBlur(e)}

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,9 @@ import MarkdownNoteDetail from './MarkdownNoteDetail'
import SnippetNoteDetail from './SnippetNoteDetail'
import ee from 'browser/main/lib/eventEmitter'
import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n'
import debounceRender from 'react-debounce-render'
import searchFromNotes from 'browser/lib/search'
const OSX = global.process.platform === 'darwin'
@@ -32,32 +35,39 @@ class Detail extends React.Component {
ee.off('detail:delete', this.deleteHandler)
}
confirmDeletion (permanent) {
if (this.props.config.ui.confirmDeletion || permanent) {
const electron = require('electron')
const { remote } = electron
const { dialog } = remote
const alertConfig = {
type: 'warning',
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
}
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
return dialogueButtonIndex === 0
}
return true
}
render () {
const { location, data, config } = this.props
const { location, data, params, config } = this.props
let note = null
if (location.query.key != null) {
const noteKey = location.query.key
note = data.noteMap.get(noteKey)
const allNotes = data.noteMap.map(note => note)
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
let displayedNotes = allNotes
if (location.pathname.match(/\/searched/)) {
const searchStr = params.searchword
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
: searchFromNotes(allNotes, searchStr)
}
if (location.pathname.match(/\/tags/)) {
const listOfTags = params.tagname.split(' ')
displayedNotes = data.noteMap.map(note => note).filter(note =>
listOfTags.every(tag => note.tags.includes(tag))
)
}
if (location.pathname.match(/\/trashed/)) {
displayedNotes = trashedNotes
} else {
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
}
const noteKeys = displayedNotes.map(note => note.key)
if (noteKeys.includes(noteKey)) {
note = data.noteMap.get(noteKey)
}
}
if (note == null) {
@@ -67,7 +77,7 @@ class Detail extends React.Component {
tabIndex='0'
>
<div styleName='empty'>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new note</div>
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
</div>
<StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
@@ -81,7 +91,6 @@ class Detail extends React.Component {
<SnippetNoteDetail
note={note}
config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root'
{..._.pick(this.props, [
'dispatch',
@@ -98,7 +107,6 @@ class Detail extends React.Component {
<MarkdownNoteDetail
note={note}
config={config}
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
ref='root'
{..._.pick(this.props, [
'dispatch',
@@ -120,4 +128,4 @@ Detail.propTypes = {
ignorePreviewPointerEvents: PropTypes.bool
}
export default CSSModules(Detail, styles)
export default debounceRender(CSSModules(Detail, styles))

View File

@@ -14,6 +14,9 @@ import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
import eventEmitter from 'browser/main/lib/eventEmitter'
import { hashHistory } from 'react-router'
import store from 'browser/main/store'
import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -139,16 +142,25 @@ class Main extends React.Component {
componentDidMount () {
const { dispatch, config } = this.props
if (config.ui.theme === 'dark') {
document.body.setAttribute('data-theme', 'dark')
} else if (config.ui.theme === 'white') {
document.body.setAttribute('data-theme', 'white')
} else if (config.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
const supportedThemes = [
'dark',
'white',
'solarized-dark',
'monokai'
]
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
document.body.setAttribute('data-theme', config.ui.theme)
} else {
document.body.setAttribute('data-theme', 'default')
}
if (getLocales().indexOf(config.ui.language) !== -1) {
i18n.setLocale(config.ui.language)
} else {
i18n.setLocale('en')
}
applyShortcuts()
// Reload all data
dataApi.init()
.then((data) => {

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,14 @@
/* global electron */
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import debounceRender from 'react-debounce-render'
import styles from './NoteList.styl'
import moment from 'moment'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import ConfigManager from 'browser/main/lib/ConfigManager'
import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple'
@@ -15,10 +18,13 @@ import path from 'path'
import { hashHistory } from 'react-router'
import copy from 'copy-to-clipboard'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import markdown from '../../lib/markdown'
import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import context from 'browser/lib/context'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
const { dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
function sortByCreatedAt (a, b) {
@@ -279,8 +285,8 @@ class NoteList extends React.Component {
ee.emit('detail:focus')
}
// F or S key
if (e.keyCode === 70 || e.keyCode === 83) {
// L or S key
if (e.keyCode === 76 || e.keyCode === 83) {
e.preventDefault()
ee.emit('top:focus-search')
}
@@ -324,8 +330,10 @@ class NoteList extends React.Component {
}
if (location.pathname.match(/\/searched/)) {
const searchInputText = document.getElementsByClassName('searchInput')[0].value
if (searchInputText === '') {
const searchInputText = params.searchword
const allNotes = data.noteMap.map((note) => note)
this.contextNotes = allNotes
if (searchInputText === undefined || searchInputText === '') {
return this.sortByPin(this.contextNotes)
}
return searchFromNotes(this.contextNotes, searchInputText)
@@ -338,11 +346,10 @@ class NoteList extends React.Component {
}
if (location.pathname.match(/\/tags/)) {
const listOfTags = params.tagname.split(' ')
return data.noteMap.map(note => {
return note
}).filter(note => {
return note.tags.includes(params.tagname)
})
}).filter(note => listOfTags.every(tag => note.tags.includes(tag)))
}
return this.getContextNotes()
@@ -443,20 +450,27 @@ class NoteList extends React.Component {
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.',
buttons: ['OK', 'Cancel']
message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'),
buttons: [i18n.__('OK'), i18n.__('Cancel')]
})
}
}
handleDragStart (e, note) {
const { selectedNoteKeys } = this.state
let { selectedNoteKeys } = this.state
const noteKey = getNoteKey(note)
if (!selectedNoteKeys.includes(noteKey)) {
selectedNoteKeys = []
selectedNoteKeys.push(noteKey)
}
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData)
this.setState({ selectedNoteKeys: [] })
this.selectNextNote()
}
handleNoteContextMenu (e, uniqueKey) {
@@ -469,61 +483,60 @@ class NoteList extends React.Component {
this.handleNoteClick(e, uniqueKey)
}
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = 'Delete Note'
const cloneNote = 'Clone Note'
const restoreNote = 'Restore Note'
const copyNoteLink = 'Copy Note Link'
const publishLabel = 'Publish Blog'
const updateLabel = 'Update Blog'
const openBlogLabel = 'Open Blog'
const pinLabel = note.isPinned ? i18n.__('Remove pin') : i18n.__('Pin to Top')
const deleteLabel = i18n.__('Delete Note')
const cloneNote = i18n.__('Clone Note')
const restoreNote = i18n.__('Restore Note')
const copyNoteLink = i18n.__('Copy Note Link')
const publishLabel = i18n.__('Publish Blog')
const updateLabel = i18n.__('Update Blog')
const openBlogLabel = i18n.__('Open Blog')
const menu = new Menu()
if (!location.pathname.match(/\/starred|\/trash/)) {
menu.append(new MenuItem({
label: pinLabel,
click: this.pinToTop
}))
}
const templates = []
if (location.pathname.match(/\/trash/)) {
menu.append(new MenuItem({
templates.push({
label: restoreNote,
click: this.restoreNote
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
menu.append(new MenuItem({
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.append(new MenuItem({
label: copyNoteLink,
click: this.copyNoteLink(note)
}))
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
menu.append(new MenuItem({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}))
menu.append(new MenuItem({
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
}))
} else {
menu.append(new MenuItem({
label: publishLabel,
click: this.publishMarkdown.bind(this)
}))
}, {
label: deleteLabel,
click: this.deleteNote
})
} else {
if (!location.pathname.match(/\/starred/)) {
templates.push({
label: pinLabel,
click: this.pinToTop
})
}
templates.push({
label: deleteLabel,
click: this.deleteNote
}, {
label: cloneNote,
click: this.cloneNote.bind(this)
}, {
label: copyNoteLink,
click: this.copyNoteLink(note)
})
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
templates.push({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}, {
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
})
} else {
templates.push({
label: publishLabel,
click: this.publishMarkdown.bind(this)
})
}
}
}
menu.popup()
context.popup(templates)
}
updateSelectedNotes (updateFunc, cleanSelection = true) {
@@ -578,16 +591,11 @@ class NoteList extends React.Component {
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const { confirmDeletion } = this.props.config.ui
if (firstNote.isTrashed) {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
if (!confirmDeleteNote(confirmDeletion, true)) return
Promise.all(
selectedNotes.map((note) => {
return dataApi
@@ -608,6 +616,8 @@ class NoteList extends React.Component {
})
console.log('Notes were all deleted')
} else {
if (!confirmDeleteNote(confirmDeletion, false)) return
Promise.all(
selectedNotes.map((note) => {
note.isTrashed = true
@@ -648,9 +658,13 @@ class NoteList extends React.Component {
.createNote(storage.key, {
type: firstNote.type,
folder: folder.key,
title: firstNote.title + ' copy',
title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content
})
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
return note
})
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
@@ -669,7 +683,7 @@ class NoteList extends React.Component {
}
copyNoteLink (note) {
const noteLink = `[${note.title}](${note.storage}-${note.key})`
const noteLink = `[${note.title}](:note:${note.key})`
return copy(noteLink)
}
@@ -708,7 +722,8 @@ class NoteList extends React.Component {
authToken = `Bearer ${token}`
}
const contentToRender = firstNote.content.replace(`# ${firstNote.title}`, '')
var data = {
const markdown = new Markdown()
const data = {
title: firstNote.title,
content: markdown.render(contentToRender),
status: 'publish'
@@ -754,9 +769,9 @@ class NoteList extends React.Component {
const { dialog } = remote
const alertError = {
type: 'warning',
message: 'Publish Failed',
detail: 'Check and update your blog setting and try again.',
buttons: ['Confirm']
message: i18n.__('Publish Failed'),
detail: i18n.__('Check and update your blog setting and try again.'),
buttons: [i18n.__('Confirm')]
}
dialog.showMessageBox(remote.getCurrentWindow(), alertError)
}
@@ -764,9 +779,9 @@ class NoteList extends React.Component {
confirmPublish (note) {
const buttonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Publish Succeeded',
message: i18n.__('Publish Succeeded'),
detail: `${note.title} is published at ${note.blog.blogLink}`,
buttons: ['Confirm', 'Open Blog']
buttons: [i18n.__('Confirm'), i18n.__('Open Blog')]
})
if (buttonIndex === 1) {
@@ -871,7 +886,7 @@ class NoteList extends React.Component {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: message,
buttons: ['OK']
buttons: [i18n.__('OK')]
})
}
@@ -910,7 +925,7 @@ class NoteList extends React.Component {
if (note.isTrashed !== true || location.pathname === '/trashed') return true
})
moment.locale('en', {
moment.updateLocale('en', {
relativeTime: {
future: 'in %s',
past: '%s ago',
@@ -931,15 +946,24 @@ class NoteList extends React.Component {
const viewType = this.getViewType()
const autoSelectFirst =
notes.length === 1 ||
selectedNoteKeys.length === 0 ||
notes.every(note => !selectedNoteKeys.includes(note.key))
const noteList = notes
.map(note => {
.map((note, index) => {
if (note == null) {
return null
}
const isDefault = config.listStyle === 'DEFAULT'
const uniqueKey = getNoteKey(note)
const isActive = selectedNoteKeys.includes(uniqueKey)
const isActive =
selectedNoteKeys.includes(uniqueKey) ||
notes.length === 1 ||
(autoSelectFirst && index === 0)
const dateDisplay = moment(
config.sortBy === 'CREATED_AT'
? note.createdAt : note.updatedAt
@@ -989,17 +1013,17 @@ class NoteList extends React.Component {
<div styleName='control-sortBy'>
<i className='fa fa-angle-down' />
<select styleName='control-sortBy-select'
title='Select filter mode'
title={i18n.__('Select filter mode')}
value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)}
>
<option title='Sort by update time' value='UPDATED_AT'>Updated</option>
<option title='Sort by create time' value='CREATED_AT'>Created</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>Alphabetically</option>
<option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option>
<option title='Sort by create time' value='CREATED_AT'>{i18n.__('Created')}</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
</select>
</div>
<div styleName='control-button-area'>
<button title='Default View' styleName={config.listStyle === 'DEFAULT'
<button title={i18n.__('Default View')} styleName={config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
@@ -1007,7 +1031,7 @@ class NoteList extends React.Component {
>
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
</button>
<button title='Compressed View' styleName={config.listStyle === 'SMALL'
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}
@@ -1041,4 +1065,4 @@ NoteList.propTypes = {
})
}
export default CSSModules(NoteList, styles)
export default debounceRender(CSSModules(NoteList, styles))

View File

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

View File

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

View File

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

View File

@@ -30,11 +30,33 @@
display flex
flex-direction column
.tag-title
padding-left 15px
padding-bottom 13px
p
color $ui-button-default-color
.tag-control
display flex
height 30px
line-height 25px
overflow hidden
.tag-control-title
padding-left 15px
padding-bottom 13px
flex 1
p
color $ui-button-default-color
.tag-control-sortTagsBy
user-select none
font-size 12px
color $ui-inactive-text-color
margin-left 12px
margin-right 12px
.tag-control-sortTagsBy-select
appearance: none;
margin-left 5px
color $ui-inactive-text-color
padding 0
border none
background-color transparent
outline none
cursor pointer
font-size 12px
.tagList
overflow-y auto
@@ -95,3 +117,8 @@ body[data-theme="solarized-dark"]
.root, .root--folded
background-color $ui-solarized-dark-backgroundColor
border-right 1px solid $ui-solarized-dark-borderColor
body[data-theme="monokai"]
.root, .root--folded
background-color $ui-monokai-backgroundColor
border-right 1px solid $ui-monokai-borderColor

View File

@@ -10,43 +10,47 @@ import dataApi from 'browser/main/lib/dataApi'
import StorageItemChild from 'browser/components/StorageItem'
import _ from 'lodash'
import { SortableElement } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context'
const { remote } = require('electron')
const { Menu, dialog } = remote
const { dialog } = remote
const escapeStringRegexp = require('escape-string-regexp')
const path = require('path')
class StorageItem extends React.Component {
constructor (props) {
super(props)
const { storage } = this.props
this.state = {
isOpen: true
isOpen: !!storage.isOpen
}
}
handleHeaderContextMenu (e) {
const menu = Menu.buildFromTemplate([
context.popup([
{
label: 'Add Folder',
label: i18n.__('Add Folder'),
click: (e) => this.handleAddFolderButtonClick(e)
},
{
type: 'separator'
},
{
label: 'Unlink Storage',
label: i18n.__('Unlink Storage'),
click: (e) => this.handleUnlinkStorageClick(e)
}
])
menu.popup()
}
handleUnlinkStorageClick (e) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Unlink Storage',
detail: 'This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
buttons: ['Confirm', 'Cancel']
message: i18n.__('Unlink Storage'),
detail: i18n.__('This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (index === 0) {
@@ -65,8 +69,18 @@ class StorageItem extends React.Component {
}
handleToggleButtonClick (e) {
const { storage, dispatch } = this.props
const isOpen = !this.state.isOpen
dataApi.toggleStorage(storage.key, isOpen)
.then((storage) => {
dispatch({
type: 'EXPAND_STORAGE',
storage,
isOpen
})
})
this.setState({
isOpen: !this.state.isOpen
isOpen: isOpen
})
}
@@ -91,23 +105,23 @@ class StorageItem extends React.Component {
}
handleFolderButtonContextMenu (e, folder) {
const menu = Menu.buildFromTemplate([
context.popup([
{
label: 'Rename Folder',
label: i18n.__('Rename Folder'),
click: (e) => this.handleRenameFolderClick(e, folder)
},
{
type: 'separator'
},
{
label: 'Export Folder',
label: i18n.__('Export Folder'),
submenu: [
{
label: 'Export as txt',
label: i18n.__('Export as txt'),
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
},
{
label: 'Export as md',
label: i18n.__('Export as md'),
click: (e) => this.handleExportFolderClick(e, folder, 'md')
}
]
@@ -116,12 +130,10 @@ class StorageItem extends React.Component {
type: 'separator'
},
{
label: 'Delete Folder',
label: i18n.__('Delete Folder'),
click: (e) => this.handleFolderDeleteClick(e, folder)
}
])
menu.popup()
}
handleRenameFolderClick (e, folder) {
@@ -135,8 +147,8 @@ class StorageItem extends React.Component {
handleExportFolderClick (e, folder, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: 'Select directory',
title: 'Select a folder to export the files to',
buttonLabel: i18n.__('Select directory'),
title: i18n.__('Select a folder to export the files to'),
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
@@ -160,9 +172,9 @@ class StorageItem extends React.Component {
handleFolderDeleteClick (e, folder) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete Folder',
detail: 'This will delete all notes in the folder and can not be undone.',
buttons: ['Confirm', 'Cancel']
message: i18n.__('Delete Folder'),
detail: i18n.__('This will delete all notes in the folder and can not be undone.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (index === 0) {
@@ -200,7 +212,7 @@ class StorageItem extends React.Component {
createdNoteData.forEach((newNote) => {
dispatch({
type: 'MOVE_NOTE',
originNote: noteData.find((note) => note.content === newNote.content),
originNote: noteData.find((note) => note.content === newNote.oldContent),
note: newNote
})
})
@@ -222,7 +234,8 @@ class StorageItem extends React.Component {
const { folderNoteMap, trashedSet } = data
const SortableStorageItemChild = SortableElement(StorageItemChild)
const folderList = storage.folders.map((folder, index) => {
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
let folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
const isActive = !!(location.pathname.match(folderRegex))
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = 0
@@ -252,7 +265,7 @@ class StorageItem extends React.Component {
)
})
const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
return (
<div styleName={isFolded ? 'root--folded' : 'root'}

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
const { remote } = require('electron')
const { Menu } = remote
import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl'
import { openModal } from 'browser/main/lib/modal'
@@ -18,6 +16,8 @@ import PreferenceButton from './PreferenceButton'
import ListButton from './ListButton'
import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context'
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
@@ -81,7 +81,7 @@ class SideNav extends React.Component {
}
SideNavComponent (isFolded, storageList) {
const { location, data } = this.props
const { location, data, config } = this.props
const isHomeActive = !!location.pathname.match(/^\/home$/)
const isStarredActive = !!location.pathname.match(/^\/starred$/)
@@ -107,15 +107,30 @@ class SideNav extends React.Component {
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
/>
<StorageList storageList={storageList} />
<StorageList storageList={storageList} isFolded={isFolded} />
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
</div>
)
} else {
component = (
<div styleName='tabBody'>
<div styleName='tag-title'>
<p>Tags</p>
<div styleName='tag-control'>
<div styleName='tag-control-title'>
<p>{i18n.__('Tags')}</p>
</div>
<div styleName='tag-control-sortTagsBy'>
<i className='fa fa-angle-down' />
<select styleName='tag-control-sortTagsBy-select'
title={i18n.__('Select filter mode')}
value={config.sortTagsBy}
onChange={(e) => this.handleSortTagsByChange(e)}
>
<option title='Sort alphabetically'
value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
<option title='Sort by update time'
value='COUNTER'>{i18n.__('Counter')}</option>
</select>
</div>
</div>
<div styleName='tagList'>
{this.tagListComponent(data)}
@@ -128,17 +143,30 @@ class SideNav extends React.Component {
}
tagListComponent () {
const { data, location } = this.props
const tagList = _.sortBy(data.tagNoteMap.map((tag, name) => {
return { name, size: tag.size }
}), ['name'])
const { data, location, config } = this.props
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
let tagList = _.sortBy(data.tagNoteMap.map(
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
), ['name']).filter(
tag => tag.size > 0
)
if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size))
}
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
tagList = tagList.filter(
tag => tag.related
)
}
return (
tagList.map(tag => {
return (
<TagListItem
name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={this.getTagActive(location.pathname, tag)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)}
isRelated={tag.related}
key={tag.name}
count={tag.size}
/>
@@ -147,10 +175,30 @@ class SideNav extends React.Component {
)
}
getRelatedTags (activeTags, noteMap) {
if (activeTags.length === 0) {
return new Set()
}
const relatedNotes = noteMap.map(
note => ({key: note.key, tags: note.tags})
).filter(
note => activeTags.every(tag => note.tags.includes(tag))
)
const relatedTags = new Set()
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
return relatedTags
}
getTagActive (path, tag) {
return this.getActiveTags(path).includes(tag)
}
getActiveTags (path) {
const pathSegments = path.split('/')
const pathTag = pathSegments[pathSegments.length - 1]
return pathTag === tag
const tags = pathSegments[pathSegments.length - 1]
return (tags === 'alltags')
? []
: tags.split(' ')
}
handleClickTagListItem (name) {
@@ -158,6 +206,33 @@ class SideNav extends React.Component {
router.push(`/tags/${name}`)
}
handleSortTagsByChange (e) {
const { dispatch } = this.props
const config = {
sortTagsBy: e.target.value
}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
}
handleClickNarrowToTag (tag) {
const { router } = this.context
const { location } = this.props
const listOfTags = this.getActiveTags(location.pathname)
const indexOfTag = listOfTags.indexOf(tag)
if (indexOfTag > -1) {
listOfTags.splice(indexOfTag, 1)
} else {
listOfTags.push(tag)
}
router.push(`/tags/${listOfTags.join(' ')}`)
}
emptyTrash (entries) {
const { dispatch } = this.props
const deletionPromises = entries.map((note) => {
@@ -178,10 +253,9 @@ class SideNav extends React.Component {
handleFilterButtonContextMenu (event) {
const { data } = this.props
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
const menu = Menu.buildFromTemplate([
{ label: 'Empty Trash', click: () => this.emptyTrash(trashedNotes) }
context.popup([
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
])
menu.popup()
}
render () {

View File

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

View File

@@ -3,10 +3,12 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StatusBar.styl'
import ZoomManager from 'browser/main/lib/ZoomManager'
import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context'
const electron = require('electron')
const { remote, ipcRenderer } = electron
const { Menu, MenuItem, dialog } = remote
const { dialog } = remote
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
@@ -14,9 +16,9 @@ class StatusBar extends React.Component {
updateApp () {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.',
buttons: ['Restart & Install', 'Not Now']
message: i18n.__('Update Boostnote'),
detail: i18n.__('New Boostnote is ready to be installed.'),
buttons: [i18n.__('Restart & Install'), i18n.__('Not Now')]
})
if (index === 0) {
@@ -25,16 +27,16 @@ class StatusBar extends React.Component {
}
handleZoomButtonClick (e) {
const menu = new Menu()
const templates = []
zoomOptions.forEach((zoom) => {
menu.append(new MenuItem({
templates.push({
label: Math.floor(zoom * 100) + '%',
click: () => this.handleZoomMenuItemClick(zoom)
}))
})
})
menu.popup(remote.getCurrentWindow())
context.popup(templates)
}
handleZoomMenuItemClick (zoomFactor) {
@@ -62,7 +64,7 @@ class StatusBar extends React.Component {
{status.updateReady
? <button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
<i styleName='update-icon' className='fa fa-cloud-download' /> {i18n.__('Ready to Update!')}
</button>
: null
}

View File

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

View File

@@ -5,6 +5,7 @@ import styles from './TopBar.styl'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n'
class TopBar extends React.Component {
constructor (props) {
@@ -27,6 +28,14 @@ class TopBar extends React.Component {
}
componentDidMount () {
const { params } = this.props
const searchWord = params.searchword
if (searchWord !== undefined) {
this.setState({
search: searchWord,
isSearching: true
})
}
ee.on('top:focus-search', this.focusSearchHandler)
ee.on('code:init', this.codeInitHandler)
}
@@ -96,9 +105,10 @@ class TopBar extends React.Component {
this.setState({
isConfirmTranslation: true
})
router.push('/searched')
const keyword = this.refs.searchInput.value
router.push(`/searched/${encodeURIComponent(keyword)}`)
this.setState({
search: this.refs.searchInput.value
search: keyword
})
}
}
@@ -107,7 +117,7 @@ class TopBar extends React.Component {
const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched')
router.push(`/searched/${encodeURIComponent(keyword)}`)
} else {
e.preventDefault()
}
@@ -146,8 +156,7 @@ class TopBar extends React.Component {
if (this.state.isSearching) {
el.blur()
} else {
el.focus()
el.setSelectionRange(0, el.value.length)
el.select()
}
}
@@ -176,7 +185,7 @@ class TopBar extends React.Component {
onChange={(e) => this.handleSearchChange(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
placeholder='Search'
placeholder={i18n.__('Search')}
type='text'
className='searchInput'
/>
@@ -185,7 +194,7 @@ class TopBar extends React.Component {
onClick={(e) => this.handleSearchClearButton(e)}
>
<i className='fa fa-fw fa-times' />
<span styleName='control-search-input-clear-tooltip'>Clear Search</span>
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>
</button>
}
</div>

View File

@@ -15,6 +15,12 @@ body
font-weight 200
-webkit-font-smoothing antialiased
::-webkit-scrollbar
width 12px
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.15)
button, input, select, textarea
font-family DEFAULT_FONTS
@@ -85,9 +91,11 @@ modalBackColor = white
absolute top left bottom right
background-color modalBackColor
z-index modalZIndex + 1
body[data-theme="dark"]
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-dark-backgroundColor
@@ -108,15 +116,39 @@ body[data-theme="dark"]
background #B1D7FE
::selection
background #B1D7FE
.CodeMirror-foldmarker
font-family: arial
.CodeMirror-foldgutter
width: .7em
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded
cursor: pointer
.CodeMirror-foldgutter-open:after
content: "\25BE"
.CodeMirror-foldgutter-folded:after
content: "\25B8"
.sortableItemHelper
z-index modalZIndex + 5
body[data-theme="solarized-dark"]
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-solarized-dark-backgroundColor
.sortableItemHelper
color: $ui-solarized-dark-text-color
body[data-theme="monokai"]
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-monokai-backgroundColor
.sortableItemHelper
color: $ui-monokai-text-color

View File

@@ -8,6 +8,7 @@ import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-rou
import { syncHistoryWithStore } from 'react-router-redux'
require('./lib/ipcClient')
require('../lib/customMeta')
import i18n from 'browser/lib/i18n'
const electron = require('electron')
@@ -23,6 +24,45 @@ document.addEventListener('dragover', function (e) {
e.stopPropagation()
})
// prevent menu from popup when alt pressed
// but still able to toggle menu when only alt is pressed
let isAltPressing = false
let isAltWithMouse = false
let isAltWithOtherKey = false
let isOtherKey = false
document.addEventListener('keydown', function (e) {
if (e.key === 'Alt') {
isAltPressing = true
if (isOtherKey) {
isAltWithOtherKey = true
}
} else {
if (isAltPressing) {
isAltWithOtherKey = true
}
isOtherKey = true
}
})
document.addEventListener('mousedown', function (e) {
if (isAltPressing) {
isAltWithMouse = true
}
})
document.addEventListener('keyup', function (e) {
if (e.key === 'Alt') {
if (isAltWithMouse || isAltWithOtherKey) {
e.preventDefault()
}
isAltWithMouse = false
isAltWithOtherKey = false
isAltPressing = false
isOtherKey = false
}
})
document.addEventListener('click', function (e) {
const className = e.target.className
if (!className && typeof (className) !== 'string') return
@@ -46,9 +86,9 @@ function notify (...args) {
function updateApp () {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.',
buttons: ['Restart & Install', 'Not Now']
message: i18n.__('Update Boostnote'),
detail: i18n.__('New Boostnote is ready to be installed.'),
buttons: [i18n.__('Restart & Install'), i18n.__('Not Now')]
})
if (index === 0) {
@@ -63,7 +103,9 @@ ReactDOM.render((
<IndexRedirect to='/home' />
<Route path='home' />
<Route path='starred' />
<Route path='searched' />
<Route path='searched'>
<Route path=':searchword' />
</Route>
<Route path='trashed' />
<Route path='alltags' />
<Route path='tags'>

View File

@@ -1,5 +1,7 @@
import _ from 'lodash'
import RcParser from 'browser/lib/RcParser'
import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
@@ -15,12 +17,15 @@ export const DEFAULT_CONFIG = {
listWidth: 280,
navWidth: 200,
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
sortTagsBy: 'ALPHABETICAL', // 'ALPHABETICAL', 'COUNTER'
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true,
hotkey: {
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Command + M' : 'Ctrl + M'
},
ui: {
language: 'en',
theme: 'default',
showCopyNotification: true,
disableDirectWrite: false,
@@ -33,6 +38,8 @@ export const DEFAULT_CONFIG = {
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
indentType: 'space',
indentSize: '2',
enableRulers: false,
rulers: [80, 120],
displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false,
@@ -48,8 +55,14 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$',
latexBlockOpen: '$$',
latexBlockClose: '$$',
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
scrollPastEnd: false,
smartQuotes: true
smartQuotes: true,
breaks: true,
smartArrows: false,
allowCustomCSS: false,
customCSS: '',
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
},
blog: {
type: 'wordpress', // Available value: wordpress, add more types in the future plz
@@ -129,10 +142,14 @@ function set (updates) {
document.body.setAttribute('data-theme', 'white')
} else if (newConfig.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
} else if (newConfig.ui.theme === 'monokai') {
document.body.setAttribute('data-theme', 'monokai')
} else {
document.body.setAttribute('data-theme', 'default')
}
i18n.setLocale(newConfig.ui.language)
let editorTheme = document.getElementById('editorTheme')
if (editorTheme == null) {
editorTheme = document.createElement('link')
@@ -155,6 +172,7 @@ function set (updates) {
ipcRenderer.send('config-renew', {
config: get()
})
ee.emit('config-renew')
}
function assignConfigValues (originalConfig, rcConfig) {
@@ -164,6 +182,17 @@ function assignConfigValues (originalConfig, rcConfig) {
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
rewriteHotkey(config)
return config
}
function rewriteHotkey (config) {
const keys = [...Object.keys(config.hotkey)]
keys.forEach(key => {
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
})
return config
}

View File

@@ -37,7 +37,8 @@ function addStorage (input) {
key,
name: input.name,
type: input.type,
path: input.path
path: input.path,
isOpen: false
}
return Promise.resolve(newStorage)
@@ -48,7 +49,8 @@ function addStorage (input) {
key: newStorage.key,
type: newStorage.type,
name: newStorage.name,
path: newStorage.path
path: newStorage.path,
isOpen: false
})
localStorage.setItem('storages', JSON.stringify(rawStorages))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
const dataApi = {
init: require('./init'),
toggleStorage: require('./toggleStorage'),
addStorage: require('./addStorage'),
renameStorage: require('./renameStorage'),
removeStorage: require('./removeStorage'),
@@ -13,6 +14,10 @@ const dataApi = {
deleteNote: require('./deleteNote'),
moveNote: require('./moveNote'),
migrateFromV5Storage: require('./migrateFromV5Storage'),
createSnippet: require('./createSnippet'),
deleteSnippet: require('./deleteSnippet'),
updateSnippet: require('./updateSnippet'),
fetchSnippet: require('./fetchSnippet'),
_migrateFromV6Storage: require('./migrateFromV6Storage'),
_resolveStorageData: require('./resolveStorageData'),

View File

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

View File

@@ -8,7 +8,8 @@ function resolveStorageData (storageCache) {
key: storageCache.key,
name: storageCache.name,
type: storageCache.type,
path: storageCache.path
path: storageCache.path,
isOpen: storageCache.isOpen
}
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')

View File

@@ -27,9 +27,12 @@ function resolveStorageNotes (storage) {
data.storage = storage.key
return data
} catch (err) {
console.error(notePath)
console.error(`error on note path: ${notePath}, error: ${err}`)
}
})
.filter(function filterOnlyNoteObject (noteObj) {
return typeof noteObj === 'object'
})
return Promise.resolve(notes)
}

View File

@@ -0,0 +1,28 @@
const _ = require('lodash')
const resolveStorageData = require('./resolveStorageData')
/**
* @param {String} key
* @param {Boolean} isOpen
* @return {Object} Storage meta data
*/
function toggleStorage (key, isOpen) {
let cachedStorageList
try {
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('invalid storages')
} catch (err) {
console.log('error got')
console.error(err)
return Promise.reject(err)
}
const targetStorage = _.find(cachedStorageList, {key: key})
if (targetStorage == null) return Promise.reject('Storage')
targetStorage.isOpen = isOpen
localStorage.setItem('storages', JSON.stringify(cachedStorageList))
return resolveStorageData(targetStorage)
}
module.exports = toggleStorage

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