You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
193 Commits
android-v2
...
android-v2
Author | SHA1 | Date | |
---|---|---|---|
|
431b95ff7b | ||
|
041fb731a6 | ||
|
5289f80394 | ||
|
6c12ce0e04 | ||
|
46982c7d64 | ||
|
359448eb69 | ||
|
32bb256cca | ||
|
219585bbcf | ||
|
1a9dbcbd1d | ||
|
6a9848ebe7 | ||
|
9e73d3590b | ||
|
08c9a25182 | ||
|
5b9a45dc1d | ||
|
9dd2fb9674 | ||
|
234b5c8363 | ||
|
1dc7ec3701 | ||
|
72773caf58 | ||
|
094249d074 | ||
|
c324f17453 | ||
|
25a31b0689 | ||
|
f2995dd196 | ||
|
ca575162f7 | ||
|
f0ade02435 | ||
|
b13c02017a | ||
|
716c8c1ce4 | ||
|
30a49b84ad | ||
|
b8e4150bfd | ||
|
ed0edcb36c | ||
|
e93046e8e2 | ||
|
18a0ca0881 | ||
|
21dbc800d5 | ||
|
5c1eda3392 | ||
|
1139317788 | ||
|
a24ccb8da9 | ||
|
0aaa396315 | ||
|
f9dc19e1c4 | ||
|
1839724d7c | ||
|
a5aeb3a2f8 | ||
|
d5d57aa360 | ||
|
9aa5df7790 | ||
|
a7697465a8 | ||
|
aeb7e5ce47 | ||
|
42cef1e918 | ||
|
b832930512 | ||
|
057ac550bd | ||
|
3a14b76a61 | ||
|
e1a8c76598 | ||
|
b62e6552cd | ||
|
ca6e50e80c | ||
|
ad8947a85d | ||
|
fb562210b2 | ||
|
34940d1c4f | ||
|
7fa1459dc3 | ||
|
625689dbb1 | ||
|
dc976047d2 | ||
|
a014e830e7 | ||
|
1e828bfdca | ||
|
60309ec6a3 | ||
|
aabba090b1 | ||
|
6ae903ef4d | ||
|
dd86940c6b | ||
|
7d7b7ed6f3 | ||
|
8de904cd3c | ||
|
c706b8dd2f | ||
|
d55d4d42e5 | ||
|
bbfeffec69 | ||
|
3c471dc120 | ||
|
be0fa69b3b | ||
|
bf1bdf0951 | ||
|
a1a10a6c55 | ||
|
02e8307093 | ||
|
285a3a211e | ||
|
80cb1471fc | ||
|
a7a194f835 | ||
|
41a0b3359a | ||
|
1f70357d37 | ||
|
107f2e128e | ||
|
e1cd8d9b85 | ||
|
293f621e46 | ||
|
c5b551bbcb | ||
|
ac776eabc1 | ||
|
05c17fbfac | ||
|
6b9de394ca | ||
|
bef9a29581 | ||
|
1546aad7e9 | ||
|
fe47fef261 | ||
|
288f4ab43b | ||
|
294cbaea4d | ||
|
f5fc1f2f22 | ||
|
c90865c4d2 | ||
|
322641ccd6 | ||
|
2656666ed8 | ||
|
2d673902a4 | ||
|
631c41a1ff | ||
|
7841c99c02 | ||
|
ac75d8f6ac | ||
|
200ff617dc | ||
|
bbdc18f371 | ||
|
0d35b64f9a | ||
|
765c4482d6 | ||
|
0a0e31a37c | ||
|
0c1f4031b4 | ||
|
9ed022458b | ||
|
ba5f0bc6e3 | ||
|
793e8f6c0f | ||
|
6182ce521d | ||
|
544c50663a | ||
|
53aa9e2b42 | ||
|
884260189c | ||
|
2f9464f21f | ||
|
c6e993b04a | ||
|
89eb012b25 | ||
|
1e2aa4e2b5 | ||
|
0019bb8d6b | ||
|
e629a4d325 | ||
|
049c769d37 | ||
|
47aed8742a | ||
|
8aad67ccfe | ||
|
af7cbcbca7 | ||
|
88a91314af | ||
|
9873c2d756 | ||
|
46b68cf461 | ||
|
6b96b1f355 | ||
|
dc819700bb | ||
|
1d1d5fea06 | ||
|
c3afc0ede7 | ||
|
2092110a6e | ||
|
fc940e9a7c | ||
|
8718310dd0 | ||
|
1b527f2bbe | ||
|
4da217bc2f | ||
|
d3abd4ebf2 | ||
|
38851edf86 | ||
|
05efb765d6 | ||
|
28dc4a6abd | ||
|
2f7b56f96f | ||
|
fdaa3735fb | ||
|
7dfaea12f7 | ||
|
18199b27d9 | ||
|
a7c52082bb | ||
|
3b5357e0c1 | ||
|
10dd4e45ed | ||
|
1963835309 | ||
|
46ec0c1381 | ||
|
bb84ae4d68 | ||
|
b3ff53c0da | ||
|
acd7bfd9f5 | ||
|
1dff50d080 | ||
|
af40970d09 | ||
|
07535a494e | ||
|
907422cefa | ||
|
f643baea25 | ||
|
55a4f33982 | ||
|
df6700959a | ||
|
fde8235f3e | ||
|
f6ba56d966 | ||
|
4a5312823b | ||
|
31a27b0e1c | ||
|
984ad868e8 | ||
|
9b657eeda2 | ||
|
6f3ad4b3b0 | ||
|
56f06fae3c | ||
|
c22d884357 | ||
|
35dc22197d | ||
|
70d56ca0be | ||
|
bca09b9476 | ||
|
b82bf16505 | ||
|
2f254d81cd | ||
|
b14ce03e5b | ||
|
90b04cbd37 | ||
|
5ae866ea85 | ||
|
b450ab9f5a | ||
|
138bc8144b | ||
|
c9831833c4 | ||
|
2813f93c18 | ||
|
27bec674a0 | ||
|
ff79ca8781 | ||
|
34a1342db6 | ||
|
e252986b98 | ||
|
3537c3e5f9 | ||
|
fdc86f94c4 | ||
|
dc5dc94ed5 | ||
|
f7682d3da3 | ||
|
8e2975d23d | ||
|
ce85489166 | ||
|
13f5738090 | ||
|
cce2ae7401 | ||
|
c9b49a50c8 | ||
|
c419c43622 | ||
|
cc5ecfba2b | ||
|
a98d5feff6 | ||
|
7aa4feffd4 | ||
|
0b46a744f1 |
@@ -142,7 +142,12 @@ packages/app-desktop/gui/EditFolderDialog/IconSelector.js
|
||||
packages/app-desktop/gui/EmojiBox.js
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js
|
||||
packages/app-desktop/gui/ErrorBoundary.js
|
||||
packages/app-desktop/gui/ExtensionBadge.js
|
||||
packages/app-desktop/gui/FolderIconBox.js
|
||||
packages/app-desktop/gui/HelpButton.js
|
||||
packages/app-desktop/gui/IconButton.js
|
||||
packages/app-desktop/gui/ImportScreen.js
|
||||
packages/app-desktop/gui/ItemList.js
|
||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
|
||||
packages/app-desktop/gui/KeymapConfig/ShortcutRecorder.js
|
||||
packages/app-desktop/gui/KeymapConfig/styles/index.js
|
||||
@@ -172,6 +177,7 @@ packages/app-desktop/gui/MainScreen/commands/openTag.js
|
||||
packages/app-desktop/gui/MainScreen/commands/print.js
|
||||
packages/app-desktop/gui/MainScreen/commands/renameFolder.js
|
||||
packages/app-desktop/gui/MainScreen/commands/renameTag.js
|
||||
packages/app-desktop/gui/MainScreen/commands/resetLayout.js
|
||||
packages/app-desktop/gui/MainScreen/commands/revealResourceFile.js
|
||||
packages/app-desktop/gui/MainScreen/commands/search.js
|
||||
packages/app-desktop/gui/MainScreen/commands/setTags.js
|
||||
@@ -193,6 +199,7 @@ packages/app-desktop/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||
packages/app-desktop/gui/MasterPasswordDialog/Dialog.js
|
||||
packages/app-desktop/gui/MenuBar.js
|
||||
packages/app-desktop/gui/MultiNoteActions.js
|
||||
packages/app-desktop/gui/Navigator.js
|
||||
packages/app-desktop/gui/NoteContentPropertiesDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/CodeMirror.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Editor.js
|
||||
@@ -215,6 +222,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteEditor.js
|
||||
@@ -222,6 +230,7 @@ packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
|
||||
@@ -252,6 +261,10 @@ packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.js
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.js
|
||||
packages/app-desktop/gui/NotePropertiesDialog.js
|
||||
packages/app-desktop/gui/NoteRevisionViewer.js
|
||||
packages/app-desktop/gui/NoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteStatusBar.js
|
||||
packages/app-desktop/gui/NoteTextViewer.js
|
||||
packages/app-desktop/gui/NoteToolbar/NoteToolbar.js
|
||||
packages/app-desktop/gui/OneDriveLoginScreen.js
|
||||
@@ -290,12 +303,14 @@ packages/app-desktop/gui/Sidebar/styles/index.js
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.js
|
||||
packages/app-desktop/gui/TagItem.js
|
||||
packages/app-desktop/gui/TagList.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/styles/index.js
|
||||
packages/app-desktop/gui/ToolbarBase.js
|
||||
packages/app-desktop/gui/ToolbarButton/ToolbarButton.js
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js
|
||||
packages/app-desktop/gui/ToolbarSpace.js
|
||||
packages/app-desktop/gui/dialogs.js
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
||||
packages/app-desktop/gui/hooks/useImperativeHandlerDebugger.js
|
||||
@@ -392,6 +407,7 @@ packages/app-mobile/components/getResponsiveValue.js
|
||||
packages/app-mobile/components/getResponsiveValue.test.js
|
||||
packages/app-mobile/components/screens/ConfigScreen.js
|
||||
packages/app-mobile/components/screens/Note.js
|
||||
packages/app-mobile/components/screens/Notes.js
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||
packages/app-mobile/components/screens/encryption-config.js
|
||||
packages/app-mobile/components/screens/search.js
|
||||
@@ -467,6 +483,7 @@ packages/lib/commands/index.js
|
||||
packages/lib/commands/openMasterPasswordDialog.js
|
||||
packages/lib/commands/synchronize.js
|
||||
packages/lib/components/EncryptionConfigScreen/utils.js
|
||||
packages/lib/components/shared/note-screen-shared.js
|
||||
packages/lib/database-driver-better-sqlite.js
|
||||
packages/lib/database.js
|
||||
packages/lib/debug/DebugService.js
|
||||
@@ -496,6 +513,7 @@ packages/lib/markdownUtils.js
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils2.test.js
|
||||
packages/lib/markupLanguageUtils.js
|
||||
packages/lib/migrations/42.js
|
||||
packages/lib/models/Alarm.js
|
||||
packages/lib/models/BaseItem.js
|
||||
packages/lib/models/Folder.js
|
||||
@@ -742,8 +760,11 @@ packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/webDAVUtils.js
|
||||
packages/lib/utils/webDAVUtils.test.js
|
||||
packages/lib/uuid.js
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.test.js
|
||||
packages/pdf-viewer/FullViewer.js
|
||||
packages/pdf-viewer/Page.js
|
||||
packages/pdf-viewer/PdfDocument.js
|
||||
@@ -784,6 +805,8 @@ packages/renderer/HtmlToHtml.js
|
||||
packages/renderer/InMemoryCache.js
|
||||
packages/renderer/MarkupToHtml.js
|
||||
packages/renderer/MdToHtml.js
|
||||
packages/renderer/MdToHtml/createEventHandlingAttrs.js
|
||||
packages/renderer/MdToHtml/createEventHandlingAttrs.test.js
|
||||
packages/renderer/MdToHtml/linkReplacement.js
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js
|
||||
packages/renderer/MdToHtml/renderMedia.js
|
||||
|
22
.eslintrc.js
22
.eslintrc.js
@@ -77,6 +77,7 @@ module.exports = {
|
||||
'no-array-constructor': ['error'],
|
||||
'radix': ['error'],
|
||||
'eqeqeq': ['error', 'always'],
|
||||
'no-console': ['error', { 'allow': ['warn', 'error'] }],
|
||||
|
||||
// Warn only for now because fixing everything would take too much
|
||||
// refactoring, but new code should try to stick to it.
|
||||
@@ -91,6 +92,7 @@ module.exports = {
|
||||
// "react-hooks/exhaustive-deps": "warn",
|
||||
|
||||
'promise/prefer-await-to-then': 'error',
|
||||
'no-unneeded-ternary': 'error',
|
||||
|
||||
// -------------------------------
|
||||
// Formatting
|
||||
@@ -135,6 +137,14 @@ module.exports = {
|
||||
'spaced-comment': ['error', 'always'],
|
||||
'keyword-spacing': ['error', { 'before': true, 'after': true }],
|
||||
'no-multi-spaces': ['error'],
|
||||
|
||||
// Regarding the keyword blacklist:
|
||||
// - err: We generally avoid using too many abbreviations, so it should
|
||||
// be "error", not "err"
|
||||
// - notebook: In code, it should always be "folder" (not "notebook").
|
||||
// In user-facing text, it should be "notebook".
|
||||
'id-denylist': ['error', 'err', 'notebook', 'notebooks'],
|
||||
'prefer-arrow-callback': ['error'],
|
||||
},
|
||||
'plugins': [
|
||||
'react',
|
||||
@@ -147,6 +157,16 @@ module.exports = {
|
||||
'promise',
|
||||
],
|
||||
'overrides': [
|
||||
{
|
||||
'files': [
|
||||
'packages/tools/**',
|
||||
'packages/app-mobile/tools/**',
|
||||
'packages/app-desktop/tools/**',
|
||||
],
|
||||
'rules': {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// enable the rule specifically for TypeScript files
|
||||
'files': ['*.ts', '*.tsx'],
|
||||
@@ -159,6 +179,7 @@ module.exports = {
|
||||
// make everything public which is not great. New code however should specify member accessibility.
|
||||
'@typescript-eslint/explicit-member-accessibility': ['warn'],
|
||||
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
|
||||
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
|
||||
'@typescript-eslint/comma-dangle': ['error', {
|
||||
'arrays': 'always-multiline',
|
||||
'objects': 'always-multiline',
|
||||
@@ -169,6 +190,7 @@ module.exports = {
|
||||
'tuples': 'always-multiline',
|
||||
'functions': 'never',
|
||||
}],
|
||||
'@typescript-eslint/object-curly-spacing': ['error', 'always'],
|
||||
'@typescript-eslint/semi': ['error', 'always'],
|
||||
'@typescript-eslint/member-delimiter-style': ['error', {
|
||||
'multiline': {
|
||||
|
23
.gitignore
vendored
23
.gitignore
vendored
@@ -130,7 +130,12 @@ packages/app-desktop/gui/EditFolderDialog/IconSelector.js
|
||||
packages/app-desktop/gui/EmojiBox.js
|
||||
packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.js
|
||||
packages/app-desktop/gui/ErrorBoundary.js
|
||||
packages/app-desktop/gui/ExtensionBadge.js
|
||||
packages/app-desktop/gui/FolderIconBox.js
|
||||
packages/app-desktop/gui/HelpButton.js
|
||||
packages/app-desktop/gui/IconButton.js
|
||||
packages/app-desktop/gui/ImportScreen.js
|
||||
packages/app-desktop/gui/ItemList.js
|
||||
packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.js
|
||||
packages/app-desktop/gui/KeymapConfig/ShortcutRecorder.js
|
||||
packages/app-desktop/gui/KeymapConfig/styles/index.js
|
||||
@@ -160,6 +165,7 @@ packages/app-desktop/gui/MainScreen/commands/openTag.js
|
||||
packages/app-desktop/gui/MainScreen/commands/print.js
|
||||
packages/app-desktop/gui/MainScreen/commands/renameFolder.js
|
||||
packages/app-desktop/gui/MainScreen/commands/renameTag.js
|
||||
packages/app-desktop/gui/MainScreen/commands/resetLayout.js
|
||||
packages/app-desktop/gui/MainScreen/commands/revealResourceFile.js
|
||||
packages/app-desktop/gui/MainScreen/commands/search.js
|
||||
packages/app-desktop/gui/MainScreen/commands/setTags.js
|
||||
@@ -181,6 +187,7 @@ packages/app-desktop/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||
packages/app-desktop/gui/MasterPasswordDialog/Dialog.js
|
||||
packages/app-desktop/gui/MenuBar.js
|
||||
packages/app-desktop/gui/MultiNoteActions.js
|
||||
packages/app-desktop/gui/Navigator.js
|
||||
packages/app-desktop/gui/NoteContentPropertiesDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/CodeMirror.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Editor.js
|
||||
@@ -203,6 +210,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteEditor.js
|
||||
@@ -210,6 +218,7 @@ packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/index.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js
|
||||
packages/app-desktop/gui/NoteEditor/commands/showRevisions.js
|
||||
packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.js
|
||||
@@ -240,6 +249,10 @@ packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/index.js
|
||||
packages/app-desktop/gui/NoteListItem.js
|
||||
packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.js
|
||||
packages/app-desktop/gui/NotePropertiesDialog.js
|
||||
packages/app-desktop/gui/NoteRevisionViewer.js
|
||||
packages/app-desktop/gui/NoteSearchBar.js
|
||||
packages/app-desktop/gui/NoteStatusBar.js
|
||||
packages/app-desktop/gui/NoteTextViewer.js
|
||||
packages/app-desktop/gui/NoteToolbar/NoteToolbar.js
|
||||
packages/app-desktop/gui/OneDriveLoginScreen.js
|
||||
@@ -278,12 +291,14 @@ packages/app-desktop/gui/Sidebar/styles/index.js
|
||||
packages/app-desktop/gui/StatusScreen/StatusScreen.js
|
||||
packages/app-desktop/gui/StyleSheets/StyleSheetContainer.js
|
||||
packages/app-desktop/gui/SyncWizard/Dialog.js
|
||||
packages/app-desktop/gui/TagItem.js
|
||||
packages/app-desktop/gui/TagList.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/styles/index.js
|
||||
packages/app-desktop/gui/ToolbarBase.js
|
||||
packages/app-desktop/gui/ToolbarButton/ToolbarButton.js
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js
|
||||
packages/app-desktop/gui/ToolbarSpace.js
|
||||
packages/app-desktop/gui/dialogs.js
|
||||
packages/app-desktop/gui/hooks/useEffectDebugger.js
|
||||
packages/app-desktop/gui/hooks/useImperativeHandlerDebugger.js
|
||||
@@ -380,6 +395,7 @@ packages/app-mobile/components/getResponsiveValue.js
|
||||
packages/app-mobile/components/getResponsiveValue.test.js
|
||||
packages/app-mobile/components/screens/ConfigScreen.js
|
||||
packages/app-mobile/components/screens/Note.js
|
||||
packages/app-mobile/components/screens/Notes.js
|
||||
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
|
||||
packages/app-mobile/components/screens/encryption-config.js
|
||||
packages/app-mobile/components/screens/search.js
|
||||
@@ -455,6 +471,7 @@ packages/lib/commands/index.js
|
||||
packages/lib/commands/openMasterPasswordDialog.js
|
||||
packages/lib/commands/synchronize.js
|
||||
packages/lib/components/EncryptionConfigScreen/utils.js
|
||||
packages/lib/components/shared/note-screen-shared.js
|
||||
packages/lib/database-driver-better-sqlite.js
|
||||
packages/lib/database.js
|
||||
packages/lib/debug/DebugService.js
|
||||
@@ -484,6 +501,7 @@ packages/lib/markdownUtils.js
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils2.test.js
|
||||
packages/lib/markupLanguageUtils.js
|
||||
packages/lib/migrations/42.js
|
||||
packages/lib/models/Alarm.js
|
||||
packages/lib/models/BaseItem.js
|
||||
packages/lib/models/Folder.js
|
||||
@@ -730,8 +748,11 @@ packages/lib/themes/type.js
|
||||
packages/lib/time.js
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/webDAVUtils.js
|
||||
packages/lib/utils/webDAVUtils.test.js
|
||||
packages/lib/uuid.js
|
||||
packages/lib/versionInfo.js
|
||||
packages/lib/versionInfo.test.js
|
||||
packages/pdf-viewer/FullViewer.js
|
||||
packages/pdf-viewer/Page.js
|
||||
packages/pdf-viewer/PdfDocument.js
|
||||
@@ -772,6 +793,8 @@ packages/renderer/HtmlToHtml.js
|
||||
packages/renderer/InMemoryCache.js
|
||||
packages/renderer/MarkupToHtml.js
|
||||
packages/renderer/MdToHtml.js
|
||||
packages/renderer/MdToHtml/createEventHandlingAttrs.js
|
||||
packages/renderer/MdToHtml/createEventHandlingAttrs.test.js
|
||||
packages/renderer/MdToHtml/linkReplacement.js
|
||||
packages/renderer/MdToHtml/linkReplacement.test.js
|
||||
packages/renderer/MdToHtml/renderMedia.js
|
||||
|
@@ -13,7 +13,8 @@
|
||||
"@joplin/turndown",
|
||||
"@joplin/turndown-plugin-gfm",
|
||||
"@joplin/tools",
|
||||
"@joplin/react-native-saf-x"
|
||||
"@joplin/react-native-saf-x",
|
||||
"@joplin/react-native-alarm-notification"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
20
.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch
Normal file
20
.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch
Normal file
@@ -0,0 +1,20 @@
|
||||
diff --git a/src/RNCamera.js b/src/RNCamera.js
|
||||
index b7a271ad64771c0f654dbd5fe3c0d9e0d2e2c4ef..1182a40ace081a32fbaefe2bc4a499b79c2e7dac 100644
|
||||
--- a/src/RNCamera.js
|
||||
+++ b/src/RNCamera.js
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
findNodeHandle,
|
||||
Platform,
|
||||
NativeModules,
|
||||
- ViewPropTypes,
|
||||
requireNativeComponent,
|
||||
View,
|
||||
ActivityIndicator,
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
PermissionsAndroid,
|
||||
} from 'react-native';
|
||||
|
||||
+import ViewPropTypes from 'deprecated-react-native-prop-types';
|
||||
import type { FaceFeature } from './FaceDetector';
|
||||
|
||||
const Rationale = PropTypes.shape({
|
@@ -728,6 +728,16 @@ footer .bottom-links-row p {
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************
|
||||
LARGE VIEW
|
||||
*****************************************************************/
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
#nav-section a {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************
|
||||
MEDIUM VIEW
|
||||
- Make menu bar elements smaller and closer to each others
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
Assets/WebsiteAssets/images/news/20230116-ga-raw-log-colored.png
Normal file
BIN
Assets/WebsiteAssets/images/news/20230116-ga-raw-log-colored.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 963 KiB |
BIN
Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png
Normal file
BIN
Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 295 KiB |
@@ -1,4 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Wed, 21 Dec 2022 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Wed, 21 Dec 2022 00:00:00 GMT</pubDate><item><title><![CDATA[Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0)]]></title><description><![CDATA[<p>As was <a href="https://discourse.joplinapp.org/t/rfc-switch-to-agpl-license-for-joplin-server/16529">discussed last year</a>, Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0) for the desktop, mobile and CLI applications, as well as the web clipper.</p>
|
||||
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Joplin]]></title><description><![CDATA[Joplin, the open source note-taking application]]></description><link>https://joplinapp.org</link><generator>RSS for Node</generator><lastBuildDate>Mon, 16 Jan 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://joplinapp.org/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate><item><title><![CDATA[Introducing the "GitHub Actions Raw Log Viewer" extension for Chrome]]></title><description><![CDATA[<p>If you've ever used GitHub Actions, you will find that they provide by default a nice coloured output for the log. It looks good and it's even interactive! (You can click to collapse/expand blocks of text) But unfortunately it doesn't scale to large workflows, like we have for Joplin - the log can freeze and it will take forever to search for something. Indeed searching is done in "real time"... which mostly means it will freeze for a minute or two for each letter you type in the search box. Not great.</p>
|
||||
<p>Thankfully GitHub provides an alternative access: the raw logs. This is much better because they will open as plain text, without any styling or JS magic, which means you can use the browser native search and it will be fast.</p>
|
||||
<p>But now the problem is that raw logs look like this:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log.png" alt="Raw log without extension"></p>
|
||||
<p>While it's not impossible to read, all colours that would display nicely in a terminal are gone and replaced by <a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI codes</a>. You can find what you need in there but it's not particularly easy.</p>
|
||||
<p>This is where the new <strong>GitHub Action Raw Log Viewer</strong> extension for Chrome can help. It will parse your raw log and convert the ANSI codes to proper colours. This results in a much more readable rendering:</p>
|
||||
<p><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-ga-raw-log-colored.png" alt="Raw log with extension"></p>
|
||||
<p>The extension is fast even for very large logs and it's of course easy to search for text since it simply works with your browser built-in search.</p>
|
||||
<p>The extension is open source, with the code available here: <a href="https://github.com/laurent22/github-actions-logs-extension">https://github.com/laurent22/github-actions-logs-extension</a></p>
|
||||
<p>And to install it, follow this link:</p>
|
||||
<p><a href="https://chrome.google.com/webstore/detail/github-action-raw-log-vie/lgejlnoopmcdglhfjblaeldbcfnmjddf"><img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/news/20230116-extension-get-it-now.png" alt="Download GitHub Action Raw Log Viewer extension"></a></p>
|
||||
]]></description><link>https://joplinapp.org/news/20230116-github-actions-log-viewer/</link><guid isPermaLink="false">20230116-github-actions-log-viewer</guid><pubDate>Mon, 16 Jan 2023 00:00:00 GMT</pubDate><twitter-text>Introducing the "GitHub Action Raw Log Viewer" extension for Chrome</twitter-text></item><item><title><![CDATA[Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0)]]></title><description><![CDATA[<p>As was <a href="https://discourse.joplinapp.org/t/rfc-switch-to-agpl-license-for-joplin-server/16529">discussed last year</a>, Joplin is switching to the GNU Affero General Public License v3 (AGPL-3.0) for the desktop, mobile and CLI applications, as well as the web clipper.</p>
|
||||
<p>Any open source or commercial fork of Joplin will have to license any changes they make under AGPL, and share these changes back with the community. This is the main reason we switch to this license. It allows us to continue releasing the project as open source while ensuring that those who benefit commercially (or not) from it share back their changes.</p>
|
||||
<h2>What is the GPL license?<a name="what-is-the-gpl-license" href="#what-is-the-gpl-license" class="heading-anchor">🔗</a></h2>
|
||||
<p>The AGPL license is based on the GPL license. This is what tldr;Legal has to say about the GPL license:</p>
|
||||
@@ -288,9 +299,4 @@
|
||||
<p>For now, since we don't have a review process, the recommended plugins are those developed by the Joplin team and frequent contributors, because we know those are safe to use.</p>
|
||||
<p>Later we might have a review process and add more recommended plugins. That being said, in the meantime even if a plugin is not marked as recommended, there's a good chance it is still safe and have good performance too. Often you can search for it on the forum and if it's active with many users commenting, you're most likely good to go.</p>
|
||||
<p>But if there's any doubt, the recommended tag is a good way to be sure.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20210901-113415/</link><guid isPermaLink="false">20210901-113415</guid><pubDate>Wed, 01 Sep 2021 11:34:15 GMT</pubDate><twitter-text></twitter-text></item><item><title><![CDATA[Joplin Cloud is officially production ready!]]></title><description><![CDATA[<p><a href="https://joplinapp.org/plans/">Joplin Cloud</a> has been out of beta for a few weeks now and since then it has been quietly running without any troubles. There is no known bugs and the service is running smoothly so it's now safe to say that it is production ready!</p>
|
||||
<p>As a reminder, Joplin Cloud is meant to provide a more seamless Joplin experience - if you want to quickly get started, it's as easy as downloading the app and getting a Joplin Cloud account. Besides improved sync performance, that will give you the ability to collaborate on notebooks with others, as well as publishing and sharing notes.</p>
|
||||
<p>Of course Joplin still supports other sync options such as Nextcloud, Dropbox and OneDrive or AWS S3. You can also self host using Joplin Server. The advantage of Joplin Cloud being that you don't need to maintain a server yourself - for a small fee you'll get that taken care of.</p>
|
||||
<p>Additionally, subscribing to Joplin Cloud is a great way to support the project as a whole, including the open source applications. Such support is needed in the long term to provide bug and security fixes, add new features, and provide support.</p>
|
||||
<p>At some level it is also an experiment, to see if such a service is financially viable and can allow me to work full time on the project. This is certainly something I would like, and perhaps Joplin Cloud combined with your donations will allow that.</p>
|
||||
]]></description><link>https://joplinapp.org/news/20210831-154354/</link><guid isPermaLink="false">20210831-154354</guid><pubDate>Tue, 31 Aug 2021 15:43:54 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
||||
]]></description><link>https://joplinapp.org/news/20210901-113415/</link><guid isPermaLink="false">20210901-113415</guid><pubDate>Wed, 01 Sep 2021 11:34:15 GMT</pubDate><twitter-text></twitter-text></item></channel></rss>
|
@@ -130,7 +130,11 @@
|
||||
});
|
||||
|
||||
setupBetaHandling(urlQuery);
|
||||
applyPeriod('yearly');
|
||||
if (urlQuery.get('period') === 'monthly') {
|
||||
// Nothing - this is the default
|
||||
} else {
|
||||
applyPeriod('yearly');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
72
README.md
72
README.md
@@ -530,47 +530,47 @@ Current translations:
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
| Language | Po File | Last translator | Percent done
|
||||
---|---|---|---|---
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 82%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 23%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 59%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 46%
|
||||
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 79%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 99%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 99%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 58%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 45%
|
||||
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | [Xavi Ivars](mailto:xavi.ivars@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Michal Stanke](mailto:michal@stanke.cz) | 78%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | ERYpTION | 97%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [MrKanister](mailto:pueblos_spatulas@aleeas.com) | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 45%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 91%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Francisco Mora](mailto:francisco.m.collao@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 26%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 98%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 98%
|
||||
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 30%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Albano Battistella](mailto:albano_battistella@hotmail.com) | 80%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 80%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 91%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 91%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 57%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 91%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 75%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 52%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 83%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 99%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 38%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 80%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 75%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 91%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 83%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 67%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [KaneGreen](mailto:737445366KG@Gmail.com) | 97%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 92%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato0 | 96%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 100%
|
||||
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 29%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [Wisnu Adi Santoso](mailto:waditos@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Manuel Tassi](mailto:mannivuwiki@gmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Magyari Balázs](mailto:balmag@gmail.com) | 78%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 79%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MHolkamp](mailto:mholkamp@users.noreply.github.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 56%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [X3NO](mailto:X3NO@disroot.org) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Renato Nunes Bastos](mailto:rnbastos@gmail.com) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 73%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 51%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 97%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 37%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/vn.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 79%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 73%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 89%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 81%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 66%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [KaneGreen](mailto:737445366KG@Gmail.com) | 95%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Kevin Hsu](mailto:kevin.hsu.hws@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 90%
|
||||
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 90%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# Contributors
|
||||
|
@@ -317,6 +317,9 @@
|
||||
"packages/app-tools/github_oauth_token.txt": true,
|
||||
"packages/generator-joplin/generators/app/templates/api/": true,
|
||||
"packages/htmlpack/dist/": true,
|
||||
"packages/react-native-alarm-notification/android/build": true,
|
||||
"packages/react-native-saf-x/android/build": true,
|
||||
"packages/react-native-saf-x/android/wrapper": true,
|
||||
"packages/renderer/**/.vscode/": true,
|
||||
"packages/renderer/**/copyLib.bat": true,
|
||||
"packages/renderer/**/node_modules/": true,
|
||||
|
19
package.json
19
package.json
@@ -65,21 +65,21 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.48.0",
|
||||
"@typescript-eslint/parser": "5.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.48.2",
|
||||
"@typescript-eslint/parser": "5.48.2",
|
||||
"cspell": "5.21.2",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-interactive": "10.3.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-import": "2.27.4",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-react": "7.31.11",
|
||||
"eslint-plugin-react": "7.32.0",
|
||||
"fs-extra": "11.1.0",
|
||||
"glob": "8.0.3",
|
||||
"glob": "8.1.0",
|
||||
"gulp": "4.0.2",
|
||||
"husky": "3.1.0",
|
||||
"lerna": "3.22.1",
|
||||
"lint-staged": "13.1.0",
|
||||
"madge": "5.0.1",
|
||||
"lint-staged": "13.1.1",
|
||||
"madge": "5.0.2",
|
||||
"npm-package-json-lint": "6.4.0",
|
||||
"typedoc": "0.17.8",
|
||||
"typescript": "4.9.4"
|
||||
@@ -90,5 +90,8 @@
|
||||
"node-gyp": "9.3.1",
|
||||
"nodemon": "2.0.20"
|
||||
},
|
||||
"packageManager": "yarn@3.3.1"
|
||||
"packageManager": "yarn@3.3.1",
|
||||
"resolutions": {
|
||||
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch"
|
||||
}
|
||||
}
|
||||
|
@@ -246,6 +246,7 @@ class Application extends BaseApplication {
|
||||
showConsole: () => {},
|
||||
maximizeConsole: () => {},
|
||||
stdout: text => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(text);
|
||||
},
|
||||
fullScreen: () => {},
|
||||
@@ -407,6 +408,7 @@ class Application extends BaseApplication {
|
||||
if (this.showStackTraces_) {
|
||||
console.error(error);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
|
@@ -125,14 +125,14 @@ async function handleAutocompletionPromise(line) {
|
||||
}
|
||||
function handleAutocompletion(str, callback) {
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
handleAutocompletionPromise(str).then(function(res) {
|
||||
handleAutocompletionPromise(str).then((res) => {
|
||||
callback(undefined, res);
|
||||
});
|
||||
}
|
||||
function toCommandLine(args) {
|
||||
if (Array.isArray(args)) {
|
||||
return args
|
||||
.map(function(a) {
|
||||
.map((a) => {
|
||||
if (a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
||||
return `'${a}'`;
|
||||
} else if (a.indexOf('\'') !== -1) {
|
||||
|
@@ -131,6 +131,7 @@ async function main() {
|
||||
const commandsText = commandBlocks.join('\n\n');
|
||||
const footerText = getFooter();
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`${headerText}\n\n` + 'USAGE' + `\n\n${commandsText}\n\n${footerText}`);
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const Logger = require('@joplin/lib/Logger').default;
|
||||
const { dirname } = require('@joplin/lib/path-utils');
|
||||
|
@@ -82,6 +82,7 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
||||
const options = cmd.options();
|
||||
const booleanFlags = [];
|
||||
const aliases = {};
|
||||
const flagSpecs = [];
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (options[i].length !== 2) throw new Error(`Invalid options: ${options[i]}`);
|
||||
let flags = options[i][0];
|
||||
@@ -96,6 +97,8 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
||||
if (flags.short && flags.long) {
|
||||
aliases[flags.long] = [flags.short];
|
||||
}
|
||||
|
||||
flagSpecs.push(flags);
|
||||
}
|
||||
|
||||
const args = yargParser(argv, {
|
||||
@@ -121,6 +124,19 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
|
||||
argOptions[key] = args[key];
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(argOptions)) {
|
||||
const flagSpec = flagSpecs.find(s => {
|
||||
return s.short === key || s.long === key;
|
||||
});
|
||||
if (flagSpec?.arg?.required) {
|
||||
// If a flag is required, and no value is provided for it, Yargs
|
||||
// sets the value to `true`.
|
||||
if (value === true) {
|
||||
throw new Error(_('Missing required flag value: %s', `-${flagSpec.short} <${flagSpec.arg.name}>`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.options = argOptions;
|
||||
|
||||
return output;
|
||||
|
@@ -39,9 +39,9 @@ class Command extends BaseCommand {
|
||||
let settingsObj;
|
||||
try {
|
||||
settingsObj = JSON.parse(json);
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
isSettled = true;
|
||||
return reject(new Error(`Invalid JSON passed to config --import: \n${err.message}.`));
|
||||
return reject(new Error(`Invalid JSON passed to config --import: \n${error.message}.`));
|
||||
}
|
||||
if (settingsObj) {
|
||||
Object.entries(settingsObj)
|
||||
|
@@ -118,6 +118,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(await api.exec('GET', 'api/items/root:/testing:'));
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action() {
|
||||
this.stdout(versionInfo(require('./package.json')).message);
|
||||
this.stdout(versionInfo(require('./package.json'), {}).message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -75,14 +75,14 @@ if (process.platform === 'win32') {
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
rl.on('SIGINT', function() {
|
||||
rl.on('SIGINT', () => {
|
||||
process.emit('SIGINT');
|
||||
});
|
||||
}
|
||||
|
||||
process.stdout.on('error', function(err) {
|
||||
process.stdout.on('error', (error) => {
|
||||
// https://stackoverflow.com/questions/12329816/error-write-epipe-when-piping-node-output-to-head#15884508
|
||||
if (err.code === 'EPIPE') {
|
||||
if (error.code === 'EPIPE') {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
@@ -11,6 +11,7 @@ function createConsoleWrapper(pluginId: string) {
|
||||
const wrapper: any = {};
|
||||
|
||||
for (const n in console) {
|
||||
// eslint-disable-next-line no-console
|
||||
if (!console.hasOwnProperty(n)) continue;
|
||||
wrapper[n] = (...args: any[]) => {
|
||||
const newArgs = args.slice();
|
||||
|
@@ -51,7 +51,7 @@
|
||||
"keytar": "7.9.0",
|
||||
"md5": "2.3.0",
|
||||
"node-rsa": "1.1.1",
|
||||
"open": "8.4.0",
|
||||
"open": "8.4.1",
|
||||
"proper-lockfile": "4.1.2",
|
||||
"read-chunk": "2.1.0",
|
||||
"server-destroy": "1.0.1",
|
||||
@@ -70,10 +70,10 @@
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.10",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/jest": "29.2.5",
|
||||
"@types/jest": "29.2.6",
|
||||
"@types/node": "18.11.18",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.3.1",
|
||||
"jest": "29.4.2",
|
||||
"temp": "0.9.4",
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ const shim = require('@joplin/lib/shim').default;
|
||||
const HtmlToHtml = require('@joplin/renderer/HtmlToHtml').default;
|
||||
const { enexXmlToMd } = require('@joplin/lib/import-enex-md-gen.js');
|
||||
|
||||
describe('HtmlToHtml', function() {
|
||||
describe('HtmlToHtml', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
@@ -49,6 +49,7 @@ describe('HtmlToHtml', function() {
|
||||
}
|
||||
|
||||
if (actualHtml !== expectedHtml) {
|
||||
/* eslint-disable no-console */
|
||||
console.info('');
|
||||
console.info(`Error converting file: ${htmlSourceFilename}`);
|
||||
console.info('--------------------------------- Got:');
|
||||
@@ -59,6 +60,7 @@ describe('HtmlToHtml', function() {
|
||||
console.info(expectedHtml.split('\n'));
|
||||
console.info('--------------------------------------------');
|
||||
console.info('');
|
||||
/* eslint-enable */
|
||||
|
||||
expect(false).toBe(true);
|
||||
// return;
|
||||
|
@@ -3,7 +3,7 @@ const os = require('os');
|
||||
const { filename } = require('@joplin/lib/path-utils');
|
||||
import HtmlToMd from '@joplin/lib/HtmlToMd';
|
||||
|
||||
describe('HtmlToMd', function() {
|
||||
describe('HtmlToMd', () => {
|
||||
|
||||
it('should convert from Html to Markdown', (async () => {
|
||||
const basePath = `${__dirname}/html_to_md`;
|
||||
@@ -57,6 +57,7 @@ describe('HtmlToMd', function() {
|
||||
result.push('--------------------------------------------');
|
||||
result.push('');
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(result.join('\n'));
|
||||
|
||||
// console.info('');
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
const MarkupToHtml = require('@joplin/renderer/MarkupToHtml').default;
|
||||
|
||||
describe('MarkupToHtml', function() {
|
||||
describe('MarkupToHtml', () => {
|
||||
|
||||
it('should strip markup', (async () => {
|
||||
const service = new MarkupToHtml();
|
||||
|
@@ -16,7 +16,7 @@ function newTestMdToHtml(options: any = null) {
|
||||
return new MdToHtml(options);
|
||||
}
|
||||
|
||||
describe('MdToHtml', function() {
|
||||
describe('MdToHtml', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
@@ -72,6 +72,7 @@ describe('MdToHtml', function() {
|
||||
'',
|
||||
];
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(msg.join('\n'));
|
||||
|
||||
expect(false).toBe(true);
|
||||
|
@@ -22,7 +22,7 @@ const goToNote = (testApp, note) => {
|
||||
testApp.dispatch({ type: 'NOTE_SELECT', id: note.id });
|
||||
};
|
||||
|
||||
describe('feature_NoteHistory', function() {
|
||||
describe('feature_NoteHistory', () => {
|
||||
beforeEach(async () => {
|
||||
testApp = new TestApp();
|
||||
await testApp.start(['--no-welcome']);
|
||||
|
@@ -8,7 +8,7 @@ const time = require('@joplin/lib/time').default;
|
||||
|
||||
let testApp = null;
|
||||
|
||||
describe('integration_NoteList', function() {
|
||||
describe('integration_NoteList', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
testApp = new TestApp();
|
||||
|
@@ -22,7 +22,7 @@ const { ALL_NOTES_FILTER_ID } = require('@joplin/lib/reserved-ids.js');
|
||||
|
||||
let testApp = null;
|
||||
|
||||
describe('integration_ShowAllNotes', function() {
|
||||
describe('integration_ShowAllNotes', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
testApp = new TestApp();
|
||||
|
@@ -8,7 +8,7 @@ const time = require('@joplin/lib/time').default;
|
||||
|
||||
let testApp = null;
|
||||
|
||||
describe('integration_TagList', function() {
|
||||
describe('integration_TagList', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
testApp = new TestApp();
|
||||
|
@@ -11,7 +11,7 @@ function describeIfCompatible(name: string, fn: any, elseFn: any) {
|
||||
}
|
||||
}
|
||||
|
||||
describeIfCompatible('services_KeychainService', function() {
|
||||
describeIfCompatible('services_KeychainService', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1, { keychainEnabled: true });
|
||||
|
@@ -29,7 +29,7 @@ function newPluginService(appVersion: string = '1.4') {
|
||||
return service;
|
||||
}
|
||||
|
||||
describe('services_PluginService', function() {
|
||||
describe('services_PluginService', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
|
@@ -8,7 +8,7 @@ async function newRepoApi(): Promise<RepositoryApi> {
|
||||
return repo;
|
||||
}
|
||||
|
||||
describe('services_plugins_RepositoryApi', function() {
|
||||
describe('services_plugins_RepositoryApi', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
|
@@ -24,7 +24,7 @@ function newPluginService(appVersion: string = '1.4') {
|
||||
return service;
|
||||
}
|
||||
|
||||
describe('defaultPluginsUtils', function() {
|
||||
describe('defaultPluginsUtils', () => {
|
||||
|
||||
const pluginsId = ['joplin.plugin.ambrt.backlinksToNote', 'org.joplinapp.plugins.ToggleSidebars'];
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const sandboxProxy = require('@joplin/lib/services/plugins/sandboxProxy');
|
||||
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
|
||||
|
||||
describe('services_plugins_sandboxProxy', function() {
|
||||
describe('services_plugins_sandboxProxy', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await setupDatabaseAndSynchronizer(1);
|
||||
|
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
// This script can be used to simulate a running production environment, by
|
||||
// having multiple users in parallel changing notes and synchronising.
|
||||
//
|
||||
|
@@ -50,7 +50,7 @@ async function browserGetZoom(tabId) {
|
||||
});
|
||||
}
|
||||
|
||||
browser_.runtime.onInstalled.addListener(function() {
|
||||
browser_.runtime.onInstalled.addListener(() => {
|
||||
if (window.joplinEnv() === 'dev') {
|
||||
browser_.browserAction.setIcon({
|
||||
path: 'icons/32-dev.png',
|
||||
@@ -165,7 +165,7 @@ async function sendClipMessage(clipType) {
|
||||
}
|
||||
}
|
||||
|
||||
browser_.commands.onCommand.addListener(function(command) {
|
||||
browser_.commands.onCommand.addListener((command) => {
|
||||
// We could enumerate these twice, but since we're in here first,
|
||||
// why not save ourselves the trouble with this convention
|
||||
if (command.startsWith('clip')) {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
(function() {
|
||||
|
||||
if (window.jopext_hasRun) return;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
// Do this as the first thing so that any code reading it knows the right env.
|
||||
process.env.BABEL_ENV = 'development';
|
||||
process.env.NODE_ENV = 'development';
|
||||
@@ -104,9 +106,9 @@ checkBrowsers(paths.appPath, isInteractive)
|
||||
);
|
||||
const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||
// Launch WebpackDevServer.
|
||||
devServer.listen(port, HOST, err => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
devServer.listen(port, HOST, error => {
|
||||
if (error) {
|
||||
return console.log(error);
|
||||
}
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
@@ -128,16 +130,16 @@ checkBrowsers(paths.appPath, isInteractive)
|
||||
openBrowser(urls.localUrlForBrowser);
|
||||
});
|
||||
|
||||
['SIGINT', 'SIGTERM'].forEach(function(sig) {
|
||||
process.on(sig, function() {
|
||||
['SIGINT', 'SIGTERM'].forEach((sig) => {
|
||||
process.on(sig, () => {
|
||||
devServer.close();
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err && err.message) {
|
||||
console.log(err.message);
|
||||
.catch(error => {
|
||||
if (error && error.message) {
|
||||
console.log(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const { randomClipperPort } = require('./randomClipperPort');
|
||||
|
||||
function msleep(ms) {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
@@ -114,7 +116,13 @@ async function main() {
|
||||
|
||||
console.info('Popup: Creating React app...');
|
||||
|
||||
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
|
||||
ReactDOM.render(
|
||||
<div style = {{ maxHeight: screen.height * 0.65, overflowY: 'scroll' }}>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
</div>,
|
||||
document.getElementById('root'));
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
@@ -3,7 +3,7 @@ import { PluginMessage } from './services/plugins/PluginRunner';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
||||
|
||||
const { BrowserWindow, Tray, screen } = require('electron');
|
||||
import { BrowserWindow, Tray, screen } from 'electron';
|
||||
const url = require('url');
|
||||
const path = require('path');
|
||||
const { dirname } = require('@joplin/lib/path-utils');
|
||||
@@ -25,7 +25,7 @@ export default class ElectronAppWrapper {
|
||||
private env_: string;
|
||||
private isDebugMode_: boolean;
|
||||
private profilePath_: string;
|
||||
private win_: any = null;
|
||||
private win_: BrowserWindow = null;
|
||||
private willQuitApp_: boolean = false;
|
||||
private tray_: any = null;
|
||||
private buildDir_: string = null;
|
||||
@@ -117,7 +117,7 @@ export default class ElectronAppWrapper {
|
||||
this.win_.setPosition(primaryDisplayWidth / 2 - windowWidth, primaryDisplayHeight / 2 - windowHeight);
|
||||
}
|
||||
|
||||
this.win_.loadURL(url.format({
|
||||
void this.win_.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true,
|
||||
|
@@ -40,6 +40,7 @@ export default class InteropServiceHelper {
|
||||
const service = InteropService.instance();
|
||||
|
||||
const result = await service.export(fullExportOptions);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Export HTML result: ', result);
|
||||
return tempFile;
|
||||
}
|
||||
@@ -190,6 +191,7 @@ export default class InteropServiceHelper {
|
||||
|
||||
try {
|
||||
const result = await service.export(exportOptions);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Export result: ', result);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { AppState } from './app.reducer';
|
||||
import appReducer, { createAppDefaultState } from './app.reducer';
|
||||
|
||||
describe('app.reducer', function() {
|
||||
describe('app.reducer', () => {
|
||||
|
||||
it('DIALOG_OPEN', async () => {
|
||||
const state: AppState = createAppDefaultState({}, {});
|
||||
|
@@ -38,6 +38,7 @@ export interface AppState extends State {
|
||||
watchedResources: any;
|
||||
mainLayout: LayoutItem;
|
||||
dialogs: AppStateDialog[];
|
||||
isResettingLayout: boolean;
|
||||
}
|
||||
|
||||
export function createAppDefaultState(windowContentSize: any, resourceEditWatcherDefaultState: any): AppState {
|
||||
@@ -60,6 +61,7 @@ export function createAppDefaultState(windowContentSize: any, resourceEditWatche
|
||||
mainLayout: null,
|
||||
startupPluginsLoaded: false,
|
||||
dialogs: [],
|
||||
isResettingLayout: false,
|
||||
...resourceEditWatcherDefaultState,
|
||||
};
|
||||
}
|
||||
@@ -308,7 +310,15 @@ export default function(state: AppState, action: any) {
|
||||
};
|
||||
break;
|
||||
|
||||
|
||||
case 'RESET_LAYOUT':
|
||||
newState = {
|
||||
...state,
|
||||
isResettingLayout: action.value,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
|
||||
throw error;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { clipboard } = require('electron');
|
||||
const ExtensionBadge = require('./ExtensionBadge.min');
|
||||
import ExtensionBadge from './ExtensionBadge';
|
||||
import bridge from '../services/bridge';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
@@ -453,6 +453,12 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
inputStyle.marginBottom = subLabel.marginBottom;
|
||||
|
||||
const splitCmd = (cmdString: string) => {
|
||||
// Normally not necessary but certain plugins found a way to
|
||||
// set the set the value to "undefined", leading to a crash.
|
||||
// This is now fixed at the model level but to be sure we
|
||||
// check here too, to handle any already existing data.
|
||||
// https://github.com/laurent22/joplin/issues/7621
|
||||
if (!cmdString) cmdString = '';
|
||||
const path = pathUtils.extractExecutablePath(cmdString);
|
||||
const args = cmdString.substr(path.length + 1);
|
||||
return [pathUtils.unquotePath(path), args];
|
||||
|
@@ -55,7 +55,7 @@ export interface PluginItem {
|
||||
hasBeenUpdated: boolean;
|
||||
}
|
||||
|
||||
const CellRoot = styled.div<{isCompatible: boolean}>`
|
||||
const CellRoot = styled.div<{ isCompatible: boolean }>`
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
background-color: ${props => props.theme.backgroundColor};
|
||||
@@ -104,7 +104,7 @@ const DevModeLabel = styled.div`
|
||||
color: ${props => props.theme.color};
|
||||
`;
|
||||
|
||||
const StyledNameAndVersion = styled.div<{mb: any}>`
|
||||
const StyledNameAndVersion = styled.div<{ mb: any }>`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
color: ${props => props.theme.color};
|
||||
font-size: ${props => props.theme.fontSize}px;
|
||||
|
@@ -20,7 +20,7 @@ const { space } = require('styled-system');
|
||||
|
||||
const logger = Logger.create('PluginState');
|
||||
|
||||
const maxWidth: number = 320;
|
||||
const maxWidth = 320;
|
||||
|
||||
const Root = styled.div`
|
||||
display: flex;
|
||||
@@ -225,7 +225,7 @@ export default function(props: Props) {
|
||||
];
|
||||
|
||||
const menu = bridge().Menu.buildFromTemplate(template);
|
||||
menu.popup(bridge().window());
|
||||
menu.popup({ window: bridge().window() });
|
||||
}, [onInstall, onBrowsePlugins]);
|
||||
|
||||
const onSearchQueryChange = useCallback((event: OnChangeEvent) => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import versionInfo from '@joplin/lib/versionInfo';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import PluginService, { Plugins } from '@joplin/lib/services/plugins/PluginService';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import restart from '../services/restart';
|
||||
const packageInfo = require('../packageInfo.js');
|
||||
@@ -21,6 +21,7 @@ interface State {
|
||||
error: Error;
|
||||
errorInfo: ErrorInfo;
|
||||
pluginInfos: PluginInfo[];
|
||||
plugins: Plugins;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -29,14 +30,16 @@ interface Props {
|
||||
|
||||
export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
|
||||
public state: State = { error: null, errorInfo: null, pluginInfos: [] };
|
||||
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
|
||||
|
||||
componentDidCatch(error: any, errorInfo: ErrorInfo) {
|
||||
if (typeof error === 'string') error = { message: error };
|
||||
|
||||
const pluginInfos: PluginInfo[] = [];
|
||||
let plugins: Plugins = {};
|
||||
try {
|
||||
const service = PluginService.instance();
|
||||
plugins = service.plugins;
|
||||
const pluginSettings = service.unserializePluginSettings(Setting.value('plugins.states'));
|
||||
for (const pluginId in pluginSettings) {
|
||||
const plugin = PluginService.instance().pluginById(pluginId);
|
||||
@@ -52,7 +55,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
console.error('Could not get plugin info:', error);
|
||||
}
|
||||
|
||||
this.setState({ error, errorInfo, pluginInfos });
|
||||
this.setState({ error, errorInfo, pluginInfos, plugins });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -91,7 +94,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||
output.push(
|
||||
<section key="versionInfo">
|
||||
<h2>Version info</h2>
|
||||
<pre>{versionInfo(packageInfo).message}</pre>
|
||||
<pre>{versionInfo(packageInfo, this.state.plugins).message}</pre>
|
||||
</section>
|
||||
);
|
||||
|
||||
|
@@ -1,45 +0,0 @@
|
||||
const React = require('react');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
const styleSelector = require('./style/ExtensionBadge');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
|
||||
function platformAssets(type) {
|
||||
if (type === 'firefox') {
|
||||
return {
|
||||
logoImage: `${bridge().buildDir()}/images/firefox-logo.svg`,
|
||||
locationLabel: _('Firefox Extension'),
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'chrome') {
|
||||
return {
|
||||
logoImage: `${bridge().buildDir()}/images/chrome-logo.svg`,
|
||||
locationLabel: _('Chrome Web Store'),
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Invalid type:${type}`);
|
||||
}
|
||||
|
||||
function ExtensionBadge(props) {
|
||||
const style = styleSelector(null, props);
|
||||
const assets = platformAssets(props.type);
|
||||
|
||||
const onClick = () => {
|
||||
bridge().openExternal(props.url);
|
||||
};
|
||||
|
||||
const rootStyle = props.style ? Object.assign({}, style.root, props.style) : style.root;
|
||||
|
||||
return (
|
||||
<a style={rootStyle} onClick={onClick} href="#">
|
||||
<img style={style.logo} src={assets.logoImage}/>
|
||||
<div style={style.labelGroup} >
|
||||
<div>{_('Get it now:')}</div>
|
||||
<div style={style.locationLabel}>{assets.locationLabel}</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = ExtensionBadge;
|
98
packages/app-desktop/gui/ExtensionBadge.tsx
Normal file
98
packages/app-desktop/gui/ExtensionBadge.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import * as React from 'react';
|
||||
import bridge from '../services/bridge';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
const { createSelector } = require('reselect');
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
type: string;
|
||||
url: string;
|
||||
style?: any;
|
||||
}
|
||||
|
||||
const themeSelector = (_state: any, props: any) => themeStyle(props.themeId);
|
||||
|
||||
const styleSelector = createSelector(
|
||||
themeSelector,
|
||||
(theme: any) => {
|
||||
const output = {
|
||||
root: {
|
||||
width: 220,
|
||||
height: 60,
|
||||
borderRadius: 4,
|
||||
border: '1px solid',
|
||||
borderColor: theme.dividerColor,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
paddingLeft: 14,
|
||||
paddingRight: 14,
|
||||
paddingTop: 8,
|
||||
paddingBottom: 8,
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
boxShadow: '0px 1px 1px rgba(0,0,0,0.3)',
|
||||
},
|
||||
logo: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
},
|
||||
labelGroup: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 14,
|
||||
fontFamily: theme.fontFamily,
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize,
|
||||
},
|
||||
locationLabel: {
|
||||
fontSize: theme.fontSize * 1.2,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
};
|
||||
|
||||
return output;
|
||||
}
|
||||
);
|
||||
|
||||
function platformAssets(type: string) {
|
||||
if (type === 'firefox') {
|
||||
return {
|
||||
logoImage: `${bridge().buildDir()}/images/firefox-logo.svg`,
|
||||
locationLabel: _('Firefox Extension'),
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'chrome') {
|
||||
return {
|
||||
logoImage: `${bridge().buildDir()}/images/chrome-logo.svg`,
|
||||
locationLabel: _('Chrome Web Store'),
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Invalid type:${type}`);
|
||||
}
|
||||
|
||||
function ExtensionBadge(props: Props) {
|
||||
const style = styleSelector(null, props);
|
||||
const assets = platformAssets(props.type);
|
||||
|
||||
const onClick = () => {
|
||||
void bridge().openExternal(props.url);
|
||||
};
|
||||
|
||||
const rootStyle = props.style ? Object.assign({}, style.root, props.style) : style.root;
|
||||
|
||||
return (
|
||||
<a style={rootStyle} onClick={onClick} href="#">
|
||||
<img style={style.logo} src={assets.logoImage}/>
|
||||
<div style={style.labelGroup} >
|
||||
<div>{_('Get it now:')}</div>
|
||||
<div style={style.locationLabel}>{assets.locationLabel}</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExtensionBadge;
|
@@ -1,10 +1,18 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
class HelpButtonComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
interface Props {
|
||||
tip: string;
|
||||
onClick: Function;
|
||||
themeId: number;
|
||||
style: any;
|
||||
}
|
||||
|
||||
class HelpButtonComponent extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
@@ -17,7 +25,7 @@ class HelpButtonComponent extends React.Component {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
|
||||
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
|
||||
const extraProps = {};
|
||||
const extraProps: any = {};
|
||||
if (this.props.tip) extraProps['data-tip'] = this.props.tip;
|
||||
return (
|
||||
<a href="#" style={style} onClick={this.onClick} {...extraProps}>
|
||||
@@ -27,7 +35,7 @@ class HelpButtonComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
@@ -35,4 +43,4 @@ const mapStateToProps = state => {
|
||||
|
||||
const HelpButton = connect(mapStateToProps)(HelpButtonComponent);
|
||||
|
||||
module.exports = HelpButton;
|
||||
export default HelpButton;
|
@@ -1,7 +1,14 @@
|
||||
const React = require('react');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
import * as React from 'react';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
|
||||
class IconButton extends React.Component {
|
||||
interface Props {
|
||||
themeId: number;
|
||||
style: any;
|
||||
iconName: string;
|
||||
onClick: Function;
|
||||
}
|
||||
|
||||
class IconButton extends React.Component<Props> {
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
@@ -42,4 +49,4 @@ class IconButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { IconButton };
|
||||
export default IconButton;
|
@@ -1,12 +1,29 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { filename, basename } from '@joplin/lib/path-utils';
|
||||
import importEnex from '@joplin/lib/import-enex';
|
||||
import { AppState } from '../app.reducer';
|
||||
const { connect } = require('react-redux');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const { filename, basename } = require('@joplin/lib/path-utils');
|
||||
const importEnex = require('@joplin/lib/import-enex').default;
|
||||
|
||||
class ImportScreenComponent extends React.Component {
|
||||
interface Props {
|
||||
filePath: string;
|
||||
themeId: number;
|
||||
}
|
||||
|
||||
interface Message {
|
||||
key: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
filePath: string;
|
||||
doImport: boolean;
|
||||
messages: Message[];
|
||||
}
|
||||
|
||||
class ImportScreenComponent extends React.Component<Props, State> {
|
||||
UNSAFE_componentWillMount() {
|
||||
this.setState({
|
||||
doImport: true,
|
||||
@@ -15,7 +32,7 @@ class ImportScreenComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
if (newProps.filePath) {
|
||||
this.setState(
|
||||
{
|
||||
@@ -24,7 +41,7 @@ class ImportScreenComponent extends React.Component {
|
||||
messages: [],
|
||||
},
|
||||
() => {
|
||||
this.doImport();
|
||||
void this.doImport();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -32,11 +49,11 @@ class ImportScreenComponent extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (this.state.filePath && this.state.doImport) {
|
||||
this.doImport();
|
||||
void this.doImport();
|
||||
}
|
||||
}
|
||||
|
||||
addMessage(key, text) {
|
||||
addMessage(key: string, text: string) {
|
||||
const messages = this.state.messages.slice();
|
||||
|
||||
messages.push({ key: key, text: text });
|
||||
@@ -66,7 +83,7 @@ class ImportScreenComponent extends React.Component {
|
||||
let lastProgress = '';
|
||||
|
||||
const options = {
|
||||
onProgress: progressState => {
|
||||
onProgress: (progressState: any) => {
|
||||
const line = [];
|
||||
line.push(_('Found: %d.', progressState.loaded));
|
||||
line.push(_('Created: %d.', progressState.created));
|
||||
@@ -77,7 +94,7 @@ class ImportScreenComponent extends React.Component {
|
||||
lastProgress = line.join(' ');
|
||||
this.addMessage('progress', lastProgress);
|
||||
},
|
||||
onError: error => {
|
||||
onError: (error: any) => {
|
||||
// Don't display the error directly because most of the time it doesn't matter
|
||||
// (eg. for weird broken HTML, but the note is still imported)
|
||||
console.warn('When importing ENEX file', error);
|
||||
@@ -116,7 +133,7 @@ class ImportScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
@@ -124,4 +141,5 @@ const mapStateToProps = state => {
|
||||
|
||||
const ImportScreen = connect(mapStateToProps)(ImportScreenComponent);
|
||||
|
||||
module.exports = { ImportScreen };
|
||||
export default ImportScreen;
|
||||
|
@@ -1,8 +1,27 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
|
||||
class ItemList extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
interface Props {
|
||||
style: any;
|
||||
itemHeight: number;
|
||||
items: any[];
|
||||
disabled?: boolean;
|
||||
onKeyDown?: Function;
|
||||
itemRenderer: Function;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
topItemIndex: number;
|
||||
bottomItemIndex: number;
|
||||
}
|
||||
|
||||
class ItemList extends React.Component<Props, State> {
|
||||
|
||||
private scrollTop_: number;
|
||||
private listRef: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.scrollTop_ = 0;
|
||||
|
||||
@@ -12,12 +31,12 @@ class ItemList extends React.Component {
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
visibleItemCount(props) {
|
||||
visibleItemCount(props: Props = undefined) {
|
||||
if (typeof props === 'undefined') props = this.props;
|
||||
return Math.ceil(props.style.height / props.itemHeight);
|
||||
}
|
||||
|
||||
updateStateItemIndexes(props) {
|
||||
updateStateItemIndexes(props: Props = undefined) {
|
||||
if (typeof props === 'undefined') props = this.props;
|
||||
|
||||
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
|
||||
@@ -44,20 +63,20 @@ class ItemList extends React.Component {
|
||||
this.updateStateItemIndexes();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
this.updateStateItemIndexes(newProps);
|
||||
}
|
||||
|
||||
onScroll(event) {
|
||||
onScroll(event: any) {
|
||||
this.scrollTop_ = event.target.scrollTop;
|
||||
this.updateStateItemIndexes();
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
onKeyDown(event: any) {
|
||||
if (this.props.onKeyDown) this.props.onKeyDown(event);
|
||||
}
|
||||
|
||||
makeItemIndexVisible(itemIndex) {
|
||||
makeItemIndexVisible(itemIndex: number) {
|
||||
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex);
|
||||
const bottom = Math.max(0, this.state.bottomItemIndex);
|
||||
|
||||
@@ -105,7 +124,7 @@ class ItemList extends React.Component {
|
||||
|
||||
if (!this.props.itemHeight) throw new Error('itemHeight is required');
|
||||
|
||||
const blankItem = function(key, height) {
|
||||
const blankItem = function(key: string, height: number) {
|
||||
return <div key={key} style={{ height: height }}></div>;
|
||||
};
|
||||
|
||||
@@ -129,4 +148,4 @@ class ItemList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ItemList };
|
||||
export default ItemList;
|
@@ -61,8 +61,8 @@ export const KeymapConfigScreen = ({ themeId }: KeymapConfigScreenProps) => {
|
||||
try {
|
||||
const keymapFile = await shim.fsDriver().readFile(actualFilePath, 'utf-8');
|
||||
overrideKeymapItems(JSON.parse(keymapFile));
|
||||
} catch (err) {
|
||||
bridge().showErrorMessageBox(_('Error: %s', err.message));
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox(_('Error: %s', error.message));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -77,8 +77,8 @@ export const KeymapConfigScreen = ({ themeId }: KeymapConfigScreenProps) => {
|
||||
try {
|
||||
// KeymapService is already synchronized with the in-state keymap
|
||||
await keymapService.saveCustomKeymap(filePath);
|
||||
} catch (err) {
|
||||
bridge().showErrorMessageBox(err.message);
|
||||
} catch (error) {
|
||||
bridge().showerrororMessageBox(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -62,11 +62,11 @@ const useKeymap = (): [
|
||||
// Then, update the state with the data from KeymapService
|
||||
// Side-effect: Changes will also be saved to the disk
|
||||
setKeymapItems(keymapService.getKeymapItems());
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
// oldKeymapItems includes even the unchanged keymap items
|
||||
// However, it is not an issue because the logic accounts for such scenarios
|
||||
keymapService.overrideKeymap(oldKeymapItems);
|
||||
throw err;
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,8 +80,8 @@ const useKeymap = (): [
|
||||
keymapService.overrideKeymap(keymapItems);
|
||||
await keymapService.saveCustomKeymap();
|
||||
setKeymapError(null);
|
||||
} catch (err) {
|
||||
const error = new Error(`Could not save file: ${err.message}`);
|
||||
} catch (error) {
|
||||
error.message = `Could not save file: ${error.message}`;
|
||||
setKeymapError(error);
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ import invitationRespond from '../../services/share/invitationRespond';
|
||||
import restart from '../../services/restart';
|
||||
const { connect } = require('react-redux');
|
||||
import PromptDialog from '../PromptDialog';
|
||||
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
|
||||
import NotePropertiesDialog from '../NotePropertiesDialog';
|
||||
const PluginManager = require('@joplin/lib/services/PluginManager');
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
|
||||
@@ -78,6 +78,7 @@ interface Props {
|
||||
isSafeMode: boolean;
|
||||
needApiAuth: boolean;
|
||||
processingShareInvitationResponse: boolean;
|
||||
isResettingLayout: boolean;
|
||||
}
|
||||
|
||||
interface ShareFolderDialogOptions {
|
||||
@@ -172,7 +173,6 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
private openCallbackUrl(url: string) {
|
||||
console.log(`openUrl ${url}`);
|
||||
const { command, params } = parseCallbackUrl(url);
|
||||
void CommandService.instance().execute(command.toString(), params.id);
|
||||
}
|
||||
@@ -372,6 +372,15 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
name: 'promptDialog',
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.isResettingLayout) {
|
||||
Setting.setValue('ui.layout', null);
|
||||
this.updateMainLayout(this.buildLayout(this.props.plugins));
|
||||
this.props.dispatch({
|
||||
type: 'RESET_LAYOUT',
|
||||
value: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
layoutModeListenerKeyDown(event: any) {
|
||||
@@ -393,6 +402,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
|
||||
async waitForNoteToSaved(noteId: string) {
|
||||
while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Waiting for note to be saved...', this.props.editorNoteStatuses);
|
||||
await time.msleep(100);
|
||||
}
|
||||
@@ -401,6 +411,7 @@ class MainScreenComponent extends React.Component<Props, State> {
|
||||
async printTo_(target: string, options: any) {
|
||||
// Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion
|
||||
if (this.isPrinting_) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(`Printing ${options.path} to ${target} disallowed, already printing.`);
|
||||
return;
|
||||
}
|
||||
@@ -879,6 +890,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
isSafeMode: state.settings.isSafeMode,
|
||||
needApiAuth: state.needApiAuth,
|
||||
showInstallTemplatesPlugin: state.hasLegacyTemplates && !state.pluginService.plugins['joplin.plugin.templates'],
|
||||
isResettingLayout: state.isResettingLayout,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -20,6 +20,7 @@ import * as openTag from './openTag';
|
||||
import * as print from './print';
|
||||
import * as renameFolder from './renameFolder';
|
||||
import * as renameTag from './renameTag';
|
||||
import * as resetLayout from './resetLayout';
|
||||
import * as revealResourceFile from './revealResourceFile';
|
||||
import * as search from './search';
|
||||
import * as setTags from './setTags';
|
||||
@@ -61,6 +62,7 @@ const index:any[] = [
|
||||
print,
|
||||
renameFolder,
|
||||
renameTag,
|
||||
resetLayout,
|
||||
revealResourceFile,
|
||||
search,
|
||||
setTags,
|
||||
|
@@ -14,7 +14,6 @@ export const runtime = (): CommandRuntime => {
|
||||
const resource = await Resource.load(resourceId);
|
||||
if (!resource) throw new Error(`No such resource: ${resourceId}`);
|
||||
if (resource.mime !== 'application/pdf') throw new Error(`Not a PDF: ${resource.mime}`);
|
||||
console.log('Opening PDF', resource);
|
||||
context.dispatch({
|
||||
type: 'DIALOG_OPEN',
|
||||
name: 'pdfViewer',
|
||||
|
25
packages/app-desktop/gui/MainScreen/commands/resetLayout.ts
Normal file
25
packages/app-desktop/gui/MainScreen/commands/resetLayout.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import dialogs from '../../dialogs';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'resetLayout',
|
||||
label: () => _('Reset application layout'),
|
||||
};
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext) => {
|
||||
|
||||
const message = _('Are you sure you want to return to the default layout? The current layout configuration will be lost.');
|
||||
const isConfirmed = await dialogs.confirm(message);
|
||||
|
||||
if (!isConfirmed) return;
|
||||
|
||||
context.dispatch({
|
||||
type: 'RESET_LAYOUT',
|
||||
value: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@@ -20,7 +20,7 @@ export const runtime = (): CommandRuntime => {
|
||||
|
||||
const menuItems = SpellCheckerService.instance().spellCheckerConfigMenuItems(selectedLanguages, useSpellChecker);
|
||||
const menu = Menu.buildFromTemplate(menuItems as any);
|
||||
menu.popup(bridge().window());
|
||||
menu.popup({ window: bridge().window() });
|
||||
},
|
||||
|
||||
mapStateToTitle(state: AppState): string {
|
||||
|
@@ -21,6 +21,7 @@ import checkForUpdates from '../checkForUpdates';
|
||||
const { connect } = require('react-redux');
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
const packageInfo = require('../packageInfo.js');
|
||||
const { clipboard } = require('electron');
|
||||
const Menu = bridge().Menu;
|
||||
@@ -270,6 +271,7 @@ function useMenu(props: Props) {
|
||||
const service = InteropService.instance();
|
||||
try {
|
||||
const result = await service.import(importOptions);
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Import result: ', result);
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
@@ -485,7 +487,8 @@ function useMenu(props: Props) {
|
||||
}
|
||||
|
||||
function _showAbout() {
|
||||
const v = versionInfo(packageInfo);
|
||||
const v = versionInfo(packageInfo, PluginService.instance().plugins);
|
||||
|
||||
|
||||
const copyToClipboard = bridge().showMessageBox(v.message, {
|
||||
icon: `${bridge().electronApp().buildDir()}/icons/128x128.png`,
|
||||
@@ -509,14 +512,14 @@ function useMenu(props: Props) {
|
||||
// Issue: https://github.com/laurent22/joplin/issues/934
|
||||
submenu: [{
|
||||
label: _('About Joplin'),
|
||||
visible: shim.isMac() ? true : false,
|
||||
visible: !!shim.isMac(),
|
||||
click: () => _showAbout(),
|
||||
}, {
|
||||
type: 'separator',
|
||||
visible: shim.isMac() ? true : false,
|
||||
visible: !!shim.isMac(),
|
||||
}, {
|
||||
label: _('Preferences...'),
|
||||
visible: shim.isMac() ? true : false,
|
||||
visible: !!shim.isMac(),
|
||||
accelerator: shim.isMac() && keymapService.getAccelerator('config'),
|
||||
click: () => {
|
||||
props.dispatch({
|
||||
@@ -526,11 +529,11 @@ function useMenu(props: Props) {
|
||||
},
|
||||
}, {
|
||||
label: _('Check for updates...'),
|
||||
visible: shim.isMac() ? true : false,
|
||||
visible: !!shim.isMac(),
|
||||
click: () => _checkForUpdates(),
|
||||
}, {
|
||||
type: 'separator',
|
||||
visible: shim.isMac() ? true : false,
|
||||
visible: !!shim.isMac(),
|
||||
},
|
||||
shim.isMac() ? noItem : newNoteItem,
|
||||
shim.isMac() ? noItem : newTodoItem,
|
||||
@@ -538,14 +541,14 @@ function useMenu(props: Props) {
|
||||
shim.isMac() ? noItem : newSubFolderItem,
|
||||
{
|
||||
type: 'separator',
|
||||
visible: shim.isMac() ? false : true,
|
||||
visible: !shim.isMac(),
|
||||
}, {
|
||||
label: _('Import'),
|
||||
visible: shim.isMac() ? false : true,
|
||||
visible: !shim.isMac(),
|
||||
submenu: importItems,
|
||||
}, {
|
||||
label: _('Export all'),
|
||||
visible: shim.isMac() ? false : true,
|
||||
visible: !shim.isMac(),
|
||||
submenu: exportItems,
|
||||
}, {
|
||||
type: 'separator',
|
||||
@@ -583,7 +586,7 @@ function useMenu(props: Props) {
|
||||
|
||||
const rootMenuFileMacOs = {
|
||||
label: _('&File'),
|
||||
visible: shim.isMac() ? true : false,
|
||||
visible: !!shim.isMac(),
|
||||
submenu: [
|
||||
newNoteItem,
|
||||
newTodoItem,
|
||||
@@ -632,6 +635,7 @@ function useMenu(props: Props) {
|
||||
menuItemDic.textCopy,
|
||||
menuItemDic.textCut,
|
||||
menuItemDic.textPaste,
|
||||
menuItemDic.pasteAsText,
|
||||
menuItemDic.textSelectAll,
|
||||
separator(),
|
||||
// Using the generic "undo"/"redo" roles mean the menu
|
||||
@@ -671,6 +675,7 @@ function useMenu(props: Props) {
|
||||
label: _('&View'),
|
||||
submenu: [
|
||||
menuItemDic.toggleLayoutMoveMode,
|
||||
menuItemDic.resetLayout,
|
||||
separator(),
|
||||
menuItemDic.toggleSideBar,
|
||||
menuItemDic.toggleNoteList,
|
||||
@@ -783,12 +788,15 @@ function useMenu(props: Props) {
|
||||
}, {
|
||||
label: _('Joplin Forum'),
|
||||
click() { void bridge().openExternal('https://discourse.joplinapp.org'); },
|
||||
}, {
|
||||
label: _('Join us on Twitter'),
|
||||
click() { void bridge().openExternal('https://twitter.com/joplinapp'); },
|
||||
}, {
|
||||
label: _('Make a donation'),
|
||||
click() { void bridge().openExternal('https://joplinapp.org/donate/'); },
|
||||
}, {
|
||||
label: _('Check for updates...'),
|
||||
visible: shim.isMac() ? false : true,
|
||||
visible: !shim.isMac(),
|
||||
click: () => _checkForUpdates(),
|
||||
},
|
||||
separator(),
|
||||
@@ -810,10 +818,10 @@ function useMenu(props: Props) {
|
||||
|
||||
{
|
||||
type: 'separator',
|
||||
visible: shim.isMac() ? false : true,
|
||||
visible: !shim.isMac(),
|
||||
}, {
|
||||
label: _('About Joplin'),
|
||||
visible: shim.isMac() ? false : true,
|
||||
visible: !shim.isMac(),
|
||||
click: () => _showAbout(),
|
||||
}],
|
||||
},
|
||||
|
@@ -42,7 +42,7 @@ export default function MultiNoteActions(props: MultiNoteActionsProps) {
|
||||
|
||||
const multiNotesButton_click = (item: any) => {
|
||||
if (item.submenu) {
|
||||
item.submenu.popup(bridge().window());
|
||||
item.submenu.popup({ window: bridge().window() });
|
||||
} else {
|
||||
item.click();
|
||||
}
|
||||
|
@@ -1,11 +1,15 @@
|
||||
const React = require('react');
|
||||
const Component = React.Component;
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const { connect } = require('react-redux');
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { AppState } from '../app.reducer';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
|
||||
class NavigatorComponent extends Component {
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
interface Props {
|
||||
route: any;
|
||||
}
|
||||
|
||||
class NavigatorComponent extends React.Component<Props> {
|
||||
UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
if (newProps.route) {
|
||||
const screenInfo = this.props.screens[newProps.route.routeName];
|
||||
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
|
||||
@@ -17,7 +21,7 @@ class NavigatorComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
updateWindowTitle(title) {
|
||||
updateWindowTitle(title: string) {
|
||||
try {
|
||||
if (bridge().window()) bridge().window().setTitle(title);
|
||||
} catch (error) {
|
||||
@@ -46,10 +50,10 @@ class NavigatorComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const Navigator = connect(state => {
|
||||
const Navigator = connect((state: AppState) => {
|
||||
return {
|
||||
route: state.route,
|
||||
};
|
||||
})(NavigatorComponent);
|
||||
|
||||
module.exports = { Navigator };
|
||||
export default Navigator;
|
@@ -31,7 +31,8 @@ import dialogs from '../../../dialogs';
|
||||
import convertToScreenCoordinates from '../../../utils/convertToScreenCoordinates';
|
||||
import { MarkupToHtml } from '@joplin/renderer';
|
||||
const { clipboard } = require('electron');
|
||||
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
|
||||
const debounce = require('debounce');
|
||||
import shared from '@joplin/lib/components/shared/note-screen-shared';
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
@@ -275,11 +276,22 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
const editorCutText = useCallback(() => {
|
||||
if (editorRef.current) {
|
||||
const selections = editorRef.current.getSelections();
|
||||
if (selections.length > 0) {
|
||||
if (selections.length > 0 && selections[0]) {
|
||||
clipboard.writeText(selections[0]);
|
||||
// Easy way to wipe out just the first selection
|
||||
selections[0] = '';
|
||||
editorRef.current.replaceSelections(selections);
|
||||
} else {
|
||||
const cursor = editorRef.current.getCursor();
|
||||
const line = editorRef.current.getLine(cursor.line);
|
||||
clipboard.writeText(`${line}\n`);
|
||||
const startLine = editorRef.current.getCursor('head');
|
||||
startLine.ch = 0;
|
||||
const endLine = {
|
||||
line: startLine.line + 1,
|
||||
ch: 0,
|
||||
};
|
||||
editorRef.current.replaceRange('', startLine, endLine);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
@@ -287,8 +299,17 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
const editorCopyText = useCallback(() => {
|
||||
if (editorRef.current) {
|
||||
const selections = editorRef.current.getSelections();
|
||||
if (selections.length > 0) {
|
||||
|
||||
|
||||
// Handle the case when there is a selection - copy the selection to the clipboard
|
||||
// When there is no selection, the selection array contains an empty string.
|
||||
if (selections.length > 0 && selections[0]) {
|
||||
clipboard.writeText(selections[0]);
|
||||
} else {
|
||||
// This is the case when there is no selection - copy the current line to the clipboard
|
||||
const cursor = editorRef.current.getCursor();
|
||||
const line = editorRef.current.getLine(cursor.line);
|
||||
clipboard.writeText(line);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
@@ -342,7 +363,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadScripts() {
|
||||
const scriptsToLoad: {src: string; id: string; loaded: boolean}[] = [
|
||||
const scriptsToLoad: { src: string; id: string; loaded: boolean }[] = [
|
||||
{
|
||||
src: `${bridge().vendorDir()}/lib/codemirror/addon/dialog/dialog.css`,
|
||||
id: 'codemirrorDialogStyle',
|
||||
@@ -404,7 +425,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
const maxWidthCss = props.contentMaxWidth ? `
|
||||
margin-right: auto !important;
|
||||
margin-left: auto !important;
|
||||
max-width: ${props.contentMaxWidth}px !important;
|
||||
max-width: ${props.contentMaxWidth}px !important;
|
||||
` : '';
|
||||
|
||||
const element = document.createElement('style');
|
||||
@@ -621,7 +642,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
contentMaxWidth: props.contentMaxWidth,
|
||||
mapsToLine: true,
|
||||
// Always using useCustomPdfViewer for now, we can add a new setting for it in future if we need to.
|
||||
useCustomPdfViewer: true,
|
||||
useCustomPdfViewer: props.useCustomPdfViewer,
|
||||
noteId: props.noteId,
|
||||
vendorDir: bridge().vendorDir(),
|
||||
}));
|
||||
@@ -673,7 +694,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
}, [renderedBody, webviewReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.searchMarkers) return;
|
||||
if (!props.searchMarkers) return () => {};
|
||||
|
||||
// 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
|
||||
@@ -688,11 +709,19 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
webviewRef.current.send('setMarkers', props.searchMarkers.keywords, props.searchMarkers.options);
|
||||
|
||||
if (editorRef.current) {
|
||||
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
|
||||
// Fixes https://github.com/laurent22/joplin/issues/7565
|
||||
const debouncedMarkers = debounce(() => {
|
||||
const matches = editorRef.current.setMarkers(props.searchMarkers.keywords, props.searchMarkers.options);
|
||||
|
||||
props.setLocalSearchResultCount(matches);
|
||||
props.setLocalSearchResultCount(matches);
|
||||
}, 50);
|
||||
debouncedMarkers();
|
||||
return () => {
|
||||
debouncedMarkers.clear();
|
||||
};
|
||||
}
|
||||
}
|
||||
return () => {};
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [props.searchMarkers, previousSearchMarkers, props.setLocalSearchResultCount, props.content, previousContent, renderedBody, previousRenderedBody, renderedBody]);
|
||||
|
||||
@@ -851,7 +880,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
|
||||
function renderEditor() {
|
||||
|
||||
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '<>()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
|
||||
const matchBracesOptions = Setting.value('editor.autoMatchingBraces') ? { override: true, pairs: '``()[]{}\'\'""‘’“”()《》「」『』【】〔〕〖〗〘〙〚〛' } : false;
|
||||
|
||||
return (
|
||||
<div style={cellEditorStyle}>
|
||||
@@ -882,6 +911,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
<div style={cellViewerStyle}>
|
||||
<NoteTextViewer
|
||||
ref={webviewRef}
|
||||
themeId={props.themeId}
|
||||
viewerStyle={styles.viewer}
|
||||
onIpcMessage={webview_ipcMessage}
|
||||
onDomReady={webview_domReady}
|
||||
@@ -908,4 +938,3 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
}
|
||||
|
||||
export default forwardRef(CodeMirror);
|
||||
|
||||
|
@@ -128,8 +128,18 @@ export default function useEditorSearch(CodeMirror: any) {
|
||||
// We only want to scroll the first keyword into view in the case of a multi keyword search
|
||||
const scrollTo = i === 0 && (previousKeywordValue !== keyword.value || previousIndex !== options.selectedIndex || options.searchTimestamp !== previousSearchTimestamp);
|
||||
|
||||
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
|
||||
if (match) marks.push(match);
|
||||
try {
|
||||
const match = highlightSearch(this, searchTerm, options.selectedIndex, scrollTo, !!options.withSelection);
|
||||
if (match) marks.push(match);
|
||||
} catch (error) {
|
||||
if (error.name !== 'SyntaxError') {
|
||||
throw error;
|
||||
}
|
||||
// An error of 'Regular expression too large' might occour in the markJs library
|
||||
// when the input is really big, this catch is here to avoid the application crashing
|
||||
// https://github.com/laurent22/joplin/issues/7634
|
||||
console.error('Error while trying to highlight words from search: ', error);
|
||||
}
|
||||
}
|
||||
|
||||
setMarkers(marks);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// Helper commands added to the the CodeMirror instance
|
||||
export default function useJoplinCommands(CodeMirror: any) {
|
||||
|
||||
CodeMirror.defineExtension('commandExists', function(name: string) {
|
||||
CodeMirror.defineExtension('commandExists', (name: string) => {
|
||||
return !!CodeMirror.commands[name];
|
||||
});
|
||||
}
|
||||
|
@@ -93,7 +93,7 @@ export default function useKeymap(CodeMirror: any) {
|
||||
}
|
||||
|
||||
|
||||
CodeMirror.defineExtension('supportsCommand', function(cmd: EditorCommand) {
|
||||
CodeMirror.defineExtension('supportsCommand', (cmd: EditorCommand) => {
|
||||
return isEditorCommand(cmd.name) && editorCommandToCodeMirror(cmd.name) in CodeMirror.commands;
|
||||
});
|
||||
|
||||
|
@@ -24,6 +24,7 @@ import { MarkupToHtmlOptions } from '../../utils/useMarkupToHtml';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { loadScript } from '../../../utils/loadScript';
|
||||
import bridge from '../../../../services/bridge';
|
||||
import { TinyMceEditorEvents } from './utils/types';
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
|
||||
@@ -76,6 +77,16 @@ function stripMarkup(markupLanguage: number, markup: string, options: any = null
|
||||
return markupToHtml_.stripMarkup(markupLanguage, markup, options);
|
||||
}
|
||||
|
||||
function createSyntheticClipboardEventWithoutHTML(): ClipboardEvent {
|
||||
const clipboardData = new DataTransfer();
|
||||
for (const format of clipboard.availableFormats()) {
|
||||
if (format !== 'text/html') {
|
||||
clipboardData.setData(format, clipboard.read(format));
|
||||
}
|
||||
}
|
||||
return new ClipboardEvent('paste', { clipboardData });
|
||||
}
|
||||
|
||||
interface TinyMceCommand {
|
||||
name: string;
|
||||
value?: any;
|
||||
@@ -102,7 +113,7 @@ interface LastOnChangeEventInfo {
|
||||
let loadedCssFiles_: string[] = [];
|
||||
let loadedJsFiles_: string[] = [];
|
||||
let dispatchDidUpdateIID_: any = null;
|
||||
let changeId_: number = 1;
|
||||
let changeId_ = 1;
|
||||
|
||||
const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
const [editor, setEditor] = useState(null);
|
||||
@@ -159,7 +170,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
const nodeName = event.target ? event.target.nodeName : '';
|
||||
|
||||
if (nodeName === 'INPUT' && event.target.getAttribute('type') === 'checkbox') {
|
||||
editor.fire('joplinChange');
|
||||
editor.fire(TinyMceEditorEvents.JoplinChange);
|
||||
dispatchDidUpdate(editor);
|
||||
}
|
||||
|
||||
@@ -251,7 +262,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
},
|
||||
replaceSelection: (value: any) => {
|
||||
editor.selection.setContent(value);
|
||||
editor.fire('joplinChange');
|
||||
editor.fire(TinyMceEditorEvents.JoplinChange);
|
||||
dispatchDidUpdate(editor);
|
||||
|
||||
// It doesn't make sense but it seems calling setContent
|
||||
@@ -260,6 +271,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
// https://github.com/tinymce/tinymce/issues/3745
|
||||
window.requestAnimationFrame(() => editor.undoManager.add());
|
||||
},
|
||||
pasteAsText: () => editor.fire(TinyMceEditorEvents.PasteAsText),
|
||||
};
|
||||
|
||||
if (additionalCommands[cmd.name]) {
|
||||
@@ -339,6 +351,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Loading script', s.src);
|
||||
|
||||
await loadScript(s);
|
||||
@@ -661,9 +674,9 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
props_onDrop.current(event);
|
||||
});
|
||||
|
||||
editor.on('ObjectResized', function(event: any) {
|
||||
editor.on('ObjectResized', (event: any) => {
|
||||
if (event.target.nodeName === 'IMG') {
|
||||
editor.fire('joplinChange');
|
||||
editor.fire(TinyMceEditorEvents.JoplinChange);
|
||||
dispatchDidUpdate(editor);
|
||||
}
|
||||
});
|
||||
@@ -972,6 +985,15 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const onSetAttrib = (event: any) => {
|
||||
// Dispatch onChange when a link is edited
|
||||
if (event.attrElm[0].nodeName === 'A') {
|
||||
if (event.attrName === 'title' || event.attrName === 'href' || event.attrName === 'rel') {
|
||||
onChangeHandler();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Keypress means that a printable key (letter, digit, etc.) has been
|
||||
// pressed so we want to always trigger onChange in this case
|
||||
function onKeypress() {
|
||||
@@ -992,7 +1014,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
async function onPaste(event: any) {
|
||||
async function onPaste(event: ClipboardEvent) {
|
||||
// We do not use the default pasting behaviour because the input has
|
||||
// to be processed in various ways.
|
||||
event.preventDefault();
|
||||
@@ -1070,33 +1092,41 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
editor.on('keyup', onKeyUp);
|
||||
editor.on('keydown', onKeyDown);
|
||||
editor.on('keypress', onKeypress);
|
||||
editor.on('paste', onPaste);
|
||||
editor.on('copy', onCopy);
|
||||
async function onPasteAsText() {
|
||||
await onPaste(createSyntheticClipboardEventWithoutHTML());
|
||||
}
|
||||
|
||||
editor.on(TinyMceEditorEvents.KeyUp, onKeyUp);
|
||||
editor.on(TinyMceEditorEvents.KeyDown, onKeyDown);
|
||||
editor.on(TinyMceEditorEvents.KeyPress, onKeypress);
|
||||
editor.on(TinyMceEditorEvents.Paste, onPaste);
|
||||
editor.on(TinyMceEditorEvents.PasteAsText, onPasteAsText);
|
||||
editor.on(TinyMceEditorEvents.Copy, onCopy);
|
||||
// `compositionend` means that a user has finished entering a Chinese
|
||||
// (or other languages that require IME) character.
|
||||
editor.on('compositionend', onChangeHandler);
|
||||
editor.on('cut', onCut);
|
||||
editor.on('joplinChange', onChangeHandler);
|
||||
editor.on('Undo', onChangeHandler);
|
||||
editor.on('Redo', onChangeHandler);
|
||||
editor.on('ExecCommand', onExecCommand);
|
||||
editor.on(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Cut, onCut);
|
||||
editor.on(TinyMceEditorEvents.JoplinChange, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Undo, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.Redo, onChangeHandler);
|
||||
editor.on(TinyMceEditorEvents.ExecCommand, onExecCommand);
|
||||
editor.on(TinyMceEditorEvents.SetAttrib, onSetAttrib);
|
||||
|
||||
return () => {
|
||||
try {
|
||||
editor.off('keyup', onKeyUp);
|
||||
editor.off('keydown', onKeyDown);
|
||||
editor.off('keypress', onKeypress);
|
||||
editor.off('paste', onPaste);
|
||||
editor.off('copy', onCopy);
|
||||
editor.off('compositionend', onChangeHandler);
|
||||
editor.off('cut', onCut);
|
||||
editor.off('joplinChange', onChangeHandler);
|
||||
editor.off('Undo', onChangeHandler);
|
||||
editor.off('Redo', onChangeHandler);
|
||||
editor.off('ExecCommand', onExecCommand);
|
||||
editor.off(TinyMceEditorEvents.KeyUp, onKeyUp);
|
||||
editor.off(TinyMceEditorEvents.KeyDown, onKeyDown);
|
||||
editor.off(TinyMceEditorEvents.KeyPress, onKeypress);
|
||||
editor.off(TinyMceEditorEvents.Paste, onPaste);
|
||||
editor.off(TinyMceEditorEvents.PasteAsText, onPasteAsText);
|
||||
editor.off(TinyMceEditorEvents.Copy, onCopy);
|
||||
editor.off(TinyMceEditorEvents.CompositionEnd, onChangeHandler);
|
||||
editor.off(TinyMceEditorEvents.Cut, onCut);
|
||||
editor.off(TinyMceEditorEvents.JoplinChange, onChangeHandler);
|
||||
editor.off(TinyMceEditorEvents.Undo, onChangeHandler);
|
||||
editor.off(TinyMceEditorEvents.Redo, onChangeHandler);
|
||||
editor.off(TinyMceEditorEvents.ExecCommand, onExecCommand);
|
||||
editor.off(TinyMceEditorEvents.SetAttrib, onSetAttrib);
|
||||
} catch (error) {
|
||||
console.warn('Error removing events', error);
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { MarkupToHtml } from '@joplin/renderer';
|
||||
import { TinyMceEditorEvents } from './types';
|
||||
const taboverride = require('taboverride');
|
||||
|
||||
interface SourceInfo {
|
||||
@@ -102,7 +103,7 @@ export default function openEditDialog(editor: any, markupToHtml: any, dispatchD
|
||||
}
|
||||
|
||||
dialogApi.close();
|
||||
editor.fire('joplinChange');
|
||||
editor.fire(TinyMceEditorEvents.JoplinChange);
|
||||
dispatchDidUpdate(editor);
|
||||
},
|
||||
onClose: () => {
|
||||
|
@@ -51,7 +51,7 @@ export default function(editor: any) {
|
||||
editor.execCommand('mceToggleFormat', false, def.name);
|
||||
},
|
||||
onSetup: function(api: any) {
|
||||
editor.formatter.formatChanged(def.name, function(state: boolean) {
|
||||
editor.formatter.formatChanged(def.name, (state: boolean) => {
|
||||
api.setActive(state);
|
||||
});
|
||||
},
|
||||
|
@@ -0,0 +1,16 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export enum TinyMceEditorEvents {
|
||||
KeyUp = 'keyup',
|
||||
KeyDown = 'keydown',
|
||||
KeyPress = 'keypress',
|
||||
Paste = 'paste',
|
||||
PasteAsText = 'pasteAsText',
|
||||
Copy = 'copy',
|
||||
CompositionEnd = 'compositionend',
|
||||
Cut = 'cut',
|
||||
JoplinChange = 'joplinChange',
|
||||
Undo = 'Undo',
|
||||
Redo = 'Redo',
|
||||
ExecCommand = 'ExecCommand',
|
||||
SetAttrib = 'SetAttrib',
|
||||
}
|
@@ -11,6 +11,7 @@ import convertToScreenCoordinates from '../../../../utils/convertToScreenCoordin
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
import { TinyMceEditorEvents } from './types';
|
||||
|
||||
const menuUtils = new MenuUtils(CommandService.instance());
|
||||
|
||||
@@ -77,6 +78,9 @@ export default function(editor: any, plugins: PluginStates, dispatch: Function)
|
||||
editor.insertContent(content);
|
||||
},
|
||||
isReadOnly: false,
|
||||
fireEditorEvent: (event: TinyMceEditorEvents) => {
|
||||
editor.fire(event);
|
||||
},
|
||||
};
|
||||
|
||||
let template = [];
|
||||
@@ -103,7 +107,7 @@ export default function(editor: any, plugins: PluginStates, dispatch: Function)
|
||||
template = template.concat(menuUtils.pluginContextMenuItems(plugins, MenuItemLocation.EditorContextMenu));
|
||||
|
||||
const menu = bridge().Menu.buildFromTemplate(template);
|
||||
menu.popup(bridge().window());
|
||||
menu.popup({ window: bridge().window() });
|
||||
}
|
||||
|
||||
bridge().window().webContents.on('context-menu', onContextMenu);
|
||||
|
@@ -34,12 +34,12 @@ import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher';
|
||||
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { substrWithEllipsis } = require('@joplin/lib/string-utils');
|
||||
const NoteSearchBar = require('../NoteSearchBar.min.js');
|
||||
import NoteSearchBar from '../NoteSearchBar';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
const NoteRevisionViewer = require('../NoteRevisionViewer.min');
|
||||
import NoteRevisionViewer from '../NoteRevisionViewer';
|
||||
|
||||
const commands = [
|
||||
require('./commands/showRevisions'),
|
||||
@@ -424,6 +424,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
fontSize: Setting.value('style.editor.fontSize'),
|
||||
contentMaxWidth: props.contentMaxWidth,
|
||||
isSafeMode: props.isSafeMode,
|
||||
useCustomPdfViewer: props.useCustomPdfViewer,
|
||||
// We need it to identify the context for which media is rendered.
|
||||
// It is currently used to remember pdf scroll position for each attacments of each note uniquely.
|
||||
noteId: props.noteId,
|
||||
@@ -630,6 +631,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
], whenClauseContext)[0],
|
||||
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
|
||||
isSafeMode: state.settings.isSafeMode,
|
||||
useCustomPdfViewer: state.settings.useCustomPdfViewer,
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -1,12 +1,14 @@
|
||||
// AUTO-GENERATED using `gulp buildCommandIndex`
|
||||
import * as focusElementNoteBody from './focusElementNoteBody';
|
||||
import * as focusElementNoteTitle from './focusElementNoteTitle';
|
||||
import * as pasteAsText from './pasteAsText';
|
||||
import * as showLocalSearch from './showLocalSearch';
|
||||
import * as showRevisions from './showRevisions';
|
||||
|
||||
const index:any[] = [
|
||||
focusElementNoteBody,
|
||||
focusElementNoteTitle,
|
||||
pasteAsText,
|
||||
showLocalSearch,
|
||||
showRevisions,
|
||||
];
|
||||
|
16
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts
Normal file
16
packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'pasteAsText',
|
||||
label: () => _('Paste as text'),
|
||||
};
|
||||
|
||||
export const runtime = (comp: any): CommandRuntime => {
|
||||
return {
|
||||
execute: async () => {
|
||||
comp.editorRef.current.execCommand({ name: 'pasteAsText' });
|
||||
},
|
||||
enabledCondition: 'oneNoteSelected && richTextEditorVisible',
|
||||
};
|
||||
};
|
@@ -2,7 +2,7 @@ import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index'
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { copyHtmlToClipboard } from './clipboardUtils';
|
||||
import bridge from '../../../services/bridge';
|
||||
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng } from './contextMenuUtils';
|
||||
import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng, svgDimensions } from './contextMenuUtils';
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
@@ -10,6 +10,7 @@ import BaseItem from '@joplin/lib/models/BaseItem';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import { processPastedHtml } from './resourceHandling';
|
||||
import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
|
||||
import { TinyMceEditorEvents } from '../NoteBody/TinyMCE/utils/types';
|
||||
const fs = require('fs-extra');
|
||||
const { writeFile } = require('fs-extra');
|
||||
const { clipboard } = require('electron');
|
||||
@@ -106,8 +107,10 @@ export function menuItems(dispatch: Function): ContextMenuItems {
|
||||
if (!options.filename) {
|
||||
throw new Error('Filename is needed to save as png');
|
||||
}
|
||||
// double dimensions to make sure it's always big enough even on hdpi screens
|
||||
const [width, height] = svgDimensions(document, options.textToCopy).map((x: number) => x * 2 || undefined);
|
||||
const dataUri = textToDataUri(options.textToCopy, options.mime);
|
||||
const png = await svgUriToPng(document, dataUri);
|
||||
const png = await svgUriToPng(document, dataUri, width, height);
|
||||
const filename = options.filename.replace('.svg', '.png');
|
||||
await saveFileData(png, filename);
|
||||
},
|
||||
@@ -174,6 +177,13 @@ export function menuItems(dispatch: Function): ContextMenuItems {
|
||||
},
|
||||
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && (!!clipboard.readText() || !!clipboard.readHTML()),
|
||||
},
|
||||
pasteAsText: {
|
||||
label: _('Paste as text'),
|
||||
onAction: async (options: ContextMenuOptions) => {
|
||||
options.fireEditorEvent(TinyMceEditorEvents.PasteAsText);
|
||||
},
|
||||
isActive: (_itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.isReadOnly && (!!clipboard.readText() || !!clipboard.readHTML()),
|
||||
},
|
||||
copyLinkUrl: {
|
||||
label: _('Copy Link Address'),
|
||||
onAction: async (options: ContextMenuOptions) => {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import Resource from '@joplin/lib/models/Resource';
|
||||
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
const logger = Logger.create('contextMenuUtils');
|
||||
export enum ContextMenuItemType {
|
||||
None = '',
|
||||
Image = 'image',
|
||||
@@ -18,6 +19,7 @@ export interface ContextMenuOptions {
|
||||
htmlToCopy: string;
|
||||
insertContent: Function;
|
||||
isReadOnly?: boolean;
|
||||
fireEditorEvent: Function;
|
||||
}
|
||||
|
||||
export interface ContextMenuItem {
|
||||
@@ -40,8 +42,23 @@ export async function resourceInfo(options: ContextMenuOptions) {
|
||||
export function textToDataUri(text: string, mime: string): string {
|
||||
return `data:${mime};base64,${Buffer.from(text).toString('base64')}`;
|
||||
}
|
||||
|
||||
export const svgUriToPng = (document: Document, svg: string) => {
|
||||
export const svgDimensions = (document: Document, svg: string) => {
|
||||
let width: number;
|
||||
let height: number;
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const id = parser.parseFromString(svg, 'text/html').querySelector('svg').id;
|
||||
({ width, height } = document.querySelector<HTMLIFrameElement>('.noteTextViewer').contentWindow.document.querySelector(`#${id}`).getBoundingClientRect());
|
||||
} catch (error) {
|
||||
logger.warn('Could not get SVG dimensions.');
|
||||
logger.warn('Error was: ', error);
|
||||
}
|
||||
if (!width || !height) {
|
||||
return [undefined, undefined];
|
||||
}
|
||||
return [width, height];
|
||||
};
|
||||
export const svgUriToPng = (document: Document, svg: string, width: number, height: number) => {
|
||||
return new Promise<Uint8Array>((resolve, reject) => {
|
||||
let canvas: HTMLCanvasElement;
|
||||
let img: HTMLImageElement;
|
||||
@@ -63,11 +80,21 @@ export const svgUriToPng = (document: Document, svg: string) => {
|
||||
try {
|
||||
canvas = document.createElement('canvas');
|
||||
if (!canvas) throw new Error('Failed to create canvas element');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
if (!width || !height) {
|
||||
const maxDimension = 1024;
|
||||
if (img.width > img.height) {
|
||||
width = maxDimension;
|
||||
height = width * (img.height / img.width);
|
||||
} else {
|
||||
height = maxDimension;
|
||||
width = height * (img.width / img.height);
|
||||
}
|
||||
}
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) throw new Error('Failed to get context');
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
|
||||
const pngUri = canvas.toDataURL('image/png');
|
||||
if (!pngUri) throw new Error('Failed to generate png uri');
|
||||
const pngBase64 = pngUri.split(',')[1];
|
||||
@@ -80,8 +107,8 @@ export const svgUriToPng = (document: Document, svg: string) => {
|
||||
canvas.remove();
|
||||
img.remove();
|
||||
resolve(buff);
|
||||
} catch (err) {
|
||||
cleanUpAndReject(err);
|
||||
} catch (error) {
|
||||
cleanUpAndReject(error);
|
||||
}
|
||||
};
|
||||
img.onerror = function(e) {
|
||||
|
@@ -43,6 +43,7 @@ export interface NoteEditorProps {
|
||||
richTextBannerDismissed: boolean;
|
||||
contentMaxWidth: number;
|
||||
isSafeMode: boolean;
|
||||
useCustomPdfViewer: boolean;
|
||||
}
|
||||
|
||||
export interface NoteBodyEditorProps {
|
||||
@@ -76,6 +77,7 @@ export interface NoteBodyEditorProps {
|
||||
contentMaxWidth: number;
|
||||
isSafeMode: boolean;
|
||||
noteId: string;
|
||||
useCustomPdfViewer: boolean;
|
||||
}
|
||||
|
||||
export interface FormNote {
|
||||
|
@@ -9,7 +9,7 @@ export default function(dependencies: HookDependencies) {
|
||||
const { folderId } = dependencies;
|
||||
const [folder, setFolder] = useState(null);
|
||||
|
||||
useEffect(function() {
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadFolder() {
|
||||
|
@@ -192,7 +192,7 @@ export default function useFormNote(dependencies: HookDependencies) {
|
||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
|
||||
}, [noteId, isProvisional, formNote]);
|
||||
|
||||
const onResourceChange = useCallback(async function(event: any = null) {
|
||||
const onResourceChange = useCallback(async (event: any = null) => {
|
||||
const resourceIds = await Note.linkedResourceIds(formNote.body);
|
||||
if (!event || resourceIds.indexOf(event.id) >= 0) {
|
||||
clearResourceCache();
|
||||
|
@@ -13,6 +13,7 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
|
||||
const args = event.args;
|
||||
const arg0 = args && args.length >= 1 ? args[0] : null;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
if (msg !== 'percentScroll') console.info(`Got ipc-message: ${msg}`, arg0);
|
||||
|
||||
if (msg.indexOf('error:') === 0) {
|
||||
@@ -41,9 +42,10 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
|
||||
linkToCopy: arg0.linkToCopy || null,
|
||||
htmlToCopy: '',
|
||||
insertContent: () => { console.warn('insertContent() not implemented'); },
|
||||
fireEditorEvent: () => { console.warn('fireEditorEvent() not implemented'); },
|
||||
}, dispatch);
|
||||
|
||||
menu.popup(bridge().window());
|
||||
menu.popup({ window: bridge().window() });
|
||||
} else if (msg.indexOf('#') === 0) {
|
||||
// This is an internal anchor, which is handled by the WebView so skip this case
|
||||
} else if (msg === 'contentScriptExecuteCommand') {
|
||||
|
@@ -9,6 +9,7 @@ const commandsWithDependencies = [
|
||||
require('../commands/showLocalSearch'),
|
||||
require('../commands/focusElementNoteTitle'),
|
||||
require('../commands/focusElementNoteBody'),
|
||||
require('../commands/pasteAsText'),
|
||||
];
|
||||
|
||||
interface HookDependencies {
|
||||
|
@@ -13,7 +13,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import styled from 'styled-components';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
const { ItemList } = require('../ItemList.min.js');
|
||||
import ItemList from '../ItemList';
|
||||
const { connect } = require('react-redux');
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
@@ -123,7 +123,7 @@ const NoteListComponent = (props: Props) => {
|
||||
customCss: props.customCss,
|
||||
});
|
||||
|
||||
menu.popup(bridge().window());
|
||||
menu.popup({ window: bridge().window() });
|
||||
}, [props.selectedNoteIds, props.notes, props.dispatch, props.watchedNoteFiles, props.plugins, props.selectedFolderId, props.customCss]);
|
||||
|
||||
const onGlobalDrop_ = () => {
|
||||
@@ -160,21 +160,27 @@ const NoteListComponent = (props: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const noteItem_noteDrop = async (event: any) => {
|
||||
if (props.notesParentType !== 'Folder') return;
|
||||
const canManuallySortNotes = async () => {
|
||||
if (props.notesParentType !== 'Folder') return false;
|
||||
|
||||
if (props.noteSortOrder !== 'order') {
|
||||
const doIt = await bridge().showConfirmMessageBox(_('To manually sort the notes, the sort order must be changed to "%s" in the menu "%s" > "%s"', _('Custom order'), _('View'), _('Sort notes by')), {
|
||||
buttons: [_('Do it now'), _('Cancel')],
|
||||
});
|
||||
if (!doIt) return;
|
||||
if (!doIt) return false;
|
||||
|
||||
Setting.setValue('notes.sortOrder.field', 'order');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const noteItem_noteDrop = async (event: any) => {
|
||||
|
||||
// TODO: check that parent type is folder
|
||||
|
||||
if (!canManuallySortNotes()) {
|
||||
return;
|
||||
}
|
||||
const dt = event.dataTransfer;
|
||||
unregisterGlobalDragEndEvent_();
|
||||
setDragOverTargetNoteIndex(null);
|
||||
@@ -182,7 +188,7 @@ const NoteListComponent = (props: Props) => {
|
||||
const targetNoteIndex = dragTargetNoteIndex_(event);
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
|
||||
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex);
|
||||
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex, props.uncompletedTodosOnTop, props.showCompletedTodos);
|
||||
};
|
||||
|
||||
|
||||
@@ -340,11 +346,31 @@ const NoteListComponent = (props: Props) => {
|
||||
return noteIndex;
|
||||
};
|
||||
|
||||
const noteItem_noteMove = async (direction: number) => {
|
||||
if (!canManuallySortNotes()) {
|
||||
return;
|
||||
}
|
||||
const noteIds = props.selectedNoteIds;
|
||||
const noteId = noteIds[0];
|
||||
let targetNoteIndex = BaseModel.modelIndexById(props.notes, noteId);
|
||||
if ((direction === 1)) {
|
||||
targetNoteIndex += 2;
|
||||
}
|
||||
if ((direction === -1)) {
|
||||
targetNoteIndex -= 1;
|
||||
}
|
||||
void Note.insertNotesAt(props.selectedFolderId, noteIds, targetNoteIndex, props.uncompletedTodosOnTop, props.showCompletedTodos);
|
||||
};
|
||||
|
||||
const onKeyDown = async (event: any) => {
|
||||
const keyCode = event.keyCode;
|
||||
const noteIds = props.selectedNoteIds;
|
||||
|
||||
if (noteIds.length > 0 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode === 36)) {
|
||||
if ((keyCode === 40 || keyCode === 38) && event.altKey) {
|
||||
// (DOWN / UP) & ALT
|
||||
await noteItem_noteMove(keyCode === 40 ? 1 : -1);
|
||||
event.preventDefault();
|
||||
} else if (noteIds.length > 0 && (keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode === 36)) {
|
||||
// DOWN / UP / PAGEDOWN / PAGEUP / END / HOME
|
||||
const noteId = noteIds[0];
|
||||
let noteIndex = BaseModel.modelIndexById(props.notes, noteId);
|
||||
@@ -528,6 +554,8 @@ const mapStateToProps = (state: AppState) => {
|
||||
provisionalNoteIds: state.provisionalNoteIds,
|
||||
isInsertingNotes: state.isInsertingNotes,
|
||||
noteSortOrder: state.settings['notes.sortOrder.field'],
|
||||
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
|
||||
showCompletedTodos: state.settings.showCompletedTodos,
|
||||
highlightedWords: state.highlightedWords,
|
||||
plugins: state.pluginService.plugins,
|
||||
customCss: state.customCss,
|
||||
|
@@ -12,6 +12,8 @@ export interface Props {
|
||||
customCss: string;
|
||||
notesParentType: string;
|
||||
noteSortOrder: string;
|
||||
uncompletedTodosOnTop: boolean;
|
||||
showCompletedTodos: boolean;
|
||||
resizableLayoutEventEmitter: any;
|
||||
isInsertingNotes: boolean;
|
||||
folders: FolderEntity[];
|
||||
|
@@ -7,6 +7,7 @@ import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { runtime as focusSearchRuntime } from './commands/focusSearch';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import { notesSortOrderNextField } from '../../services/sortOrder/notesSortOrderUtils';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
const { connect } = require('react-redux');
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
@@ -23,21 +24,24 @@ const StyledRoot = styled.div`
|
||||
box-sizing: border-box;
|
||||
height: ${(props: any) => props.height}px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
padding: ${(props: any) => props.theme.mainPadding}px;
|
||||
background-color: ${(props: any) => props.theme.backgroundColor3};
|
||||
gap: 5px;
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
margin-left: 8px;
|
||||
width: 26px;
|
||||
width: auto;
|
||||
height: 26px;
|
||||
min-width: 26px;
|
||||
min-height: 26px;
|
||||
flex: 1 0 auto;
|
||||
|
||||
.fa, .fas {
|
||||
font-size: 11px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPairButtonL = styled(Button)`
|
||||
margin-left: 8px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
min-width: ${(props: any) => buttonSizePx(props)}px;
|
||||
max-width: ${(props: any) => buttonSizePx(props)}px;
|
||||
@@ -45,21 +49,28 @@ const StyledPairButtonL = styled(Button)`
|
||||
|
||||
const StyledPairButtonR = styled(Button)`
|
||||
min-width: 8px;
|
||||
margin-left: 0px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
border-width: 1px 1px 1px 0;
|
||||
width: auto;
|
||||
`;
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
const RowContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1 1 auto;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const SortOrderButtonsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1 1 auto;
|
||||
`;
|
||||
|
||||
function NoteListControls(props: Props) {
|
||||
const searchBarRef = useRef(null);
|
||||
|
||||
useEffect(function() {
|
||||
useEffect(() => {
|
||||
CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef));
|
||||
|
||||
return function() {
|
||||
@@ -116,8 +127,36 @@ function NoteListControls(props: Props) {
|
||||
if (!props.showNewNoteButtons) return null;
|
||||
|
||||
return (
|
||||
<ButtonContainer>
|
||||
{showsSortOrderButtons() &&
|
||||
<RowContainer>
|
||||
<StyledButton
|
||||
className="new-note-button"
|
||||
tooltip={CommandService.instance().label('newNote')}
|
||||
iconName="fas fa-plus"
|
||||
title={_('%s', 'New note')}
|
||||
level={ButtonLevel.Primary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewNoteButtonClick}
|
||||
/>
|
||||
<StyledButton
|
||||
className="new-todo-button"
|
||||
tooltip={CommandService.instance().label('newTodo')}
|
||||
iconName="fas fa-plus"
|
||||
title={_('%s', 'New to-do')}
|
||||
level={ButtonLevel.Secondary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewTodoButtonClick}
|
||||
/>
|
||||
</RowContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
{renderNewNoteButtons()}
|
||||
<RowContainer>
|
||||
<SearchBar inputRef={searchBarRef}/>
|
||||
<SortOrderButtonsContainer>
|
||||
{showsSortOrderButtons() &&
|
||||
<StyledPairButtonL
|
||||
className="sort-order-field-button"
|
||||
tooltip={sortOrderFieldTooltip()}
|
||||
@@ -126,8 +165,8 @@ function NoteListControls(props: Props) {
|
||||
size={ButtonSize.Small}
|
||||
onClick={onSortOrderFieldButtonClick}
|
||||
/>
|
||||
}
|
||||
{showsSortOrderButtons() &&
|
||||
}
|
||||
{showsSortOrderButtons() &&
|
||||
<StyledPairButtonR
|
||||
className="sort-order-reverse-button"
|
||||
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
|
||||
@@ -136,31 +175,9 @@ function NoteListControls(props: Props) {
|
||||
size={ButtonSize.Small}
|
||||
onClick={onSortOrderReverseButtonClick}
|
||||
/>
|
||||
}
|
||||
<StyledButton
|
||||
className="new-todo-button"
|
||||
tooltip={CommandService.instance().label('newTodo')}
|
||||
iconName="far fa-check-square"
|
||||
level={ButtonLevel.Primary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewTodoButtonClick}
|
||||
/>
|
||||
<StyledButton
|
||||
className="new-note-button"
|
||||
tooltip={CommandService.instance().label('newNote')}
|
||||
iconName="icon-note"
|
||||
level={ButtonLevel.Primary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewNoteButtonClick}
|
||||
/>
|
||||
</ButtonContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot height={props.height}>
|
||||
<SearchBar inputRef={searchBarRef}/>
|
||||
{renderNewNoteButtons()}
|
||||
}
|
||||
</SortOrderButtonsContainer>
|
||||
</RowContainer>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
||||
|
@@ -127,13 +127,21 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
|
||||
|
||||
mark.unmark();
|
||||
|
||||
for (let i = 0; i < props.highlightedWords.length; i++) {
|
||||
const w = props.highlightedWords[i];
|
||||
|
||||
markJsUtils.markKeyword(mark, w, {
|
||||
pregQuote: pregQuote,
|
||||
replaceRegexDiacritics: replaceRegexDiacritics,
|
||||
});
|
||||
try {
|
||||
for (const wordToBeHighlighted of props.highlightedWords) {
|
||||
markJsUtils.markKeyword(mark, wordToBeHighlighted, {
|
||||
pregQuote: pregQuote,
|
||||
replaceRegexDiacritics: replaceRegexDiacritics,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name !== 'SyntaxError') {
|
||||
throw error;
|
||||
}
|
||||
// An error of 'Regular expression too large' might occour in the markJs library
|
||||
// when the input is really big, this catch is here to avoid the application crashing
|
||||
// https://github.com/laurent22/joplin/issues/7634
|
||||
console.error('Error while trying to highlight words from search: ', error);
|
||||
}
|
||||
|
||||
// Note: in this case it is safe to use dangerouslySetInnerHTML because titleElement
|
||||
|
@@ -33,7 +33,7 @@ export default function NoteListWrapper(props: Props) {
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<NoteListControls height={controlHeight} />
|
||||
<NoteListControls height={controlHeight}/>
|
||||
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
|
||||
</StyledRoot>
|
||||
);
|
||||
|
@@ -1,18 +1,38 @@
|
||||
const React = require('react');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const DialogButtonRow = require('./DialogButtonRow').default;
|
||||
import * as React from 'react';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import time from '@joplin/lib/time';
|
||||
import DialogButtonRow from './DialogButtonRow';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import bridge from '../services/bridge';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
const Datetime = require('react-datetime').default;
|
||||
const Note = require('@joplin/lib/models/Note').default;
|
||||
const formatcoords = require('formatcoords');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const { clipboard } = require('electron');
|
||||
const formatcoords = require('formatcoords');
|
||||
|
||||
class NotePropertiesDialog extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
interface Props {
|
||||
noteId: string;
|
||||
onClose: Function;
|
||||
onRevisionLinkClick: Function;
|
||||
themeId: number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
editedKey: string;
|
||||
formNote: any;
|
||||
editedValue: any;
|
||||
}
|
||||
|
||||
class NotePropertiesDialog extends React.Component<Props, State> {
|
||||
|
||||
private okButton: any;
|
||||
private keyToLabel_: Record<string, string>;
|
||||
private styleKey_: number;
|
||||
private styles_: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.revisionsLink_click = this.revisionsLink_click.bind(this);
|
||||
this.buttonRow_click = this.buttonRow_click.bind(this);
|
||||
@@ -37,7 +57,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadNote(this.props.noteId);
|
||||
void this.loadNote(this.props.noteId);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -46,7 +66,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
async loadNote(noteId) {
|
||||
async loadNote(noteId: string) {
|
||||
if (!noteId) {
|
||||
this.setState({ formNote: null });
|
||||
} else {
|
||||
@@ -56,8 +76,8 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
latLongFromLocation(location) {
|
||||
const o = {};
|
||||
latLongFromLocation(location: string) {
|
||||
const o: any = {};
|
||||
const l = location.split(',');
|
||||
if (l.length === 2) {
|
||||
o.latitude = l[0].trim();
|
||||
@@ -69,8 +89,8 @@ class NotePropertiesDialog extends React.Component {
|
||||
return o;
|
||||
}
|
||||
|
||||
noteToFormNote(note) {
|
||||
const formNote = {};
|
||||
noteToFormNote(note: NoteEntity) {
|
||||
const formNote: any = {};
|
||||
|
||||
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
|
||||
formNote.user_created_time = time.formatMsToLocal(note.user_created_time);
|
||||
@@ -93,7 +113,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
return formNote;
|
||||
}
|
||||
|
||||
formNoteToNote(formNote) {
|
||||
formNoteToNote(formNote: any) {
|
||||
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
|
||||
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
|
||||
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
|
||||
@@ -107,7 +127,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
return note;
|
||||
}
|
||||
|
||||
styles(themeId) {
|
||||
styles(themeId: number) {
|
||||
const styleKey = themeId;
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
@@ -148,7 +168,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
async closeDialog(applyChanges) {
|
||||
async closeDialog(applyChanges: boolean) {
|
||||
if (applyChanges) {
|
||||
await this.saveProperty();
|
||||
const note = this.formNoteToNote(this.state.formNote);
|
||||
@@ -163,26 +183,26 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
buttonRow_click(event) {
|
||||
this.closeDialog(event.buttonName === 'ok');
|
||||
buttonRow_click(event: any) {
|
||||
void this.closeDialog(event.buttonName === 'ok');
|
||||
}
|
||||
|
||||
revisionsLink_click() {
|
||||
this.closeDialog(false);
|
||||
void this.closeDialog(false);
|
||||
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
|
||||
}
|
||||
|
||||
editPropertyButtonClick(key, initialValue) {
|
||||
editPropertyButtonClick(key: string, initialValue: any) {
|
||||
this.setState({
|
||||
editedKey: key,
|
||||
editedValue: initialValue,
|
||||
});
|
||||
|
||||
shim.setTimeout(() => {
|
||||
if (this.refs.editField.openCalendar) {
|
||||
this.refs.editField.openCalendar();
|
||||
if ((this.refs.editField as any).openCalendar) {
|
||||
(this.refs.editField as any).openCalendar();
|
||||
} else {
|
||||
this.refs.editField.focus();
|
||||
(this.refs.editField as any).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
@@ -190,7 +210,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
async saveProperty() {
|
||||
if (!this.state.editedKey) return;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve: Function) => {
|
||||
const newFormNote = Object.assign({}, this.state.formNote);
|
||||
|
||||
if (this.state.editedKey.indexOf('_time') >= 0) {
|
||||
@@ -214,7 +234,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
|
||||
async cancelProperty() {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve: Function) => {
|
||||
this.okButton.current.focus();
|
||||
this.setState({
|
||||
editedKey: null,
|
||||
@@ -225,7 +245,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
createNoteField(key, value) {
|
||||
createNoteField(key: string, value: any) {
|
||||
const styles = this.styles(this.props.themeId);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
|
||||
@@ -234,11 +254,11 @@ class NotePropertiesDialog extends React.Component {
|
||||
let editCompHandler = null;
|
||||
let editCompIcon = null;
|
||||
|
||||
const onKeyDown = event => {
|
||||
const onKeyDown = (event: any) => {
|
||||
if (event.keyCode === 13) {
|
||||
this.saveProperty();
|
||||
void this.saveProperty();
|
||||
} else if (event.keyCode === 27) {
|
||||
this.cancelProperty();
|
||||
void this.cancelProperty();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -251,17 +271,17 @@ class NotePropertiesDialog extends React.Component {
|
||||
dateFormat={time.dateFormat()}
|
||||
timeFormat={time.timeFormat()}
|
||||
inputProps={{
|
||||
onKeyDown: event => onKeyDown(event, key),
|
||||
onKeyDown: (event: any) => onKeyDown(event),
|
||||
style: styles.input,
|
||||
}}
|
||||
onChange={momentObject => {
|
||||
onChange={(momentObject: any) => {
|
||||
this.setState({ editedValue: momentObject });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
editCompHandler = () => {
|
||||
this.saveProperty();
|
||||
void this.saveProperty();
|
||||
};
|
||||
editCompIcon = 'fa-save';
|
||||
} else {
|
||||
@@ -344,12 +364,12 @@ class NotePropertiesDialog extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
formatLabel(key) {
|
||||
formatLabel(key: string) {
|
||||
if (this.keyToLabel_[key]) return this.keyToLabel_[key];
|
||||
return key;
|
||||
}
|
||||
|
||||
formatValue(key, note) {
|
||||
formatValue(key: string, note: NoteEntity) {
|
||||
if (key === 'location') {
|
||||
if (!Number(note.latitude) && !Number(note.longitude)) return null;
|
||||
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
|
||||
@@ -357,10 +377,10 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
|
||||
if (['user_updated_time', 'user_created_time', 'todo_completed'].indexOf(key) >= 0) {
|
||||
return time.formatMsToLocal(note[key]);
|
||||
return time.formatMsToLocal((note as any)[key]);
|
||||
}
|
||||
|
||||
return note[key];
|
||||
return (note as any)[key];
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -389,4 +409,4 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NotePropertiesDialog;
|
||||
export default NotePropertiesDialog;
|
@@ -1,25 +1,45 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const NoteTextViewer = require('./NoteTextViewer').default;
|
||||
const HelpButton = require('./HelpButton.min');
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
const Revision = require('@joplin/lib/models/Revision').default;
|
||||
import * as React from 'react';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import NoteTextViewer from './NoteTextViewer';
|
||||
import HelpButton from './HelpButton';
|
||||
import BaseModel from '@joplin/lib/BaseModel';
|
||||
import Revision from '@joplin/lib/models/Revision';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import RevisionService from '@joplin/lib/services/RevisionService';
|
||||
import { MarkupToHtml } from '@joplin/renderer';
|
||||
import time from '@joplin/lib/time';
|
||||
import bridge from '../services/bridge';
|
||||
import markupLanguageUtils from '../utils/markupLanguageUtils';
|
||||
import { NoteEntity, RevisionEntity } from '@joplin/lib/services/database/types';
|
||||
import { AppState } from '../app.reducer';
|
||||
const urlUtils = require('@joplin/lib/urlUtils');
|
||||
const Setting = require('@joplin/lib/models/Setting').default;
|
||||
const RevisionService = require('@joplin/lib/services/RevisionService').default;
|
||||
const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
|
||||
const { MarkupToHtml } = require('@joplin/renderer');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const ReactTooltip = require('react-tooltip');
|
||||
const { urlDecode } = require('@joplin/lib/string-utils');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
const markupLanguageUtils = require('../utils/markupLanguageUtils').default;
|
||||
const { connect } = require('react-redux');
|
||||
import shared from '@joplin/lib/components/shared/note-screen-shared';
|
||||
|
||||
class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
interface Props {
|
||||
themeId: number;
|
||||
noteId: string;
|
||||
onBack: Function;
|
||||
customCss: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
note: NoteEntity;
|
||||
revisions: RevisionEntity[];
|
||||
currentRevId: string;
|
||||
restoring: boolean;
|
||||
}
|
||||
|
||||
class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
|
||||
|
||||
private viewerRef_: any;
|
||||
private helpButton_onClick: Function;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
revisions: [],
|
||||
@@ -65,7 +85,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
|
||||
},
|
||||
() => {
|
||||
this.reloadNote();
|
||||
void this.reloadNote();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -82,7 +102,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
if (this.props.onBack) this.props.onBack();
|
||||
}
|
||||
|
||||
revisionList_onChange(event) {
|
||||
revisionList_onChange(event: any) {
|
||||
const value = event.target.value;
|
||||
|
||||
if (!value) {
|
||||
@@ -93,7 +113,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
currentRevId: value,
|
||||
},
|
||||
() => {
|
||||
this.reloadNote();
|
||||
void this.reloadNote();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -128,12 +148,12 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
});
|
||||
|
||||
this.viewerRef_.current.send('setHtml', result.html, {
|
||||
cssFiles: result.cssFiles,
|
||||
// cssFiles: result.cssFiles,
|
||||
pluginAssets: result.pluginAssets,
|
||||
});
|
||||
}
|
||||
|
||||
async webview_ipcMessage(event) {
|
||||
async webview_ipcMessage(event: any) {
|
||||
// For the revision view, we only suppport a minimal subset of the IPC messages.
|
||||
// For example, we don't need interactive checkboxes or sync between viewer and editor view.
|
||||
// We try to get most links work though, except for internal (joplin://) links.
|
||||
@@ -148,9 +168,9 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
throw new Error(_('Unsupported link or message: %s', msg));
|
||||
} else if (urlUtils.urlProtocol(msg)) {
|
||||
if (msg.indexOf('file://') === 0) {
|
||||
require('electron').shell.openExternal(urlDecode(msg));
|
||||
void require('electron').shell.openExternal(urlDecode(msg));
|
||||
} else {
|
||||
require('electron').shell.openExternal(msg);
|
||||
void require('electron').shell.openExternal(msg);
|
||||
}
|
||||
} else if (msg.indexOf('#') === 0) {
|
||||
// This is an internal anchor, which is handled by the WebView so skip this case
|
||||
@@ -202,7 +222,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
const viewer = <NoteTextViewer themeId={this.props.themeId} viewerStyle={{ display: 'flex', flex: 1, borderLeft: 'none' }} ref={this.viewerRef_} onDomReady={this.viewer_domReady} onIpcMessage={this.webview_ipcMessage} />;
|
||||
|
||||
return (
|
||||
<div style={style.root}>
|
||||
<div style={style.root as any}>
|
||||
{titleInput}
|
||||
{viewer}
|
||||
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip" />
|
||||
@@ -211,7 +231,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
@@ -219,4 +239,4 @@ const mapStateToProps = state => {
|
||||
|
||||
const NoteRevisionViewer = connect(mapStateToProps)(NoteRevisionViewerComponent);
|
||||
|
||||
module.exports = NoteRevisionViewer;
|
||||
export default NoteRevisionViewer;
|
@@ -1,10 +1,27 @@
|
||||
const React = require('react');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
import * as React from 'react';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
class NoteSearchBar extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
interface Props {
|
||||
themeId: number;
|
||||
onNext: Function;
|
||||
onPrevious: Function;
|
||||
onClose: Function;
|
||||
onChange: Function;
|
||||
query: string;
|
||||
searching: boolean;
|
||||
resultCount: number;
|
||||
selectedIndex: number;
|
||||
visiblePanes: string[];
|
||||
style: any;
|
||||
}
|
||||
|
||||
class NoteSearchBar extends React.Component<Props> {
|
||||
|
||||
private backgroundColor: any;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.searchInput_change = this.searchInput_change.bind(this);
|
||||
this.searchInput_keyDown = this.searchInput_keyDown.bind(this);
|
||||
@@ -29,7 +46,7 @@ class NoteSearchBar extends React.Component {
|
||||
return style;
|
||||
}
|
||||
|
||||
buttonIconComponent(iconName, clickHandler, isEnabled) {
|
||||
buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const searchButton = {
|
||||
@@ -57,12 +74,12 @@ class NoteSearchBar extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
searchInput_change(event) {
|
||||
searchInput_change(event: any) {
|
||||
const query = event.currentTarget.value;
|
||||
this.triggerOnChange(query);
|
||||
}
|
||||
|
||||
searchInput_keyDown(event) {
|
||||
searchInput_keyDown(event: any) {
|
||||
if (event.keyCode === 13) {
|
||||
// ENTER
|
||||
event.preventDefault();
|
||||
@@ -101,13 +118,13 @@ class NoteSearchBar extends React.Component {
|
||||
if (this.props.onClose) this.props.onClose();
|
||||
}
|
||||
|
||||
triggerOnChange(query) {
|
||||
triggerOnChange(query: string) {
|
||||
if (this.props.onChange) this.props.onChange(query);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.refs.searchInput.focus();
|
||||
this.refs.searchInput.select();
|
||||
(this.refs.searchInput as any).focus();
|
||||
(this.refs.searchInput as any).select();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -177,4 +194,4 @@ class NoteSearchBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NoteSearchBar;
|
||||
export default NoteSearchBar;
|
@@ -1,9 +1,16 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
import time from '@joplin/lib/time';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||
import { AppState } from '../app.reducer';
|
||||
const { connect } = require('react-redux');
|
||||
const time = require('@joplin/lib/time').default;
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
|
||||
class NoteStatusBarComponent extends React.Component {
|
||||
interface Props {
|
||||
themeId: number;
|
||||
note: NoteEntity;
|
||||
}
|
||||
|
||||
class NoteStatusBarComponent extends React.Component<Props> {
|
||||
style() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
@@ -23,7 +30,7 @@ class NoteStatusBarComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
// notes: state.notes,
|
||||
// folders: state.folders,
|
||||
@@ -34,4 +41,4 @@ const mapStateToProps = state => {
|
||||
|
||||
const NoteStatusBar = connect(mapStateToProps)(NoteStatusBarComponent);
|
||||
|
||||
module.exports = { NoteStatusBar };
|
||||
export default NoteStatusBar;
|
@@ -6,7 +6,8 @@ interface Props {
|
||||
onDomReady: Function;
|
||||
onIpcMessage: Function;
|
||||
viewerStyle: any;
|
||||
contentMaxWidth: number;
|
||||
contentMaxWidth?: number;
|
||||
themeId: number;
|
||||
}
|
||||
|
||||
export default class NoteTextViewerComponent extends React.Component<Props, any> {
|
||||
|
@@ -59,9 +59,10 @@ export default function PdfViewer(props: Props) {
|
||||
linkToCopy: null,
|
||||
htmlToCopy: '',
|
||||
insertContent: () => { console.warn('insertContent() not implemented'); },
|
||||
fireEditorEvent: () => { console.warn('fireEditorEvent() not implemented'); },
|
||||
} as ContextMenuOptions, props.dispatch);
|
||||
|
||||
menu.popup(bridge().window());
|
||||
menu.popup({ window: bridge().window() });
|
||||
}, [props.dispatch]);
|
||||
|
||||
const onMessage_ = useCallback(async (event: any) => {
|
||||
|
@@ -24,9 +24,9 @@ import MasterPasswordDialog from './MasterPasswordDialog/Dialog';
|
||||
import EditFolderDialog from './EditFolderDialog/Dialog';
|
||||
import PdfViewer from './PdfViewer';
|
||||
import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
|
||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||
import ImportScreen from './ImportScreen';
|
||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||
const { Navigator } = require('./Navigator.min.js');
|
||||
import Navigator from './Navigator';
|
||||
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
|
||||
const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
@@ -98,7 +98,7 @@ const GlobalStyle = createGlobalStyle`
|
||||
let wcsTimeoutId_: any = null;
|
||||
|
||||
async function initialize() {
|
||||
bridge().window().on('resize', function() {
|
||||
bridge().window().on('resize', () => {
|
||||
if (wcsTimeoutId_) shim.clearTimeout(wcsTimeoutId_);
|
||||
|
||||
wcsTimeoutId_ = shim.setTimeout(() => {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user