You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-09-02 20:46:21 +02:00
Compare commits
44 Commits
android-v1
...
android-v1
Author | SHA1 | Date | |
---|---|---|---|
|
28e00fdf2e | ||
|
3bd0656eab | ||
|
e9af71dd76 | ||
|
73b33e8e32 | ||
|
c2c7efee91 | ||
|
0836fca822 | ||
|
566df5039c | ||
|
559655bf33 | ||
|
0eab23fbcf | ||
|
f334f4f487 | ||
|
00057da17d | ||
|
0a05464013 | ||
|
9ebb574059 | ||
|
d29c3c2466 | ||
|
a71f1c19ec | ||
|
485921d879 | ||
|
15de7572c0 | ||
|
09f41dd50e | ||
|
7b8ee467a0 | ||
|
99a496d684 | ||
|
f43ee123d8 | ||
|
f42fb1b871 | ||
|
cf2442c5b2 | ||
|
e0e4735b03 | ||
|
8bd58c9608 | ||
|
215a725ded | ||
|
12c0a05af0 | ||
|
a7fa119041 | ||
|
7fb52b8b0e | ||
|
3e86ae4a82 | ||
|
947d81d96d | ||
|
6ca640d2ed | ||
|
6aca233b21 | ||
|
2200be697e | ||
|
25ab3c323b | ||
|
5bf30a9586 | ||
|
b6779a8074 | ||
|
59599d318c | ||
|
538600fd6c | ||
|
63264ba471 | ||
|
95e7f3df7d | ||
|
366fd2a333 | ||
|
5be99a4a16 | ||
|
d86f6a1fbd |
@@ -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
5
.gitignore
vendored
@@ -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 |
@@ -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"
|
||||
|
@@ -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."
|
||||
|
2
CliClient/package-lock.json
generated
2
CliClient/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -28,7 +28,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.2",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
|
@@ -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);
|
||||
// }));
|
||||
|
||||
});
|
||||
// });
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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}>
|
||||
|
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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 () => {};
|
||||
|
||||
|
@@ -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} />;
|
||||
}
|
||||
|
@@ -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();
|
||||
}, []);
|
||||
}
|
@@ -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;
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
|
@@ -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');
|
||||
|
@@ -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;
|
@@ -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) => {
|
||||
|
@@ -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};
|
||||
}
|
||||
`;
|
||||
|
@@ -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>
|
||||
|
@@ -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);
|
||||
}}
|
||||
|
@@ -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`
|
||||
|
@@ -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);
|
10
ElectronClient/package-lock.json
generated
10
ElectronClient/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.4",
|
||||
"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
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Joplin",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.4",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
@@ -125,8 +125,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097578
|
||||
versionName "1.2.1"
|
||||
versionCode 2097582
|
||||
versionName "1.2.5"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
|
@@ -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 |
@@ -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
|
||||
|
@@ -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) {
|
||||
|
@@ -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 });
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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) {
|
||||
|
13
ReactNativeClient/lib/hooks/usePropsDebugger.ts
Normal file
13
ReactNativeClient/lib/hooks/usePropsDebugger.ts
Normal 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);
|
||||
}
|
@@ -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_ = {};
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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++) {
|
||||
|
@@ -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('","')}")`);
|
||||
}
|
||||
|
||||
|
56
ReactNativeClient/lib/services/debug/populateDatabase.ts
Normal file
56
ReactNativeClient/lib/services/debug/populateDatabase.ts
Normal 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 = [];
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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() {
|
||||
|
@@ -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...');
|
||||
|
@@ -3,7 +3,11 @@
|
||||
{
|
||||
"name": ".",
|
||||
"path": "."
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "D:/Web/www/nextcloud/apps/joplin",
|
||||
"path": "D:/Web/www/nextcloud/apps/joplin"
|
||||
},
|
||||
],
|
||||
"settings": {
|
||||
"files.exclude": {
|
||||
|
@@ -1,5 +1,17 @@
|
||||
# Joplin terminal app changelog
|
||||
|
||||
## [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
|
||||
|
Reference in New Issue
Block a user