1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-02 20:46:21 +02:00

Compare commits

...

51 Commits

Author SHA1 Message Date
Laurent Cozic
2bb5acdfb1 Android release v1.2.6 2020-10-11 15:06:41 +01:00
Laurent Cozic
dda0d8ca08 Mobile: Fixes #3815: Fixed btoa error 2020-10-11 14:56:30 +01:00
Laurent Cozic
f007735936 ios-v10.2.0 2020-10-09 17:26:38 +01:00
Laurent Cozic
f5f117cb72 Electron release v1.2.6 2020-10-09 12:17:54 +01:00
Laurent Cozic
fc6da04081 CLI v1.2.3 2020-10-09 12:17:40 +01:00
Laurent Cozic
12ff654986 Merge branch 'release-1.2' of github.com:laurent22/joplin into release-1.2 2020-10-09 12:10:16 +01:00
Laurent Cozic
e852ad846f Electron release v1.2.5 2020-10-09 12:10:06 +01:00
Laurent Cozic
28e00fdf2e Android release v1.2.5 2020-10-08 12:56:12 +01:00
Laurent Cozic
3bd0656eab Android release v1.2.4 2020-10-08 12:51:48 +01:00
Laurent Cozic
e9af71dd76 Android: Reverted app to singleTop launch mode and fixed potential crash when sharing with app 2020-10-08 11:49:39 +01:00
Laurent Cozic
73b33e8e32 Android: Fixes #3800: Simplify initialisation code to prevent sharing
with app to create multiple instance of app and break settings.

Revert "Mobile: Add startup screen to show progress of db migration"

This reverts commit 569355a318.
2020-10-08 11:35:29 +01:00
Laurent Cozic
c2c7efee91 Desktop: Also make toggle button area wider 2020-10-07 21:03:56 +01:00
Laurent Cozic
0836fca822 Merge branch 'release-1.2' of github.com:laurent22/joplin into release-1.2 2020-10-07 20:59:50 +01:00
Laurent Cozic
566df5039c Desktop: Fixes #3876: Notebooks and tags click area was too narrow 2020-10-07 20:58:43 +01:00
Laurent Cozic
559655bf33 Android release v1.2.3 2020-10-06 13:06:48 +01:00
Laurent Cozic
0eab23fbcf Android: Set app launchMode to singleInstance to try to fix lost settings issue 2020-10-06 13:02:41 +01:00
Laurent Cozic
f334f4f487 All: Improved handling of database migration failures 2020-10-06 12:47:33 +01:00
Laurent Cozic
00057da17d Electron release v1.2.4 2020-09-30 08:16:46 +01:00
Laurent Cozic
0a05464013 Desktop: Regression: Context menu on sidebar did not work anymore 2020-09-30 08:16:20 +01:00
Laurent Cozic
9ebb574059 Merge branch 'release-1.2' of github.com:laurent22/joplin into release-1.2 2020-09-29 14:27:33 +01:00
Laurent Cozic
d29c3c2466 Desktop: Regression: Sidebar toggle button did not work anymore 2020-09-29 14:26:05 +01:00
Laurent Cozic
a71f1c19ec Android release v1.2.2 2020-09-29 12:40:46 +01:00
Laurent Cozic
485921d879 CLI v1.2.2 2020-09-29 12:34:42 +01:00
Laurent Cozic
15de7572c0 Electron release v1.2.3 2020-09-29 12:32:24 +01:00
Laurent Cozic
09f41dd50e Desktop: Make global search field wider when it has focus 2020-09-29 12:31:19 +01:00
Laurent Cozic
7b8ee467a0 Desktop: Improved rendering of All Notes item in sidebar 2020-09-29 11:49:51 +01:00
Laurent Cozic
99a496d684 Desktop: Always label "Click to add tags" 2020-09-29 11:33:22 +01:00
Laurent Cozic
f43ee123d8 Tools: Fixed tests 2020-09-29 10:54:31 +01:00
Laurent Cozic
f42fb1b871 Changed tag label 2020-09-29 10:51:47 +01:00
Laurent Cozic
cf2442c5b2 Desktop: Fixes #3835: Prevent crash in rare case when opening the config screen 2020-09-29 08:40:14 +01:00
Laurent Cozic
e0e4735b03 Desktop: Fixes #3754: Refresh search results when searching by tag and when a tag is changed 2020-09-29 08:11:52 +01:00
Laurent Cozic
8bd58c9608 Merge branch 'release-1.2' of github.com:laurent22/joplin into release-1.2 2020-09-28 19:19:52 +01:00
Laurent Cozic
215a725ded Mobile: Fixes #3834: Fixed search highlights 2020-09-28 19:19:21 +01:00
Naveen M V
12c0a05af0 Desktop: Keep search fuzzy scores between 0 and 2 (#3812) 2020-09-28 18:58:19 +01:00
Caleb John
a7fa119041 Desktop: Extend functionality of codemirror vim (#3823)
add swapLine(Up/Down)
have `o` use the more complex list indent
enable sync initializing from vim (and maybe emacs)
split keymap stuff into it's own file
2020-09-28 18:57:17 +01:00
Laurent Cozic
7fb52b8b0e Desktop: Fix issue with highlighted search terms in CodeMirror viewer 2020-09-28 18:44:21 +01:00
Laurent Cozic
3e86ae4a82 Desktop: Disable fuzzy search for now due to performance issues 2020-09-28 18:41:16 +01:00
Laurent Cozic
947d81d96d Desktop: Optimised sidebar rendering speed 2020-09-24 14:30:20 +01:00
Laurent Cozic
6ca640d2ed Desktop: Fix: Fade out checked items in Rich Text editor too 2020-09-23 17:49:25 +01:00
Laurent Cozic
6aca233b21 CLI v1.2.1 2020-09-23 12:16:58 +01:00
Laurent Cozic
2200be697e Cli: Fixed crash due to missing spellfix extension 2020-09-23 12:14:17 +01:00
Laurent Cozic
25ab3c323b Desktop: Fixes #3801: Fixed editor font size 2020-09-23 11:39:36 +01:00
Laurent Cozic
5bf30a9586 Merge branch 'release-1.2' into dev 2020-09-23 10:24:55 +01:00
Laurent Cozic
b6779a8074 Desktop: Fixes #3810: Only disable relevant toolbar buttons when editor is read-only 2020-09-23 10:21:24 +01:00
Caleb John
59599d318c Desktop: Adjust the codemirror code block colors for the dark theme (#3794) 2020-09-23 09:34:39 +01:00
Arda Kılıçdağı
538600fd6c All: Translation: Update tr_TR.po (#3798) 2020-09-22 21:12:31 -04:00
Ji-Hyeon Gim
63264ba471 All: Translation: Update ko.po (#3778)
This patch includes the translation of missing strings, the improvement of the existing translation

Signed-off-by: Ji-Hyeon Gim <potatogim@potatogim.net>
2020-09-22 21:11:55 -04:00
Laurent Cozic
95e7f3df7d Electron release v1.2.2 2020-09-22 16:39:18 +01:00
Laurent Cozic
366fd2a333 Fixed desktop build 2020-09-22 16:38:47 +01:00
Laurent Cozic
5be99a4a16 Merge branch 'release-1.2' of github.com:laurent22/joplin into release-1.2 2020-09-22 16:37:23 +01:00
Laurent Cozic
d86f6a1fbd Tools: Require setting type flag for new Android releases 2020-09-22 16:36:46 +01:00
55 changed files with 965 additions and 584 deletions

View File

@@ -119,6 +119,7 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useCursorUtils.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearch.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useKeymap.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useLineSorting.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
@@ -145,6 +146,7 @@ ElectronClient/gui/NoteList/NoteList.js
ElectronClient/gui/NoteListControls/commands/focusSearch.js
ElectronClient/gui/NoteListControls/NoteListControls.js
ElectronClient/gui/NoteListItem.js
ElectronClient/gui/NoteTextViewer.js
ElectronClient/gui/NoteToolbar/NoteToolbar.js
ElectronClient/gui/OneDriveLoginScreen.js
ElectronClient/gui/ResizableLayout/hooks/useLayoutItemSizes.js
@@ -164,6 +166,7 @@ ElectronClient/gui/style/StyledInput.js
ElectronClient/gui/style/StyledTextInput.js
ElectronClient/gui/ToggleEditorsButton/styles/index.js
ElectronClient/gui/ToggleEditorsButton/ToggleEditorsButton.js
ElectronClient/gui/ToolbarBase.js
ElectronClient/gui/ToolbarButton/styles/index.js
ElectronClient/gui/ToolbarButton/ToolbarButton.js
ReactNativeClient/lib/AsyncActionQueue.js
@@ -175,6 +178,7 @@ ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/hooks/usePropsDebugger.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
@@ -182,6 +186,7 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/services/CommandService.js
ReactNativeClient/lib/services/debug/populateDatabase.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js

5
.gitignore vendored
View File

@@ -112,6 +112,7 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useCursorUtils.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearch.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useKeymap.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useLineSorting.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
@@ -138,6 +139,7 @@ ElectronClient/gui/NoteList/NoteList.js
ElectronClient/gui/NoteListControls/commands/focusSearch.js
ElectronClient/gui/NoteListControls/NoteListControls.js
ElectronClient/gui/NoteListItem.js
ElectronClient/gui/NoteTextViewer.js
ElectronClient/gui/NoteToolbar/NoteToolbar.js
ElectronClient/gui/OneDriveLoginScreen.js
ElectronClient/gui/ResizableLayout/hooks/useLayoutItemSizes.js
@@ -157,6 +159,7 @@ ElectronClient/gui/style/StyledInput.js
ElectronClient/gui/style/StyledTextInput.js
ElectronClient/gui/ToggleEditorsButton/styles/index.js
ElectronClient/gui/ToggleEditorsButton/ToggleEditorsButton.js
ElectronClient/gui/ToolbarBase.js
ElectronClient/gui/ToolbarButton/styles/index.js
ElectronClient/gui/ToolbarButton/ToolbarButton.js
ReactNativeClient/lib/AsyncActionQueue.js
@@ -168,6 +171,7 @@ ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/hooks/usePropsDebugger.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
@@ -175,6 +179,7 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/services/CommandService.js
ReactNativeClient/lib/services/debug/populateDatabase.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Last-Translator: Ji-Hyeon Gim <potatogim@potatogim.net>\n"
"Language-Team: \n"
"Language: ko\n"
"MIME-Version: 1.0\n"
@@ -122,7 +122,7 @@ msgstr "생성됨: %d."
#: CliClient/app/command-import.js:49 ElectronClient/gui/ImportScreen.min.js:71
#, javascript-format
msgid "Updated: %d."
msgstr "업데이트됨: %d."
msgstr "갱신됨: %d."
#: CliClient/app/command-import.js:50 ElectronClient/gui/ImportScreen.min.js:72
#, javascript-format
@@ -312,7 +312,7 @@ msgstr "지정된 대상으로 동기화 합니다 (기본값은 sync.target con
#: CliClient/app/command-sync.js:35
msgid "Upgrade the sync target to the latest version."
msgstr ""
msgstr "동기화 대상을 최신 버전으로 업그레이드합니다."
#: CliClient/app/command-sync.js:81 CliClient/app/command-sync.js:95
#: ElectronClient/gui/OneDriveLoginScreen.min.js:40
@@ -724,7 +724,7 @@ msgstr "노트와 노트북에 관한 요약 정보를 표시합니다."
msgid ""
"To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`"
msgstr ""
"항목들을 다시 복호화하라면 `e2ee decrypt --retry-failed-items`를 실행합니다."
"항목들을 다시 복호화하라면 `e2ee decrypt --retry-failed-items`를 실행합니다"
#: CliClient/app/command-tag.js:14
msgid ""
@@ -794,7 +794,7 @@ msgstr "취소"
#: ElectronClient/bridge.js:198
msgid ""
"The app is now going to close. Please relaunch it to complete the process."
msgstr ""
msgstr "애플리케이션이 곧 종료됩니다. 프로세스를 완료하려면 다시 실해주세요."
#: ElectronClient/plugins/GotoAnything.min.js:446
msgid ""
@@ -1154,13 +1154,13 @@ msgstr "%s 새로 만들기..."
#: ElectronClient/gui/NoteEditor/NoteEditor.js:344
msgid "The following attachments are being watched for changes:"
msgstr ""
msgstr "다음 첨부 파일들이 변경되었는지 감시하고 있습니다:"
#: ElectronClient/gui/NoteEditor/NoteEditor.js:347
msgid ""
"The attachments will no longer be watched when you switch to a different "
"note."
msgstr ""
msgstr "다른 노트로 전환하면 첨부 파일이 더 이상 감시되지 않습니다."
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:25
msgid "Select all"
@@ -1189,7 +1189,7 @@ msgstr "코드"
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:55
#: ElectronClient/gui/NoteText.min.js:1738
msgid "Numbered List"
msgstr "숫자가 매겨진 목록"
msgstr "번호 매기 목록"
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:60
#: ElectronClient/gui/NoteText.min.js:1746
@@ -1199,7 +1199,7 @@ msgstr "글머리 기호 목록"
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:65
#: ElectronClient/gui/NoteText.min.js:1754
msgid "Checkbox"
msgstr "체크박스"
msgstr "확인 상자"
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:70
#: ElectronClient/gui/NoteText.min.js:1762
@@ -1228,7 +1228,7 @@ msgstr "노트 본문"
#: ElectronClient/gui/EncryptionConfigScreen.min.js:163
#: ElectronClient/gui/ResourceScreen.js:38
msgid "ID"
msgstr "ID"
msgstr "식별자"
#: ElectronClient/gui/NotePropertiesDialog.min.js:27
msgid "Created"
@@ -1236,7 +1236,7 @@ msgstr "생성됨"
#: ElectronClient/gui/NotePropertiesDialog.min.js:28
msgid "Updated"
msgstr "업데이트됨"
msgstr "갱신됨"
#: ElectronClient/gui/NotePropertiesDialog.min.js:29
msgid "Completed"
@@ -1271,7 +1271,7 @@ msgstr "노트 속성"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:62
msgid "An unexpected error occured while importing the keymap!"
msgstr ""
msgstr "키맵을 가져오는 중에 예기치 못한 오류가 발생했습니다!"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:119
#: ElectronClient/gui/MainScreen/MainScreen.min.js:437
@@ -1310,7 +1310,7 @@ msgstr "실제 크기"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:22
#: ElectronClient/app.js:868
msgid "Website and documentation"
msgstr "웹사이트 및 각종 문서"
msgstr "웹사이트 및 참고 문서"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:24
msgid "Hide Joplin"
@@ -1354,6 +1354,9 @@ msgid ""
"may take a few minutes to complete and the app needs to be restarted. To "
"proceed please click on the link."
msgstr ""
"Joplin이 동기화를 하기에 앞서 동기화 대상의 업그레이드가 필요합니다. 작업이 "
"완료되기 위해서 몇 분 정도 걸릴 수 있으며, 앱을 다시 시작해야 합니다. 계속하"
"려면 링크를 눌러주세요."
#: ElectronClient/gui/MainScreen/MainScreen.min.js:306
msgid "Restart and upgrade"
@@ -2000,6 +2003,8 @@ msgid ""
"notes. Please be careful when deleting one of them as they cannot be "
"restored afterwards."
msgstr ""
"노트에 링크된 첨부 파일을 보여주는 고급 도구입니다. 여기에서 보여지는 항목을 "
"삭제할 경우, 나중에 복원할 수 없으므로 주의하시기 바랍니다."
#: ElectronClient/gui/ResourceScreen.js:140
msgid "Please wait..."
@@ -2210,6 +2215,8 @@ msgid ""
"You are about to attach a large image (%dx%d pixels). Would you like to "
"resize it down to %d pixels before attaching it?"
msgstr ""
"크기가 큰 이미지(%dx%d 픽셀)를 첨부하려고 합니다. 첨부하기 전에 크기를 %d 픽"
"셀로 줄이시겠습니까?"
#: ReactNativeClient/lib/shim-init-node.js:157
#, javascript-format
@@ -2224,7 +2231,7 @@ msgstr "제목"
#: ReactNativeClient/lib/models/Folder.js:28
#: ReactNativeClient/lib/models/Note.js:26
msgid "updated date"
msgstr "업데이트된 날짜"
msgstr "수정 날짜"
#: ReactNativeClient/lib/models/Folder.js:88
msgid "Conflicts"
@@ -2578,16 +2585,16 @@ msgstr "Joplin 앱에 전역적으로 적용되는 사용자 정의 스타일시
#: ReactNativeClient/lib/models/Setting.js:598
msgid "Automatically update the application"
msgstr "자동으로 업데이트 하기"
msgstr "자동으로 업데이트하기"
#: ReactNativeClient/lib/models/Setting.js:599
msgid "Get pre-releases when checking for updates"
msgstr "업데이트 시 프리-릴리즈 받기"
msgstr "사전-출시 버전일 경우에도 업데이트"
#: ReactNativeClient/lib/models/Setting.js:599
#, javascript-format
msgid "See the pre-release page for more details: %s"
msgstr "자세한 내용은 프리-릴리즈 페이지를 참조해주세요: %s"
msgstr "자세한 내용은 사전-출시 페이지를 참조해주세요: %s"
#: ReactNativeClient/lib/models/Setting.js:607
msgid "Synchronisation interval"
@@ -2653,7 +2660,7 @@ msgstr "Legal"
#: ReactNativeClient/lib/models/Setting.js:636
msgid "Page orientation for PDF export"
msgstr "PDF를 내보낼때의 페이지 방향"
msgstr "PDF를 내보낼 때의 페이지 방향"
#: ReactNativeClient/lib/models/Setting.js:638
msgid "Portrait"
@@ -2701,14 +2708,14 @@ msgstr "TLS 인증서 오류 무시"
#: ReactNativeClient/lib/models/Setting.js:692
msgid "Fail-safe"
msgstr "안전 작동"
msgstr "안전 장치"
#: ReactNativeClient/lib/models/Setting.js:693
msgid ""
"Fail-safe: Do not wipe out local data when sync target is empty (often the "
"result of a misconfiguration or bug)"
msgstr ""
"안전 장치: 동기화 목표가 비어 있을 경우, 로컬 데이터를 지우마세요 (종종 "
"안전 장치: 동기화할 대상이 비어 있다면, 로컬 데이터를 지우지 않습니다. (종종 "
"잘못된 설정이나 버그로 인해 발생됩니다)"
#: ReactNativeClient/lib/models/Setting.js:697
@@ -2716,12 +2723,12 @@ msgid ""
"Specify the port that should be used by the API server. If not set, a "
"default will be used."
msgstr ""
"API 서버 사용할 포트를 선택해주세요. 선택되지 않으면, 기본 포트를 선택합니"
"API 서버 사용할 포트를 선택해주세요. 선택되지 않으면, 기본 포트를 선택합니"
"다."
#: ReactNativeClient/lib/models/Setting.js:705
msgid "Enable note history"
msgstr "노트 기록 활성화"
msgstr "노트 변경 이력 활성화"
#: ReactNativeClient/lib/models/Setting.js:715
msgid "days"
@@ -2734,7 +2741,7 @@ msgstr "%d 일"
#: ReactNativeClient/lib/models/Setting.js:717
msgid "Keep note history for"
msgstr "다음과 같이 노트 기록을 유지"
msgstr "다음과 같이 노트 변경 이력을 유지"
#: ReactNativeClient/lib/models/Setting.js:744
msgid "Notebook list growth factor"
@@ -2749,6 +2756,10 @@ msgid ""
"item with a factor of 2 will take twice as much space as an item with a "
"factor of 1.Restart app to see changes."
msgstr ""
"이 인수는 각 항목들이 컨테이너 상에서 가용 공간에 적합하게 증가 혹은 감소되"
"는 방식을 설정합니다. 따라서 인수가 2인 항목은 인수가 1인 항목보다 약 2배 더 "
"많은 공간을 차지하게 될 것입니다. 변경은 애플리케이션을 재시작한 후에 적용됩"
"니다."
#: ReactNativeClient/lib/models/Setting.js:757
msgid "Note list growth factor"
@@ -2809,6 +2820,12 @@ msgid ""
"formatting. It is indicated below which plugins are compatible or not with "
"the WYSIWYG editor."
msgstr ""
"이 플러그인들은 마크다운 렌더러에 부가적인 기능들을 추가합니다. 일부 기능들"
"은 표준 마크다운에서 지원하지 않기 때문에 Jpolin에서만 동작할 수도 있음을 참"
"고하세요. 추가로, 일부 기능들은 위지윅 편집기에 *호환되지 않습니다*. 편집기에"
"서 이 플러그인을 사용하는 노트를 열게 된다면, 플러그인의 편집 내용이 유실될 "
"수도 있습니다. 아래에 어떤 플러그인들이 위지윅 편집기와의 호환되는지 표시됩니"
"다."
#: ReactNativeClient/lib/models/Setting.js:1265
#, javascript-format
@@ -2880,7 +2897,7 @@ msgstr "암호화된 항목은 변경될 수 없습니다"
#: ReactNativeClient/lib/SyncTargetOneDriveDev.js:15
msgid "OneDrive Dev (For testing only)"
msgstr "OneDrive Dev (실험용)"
msgstr "OneDrive Dev (실험용)"
#: ReactNativeClient/lib/BaseApplication.js:142
#: ReactNativeClient/lib/BaseApplication.js:155
@@ -2933,7 +2950,7 @@ msgstr "노트북으로 옮기기..."
#: ReactNativeClient/lib/components/screen-header.js:433
#, javascript-format
msgid "Move %d notes to notebook \"%s\"?"
msgstr "%d 노트를 \"%s\" 노트북으로 옮길까요?"
msgstr "%d개의 노트를 \"%s\" 노트북으로 옮길까요?"
#: ReactNativeClient/lib/components/screen-header.js:451
msgid "Press to set the decryption password."
@@ -2941,11 +2958,11 @@ msgstr "복호화 암호를 설정하려면 누르세요."
#: ReactNativeClient/lib/components/screen-header.js:452
msgid "Some items cannot be synchronised. Press for more info."
msgstr "일부 항목들 동기화할 수 없습니다. 자세한 정보를 보려면 누르세요."
msgstr "일부 항목들 동기화할 수 없습니다. 자세한 정보를 보려면 누르세요."
#: ReactNativeClient/lib/components/screen-header.js:453
msgid "The sync target needs to be upgraded. Press this banner to proceed."
msgstr ""
msgstr "동기화 대상의 업그레이드가 필요합니다. 처리를 위해 이 배너를 누르세요."
#: ReactNativeClient/lib/components/side-menu-content.js:126
#, javascript-format
@@ -2991,7 +3008,7 @@ msgid ""
"the sync target is accessible. The reported error was:"
msgstr ""
"오류. URL을 확인하시거나, 혹은 사용자명이나 비밀번호를 확인해주세요, 또한 목"
"표하는 동기화 장소가 접근가능한지 확인해주세요. 보고된 오류는 다음과 같습니"
"표하는 동기화 장소가 접근 가능한지 확인해주세요. 보고된 오류는 다음과 같습니"
"다:"
#: ReactNativeClient/lib/components/shared/dropbox-login-shared.js:39
@@ -3068,7 +3085,7 @@ msgstr "새로고침"
#: ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js:42
msgid "Sync Target Upgrade"
msgstr ""
msgstr "동기화 대상 업그레이드"
#: ReactNativeClient/lib/components/screens/NoteTagsDialog.js:163
msgid "New tags:"
@@ -3117,10 +3134,10 @@ msgid ""
"password as, for security purposes, this will be the *only* way to decrypt "
"the data! To enable encryption, please enter your password below."
msgstr ""
"암호화를 사용하게 되면 모든 노트와 첨부 파일이 다시 동기화되며, 암호화 된 상"
"로 동기화 대상에게 전달됩니다. 비밀번호는 데이터를 복구할수 있는 유일한 수"
"으로, *절대로* 비밀번호를 잃어버리지 마세요. 암호화를 사용하려면 아래에 "
"비밀번호를 넣어주세요."
"암호화를 사용하게 되면 모든 노트와 첨부 파일이 다시 동기화되며, 암호화된 상"
"로 동기화 대상에게 전달됩니다. 비밀번호는 데이터를 복구할 수 있는 유일한 수"
"으로, *절대로* 비밀번호를 잃어버리지 마세요. 암호화를 사용하려면 아래에 비밀"
"번호를 넣어주세요."
#: ReactNativeClient/lib/components/screens/encryption-config.js:177
msgid "Enable"
@@ -3181,8 +3198,7 @@ msgstr "경고"
msgid ""
"In order to use file system synchronisation your permission to write to "
"external storage is required."
msgstr ""
"파일 시스템 동기화를 사용하실려면 외부 저장소 쓰기 권한을 필요로 합니다."
msgstr "파일 시스템 동기화는 외부 저장소 쓰기 권한을 필요로 합니다."
#: ReactNativeClient/lib/components/screens/config.js:436
msgid "Tools"
@@ -3213,7 +3229,7 @@ msgid ""
"Use this to rebuild the search index if there is a problem with search. It "
"may take a long time depending on the number of notes."
msgstr ""
"검색에 오류가 있는 경우, 이를 이용하여 검색 인덱스를 다시 만들수 있습니다. 이"
"검색에 오류가 있는 경우, 이를 이용하여 검색 색인을 다시 만들 수 있습니다. 이"
"는 노트의 수에 따라 시간이 오래 걸릴수 있습니다."
#: ReactNativeClient/lib/components/screens/config.js:446
@@ -3299,7 +3315,7 @@ msgstr "Joplin 모바일 앱에서는 현재 해당 형식의 링크를 지원
#: ReactNativeClient/lib/components/screens/note.js:187
#, javascript-format
msgid "Links with protocol \"%s\" are not supported"
msgstr "링크를 위한 프로토콜 \\“%s\\”는 지원하지 않습니다"
msgstr "링크를 위한 프로토콜 “%s”는 지원하지 않습니다"
#: ReactNativeClient/lib/components/screens/note.js:617
#, javascript-format
@@ -3494,7 +3510,7 @@ msgstr "프로파일 버전: %s"
#: ReactNativeClient/lib/versionInfo.js:23
#, javascript-format
msgid "Keychain Supported: %s"
msgstr ""
msgstr "키체인 지원: %s"
#: ReactNativeClient/lib/services/InteropService_Exporter_Jex.js:29
msgid "There is no data to export."
@@ -3511,32 +3527,32 @@ msgstr "노트를 가져와서 저장할 노트북을 지정해주세요."
#: ReactNativeClient/lib/services/KeymapService.js:124
#, javascript-format
msgid "Error loading the keymap from file: %s"
msgstr ""
msgstr "파일로부터 키맵을 불러오는 중에 오류가 발생했습니다: %s"
#: ReactNativeClient/lib/services/KeymapService.js:141
#, javascript-format
msgid "Error saving the keymap to file: %s"
msgstr ""
msgstr "파일에 키맵을 저장하는 중에 오류가 발생했습니다: %s"
#: ReactNativeClient/lib/services/KeymapService.js:204
#, javascript-format
msgid "Keymap item %s is missing the required \"command\" property."
msgstr ""
msgstr "키맵 항목 %s에 필수 속성인 \"명령\"이 없습니다."
#: ReactNativeClient/lib/services/KeymapService.js:207
#, javascript-format
msgid "Keymap item %s is invalid because %s is not a valid command."
msgstr ""
msgstr "키맵 항목 %s에 유효하지 않은 명령인 %s가 지정되었습니다."
#: ReactNativeClient/lib/services/KeymapService.js:210
#, javascript-format
msgid "Keymap item %s is missing the required \"accelerator\" property."
msgstr ""
msgstr "키맵 항목 %s에 필수 속성인 \"단축키\"가 없습니다."
#: ReactNativeClient/lib/services/KeymapService.js:217
#, javascript-format
msgid "Keymap item %s is invalid because %s is not a valid accelerator."
msgstr ""
msgstr "키맵 항목 %s에 유효하지 않은 단축키인 %s가 지정되었습니다."
#: ReactNativeClient/lib/services/KeymapService.js:235
#, javascript-format
@@ -3544,11 +3560,13 @@ msgid ""
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to "
"unexpected behaviour."
msgstr ""
"단축키 \"%s\"는 \"%s\"와 \"%s\" 명령으로 사용 중입니다. 이러한 설정은 예기치 "
"못한 동작을 할 수도 있습니다."
#: ReactNativeClient/lib/services/KeymapService.js:260
#, javascript-format
msgid "Accelerator \"%s\" is not valid."
msgstr ""
msgstr "단축키 \"%s\"는 유효하지 않습니다."
#: ReactNativeClient/lib/services/report.js:121
msgid "Items that cannot be synchronised"

View File

@@ -14,7 +14,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.3.1\n"
"X-Generator: Poedit 2.4.1\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: CliClient/app/command-cp.js:13
msgid ""
@@ -319,7 +321,7 @@ msgstr ""
#: CliClient/app/command-sync.js:35
msgid "Upgrade the sync target to the latest version."
msgstr ""
msgstr "Senkronizasyon hedefini en son sürüme yükseltin"
#: CliClient/app/command-sync.js:81 CliClient/app/command-sync.js:95
#: ElectronClient/gui/OneDriveLoginScreen.min.js:40
@@ -813,6 +815,8 @@ msgstr "İptal et"
msgid ""
"The app is now going to close. Please relaunch it to complete the process."
msgstr ""
"Uygulama şimdi kapanacak. İşlemi tamamlamak için lütfen uygulamayı "
"kapandıktan sonar yeniden çalıştırın."
#: ElectronClient/plugins/GotoAnything.min.js:446
msgid ""
@@ -1173,13 +1177,13 @@ msgstr "Yeni %s oluşturuluyor..."
#: ElectronClient/gui/NoteEditor/NoteEditor.js:344
msgid "The following attachments are being watched for changes:"
msgstr ""
msgstr "Şu ek dosyaları değişiklikler için izlenmekte:"
#: ElectronClient/gui/NoteEditor/NoteEditor.js:347
msgid ""
"The attachments will no longer be watched when you switch to a different "
"note."
msgstr ""
msgstr "Eğer başka bir not'a geçerseniz ek dosyalar artık izlenmeyecek."
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:25
msgid "Select all"
@@ -1290,7 +1294,7 @@ msgstr "Not özellikleri"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:62
msgid "An unexpected error occured while importing the keymap!"
msgstr ""
msgstr "Tuş dizimini içe aktarırken bir hata oluştu!"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:119
#: ElectronClient/gui/MainScreen/MainScreen.min.js:437
@@ -1305,12 +1309,11 @@ msgstr "İçe aktar"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:125
msgid "Command"
msgstr ""
msgstr "Komut"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:126
#, fuzzy
msgid "Keyboard Shortcut"
msgstr "Klavye modu"
msgstr "Klavye Kısayolu"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:14
#: ElectronClient/app.js:690
@@ -1333,9 +1336,8 @@ msgid "Website and documentation"
msgstr "Web sitesi ve dökümanlar"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:24
#, fuzzy
msgid "Hide Joplin"
msgstr "Joplin hakkında"
msgstr "Joplin'i Gizle"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:26
#: ElectronClient/app.js:703
@@ -1343,9 +1345,8 @@ msgid "Close Window"
msgstr "Pencereyi Kapat"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:28
#, fuzzy
msgid "Preferences"
msgstr "Tercihler..."
msgstr "Tercihler"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:28
#: ElectronClient/gui/Root.min.js:92 ElectronClient/app.js:572
@@ -1354,13 +1355,15 @@ msgstr "Seçenekler"
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:48
msgid "Press the shortcut"
msgstr ""
msgstr "Kısayolu girin"
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:48
msgid ""
"Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the "
"shortcut."
msgstr ""
"Kısayolu girin ve ardından ENTER tuşuna basın. Veya BACKSPACE tuşuna basarak "
"kısayolu temizleyin."
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
#: ElectronClient/gui/EncryptionConfigScreen.min.js:95
@@ -1374,11 +1377,14 @@ msgid ""
"may take a few minutes to complete and the app needs to be restarted. To "
"proceed please click on the link."
msgstr ""
"Senkronizasyon hedefi'nin Joplin senkronizasyona yeniden başlamadan once "
"güncellenmesi gerekir. Bu işlem notlarınızıın yoğunluğuna göre birkaç dakika "
"sürebilir, ve de bu işlem ardından uygulama yeniden başlatılacaktır. Bu "
"işlemi başlatmak için lütfen linke tıklayın."
#: ElectronClient/gui/MainScreen/MainScreen.min.js:306
#, fuzzy
msgid "Restart and upgrade"
msgstr "Ana anahtarların güncellenmesi lazım"
msgstr "Yeniden başlat ve güncelle"
#: ElectronClient/gui/MainScreen/MainScreen.min.js:313
msgid "Some items cannot be synchronised."
@@ -2138,9 +2144,8 @@ msgid "Templates"
msgstr "Şablonlar"
#: ElectronClient/app.js:668
#, fuzzy
msgid "Export all"
msgstr "Dışa aktar"
msgstr "Tümünü dışa aktar"
#: ElectronClient/app.js:683
#, javascript-format
@@ -2241,7 +2246,7 @@ msgstr "Bilinmeyen ID seviyesi: %s"
#: ReactNativeClient/lib/SyncTargetAmazonS3.js:28
msgid "AWS S3"
msgstr ""
msgstr "AWS S3"
#: ReactNativeClient/lib/SyncTargetDropbox.js:25
msgid "Dropbox"
@@ -2384,15 +2389,15 @@ msgstr "WebDAV şifresi"
#: ReactNativeClient/lib/models/Setting.js:195
msgid "AWS S3 bucket"
msgstr ""
msgstr "AWS S3 deposu"
#: ReactNativeClient/lib/models/Setting.js:206
msgid "AWS key"
msgstr ""
msgstr "AWS anahtarı"
#: ReactNativeClient/lib/models/Setting.js:216
msgid "AWS secret"
msgstr ""
msgstr "AWS gizli anahtarı"
#: ReactNativeClient/lib/models/Setting.js:230
msgid "Attachment download behaviour"
@@ -2850,9 +2855,8 @@ msgid "Web Clipper"
msgstr "Web Alıntılayıcısı"
#: ReactNativeClient/lib/models/Setting.js:1259
#, fuzzy
msgid "Keyboard Shortcuts"
msgstr "Klavye modu"
msgstr "Klavye Kısayolları"
#: ReactNativeClient/lib/models/Setting.js:1264
msgid ""
@@ -3008,6 +3012,8 @@ msgstr "Bazı öğeler senkronize edilemiyor. Detayları için tıklayın."
#: ReactNativeClient/lib/components/screen-header.js:453
msgid "The sync target needs to be upgraded. Press this banner to proceed."
msgstr ""
"Senkronizasyon hedefinin güncellenmesi gerekmekte. Bu banner'a tıklayarak "
"devam edebilirsiniz."
#: ReactNativeClient/lib/components/side-menu-content.js:126
#, javascript-format
@@ -3129,7 +3135,7 @@ msgstr "Yenile"
#: ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js:42
msgid "Sync Target Upgrade"
msgstr ""
msgstr "Senkronizasyon Hedefi Güncellemesi"
#: ReactNativeClient/lib/components/screens/NoteTagsDialog.js:163
msgid "New tags:"
@@ -3578,32 +3584,37 @@ msgstr "Lütfen notların alınacağı not defterini belirtin."
#: ReactNativeClient/lib/services/KeymapService.js:124
#, javascript-format
msgid "Error loading the keymap from file: %s"
msgstr ""
msgstr "%s dosyasından tuş haritası yüklenemedi"
#: ReactNativeClient/lib/services/KeymapService.js:141
#, javascript-format
msgid "Error saving the keymap to file: %s"
msgstr ""
msgstr "%s dosyasına tuş haritası kaydedilemedi"
#: ReactNativeClient/lib/services/KeymapService.js:204
#, javascript-format
msgid "Keymap item %s is missing the required \"command\" property."
msgstr ""
"%s tuş haritası, işlemler için gerekli olan \"command\" özelliğini "
"barındırmıyor."
#: ReactNativeClient/lib/services/KeymapService.js:207
#, javascript-format
msgid "Keymap item %s is invalid because %s is not a valid command."
msgstr ""
msgstr "%s tuş haritası geçersizdir, çünkü %s geçerli bir komut değildir."
#: ReactNativeClient/lib/services/KeymapService.js:210
#, javascript-format
msgid "Keymap item %s is missing the required \"accelerator\" property."
msgstr ""
"%s tuş haritası, işlemler için gerekli olan \"accelerator\" özelliğini "
"barındırmıyor."
#: ReactNativeClient/lib/services/KeymapService.js:217
#, javascript-format
msgid "Keymap item %s is invalid because %s is not a valid accelerator."
msgstr ""
"%s tuş haritası geçersizdir, çünkü %s geçerli bir accelerator değildir."
#: ReactNativeClient/lib/services/KeymapService.js:235
#, javascript-format
@@ -3611,11 +3622,13 @@ msgid ""
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to "
"unexpected behaviour."
msgstr ""
"Kısayol \"%s\", \"%s\" ve \"%s\" komutları için kullanılıyor. Bu beklenmeyen "
"sonuçlara sebep verebilir."
#: ReactNativeClient/lib/services/KeymapService.js:260
#, javascript-format
msgid "Accelerator \"%s\" is not valid."
msgstr ""
msgstr "Kısayol \"%s\" geçersiz."
#: ReactNativeClient/lib/services/report.js:121
msgid "Items that cannot be synchronised"
@@ -3949,7 +3962,7 @@ msgstr ""
#~ "Şu anda not defteriniz yok. (+) butonuna tıklayarak bir tane oluşturun."
#~ msgid "Welcome"
#~ msgstr "Hoşgeldiniz"
#~ msgstr "Hoş Geldiniz"
#~ msgid "Separate each tag by a comma."
#~ msgstr "Her etiketi virgülle ayırın."

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.2.0",
"version": "1.2.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -28,7 +28,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.2.0",
"version": "1.2.3",
"bin": {
"joplin": "./main.js"
},

View File

@@ -1,163 +1,163 @@
/* eslint-disable no-unused-vars */
/* eslint prefer-const: 0*/
require('app-module-path').addPath(__dirname);
// require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');
const Folder = require('lib/models/Folder');
const Tag = require('lib/models/Tag');
const ItemChange = require('lib/models/ItemChange');
const Setting = require('lib/models/Setting');
const Resource = require('lib/models/Resource.js');
const { shim } = require('lib/shim');
const ResourceService = require('lib/services/ResourceService.js');
// const { time } = require('lib/time-utils.js');
// const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
// const SearchEngine = require('lib/services/searchengine/SearchEngine');
// const Note = require('lib/models/Note');
// const Folder = require('lib/models/Folder');
// const Tag = require('lib/models/Tag');
// const ItemChange = require('lib/models/ItemChange');
// const Setting = require('lib/models/Setting');
// const Resource = require('lib/models/Resource.js');
// const { shim } = require('lib/shim');
// const ResourceService = require('lib/services/ResourceService.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
// process.on('unhandledRejection', (reason, p) => {
// console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
// });
let engine = null;
// let engine = null;
const ids = (array) => array.map(a => a.id);
// const ids = (array) => array.map(a => a.id);
describe('services_SearchFuzzy', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
// describe('services_SearchFuzzy', function() {
// beforeEach(async (done) => {
// await setupDatabaseAndSynchronizer(1);
// await switchClient(1);
engine = new SearchEngine();
engine.setDb(db());
// engine = new SearchEngine();
// engine.setDb(db());
Setting.setValue('db.fuzzySearchEnabled', 1);
done();
});
// Setting.setValue('db.fuzzySearchEnabled', 1);
// done();
// });
it('should return note almost matching title', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'If It Ain\'t Baroque, Don\'t Fix It' });
const n2 = await Note.save({ title: 'Important note' });
// it('should return note almost matching title', asyncTest(async () => {
// let rows;
// const n1 = await Note.save({ title: 'If It Ain\'t Baroque, Don\'t Fix It' });
// const n2 = await Note.save({ title: 'Important note' });
await engine.syncTables();
rows = await engine.search('Broke', { fuzzy: false });
expect(rows.length).toBe(0);
// await engine.syncTables();
// rows = await engine.search('Broke', { fuzzy: false });
// expect(rows.length).toBe(0);
rows = await engine.search('Broke', { fuzzy: true });
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
// rows = await engine.search('Broke', { fuzzy: true });
// expect(rows.length).toBe(1);
// expect(rows[0].id).toBe(n1.id);
rows = await engine.search('title:Broke', { fuzzy: true });
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
// rows = await engine.search('title:Broke', { fuzzy: true });
// expect(rows.length).toBe(1);
// expect(rows[0].id).toBe(n1.id);
rows = await engine.search('title:"Broke"', { fuzzy: true });
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
// rows = await engine.search('title:"Broke"', { fuzzy: true });
// expect(rows.length).toBe(1);
// expect(rows[0].id).toBe(n1.id);
rows = await engine.search('Imprtant', { fuzzy: true });
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n2.id);
}));
// rows = await engine.search('Imprtant', { fuzzy: true });
// expect(rows.length).toBe(1);
// expect(rows[0].id).toBe(n2.id);
// }));
it('should order results by min fuzziness', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I demand you take me to him' });
const n2 = await Note.save({ title: 'He demanded an answer' });
const n3 = await Note.save({ title: 'Don\'t you make demands of me' });
const n4 = await Note.save({ title: 'No drama for me' });
const n5 = await Note.save({ title: 'Just minding my own business' });
// it('should order results by min fuzziness', asyncTest(async () => {
// let rows;
// const n1 = await Note.save({ title: 'I demand you take me to him' });
// const n2 = await Note.save({ title: 'He demanded an answer' });
// const n3 = await Note.save({ title: 'Don\'t you make demands of me' });
// const n4 = await Note.save({ title: 'No drama for me' });
// const n5 = await Note.save({ title: 'Just minding my own business' });
await engine.syncTables();
rows = await engine.search('demand', { fuzzy: false });
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
// await engine.syncTables();
// rows = await engine.search('demand', { fuzzy: false });
// expect(rows.length).toBe(1);
// expect(rows[0].id).toBe(n1.id);
rows = await engine.search('demand', { fuzzy: true });
expect(rows.length).toBe(3);
expect(rows[0].id).toBe(n1.id);
expect(rows[1].id).toBe(n3.id);
expect(rows[2].id).toBe(n2.id);
}));
// rows = await engine.search('demand', { fuzzy: true });
// expect(rows.length).toBe(3);
// expect(rows[0].id).toBe(n1.id);
// expect(rows[1].id).toBe(n3.id);
// expect(rows[2].id).toBe(n2.id);
// }));
it('should consider any:1', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'cat' });
const n2 = await Note.save({ title: 'cats' });
const n3 = await Note.save({ title: 'cot' });
// it('should consider any:1', asyncTest(async () => {
// let rows;
// const n1 = await Note.save({ title: 'cat' });
// const n2 = await Note.save({ title: 'cats' });
// const n3 = await Note.save({ title: 'cot' });
const n4 = await Note.save({ title: 'defenestrate' });
const n5 = await Note.save({ title: 'defenstrate' });
const n6 = await Note.save({ title: 'defenestrated' });
// const n4 = await Note.save({ title: 'defenestrate' });
// const n5 = await Note.save({ title: 'defenstrate' });
// const n6 = await Note.save({ title: 'defenestrated' });
const n7 = await Note.save({ title: 'he defenestrated the cat' });
// const n7 = await Note.save({ title: 'he defenestrated the cat' });
await engine.syncTables();
// await engine.syncTables();
rows = await engine.search('defenestrated cat', { fuzzy: true });
expect(rows.length).toBe(1);
// rows = await engine.search('defenestrated cat', { fuzzy: true });
// expect(rows.length).toBe(1);
rows = await engine.search('any:1 defenestrated cat', { fuzzy: true });
expect(rows.length).toBe(7);
}));
// rows = await engine.search('any:1 defenestrated cat', { fuzzy: true });
// expect(rows.length).toBe(7);
// }));
it('should leave phrase searches alone', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abc def' });
const n2 = await Note.save({ title: 'def ghi' });
const n3 = await Note.save({ title: 'ghi jkl' });
const n4 = await Note.save({ title: 'def abc' });
const n5 = await Note.save({ title: 'mno pqr ghi jkl' });
// it('should leave phrase searches alone', asyncTest(async () => {
// let rows;
// const n1 = await Note.save({ title: 'abc def' });
// const n2 = await Note.save({ title: 'def ghi' });
// const n3 = await Note.save({ title: 'ghi jkl' });
// const n4 = await Note.save({ title: 'def abc' });
// const n5 = await Note.save({ title: 'mno pqr ghi jkl' });
await engine.syncTables();
// await engine.syncTables();
rows = await engine.search('abc def', { fuzzy: true });
expect(rows.length).toBe(2);
expect(rows.map(r=>r.id)).toContain(n1.id);
expect(rows.map(r=>r.id)).toContain(n4.id);
// rows = await engine.search('abc def', { fuzzy: true });
// expect(rows.length).toBe(2);
// expect(rows.map(r=>r.id)).toContain(n1.id);
// expect(rows.map(r=>r.id)).toContain(n4.id);
rows = await engine.search('"abc def"', { fuzzy: true });
expect(rows.length).toBe(1);
expect(rows.map(r=>r.id)).toContain(n1.id);
// rows = await engine.search('"abc def"', { fuzzy: true });
// expect(rows.length).toBe(1);
// expect(rows.map(r=>r.id)).toContain(n1.id);
rows = await engine.search('"ghi jkl"', { fuzzy: true });
expect(rows.length).toBe(2);
expect(rows.map(r=>r.id)).toContain(n3.id);
expect(rows.map(r=>r.id)).toContain(n5.id);
// rows = await engine.search('"ghi jkl"', { fuzzy: true });
// expect(rows.length).toBe(2);
// expect(rows.map(r=>r.id)).toContain(n3.id);
// expect(rows.map(r=>r.id)).toContain(n5.id);
rows = await engine.search('"ghi jkl" mno', { fuzzy: true });
expect(rows.length).toBe(1);
expect(rows.map(r=>r.id)).toContain(n5.id);
// rows = await engine.search('"ghi jkl" mno', { fuzzy: true });
// expect(rows.length).toBe(1);
// expect(rows.map(r=>r.id)).toContain(n5.id);
rows = await engine.search('any:1 "ghi jkl" mno', { fuzzy: true });
expect(rows.length).toBe(2);
expect(rows.map(r=>r.id)).toContain(n3.id);
expect(rows.map(r=>r.id)).toContain(n5.id);
}));
// rows = await engine.search('any:1 "ghi jkl" mno', { fuzzy: true });
// expect(rows.length).toBe(2);
// expect(rows.map(r=>r.id)).toContain(n3.id);
// expect(rows.map(r=>r.id)).toContain(n5.id);
// }));
it('should leave wild card searches alone', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abc def' });
const n2 = await Note.save({ title: 'abcc ghi' });
const n3 = await Note.save({ title: 'abccc ghi' });
const n4 = await Note.save({ title: 'abcccc ghi' });
const n5 = await Note.save({ title: 'wxy zzz' });
// it('should leave wild card searches alone', asyncTest(async () => {
// let rows;
// const n1 = await Note.save({ title: 'abc def' });
// const n2 = await Note.save({ title: 'abcc ghi' });
// const n3 = await Note.save({ title: 'abccc ghi' });
// const n4 = await Note.save({ title: 'abcccc ghi' });
// const n5 = await Note.save({ title: 'wxy zzz' });
await engine.syncTables();
// await engine.syncTables();
rows = await engine.search('abc*', { fuzzy: true });
// rows = await engine.search('abc*', { fuzzy: true });
expect(rows.length).toBe(4);
expect(rows.map(r=>r.id)).toContain(n1.id);
expect(rows.map(r=>r.id)).toContain(n2.id);
expect(rows.map(r=>r.id)).toContain(n3.id);
expect(rows.map(r=>r.id)).toContain(n4.id);
}));
// expect(rows.length).toBe(4);
// expect(rows.map(r=>r.id)).toContain(n1.id);
// expect(rows.map(r=>r.id)).toContain(n2.id);
// expect(rows.map(r=>r.id)).toContain(n3.id);
// expect(rows.map(r=>r.id)).toContain(n4.id);
// }));
});
// });

View File

@@ -34,6 +34,7 @@ const KeymapService = require('lib/services/KeymapService').default;
const TemplateUtils = require('lib/TemplateUtils');
const CssUtils = require('lib/CssUtils');
const resourceEditWatcherReducer = require('lib/services/ResourceEditWatcher/reducer').default;
// const populateDatabase = require('lib/services/debug/populateDatabase').default;
const versionInfo = require('lib/versionInfo').default;
const commands = [
@@ -100,6 +101,7 @@ const appDefaultState = Object.assign({}, defaultState, {
lastEditorScrollPercents: {},
devToolsVisible: false,
visibleDialogs: {}, // empty object if no dialog is visible. Otherwise contains the list of visible dialogs.
focusedField: null,
});
class Application extends BaseApplication {
@@ -291,6 +293,21 @@ class Application extends BaseApplication {
delete newState.visibleDialogs[state.name];
break;
case 'FOCUS_SET':
newState = Object.assign({}, state);
newState.focusedField = action.field;
break;
case 'FOCUS_CLEAR':
// A field can only clear its own state
if (action.field === state.focusedField) {
newState = Object.assign({}, state);
newState.focusedField = null;
}
break;
}
} catch (error) {
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
@@ -1109,7 +1126,7 @@ class Application extends BaseApplication {
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (err) {
bridge().showErrorMessageBox(err.message);
reg.logger().error(err.message);
}
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
@@ -1267,6 +1284,8 @@ class Application extends BaseApplication {
};
bridge().addEventListener('nativeThemeUpdated', this.bridge_nativeThemeUpdated);
// await populateDatabase(reg.db());
}
}

View File

@@ -526,7 +526,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
const label = [md.label()];
if (md.unitLabel) label.push(`(${md.unitLabel()})`);
const inputStyle = Object.assign({}, textInputBaseStyle);
const inputStyle:any = Object.assign({}, textInputBaseStyle);
return (
<div key={key} style={rowStyle}>

View File

@@ -566,7 +566,7 @@ class MainScreenComponent extends React.Component<any, any> {
const bodyEditor = this.props.settingEditorCodeView ? 'CodeMirror' : 'TinyMCE';
return <NoteEditor key={key} bodyEditor={bodyEditor} />;
} else if (key === 'noteListControls') {
return <NoteListControls key={key} />;
return <NoteListControls key={key} showNewNoteButtons={this.props.focusedField !== 'globalSearch'} />;
}
throw new Error(`Invalid layout component: ${key}`);
@@ -650,6 +650,7 @@ const mapStateToProps = (state:any) => {
customCss: state.customCss,
editorNoteStatuses: state.editorNoteStatuses,
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
focusedField: state.focusedField,
};
};

View File

@@ -9,6 +9,7 @@ import { useScrollHandler, usePrevious, cursorPositionToTextOffset, useRootSize
import Toolbar from './Toolbar';
import styles_ from './styles';
import { RenderedBody, defaultRenderedBody } from './utils/types';
import NoteTextViewer from '../../../NoteTextViewer';
import Editor from './Editor';
// @ts-ignore
@@ -17,7 +18,6 @@ const { bridge } = require('electron').remote.require('./bridge');
const Note = require('lib/models/Note.js');
const { clipboard } = require('electron');
const Setting = require('lib/models/Setting.js');
const NoteTextViewer = require('../../../NoteTextViewer.min');
const shared = require('lib/components/shared/note-screen-shared.js');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
@@ -38,6 +38,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
const [webviewReady, setWebviewReady] = useState(false);
const previousContent = usePrevious(props.content);
const previousRenderedBody = usePrevious(renderedBody);
const previousSearchMarkers = usePrevious(props.searchMarkers);
const previousContentKey = usePrevious(props.contentKey);
@@ -359,6 +360,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
/* These must be important to prevent the codemirror defaults from taking over*/
.CodeMirror {
font-family: monospace;
font-size: ${theme.editorFontSize}px;
height: 100% !important;
width: 100% !important;
color: inherit !important;
@@ -372,37 +374,37 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
/* be applied to the viewer. */
padding-bottom: 400px !important;
}
.cm-header-1 {
font-size: 1.5em;
}
.cm-header-2 {
font-size: 1.3em;
}
.cm-header-3 {
font-size: 1.1em;
}
.cm-header-4, .cm-header-5, .cm-header-6 {
font-size: 1em;
}
.cm-header-1, .cm-header-2, .cm-header-3, .cm-header-4, .cm-header-5, .cm-header-6 {
line-height: 1.5em;
}
.cm-search-marker {
background: ${theme.searchMarkerBackgroundColor};
color: ${theme.searchMarkerColor} !important;
}
.cm-search-marker-selected {
background: ${theme.selectedColor2};
color: ${theme.color2} !important;
}
.cm-search-marker-scrollbar {
background: ${theme.searchMarkerBackgroundColor};
-moz-box-sizing: border-box;
@@ -416,6 +418,27 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
background-color: inherit !important;
border-bottom: 1px dotted #dc322f;
}
/* The default dark theme colors don't have enough contrast with the background */
.cm-s-nord span.cm-comment {
color: #9aa4b6 !important;
}
.cm-s-dracula span.cm-comment {
color: #a1abc9 !important;
}
.cm-s-monokai span.cm-comment {
color: #908b74 !important;
}
.cm-s-material-darker span.cm-comment {
color: #878787 !important;
}
.cm-s-solarized.cm-s-dark span.cm-comment {
color: #8ba1a7 !important;
}
`));
return () => {
@@ -484,7 +507,11 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
// If there is a currently active search, it's important to re-search the text as the user
// types. However this is slow for performance so we ONLY want it to happen when there is
// a search
const textChanged = props.searchMarkers.keywords.length > 0 && props.content !== previousContent;
// Note that since the CodeMirror component also needs to handle the viewer pane, we need
// to check if the rendered body has changed too (it will be changed with a delay after
// props.content has been updated).
const textChanged = props.searchMarkers.keywords.length > 0 && (props.content !== previousContent || renderedBody !== previousRenderedBody);
if (props.searchMarkers !== previousSearchMarkers || textChanged) {
webviewRef.current.wrappedInstance.send('setMarkers', props.searchMarkers.keywords, props.searchMarkers.options);
@@ -495,7 +522,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
props.setLocalSearchResultCount(matches);
}
}
}, [props.searchMarkers, previousSearchMarkers, props.setLocalSearchResultCount, props.content, previousContent]);
}, [props.searchMarkers, previousSearchMarkers, props.setLocalSearchResultCount, props.content, previousContent, renderedBody, previousRenderedBody, renderedBody]);
const cellEditorStyle = useMemo(() => {
const output = { ...styles.cellEditor };
@@ -547,8 +574,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
editorRef.current.refresh();
}, [rootSize, styles.editor, props.visiblePanes]);
const editorReadOnly = props.visiblePanes.indexOf('editor') < 0;
function renderEditor() {
return (
@@ -591,7 +616,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
<Toolbar
themeId={props.themeId}
dispatch={props.dispatch}
disabled={editorReadOnly}
/>
{props.noteToolbar}
</div>

View File

@@ -17,13 +17,13 @@ import useCursorUtils from './utils/useCursorUtils';
import useLineSorting from './utils/useLineSorting';
import useEditorSearch from './utils/useEditorSearch';
import useJoplinMode from './utils/useJoplinMode';
import useKeymap from './utils/useKeymap';
import 'codemirror/keymap/emacs';
import 'codemirror/keymap/vim';
import 'codemirror/keymap/sublime'; // Used for swapLineUp and swapLineDown
import 'codemirror/mode/meta';
const { shim } = require('lib/shim.js');
const { reg } = require('lib/registry.js');
@@ -50,7 +50,7 @@ const topLanguages = [
'haskell',
'pascal',
'css',
// Additional languages, not in the PYPL list
'xml', // For HTML too
'markdown',
@@ -99,6 +99,7 @@ function Editor(props: EditorProps, ref: any) {
useLineSorting(CodeMirror);
useEditorSearch(CodeMirror);
useJoplinMode(CodeMirror);
useKeymap(CodeMirror);
useImperativeHandle(ref, () => {
return editor;
@@ -141,87 +142,6 @@ function Editor(props: EditorProps, ref: any) {
}
}, []);
useEffect(() => {
CodeMirror.keyMap.basic = {
'Left': 'goCharLeft',
'Right': 'goCharRight',
'Up': 'goLineUp',
'Down': 'goLineDown',
'End': 'goLineRight',
'Home': 'goLineLeftSmart',
'PageUp': 'goPageUp',
'PageDown': 'goPageDown',
'Delete': 'delCharAfter',
'Backspace': 'delCharBefore',
'Shift-Backspace': 'delCharBefore',
'Tab': 'smartListIndent',
'Shift-Tab': 'smartListUnindent',
'Enter': 'insertListElement',
'Insert': 'toggleOverwrite',
'Esc': 'singleSelection',
};
// Add some of the Joplin smart list handling to emacs mode
CodeMirror.keyMap.emacs['Tab'] = 'smartListIndent';
CodeMirror.keyMap.emacs['Enter'] = 'insertListElement';
CodeMirror.keyMap.emacs['Shift-Tab'] = 'smartListUnindent';
if (shim.isMac()) {
CodeMirror.keyMap.default = {
// MacOS
'Cmd-A': 'selectAll',
'Cmd-D': 'deleteLine',
'Cmd-Z': 'undo',
'Shift-Cmd-Z': 'redo',
'Cmd-Y': 'redo',
'Cmd-Home': 'goDocStart',
'Cmd-Up': 'goDocStart',
'Cmd-End': 'goDocEnd',
'Cmd-Down': 'goDocEnd',
'Cmd-Left': 'goLineLeft',
'Cmd-Right': 'goLineRight',
'Alt-Left': 'goGroupLeft',
'Alt-Right': 'goGroupRight',
'Alt-Backspace': 'delGroupBefore',
'Alt-Delete': 'delGroupAfter',
'Cmd-[': 'indentLess',
'Cmd-]': 'indentMore',
'Cmd-/': 'toggleComment',
'Cmd-Opt-S': 'sortSelectedLines',
'Opt-Up': 'swapLineUp',
'Opt-Down': 'swapLineDown',
'fallthrough': 'basic',
};
} else {
CodeMirror.keyMap.default = {
// Windows/linux
'Ctrl-A': 'selectAll',
'Ctrl-D': 'deleteLine',
'Ctrl-Z': 'undo',
'Shift-Ctrl-Z': 'redo',
'Ctrl-Y': 'redo',
'Ctrl-Home': 'goDocStart',
'Ctrl-End': 'goDocEnd',
'Ctrl-Up': 'goLineUp',
'Ctrl-Down': 'goLineDown',
'Ctrl-Left': 'goGroupLeft',
'Ctrl-Right': 'goGroupRight',
'Alt-Left': 'goLineStart',
'Alt-Right': 'goLineEnd',
'Ctrl-Backspace': 'delGroupBefore',
'Ctrl-Delete': 'delGroupAfter',
'Ctrl-[': 'indentLess',
'Ctrl-]': 'indentMore',
'Ctrl-/': 'toggleComment',
'Ctrl-Alt-S': 'sortSelectedLines',
'Alt-Up': 'swapLineUp',
'Alt-Down': 'swapLineDown',
'fallthrough': 'basic',
};
}
}, []);
useEffect(() => {
if (!editorParent.current) return () => {};

View File

@@ -1,13 +1,11 @@
import * as React from 'react';
import CommandService from '../../../../lib/services/CommandService';
const ToolbarBase = require('../../../Toolbar.min.js');
import CommandService from 'lib/services/CommandService';
import ToolbarBase from '../../../ToolbarBase';
const { buildStyle } = require('lib/theme');
interface ToolbarProps {
themeId: number,
dispatch: Function,
disabled: boolean,
}
function styles_(props:ToolbarProps) {
@@ -49,5 +47,5 @@ export default function Toolbar(props:ToolbarProps) {
cmdService.commandToToolbarButton('toggleEditors'),
];
return <ToolbarBase disabled={props.disabled} style={styles.root} items={toolbarItems} />;
return <ToolbarBase style={styles.root} items={toolbarItems} />;
}

View File

@@ -0,0 +1,107 @@
import { useEffect } from 'react';
import CommandService from 'lib/services/CommandService';
const { shim } = require('lib/shim.js');
export default function useKeymap(CodeMirror: any) {
function save() {
CommandService.instance().execute('synchronize');
}
function setupEmacs() {
CodeMirror.keyMap.emacs['Tab'] = 'smartListIndent';
CodeMirror.keyMap.emacs['Enter'] = 'insertListElement';
CodeMirror.keyMap.emacs['Shift-Tab'] = 'smartListUnindent';
}
function setupVim() {
CodeMirror.Vim.defineAction('swapLineDown', CodeMirror.commands.swapLineDown);
CodeMirror.Vim.mapCommand('<A-j>', 'action', 'swapLineDown', {}, { context: 'normal', isEdit: true });
CodeMirror.Vim.defineAction('swapLineUp', CodeMirror.commands.swapLineUp);
CodeMirror.Vim.mapCommand('<A-k>', 'action', 'swapLineUp', {}, { context: 'normal', isEdit: true });
CodeMirror.Vim.defineAction('insertListElement', CodeMirror.commands.vimInsertListElement);
CodeMirror.Vim.mapCommand('o', 'action', 'insertListElement', { after: true }, { context: 'normal', isEdit: true, interlaceInsertRepeat: true });
}
useEffect(() => {
// This enables the special modes (emacs and vim) to initiate sync by the save action
CodeMirror.commands.save = save;
CodeMirror.keyMap.basic = {
'Left': 'goCharLeft',
'Right': 'goCharRight',
'Up': 'goLineUp',
'Down': 'goLineDown',
'End': 'goLineRight',
'Home': 'goLineLeftSmart',
'PageUp': 'goPageUp',
'PageDown': 'goPageDown',
'Delete': 'delCharAfter',
'Backspace': 'delCharBefore',
'Shift-Backspace': 'delCharBefore',
'Tab': 'smartListIndent',
'Shift-Tab': 'smartListUnindent',
'Enter': 'insertListElement',
'Insert': 'toggleOverwrite',
'Esc': 'singleSelection',
};
if (shim.isMac()) {
CodeMirror.keyMap.default = {
// MacOS
'Cmd-A': 'selectAll',
'Cmd-D': 'deleteLine',
'Cmd-Z': 'undo',
'Shift-Cmd-Z': 'redo',
'Cmd-Y': 'redo',
'Cmd-Home': 'goDocStart',
'Cmd-Up': 'goDocStart',
'Cmd-End': 'goDocEnd',
'Cmd-Down': 'goDocEnd',
'Cmd-Left': 'goLineLeft',
'Cmd-Right': 'goLineRight',
'Alt-Left': 'goGroupLeft',
'Alt-Right': 'goGroupRight',
'Alt-Backspace': 'delGroupBefore',
'Alt-Delete': 'delGroupAfter',
'Cmd-[': 'indentLess',
'Cmd-]': 'indentMore',
'Cmd-/': 'toggleComment',
'Cmd-Opt-S': 'sortSelectedLines',
'Opt-Up': 'swapLineUp',
'Opt-Down': 'swapLineDown',
'fallthrough': 'basic',
};
} else {
CodeMirror.keyMap.default = {
// Windows/linux
'Ctrl-A': 'selectAll',
'Ctrl-D': 'deleteLine',
'Ctrl-Z': 'undo',
'Shift-Ctrl-Z': 'redo',
'Ctrl-Y': 'redo',
'Ctrl-Home': 'goDocStart',
'Ctrl-End': 'goDocEnd',
'Ctrl-Up': 'goLineUp',
'Ctrl-Down': 'goLineDown',
'Ctrl-Left': 'goGroupLeft',
'Ctrl-Right': 'goGroupRight',
'Alt-Left': 'goLineStart',
'Alt-Right': 'goLineEnd',
'Ctrl-Backspace': 'delGroupBefore',
'Ctrl-Delete': 'delGroupAfter',
'Ctrl-[': 'indentLess',
'Ctrl-]': 'indentMore',
'Ctrl-/': 'toggleComment',
'Ctrl-Alt-S': 'sortSelectedLines',
'Alt-Up': 'swapLineUp',
'Alt-Down': 'swapLineDown',
'fallthrough': 'basic',
};
}
setupEmacs();
setupVim();
}, []);
}

View File

@@ -126,6 +126,24 @@ export default function useListIdent(CodeMirror: any) {
});
};
// This is a special case of insertList element because it happens when
// vim is in normal mode and input is disabled and the cursor is not
// necessarily at the end of line (but it should pretend it is
CodeMirror.commands.vimInsertListElement = function(cm: any) {
cm.setOption('disableInput', false);
const ranges = cm.listSelections();
if (ranges.length === 0) return;
const { anchor } = ranges[0];
// Need to move the cursor to end of line as this is the vim behavior
const line = cm.getLine(anchor.line);
cm.setCursor({ line: anchor.line, ch: line.length });
cm.execCommand('insertListElement');
cm.setOption('disableInput', true);
};
CodeMirror.commands.insertListElement = function(cm: any) {
if (cm.getOption('disableInput')) return CodeMirror.Pass;

View File

@@ -338,12 +338,8 @@ function NoteEditor(props: NoteEditorProps) {
}
function renderNoteToolbar() {
// const theme = themeStyle(props.themeId);
const toolbarStyle = {
marginBottom: 0,
// paddingTop: theme.mainPadding,
// paddingBottom: theme.mainPadding,
};
return <NoteToolbar
@@ -363,16 +359,12 @@ function NoteEditor(props: NoteEditorProps) {
function renderTagBar() {
const theme = themeStyle(props.themeId);
let control = null;
if (!props.selectedNoteTags.length) {
const noteIds = [formNote.id];
control = <span onClick={() => { CommandService.instance().execute('setTags', { noteIds }); }} style={theme.clickableTextStyle}>Click to add some tags...</span>;
} else {
control = <TagList items={props.selectedNoteTags} />;
}
const noteIds = [formNote.id];
const instructions = <span onClick={() => { CommandService.instance().execute('setTags', { noteIds }); }} style={{ ...theme.clickableTextStyle, whiteSpace: 'nowrap' }}>Click to add tags...</span>;
const tagList = props.selectedNoteTags.length ? <TagList items={props.selectedNoteTags} /> : null;
return (
<div style={{ paddingLeft: 8 }}>{control}</div>
<div style={{ paddingLeft: 8, display: 'flex', flexDirection: 'row', alignItems: 'center' }}>{tagList}{instructions}</div>
);
}

View File

@@ -6,9 +6,12 @@ import CommandService from 'lib/services/CommandService';
import { runtime as focusSearchRuntime } from './commands/focusSearch';
const styled = require('styled-components').default;
interface Props {
showNewNoteButtons: boolean,
}
const StyledRoot = styled.div`
width: 100%;
/*height: 100%;*/
display: flex;
flex-direction: row;
padding: ${(props:any) => props.theme.mainPadding}px;
@@ -19,7 +22,12 @@ const StyledButton = styled(Button)`
margin-left: 8px;
`;
export default function NoteListControls() {
const ButtonContainer = styled.div`
display: flex;
flex-direction: row;
`;
export default function NoteListControls(props:Props) {
const searchBarRef = useRef(null);
useEffect(function() {
@@ -38,21 +46,31 @@ export default function NoteListControls() {
CommandService.instance().execute('newNote');
}
function renderNewNoteButtons() {
if (!props.showNewNoteButtons) return null;
return (
<ButtonContainer>
<StyledButton
tooltip={CommandService.instance().title('newTodo')}
iconName="far fa-check-square"
level={ButtonLevel.Primary}
onClick={onNewTodoButtonClick}
/>
<StyledButton
tooltip={CommandService.instance().title('newNote')}
iconName="icon-note"
level={ButtonLevel.Primary}
onClick={onNewNoteButtonClick}
/>
</ButtonContainer>
);
}
return (
<StyledRoot>
<SearchBar inputRef={searchBarRef}/>
<StyledButton
tooltip={CommandService.instance().title('newTodo')}
iconName="far fa-check-square"
level={ButtonLevel.Primary}
onClick={onNewTodoButtonClick}
/>
<StyledButton
tooltip={CommandService.instance().title('newNote')}
iconName="icon-note"
level={ButtonLevel.Primary}
onClick={onNewNoteButtonClick}
/>
{renderNewNoteButtons()}
</StyledRoot>
);
}

View File

@@ -2,7 +2,7 @@ const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('lib/theme');
const { _ } = require('lib/locale.js');
const NoteTextViewer = require('./NoteTextViewer.min');
const NoteTextViewer = require('./NoteTextViewer').default;
const HelpButton = require('./HelpButton.min');
const BaseModel = require('lib/BaseModel');
const Revision = require('lib/models/Revision');

View File

@@ -1,15 +1,24 @@
const React = require('react');
import * as React from 'react';
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
class NoteTextViewerComponent extends React.Component {
constructor() {
super();
interface Props {
onDomReady: Function,
onIpcMessage: Function,
viewerStyle: any,
}
this.initialized_ = false;
this.domReady_ = false;
class NoteTextViewerComponent extends React.Component<Props, any> {
private initialized_:boolean = false;
private domReady_:boolean = false;
private webviewRef_:any;
private webviewListeners_:any = null;
constructor(props:any) {
super(props);
this.webviewRef_ = React.createRef();
this.webviewListeners_ = null;
this.webview_domReady = this.webview_domReady.bind(this);
this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
@@ -17,20 +26,20 @@ class NoteTextViewerComponent extends React.Component {
this.webview_message = this.webview_message.bind(this);
}
webview_domReady(event) {
webview_domReady(event:any) {
this.domReady_ = true;
if (this.props.onDomReady) this.props.onDomReady(event);
}
webview_ipcMessage(event) {
webview_ipcMessage(event:any) {
if (this.props.onIpcMessage) this.props.onIpcMessage(event);
}
webview_load() {
this.webview_domReady();
this.webview_domReady({});
}
webview_message(event) {
webview_message(event:any) {
if (!event.data || event.data.target !== 'main') return;
const callName = event.data.name;
@@ -78,7 +87,14 @@ class NoteTextViewerComponent extends React.Component {
wv.removeEventListener(n, fn);
}
this.webviewRef_.current.contentWindow.removeEventListener('message', this.webview_message);
try {
// It seems this can throw a cross-origin error in a way that is hard to replicate so just wrap
// it in try/catch since it's not critical.
// https://github.com/laurent22/joplin/issues/3835
this.webviewRef_.current.contentWindow.removeEventListener('message', this.webview_message);
} catch (error) {
reg.logger().warn('Error destroying note viewer', error);
}
this.initialized_ = false;
this.domReady_ = false;
@@ -107,7 +123,7 @@ class NoteTextViewerComponent extends React.Component {
// Wrap WebView functions
// ----------------------------------------------------------------
send(channel, arg0 = null, arg1 = null) {
send(channel:string, arg0:any = null, arg1:any = null) {
const win = this.webviewRef_.current.contentWindow;
if (channel === 'setHtml') {
@@ -127,36 +143,6 @@ class NoteTextViewerComponent extends React.Component {
}
}
printToPDF() { // options, callback) {
// In Electron 4x, printToPDF is broken so need to use this hack:
// https://github.com/electron/electron/issues/16171#issuecomment-451090245
// return this.webviewRef_.current.printToPDF(options, callback);
// return this.webviewRef_.current.getWebContents().printToPDF(options, callback);
}
print() {
// In Electron 4x, print is broken so need to use this hack:
// https://github.com/electron/electron/issues/16219#issuecomment-451454948
// Note that this is not a perfect workaround since it means the options are ignored
// In particular it means that background images and colours won't be printed (printBackground property will be ignored)
// return this.webviewRef_.current.getWebContents().print({});
return this.webviewRef_.current.getWebContents().executeJavaScript('window.print()');
}
openDevTools() {
// return this.webviewRef_.current.openDevTools();
}
closeDevTools() {
// return this.webviewRef_.current.closeDevTools();
}
isDevToolsOpened() {
// return this.webviewRef_.current.isDevToolsOpened();
}
// ----------------------------------------------------------------
// Wrap WebView functions (END)
// ----------------------------------------------------------------
@@ -167,7 +153,7 @@ class NoteTextViewerComponent extends React.Component {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state:any) => {
return {
themeId: state.settings.theme,
};
@@ -180,4 +166,4 @@ const NoteTextViewer = connect(
{ withRef: true }
)(NoteTextViewerComponent);
module.exports = NoteTextViewer;
export default NoteTextViewer;

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import { useEffect, useState } from 'react';
import CommandService from '../../lib/services/CommandService';
import ToolbarBase from '../ToolbarBase';
const { connect } = require('react-redux');
const { buildStyle } = require('lib/theme');
const Toolbar = require('../Toolbar.min.js');
// const Folder = require('lib/models/Folder');
// const { _ } = require('lib/locale');
// const { substrWithEllipsis } = require('lib/string-utils');
@@ -40,21 +40,12 @@ function styles_(props:NoteToolbarProps) {
function NoteToolbar(props:NoteToolbarProps) {
const styles = styles_(props);
const [toolbarItems, setToolbarItems] = useState([]);
// const selectedNoteFolder = Folder.byId(props.folders, props.note.parent_id);
// const folderId = selectedNoteFolder ? selectedNoteFolder.id : '';
// const folderTitle = selectedNoteFolder && selectedNoteFolder.title ? selectedNoteFolder.title : '';
const cmdService = CommandService.instance();
function updateToolbarItems() {
const output = [];
// if (props.watchedNoteFiles.indexOf(props.note.id) >= 0) {
// output.push(cmdService.commandToToolbarButton('stopExternalEditing'));
// } else {
// output.push(cmdService.commandToToolbarButton('startExternalEditing'));
// }
output.push(cmdService.commandToToolbarButton('editAlarm'));
output.push(cmdService.commandToToolbarButton('toggleVisiblePanes'));
output.push(cmdService.commandToToolbarButton('showNoteProperties'));
@@ -70,7 +61,7 @@ function NoteToolbar(props:NoteToolbarProps) {
};
}, []);
return <Toolbar style={styles.root} items={toolbarItems} />;
return <ToolbarBase style={styles.root} items={toolbarItems} />;
}
const mapStateToProps = (state:any) => {

View File

@@ -24,7 +24,7 @@ const { bridge } = require('electron').remote.require('./bridge');
const GlobalStyle = createGlobalStyle`
div, span, a {
color: ${(props) => props.theme.color};
font-size: ${(props) => props.theme.fontSize}px;
/*font-size: ${(props) => props.theme.fontSize}px;*/
font-family: ${(props) => props.theme.fontFamily};
}
`;

View File

@@ -10,15 +10,42 @@ const { _ } = require('lib/locale.js');
interface Props {
inputRef?: any,
notesParentType: string,
dispatch?: Function,
}
function SearchBar(props:Props) {
const [query, setQuery] = useState('');
const iconName = !query ? CommandService.instance().iconName('search') : 'fa fa-times';
const onChange = (event:any) => {
function onChange(event:any) {
setQuery(event.currentTarget.value);
};
}
function onFocus() {
props.dispatch({
type: 'FOCUS_SET',
field: 'globalSearch',
});
}
function onBlur() {
// Do it after a delay so that the "Clear" button
// can be clicked on (otherwise the field loses focus
// and is resized before the click event has been processed)
setTimeout(() => {
props.dispatch({
type: 'FOCUS_CLEAR',
field: 'globalSearch',
});
}, 300);
}
function onKeyDown(event:any) {
if (event.key === 'Escape') {
setQuery('');
if (document.activeElement) (document.activeElement as any).blur();
}
}
const onSearchButtonClick = useCallback(() => {
setQuery('');
@@ -34,7 +61,16 @@ function SearchBar(props:Props) {
return (
<Root>
<SearchInput ref={props.inputRef} value={query} type="text" placeholder={_('Search...')} onChange={onChange}/>
<SearchInput
ref={props.inputRef}
value={query}
type="text"
placeholder={_('Search...')}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
onKeyDown={onKeyDown}
/>
<SearchButton onClick={onSearchButtonClick}>
<SearchButtonIcon className={iconName}/>
</SearchButton>

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { StyledRoot, StyledAddButton, StyledHeader, StyledHeaderIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
import { StyledRoot, StyledAddButton, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
import { ButtonLevel } from '../Button/Button';
import CommandService from 'lib/services/CommandService';
@@ -45,6 +45,52 @@ const commands = [
require('./commands/focusElementSideBar'),
];
function ExpandIcon(props:any) {
const theme = themeStyle(props.themeId);
const style:any = { width: 16, maxWidth: 16, opacity: 0.5, fontSize: Math.round(theme.toolbarIconSize * 0.8), display: 'flex', justifyContent: 'center' };
if (!props.isVisible) style.visibility = 'hidden';
return <i className={props.isExpanded ? 'fas fa-caret-down' : 'fas fa-caret-right'} style={style}></i>;
}
function ExpandLink(props:any) {
return props.hasChildren ? (
<StyledExpandLink href="#" data-folder-id={props.folderId} onClick={props.onClick}>
<ExpandIcon themeId={props.themeId} isVisible={true} isExpanded={props.isExpanded}/>
</StyledExpandLink>
) : (
<StyledExpandLink><ExpandIcon themeId={props.themeId} isVisible={false} isExpanded={false}/></StyledExpandLink>
);
}
function FolderItem(props:any) {
const { hasChildren, isExpanded, depth, selected, folderId, folderTitle, anchorRef, noteCount, onFolderDragStart_, onFolderDragOver_, onFolderDrop_, itemContextMenu, folderItem_click, onFolderToggleClick_ } = props;
const noteCountComp = noteCount ? <StyledNoteCount>{noteCount}</StyledNoteCount> : null;
return (
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth}`} onDragStart={onFolderDragStart_} onDragOver={onFolderDragOver_} onDrop={onFolderDrop_} draggable={true} data-folder-id={folderId}>
<ExpandLink themeId={props.themeId} hasChildren={hasChildren} folderId={folderId} onClick={onFolderToggleClick_} isExpanded={isExpanded}/>
<StyledListItemAnchor
ref={anchorRef}
className="list-item"
isConflictFolder={folderId === Folder.conflictFolderId()}
href="#"
selected={selected}
data-id={folderId}
data-type={BaseModel.TYPE_FOLDER}
onContextMenu={itemContextMenu}
data-folder-id={folderId}
onClick={() => {
folderItem_click(folderId);
}}
onDoubleClick={onFolderToggleClick_}
>
{folderTitle} {noteCountComp}
</StyledListItemAnchor>
</StyledListItem>
);
}
class SideBarComponent extends React.Component<Props, State> {
private folderItemsOrder_:any[] = [];
@@ -68,6 +114,8 @@ class SideBarComponent extends React.Component<Props, State> {
this.onAllNotesClick_ = this.onAllNotesClick_.bind(this);
this.header_contextMenu = this.header_contextMenu.bind(this);
this.onAddFolderButtonClick = this.onAddFolderButtonClick.bind(this);
this.folderItem_click = this.folderItem_click.bind(this);
this.itemContextMenu = this.itemContextMenu.bind(this);
}
onFolderDragStart_(event:any) {
@@ -257,10 +305,10 @@ class SideBarComponent extends React.Component<Props, State> {
menu.popup(bridge().window());
}
folderItem_click(folder:any) {
folderItem_click(folderId:string) {
this.props.dispatch({
type: 'FOLDER_SELECT',
id: folder ? folder.id : null,
id: folderId ? folderId : null,
});
}
@@ -291,7 +339,7 @@ class SideBarComponent extends React.Component<Props, State> {
}
renderNoteCount(count:number) {
return <StyledNoteCount>{count}</StyledNoteCount>;
return count ? <StyledNoteCount>{count}</StyledNoteCount> : null;
}
renderExpandIcon(isExpanded:boolean, isVisible:boolean = true) {
@@ -305,57 +353,41 @@ class SideBarComponent extends React.Component<Props, State> {
return (
<StyledListItem key="allNotesHeader" selected={selected} className={'list-item-container list-item-depth-0'} isSpecialItem={true}>
<StyledExpandLink>{this.renderExpandIcon(false, false)}</StyledExpandLink>
<StyledAllNotesIcon className="icon-notes"/>
<StyledListItemAnchor
className="list-item"
isSpecialItem={true}
href="#"
selected={selected}
onClick={() => {
this.onAllNotesClick_();
}}
onClick={this.onAllNotesClick_}
>
({_('All notes')})
{_('All notes')}
</StyledListItemAnchor>
</StyledListItem>
);
}
renderFolderItem(folder:any, selected:boolean, hasChildren:boolean, depth:number) {
const isExpanded = this.props.collapsedFolderIds.indexOf(folder.id) < 0;
const expandIcon = this.renderExpandIcon(isExpanded, hasChildren);
const expandLink = hasChildren ? (
<StyledExpandLink href="#" data-folder-id={folder.id} onClick={this.onFolderToggleClick_}>
{expandIcon}
</StyledExpandLink>
) : (
<StyledExpandLink>{expandIcon}</StyledExpandLink>
);
const anchorRef = this.anchorItemRef('folder', folder.id);
const noteCount = folder.note_count ? this.renderNoteCount(folder.note_count) : '';
return (
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth}`} key={folder.id} onDragStart={this.onFolderDragStart_} onDragOver={this.onFolderDragOver_} onDrop={this.onFolderDrop_} draggable={true} data-folder-id={folder.id}>
{expandLink}
<StyledListItemAnchor
ref={anchorRef}
className="list-item"
isConflictFolder={folder.id === Folder.conflictFolderId()}
href="#"
selected={selected}
data-id={folder.id}
data-type={BaseModel.TYPE_FOLDER}
onContextMenu={(event:any) => this.itemContextMenu(event)}
data-folder-id={folder.id}
onClick={() => {
this.folderItem_click(folder);
}}
onDoubleClick={this.onFolderToggleClick_}
>
{Folder.displayTitle(folder)} {noteCount}
</StyledListItemAnchor>
</StyledListItem>
);
return <FolderItem
key={folder.id}
folderId={folder.id}
folderTitle={Folder.displayTitle(folder)}
themeId={this.props.themeId}
depth={depth}
selected={selected}
isExpanded={this.props.collapsedFolderIds.indexOf(folder.id) < 0}
hasChildren={hasChildren}
anchorRef={anchorRef}
noteCount={folder.note_count}
onFolderDragStart_={this.onFolderDragStart_}
onFolderDragOver_={this.onFolderDragOver_}
onFolderDrop_={this.onFolderDrop_}
itemContextMenu={this.itemContextMenu}
folderItem_click={this.folderItem_click}
onFolderToggleClick_={this.onFolderToggleClick_}
/>;
}
renderTag(tag:any, selected:boolean) {
@@ -372,7 +404,7 @@ class SideBarComponent extends React.Component<Props, State> {
selected={selected}
data-id={tag.id}
data-type={BaseModel.TYPE_TAG}
onContextMenu={(event:any) => this.itemContextMenu(event)}
onContextMenu={this.itemContextMenu}
onClick={() => {
this.tagItem_click(tag);
}}

View File

@@ -31,6 +31,12 @@ export const StyledHeaderIcon = styled.i`
margin-right: 8px;
`;
export const StyledAllNotesIcon = styled(StyledHeaderIcon)`
font-size: ${(props:any) => props.theme.toolbarIconSize * 0.8}px;
color: ${(props:any) => props.theme.colorFaded2};
margin-right: 8px;
`;
export const StyledHeaderLabel = styled.span`
flex: 1;
color: ${(props:any) => props.theme.color2};
@@ -43,9 +49,10 @@ export const StyledListItem = styled.div`
height: 25px;
display: flex;
flex-direction: row;
align-items: center;
padding-left: ${(props:any) => props.theme.mainPadding + ('depth' in props ? props.depth : 0) * 16}px;
background: ${(props:any) => props.selected ? props.theme.selectedColor2 : 'none'};
text-transform: ${(props:any) => props.isSpecialItem ? 'uppercase' : 'none'};
/*text-transform: ${(props:any) => props.isSpecialItem ? 'uppercase' : 'none'};*/
transition: 0.1s;
&:hover {
@@ -71,6 +78,7 @@ export const StyledListItemAnchor = styled.a`
flex: 1;
align-items: center;
user-select: none;
height: 100%;
`;
export const StyledExpandLink = styled.a`
@@ -84,6 +92,7 @@ export const StyledExpandLink = styled.a`
width: 16px;
max-width: 16px;
min-width: 16px;
height: 100%;
`;
export const StyledNoteCount = styled.div`

View File

@@ -5,11 +5,18 @@ const ToolbarButton = require('./ToolbarButton/ToolbarButton.js').default;
const ToolbarSpace = require('./ToolbarSpace.min.js');
const ToggleEditorsButton = require('./ToggleEditorsButton/ToggleEditorsButton.js').default;
class ToolbarComponent extends React.Component {
interface Props {
themeId: number,
style: any,
items: any[],
}
class ToolbarBaseComponent extends React.Component<Props, any> {
render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({
const style:any = Object.assign({
display: 'flex',
flexDirection: 'row',
boxSizing: 'border-box',
@@ -18,15 +25,15 @@ class ToolbarComponent extends React.Component {
paddingRight: theme.mainPadding,
}, this.props.style);
const groupStyle = {
const groupStyle:any = {
display: 'flex',
flexDirection: 'row',
boxSizing: 'border-box',
};
const leftItemComps = [];
const centerItemComps = [];
const rightItemComps = [];
const leftItemComps:any[] = [];
const centerItemComps:any[] = [];
const rightItemComps:any[] = [];
if (this.props.items) {
for (let i = 0; i < this.props.items.length; i++) {
@@ -45,8 +52,6 @@ class ToolbarComponent extends React.Component {
o
);
if (this.props.disabled) props.disabled = true;
if (o.name === 'toggleEditors') {
rightItemComps.push(<ToggleEditorsButton
key={o.name}
@@ -79,10 +84,8 @@ class ToolbarComponent extends React.Component {
}
}
const mapStateToProps = state => {
const mapStateToProps = (state:any) => {
return { themeId: state.settings.theme };
};
const Toolbar = connect(mapStateToProps)(ToolbarComponent);
module.exports = Toolbar;
export default connect(mapStateToProps)(ToolbarBaseComponent);

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.2.1",
"version": "1.2.6",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1535,7 +1535,7 @@
},
"mkdirp": {
"version": "0.5.1",
"resolved": false,
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
@@ -1701,7 +1701,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true,
"optional": true
@@ -1817,7 +1817,7 @@
},
"tar": {
"version": "4.4.8",
"resolved": false,
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
"dev": true,
"optional": true,
@@ -6122,7 +6122,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": false,
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true,
"optional": true

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.2.1",
"version": "1.2.6",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {

View File

@@ -28,7 +28,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/
Operating System | Download | Alt. Download
-----------------|----------|----------------
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.2.1/joplin-v1.2.1.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.2.1/joplin-v1.2.1-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.2.6/joplin-v1.2.6.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.2.6/joplin-v1.2.6-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplinapp.org/images/BadgeIOS.png'/></a> | -
## Terminal application

View File

@@ -125,8 +125,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097578
versionName "1.2.1"
versionCode 2097583
versionName "1.2.6"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -80,8 +80,19 @@
<!-- END RN-push-notitication -->
<!-- ============================= -->
<!-- 2018-12-16: Changed android:launchMode from "singleInstance" to "singleTop" for Firebase notification -->
<!-- Previously singleInstance was necessary to prevent multiple instance of the RN app from running at the same time, but maybe no longer needed. -->
<!--
2018-12-16: Changed android:launchMode from "singleInstance" to "singleTop" for Firebase notification
Previously singleInstance was necessary to prevent multiple instance of the RN app from running at the same time, but maybe no longer needed.
2020-10-06: Changed back again to "singleInstance" and notifications still seem to work. Changing to singleInstance
to try to fix this bug: https://discourse.joplinapp.org/t/joplin-android-app-looses-nextcloud-sync-settings/10997/6
Users would lose their settings, and it's possibly due to multiple instances of the app running at the same time, perhaps
due to sharing with the app. When checking the log, it would show "saving settings", then the app startup message, and after the app
has started, it would show "setting saved". So basically the app, or one instance of it, has started while settings were being saved
2020-10-08: Changed back again to "singleTop" as it has worked so far. The multiple instance bug was "fixed" in a different way
See ReactNativeClient/root.js for more info about the bug.
-->
<activity
android:name=".MainActivity"
android:label="@string/app_name"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -337,7 +337,7 @@
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 53;
CURRENT_PROJECT_VERSION = 54;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = A9BXAFS6CT;
HEADER_SEARCH_PATHS = (
@@ -380,7 +380,7 @@
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 53;
CURRENT_PROJECT_VERSION = 54;
DEVELOPMENT_TEAM = A9BXAFS6CT;
HEADER_SEARCH_PATHS = (
"$(inherited)",

View File

@@ -499,7 +499,11 @@ class BaseApplication {
refreshNotesUseSelectedNoteId = true;
}
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
// Should refresh the notes when:
// - A tag is selected, to show the notes for that tag
// - When a tag is updated so that when searching by tags, the search results are updated
// https://github.com/laurent22/joplin/issues/3754
if (['TAG_SELECT', 'TAG_DELETE', 'TAG_UPDATE_ONE', 'NOTE_TAG_REMOVE'].includes(action.type)) {
refreshNotes = true;
}
@@ -736,10 +740,23 @@ class BaseApplication {
setLocale(Setting.value('locale'));
}
if (Setting.value('db.fuzzySearchEnabled') === -1) {
const fuzzySearchEnabled = await this.database_.fuzzySearchEnabled();
Setting.setValue('db.fuzzySearchEnabled', fuzzySearchEnabled ? 1 : 0);
}
// if (Setting.value('db.fuzzySearchEnabled') === -1) {
// const fuzzySearchEnabled = await this.database_.fuzzySearchEnabled();
// Setting.setValue('db.fuzzySearchEnabled', fuzzySearchEnabled ? 1 : 0);
// }
// // Always disable on CLI because building and packaging the extension is not working
// // and is too error-prone - requires gcc on the machine, or we should package the .so
// // and dylib files, but it's not sure it would work everywhere if not built from
// // source on the target machine.
// if (Setting.value('appType') !== 'desktop') {
// Setting.setValue('db.fuzzySearchEnabled', 0);
// }
// For now always disable fuzzy search due to performance issues:
// https://discourse.joplinapp.org/t/1-1-4-keyboard-locks-up-while-typing/11231/11
// https://discourse.joplinapp.org/t/serious-lagging-when-there-are-tens-of-thousands-of-notes/11215/23
Setting.setValue('db.fuzzySearchEnabled', 0);
if (Setting.value('encryption.shouldReencrypt') < 0) {
// We suggest re-encryption if the user has at least one notebook

View File

@@ -407,7 +407,9 @@ class NoteScreenComponent extends BaseScreenComponent {
this.saveActionQueue(this.state.note.id).processAllNow();
this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange);
// It cannot theoretically be undefined, since componentDidMount should always be called before
// componentWillUnmount, but with React Native the impossible often becomes possible.
if (this.undoRedoService_) this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange);
}
title_changeText(text) {

View File

@@ -11,6 +11,7 @@ const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { themeStyle } = require('lib/components/global-style.js');
const DialogBox = require('react-native-dialogbox').default;
const SearchEngineUtils = require('lib/services/searchengine/SearchEngineUtils');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
Icon.loadFont();
@@ -72,18 +73,6 @@ class SearchScreenComponent extends BaseScreenComponent {
this.isMounted_ = false;
}
// UNSAFE_componentWillReceiveProps(newProps) {
// console.info('UNSAFE_componentWillReceiveProps', newProps);
// let newState = {};
// if ('query' in newProps && !this.state.query) newState.query = newProps.query;
// if (Object.getOwnPropertyNames(newState).length) {
// this.setState(newState);
// this.refreshSearch(newState.query);
// }
// }
searchTextInput_submit() {
const query = this.state.query.trim();
if (!query) return;
@@ -134,6 +123,14 @@ class SearchScreenComponent extends BaseScreenComponent {
if (!this.isMounted_) return;
const parsedQuery = await SearchEngine.instance().parseQuery(query);
const highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
this.props.dispatch({
type: 'SET_HIGHLIGHTED',
words: highlightedWords,
});
this.setState({ notes: notes });
}

View File

@@ -37,7 +37,15 @@ class DatabaseDriverNode {
}
loadExtension(path) {
return this.db_.loadExtension(path);
return new Promise((resolve, reject) => {
this.db_.loadExtension(path, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
selectAll(sql, params = null) {

View File

@@ -9,6 +9,8 @@ class Database {
this.logger_ = new Logger();
this.logExcludedQueryTypes_ = [];
this.batchTransactionMutex_ = new Mutex();
this.profilingEnabled_ = false;
this.queryId_ = 1;
}
setLogExcludedQueryTypes(v) {
@@ -71,10 +73,30 @@ class Database {
let waitTime = 50;
let totalWaitTime = 0;
const callStartTime = Date.now();
let profilingTimeoutId = null;
while (true) {
try {
this.logQuery(sql, params);
const queryId = this.queryId_++;
if (this.profilingEnabled_) {
console.info(`SQL START ${queryId}`, sql, params);
profilingTimeoutId = setInterval(() => {
console.warn(`SQL ${queryId} has been running for ${Date.now() - callStartTime}: ${sql}`);
}, 3000);
}
const result = await this.driver()[callName](sql, params);
if (this.profilingEnabled_) {
clearInterval(profilingTimeoutId);
profilingTimeoutId = null;
const elapsed = Date.now() - callStartTime;
if (elapsed > 10) console.info(`SQL END ${queryId}`, elapsed, sql, params);
}
return result; // No exception was thrown
} catch (error) {
if (error && (error.code == 'SQLITE_IOERR' || error.code == 'SQLITE_BUSY')) {
@@ -89,6 +111,8 @@ class Database {
} else {
throw this.sqliteErrorToJsError(error, sql, params);
}
} finally {
if (profilingTimeoutId) clearInterval(profilingTimeoutId);
}
}
}
@@ -97,14 +121,16 @@ class Database {
return this.tryCall('selectOne', sql, params);
}
async loadExtension(path) {
let result = null;
try {
result = await this.driver().loadExtension(path);
return result;
} catch (e) {
throw new Error(`Could not load extension ${path}`);
}
async loadExtension(/* path */) {
return; // Disabled for now as fuzzy search extension is not in use
// let result = null;
// try {
// result = await this.driver().loadExtension(path);
// return result;
// } catch (e) {
// throw new Error(`Could not load extension ${path}`);
// }
}
async selectAll(sql, params = null) {

View File

@@ -0,0 +1,13 @@
import useEffectDebugger from './useEffectDebugger';
export default function usePropsDebugger(effectHook:any, props:any) {
const dependencies:any[] = [];
const dependencyNames:string[] = [];
for (const k in props) {
dependencies.push(props[k]);
dependencyNames.push(k);
}
useEffectDebugger(effectHook, dependencies, dependencyNames);
}

View File

@@ -3,7 +3,6 @@ const { Database } = require('lib/database.js');
const { sprintf } = require('sprintf-js');
const Resource = require('lib/models/Resource');
const { shim } = require('lib/shim.js');
const EventEmitter = require('events');
const structureSql = `
CREATE TABLE folders (
@@ -127,11 +126,6 @@ class JoplinDatabase extends Database {
this.version_ = null;
this.tableFieldNames_ = {};
this.extensionToLoad = './build/lib/sql-extensions/spellfix';
this.eventEmitter_ = new EventEmitter();
}
eventEmitter() {
return this.eventEmitter_;
}
initialized() {
@@ -349,6 +343,8 @@ class JoplinDatabase extends Database {
+ `Expected version: ${existingDatabaseVersions[existingDatabaseVersions.length - 1]}`);
}
this.logger().info(`Upgrading database from version ${fromVersion}`);
if (currentVersionIndex == existingDatabaseVersions.length - 1) return fromVersion;
let latestVersion = fromVersion;
@@ -359,8 +355,6 @@ class JoplinDatabase extends Database {
let queries = [];
this.eventEmitter_.emit('startMigration', { version: targetVersion });
if (targetVersion == 1) {
queries = this.wrapQueries(this.sqlStringToLines(structureSql));
}
@@ -856,18 +850,32 @@ class JoplinDatabase extends Database {
queries.push('CREATE VIRTUAL TABLE notes_spellfix USING spellfix1');
}
queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] });
const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
queries.push(updateVersionQuery);
try {
await this.transactionExecBatch(queries);
} catch (error) {
// In some cases listed below, when the upgrade fail it is acceptable (a fallback will be used)
// and in those cases, even though it fails, we still want to set the version number so that the
// migration is not repeated on next upgrade.
let saveVersionAgain = false;
if (targetVersion === 15 || targetVersion === 18 || targetVersion === 33) {
this.logger().warn('Could not upgrade to database v15 or v18 or v33 - FTS feature will not be used', error);
saveVersionAgain = true;
} else if (targetVersion === 34) {
this.logger().warn('Could not upgrade to database v34 - fuzzy search will not be used', error);
saveVersionAgain = true;
} else {
throw error;
}
if (saveVersionAgain) {
this.logger().info('Migration failed with fallback and will not be repeated - saving version number');
await this.transactionExecBatch([updateVersionQuery]);
}
}
latestVersion = targetVersion;
@@ -910,9 +918,12 @@ class JoplinDatabase extends Database {
this.logger().info('Checking for database schema update...');
try {
// Note that the only extension that can be loaded as of now is spellfix.
// If it fails here, it will fail on the fuzzySearchEnabled() check above
// too, thus disabling spellfix for the app.
await this.loadExtension(this.extensionToLoad);
} catch (error) {
console.info(error);
this.logger().error(error);
}
let versionRow = null;
@@ -923,17 +934,20 @@ class JoplinDatabase extends Database {
if (error.message && error.message.indexOf('no such table: version') >= 0) {
// Ignore
} else {
console.info(error);
this.logger().info(error);
}
}
const version = !versionRow ? 0 : versionRow.version;
const tableFieldsVersion = !versionRow ? 0 : versionRow.table_fields_version;
this.version_ = version;
this.logger().info('Current database version', version);
this.logger().info('Current database version', versionRow);
const newVersion = await this.upgradeDatabase(version);
this.version_ = newVersion;
this.logger().info(`New version: ${newVersion}. Previously recorded version: ${tableFieldsVersion}`);
if (newVersion !== tableFieldsVersion) await this.refreshTableFields(newVersion);
this.tableFields_ = {};

View File

@@ -286,6 +286,7 @@ module.exports = function(theme) {
opacity: 0.7;
}
.jop-tinymce ul.joplin-checklist .checked,
.md-checkbox .checkbox-label-checked {
opacity: 0.5;
}

View File

@@ -156,6 +156,8 @@ class BaseItem extends BaseModel {
}
static async loadItemsByIds(ids) {
if (!ids.length) return [];
const classes = this.syncItemClassNames();
let output = [];
for (let i = 0; i < classes.length; i++) {

View File

@@ -109,6 +109,7 @@ class Tag extends BaseItem {
static async tagsByNoteId(noteId) {
const tagIds = await NoteTag.tagIdsByNoteId(noteId);
if (!tagIds.length) return [];
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${tagIds.join('","')}")`);
}

View File

@@ -0,0 +1,56 @@
const Folder = require('lib/models/Folder');
const Note = require('lib/models/Note');
function randomIndex(array:any[]):number {
return Math.round(Math.random() * (array.length - 1));
}
export default async function populateDatabase(db:any) {
await db.clearForTesting();
const folderCount = 2000;
const noteCount = 20000;
const createdFolderIds:string[] = [];
const createdNoteIds:string[] = [];
for (let i = 0; i < folderCount; i++) {
const folder:any = {
title: `folder${i}`,
};
const isRoot = Math.random() <= 0.1 || i === 0;
if (!isRoot) {
const parentIndex = randomIndex(createdFolderIds);
folder.parent_id = createdFolderIds[parentIndex];
}
const savedFolder = await Folder.save(folder);
createdFolderIds.push(savedFolder.id);
console.info(`Folders: ${i} / ${folderCount}`);
}
let noteBatch = [];
for (let i = 0; i < noteCount; i++) {
const note:any = { title: `note${i}`, body: `This is note num. ${i}` };
const parentIndex = randomIndex(createdFolderIds);
note.parent_id = createdFolderIds[parentIndex];
noteBatch.push(Note.save(note, { dispatchUpdateAction: false }).then((savedNote:any) => {
createdNoteIds.push(savedNote.id);
console.info(`Notes: ${i} / ${noteCount}`);
}));
if (noteBatch.length > 1000) {
await Promise.all(noteBatch);
noteBatch = [];
}
}
if (noteBatch.length) {
await Promise.all(noteBatch);
noteBatch = [];
}
}

View File

@@ -460,6 +460,15 @@ class SearchEngine {
const fuzzyTitle = await this.fuzzifier(titleTerms.filter(x => !x.wildcard).map(x => trimQuotes(x.value)));
const fuzzyBody = await this.fuzzifier(bodyTerms.filter(x => !x.wildcard).map(x => trimQuotes(x.value)));
// Floor the fuzzy scores to 0, 1 and 2.
const floorFuzzyScore = (matches) => {
for (let i = 0; i < matches.length; i++) matches[i].score = i;
};
fuzzyText.forEach(floorFuzzyScore);
fuzzyTitle.forEach(floorFuzzyScore);
fuzzyBody.forEach(floorFuzzyScore);
const phraseTextSearch = textTerms.filter(x => x.quoted);
const wildCardSearch = textTerms.concat(titleTerms).concat(bodyTerms).filter(x => x.wildcard);

View File

@@ -4881,7 +4881,6 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -8078,9 +8077,9 @@
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
"patch-package": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.1.tgz",
"integrity": "sha512-dfCtQor63PPij6DDYtCzBRoO5nNAcMSg7Cmh+DLhR+s3t0OLQBdvFxJksZHBe1J2MjsSWDjTF4+oQKFbdkssIg==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.2.tgz",
"integrity": "sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg==",
"dev": true,
"requires": {
"@yarnpkg/lockfile": "^1.1.0",
@@ -9993,9 +9992,9 @@
"integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
},
"slug": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/slug/-/slug-3.3.4.tgz",
"integrity": "sha512-VpHbtRCEWmgaZsrZcTsVl/Dhw98lcrOYDO17DNmJCNpppI6s3qJvnNu2Q3D4L84/2bi6vkW40mjNQI9oGQsflg=="
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/slug/-/slug-3.4.0.tgz",
"integrity": "sha512-s234DYtuRCkzVNL8dL9BRFNmlZUF9NUGjxWG+wwBKPzUFUADrhkjKGVNhDaZs2Lc+UKh3085KItaKilwJA9I2Q=="
},
"slugify": {
"version": "1.3.6",

View File

@@ -83,13 +83,13 @@
"redux": "4.0.0",
"reselect": "^4.0.0",
"rn-fetch-blob": "^0.12.0",
"slug": "^3.4.0",
"stream": "0.0.2",
"string-natural-compare": "^2.0.2",
"string-padding": "^1.0.2",
"timers": "^0.1.1",
"url": "^0.11.0",
"url-parse": "^1.4.7",
"slug": "^3.3.4",
"uuid": "^3.0.1",
"valid-url": "^1.0.9",
"word-wrap": "^1.2.3",
@@ -104,7 +104,7 @@
"gulp": "^4.0.2",
"jetifier": "^1.6.5",
"metro-react-native-babel-preset": "^0.54.1",
"patch-package": "^6.2.1",
"patch-package": "^6.2.2",
"react-test-renderer": "^16.8.3"
}
}

View File

@@ -2,7 +2,7 @@ import setUpQuickActions from './setUpQuickActions';
import PluginAssetsLoader from './PluginAssetsLoader';
const React = require('react');
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Text, Image } = require('react-native');
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native');
const SafeAreaView = require('lib/components/SafeAreaView');
const { connect, Provider } = require('react-redux');
const { BackButtonService } = require('lib/services/back-button.js');
@@ -376,7 +376,7 @@ function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
ResourceFetcher.instance().scheduleAutoAddResources();
}
async function initialize(dispatch, messageHandler) {
async function initialize(dispatch) {
shimInit();
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
@@ -415,13 +415,8 @@ async function initialize(dispatch, messageHandler) {
dbLogger.setLevel(Logger.LEVEL_INFO);
}
const db_startUpgrade = (event) => {
messageHandler(`Upgrading database to v${event.version}...`);
};
const db = new JoplinDatabase(new DatabaseDriverReactNative());
db.setLogger(dbLogger);
db.eventEmitter().on('startMigration', db_startUpgrade);
reg.setDb(db);
reg.dispatch = dispatch;
@@ -458,13 +453,9 @@ async function initialize(dispatch, messageHandler) {
// await db.clearForTesting();
}
db.eventEmitter().removeListener('startMigration', db_startUpgrade);
reg.logger().info('Database is ready.');
reg.logger().info('Loading settings...');
messageHandler('Initialising application...');
await loadKeychainServiceAndSettings(KeychainServiceDriverMobile);
if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create());
@@ -611,7 +602,6 @@ class AppComponent extends React.Component {
this.state = {
sideMenuContentOpacity: new Animated.Value(0),
initMessage: '',
};
this.lastSyncStarted_ = defaultState.syncStarted;
@@ -625,52 +615,66 @@ class AppComponent extends React.Component {
};
}
componentDidMount() {
setTimeout(async () => {
// We run initialization code with a small delay to give time
// to the view to render "please wait" messages.
// 2020-10-08: It seems the initialisation code is quite fragile in general and should be kept simple.
// For example, adding a loading screen as was done in this commit: https://github.com/laurent22/joplin/commit/569355a3182bc12e50a54249882e3d68a72c2b28.
// had for effect that sharing with the app would create multiple instances of the app, thus breaking
// database access and so on. It's unclear why it happens and how to fix it but reverting that commit
// fixed the issue for now.
//
// Changing app launch mode doesn't help.
//
// It's possible that it's a bug in React Native, or perhaps the framework expects that the whole app can be
// mounted/unmounted or multiple ones can be running at the same time, but the app was not designed in this
// way.
//
// More reports and info about the multiple instance bug:
//
// https://github.com/laurent22/joplin/issues/3800
// https://github.com/laurent22/joplin/issues/3804
// https://github.com/laurent22/joplin/issues/3807
// https://discourse.joplinapp.org/t/webdav-config-encryption-config-randomly-lost-on-android/11364
// https://discourse.joplinapp.org/t/android-keeps-on-resetting-my-sync-and-theme/11443
async componentDidMount() {
if (this.props.appState == 'starting') {
this.props.dispatch({
type: 'APP_STATE_SET',
state: 'initializing',
});
await initialize(this.props.dispatch, (message) => {
this.setState({ initMessage: message });
});
BackButtonService.initialize(this.backButtonHandler_);
AlarmService.setInAppNotificationHandler(async (alarmId) => {
const alarm = await Alarm.load(alarmId);
const notification = await Alarm.makeNotification(alarm);
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
});
AppState.addEventListener('change', this.onAppStateChange_);
const sharedData = await ShareExtension.data();
if (sharedData) {
reg.logger().info('Received shared data');
if (this.props.selectedFolderId) {
handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
} else {
reg.logger.info('Cannot handle share - default folder id is not set');
}
}
await initialize(this.props.dispatch);
this.props.dispatch({
type: 'APP_STATE_SET',
state: 'ready',
});
}, 100);
}
BackButtonService.initialize(this.backButtonHandler_);
AlarmService.setInAppNotificationHandler(async (alarmId) => {
const alarm = await Alarm.load(alarmId);
const notification = await Alarm.makeNotification(alarm);
this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : '');
});
AppState.addEventListener('change', this.onAppStateChange_);
const sharedData = await ShareExtension.data();
if (sharedData) {
reg.logger().info('Received shared data');
if (this.props.selectedFolderId) {
handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch);
} else {
reg.logger.info('Cannot handle share - default folder id is not set');
}
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onAppStateChange_);
}
async componentDidUpdate(prevProps) {
componentDidUpdate(prevProps) {
if (this.props.showSideMenu !== prevProps.showSideMenu) {
Animated.timing(this.state.sideMenuContentOpacity, {
toValue: this.props.showSideMenu ? 0.5 : 0,
@@ -715,19 +719,8 @@ class AppComponent extends React.Component {
});
}
renderStartupScreen() {
return (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<View style={{ alignItems: 'center' }}>
<Image style={{ marginBottom: 5 }} source={require('./images/StartUpIcon.png')} />
<Text style={{ color: '#444444' }}>{this.state.initMessage}</Text>
</View>
</View>
);
}
render() {
if (this.props.appState != 'ready') return this.renderStartupScreen();
if (this.props.appState != 'ready') return null;
const theme = themeStyle(this.props.themeId);
let sideMenuContent = null;

View File

@@ -281,29 +281,6 @@ const operations = [
iconWidth: 46,
iconHeight: 46,
},
// ============================================================================
// Mobile startup icon
// ============================================================================
{
source: 7,
dest: 'ReactNativeClient/images/StartUpIcon.png',
width: 64,
height: 64,
},
{
source: 7,
dest: 'ReactNativeClient/images/StartUpIcon@2x.png',
width: 128,
height: 128,
},
{
source: 7,
dest: 'ReactNativeClient/images/StartUpIcon@3x.png',
width: 192,
height: 192,
},
];
async function main() {

View File

@@ -151,7 +151,9 @@ async function createRelease(name, tagName, version) {
async function main() {
const argv = require('yargs').argv;
const isPreRelease = !!argv.prerelease;
if (!['release', 'prerelease'].includes(argv.type)) throw new Error('Must specify release type. Either --type=release or --type=prerelease');
const isPreRelease = argv.type === 'prerelease';
if (isPreRelease) console.info('Creating pre-release');
console.info('Updating version numbers in build.gradle...');

View File

@@ -3,7 +3,11 @@
{
"name": ".",
"path": "."
}
},
{
"name": "D:/Web/www/nextcloud/apps/joplin",
"path": "D:/Web/www/nextcloud/apps/joplin"
},
],
"settings": {
"files.exclude": {

38
patches/slug+3.4.0.patch Normal file
View File

@@ -0,0 +1,38 @@
diff --git a/node_modules/slug/slug.js b/node_modules/slug/slug.js
index b40320b..2be9650 100644
--- a/node_modules/slug/slug.js
+++ b/node_modules/slug/slug.js
@@ -51,13 +51,31 @@
throw new Error('String "' + str + '" reaches code believed to be unreachable; please open an issue at https://github.com/Trott/slug/issues/new')
}
- if (typeof window === 'undefined') {
+ if (typeof window !== 'undefined' && window.btoa) {
+ base64 = function (input) {
+ return btoa(unescape(encodeURIComponent(input)))
+ }
+ } else if (typeof Buffer !== 'undefined') {
base64 = function (input) {
return Buffer.from(input).toString('base64')
}
} else {
+ // Polyfill for environments that don't have any btoa or Buffer class (eg. React Native)
+ // Copied from https://github.com/davidchambers/Base64.js/blob/a121f75bb10c8dd5d557886c4b1069b31258d230/base64.js#L22
base64 = function (input) {
- return btoa(unescape(encodeURIComponent(input)))
+ var str = unescape(encodeURIComponent(input + ''))
+ for (
+ var block, charCode, idx = 0, map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', output = '';
+ str.charAt(idx | 0) || (map = '=', idx % 1);
+ output += map.charAt(63 & block >> 8 - idx % 1 * 8)
+ ) {
+ charCode = str.charCodeAt(idx += 3 / 4)
+ if (charCode > 0xFF) {
+ throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.")
+ }
+ block = block << 8 | charCode
+ }
+ return output
}
}

View File

@@ -1,5 +1,21 @@
# Joplin terminal app changelog
## [cli-v1.2.3](https://github.com/laurent22/joplin/releases/tag/cli-v1.2.3) - 2020-10-09T11:17:18Z
- Improved: Improved handling of database migration failures
## [cli-v1.2.2](https://github.com/laurent22/joplin/releases/tag/cli-v1.2.2) - 2020-09-29T11:33:53Z
- Fixed: Fixed crash due to missing spellfix extension
- Fixed: Fixed link generation when exporting to PDF or HTML (#3780)
- Fixed: Improved handling of special characters when exporting to Markdown (#3760)
## [cli-v1.2.1](https://github.com/laurent22/joplin/releases/tag/cli-v1.2.1) - 2020-09-23T11:15:12Z
- Fixed: Fixed crash due to missing spellfix extension
- Fixed: Fixed link generation when exporting to PDF or HTML (#3780)
- Fixed: Improved handling of special characters when exporting to Markdown (#3760)
## [cli-v1.1.8](https://github.com/laurent22/joplin/releases/tag/cli-v1.1.8) - 2020-09-21T12:02:29Z
- Improved: Do not prevent export when one item is still encrypted