Compare commits

..
119 Commits
Author SHA1 Message Date
yashpalgoyal1304 96475f82b2 Doc: Add Badge "Get it on F-Droid" ... (#6216) 2022-03-18 10:54:02 +00:00
Reinhart Previano K e720c91f72 Mobile: Fixes #3533: Add marker to Geolocation URL (#3977)
* Update React Native build documentation

* Mobile: Fixes #3533: Add marker to Geolocation URL

* Try to remove BUILD.md from #3977
2020-10-29 11:15:26 +00:00
Arda Kılıçdağı 771acbe463 All: Translation: Update tr_TR.po (#3773) 2020-09-19 13:34:18 -04:00
Laurent Cozic a5e4c13759 Merge branch 'dev' 2020-09-06 12:59:20 +01:00
Laurent Cozic 0998fc0ad7 Doc: Update dontate page 2020-09-05 22:49:40 +01:00
Laurent Cozic d5f3e860b9 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-09-05 22:48:42 +01:00
Laurent Cozic 4e624f7db5 Doc: Added new sponsor 2020-09-05 22:28:28 +01:00
Anton Deriabin 5b697b7e16 Doc: Fixed wrong e2ee spec link (#3655) 2020-09-05 00:14:49 +01:00
Laurent Cozic 62c3945b8f Doc: Restored E2EE spec 2020-09-05 00:14:16 +01:00
Laurent Cozic f8a3f509e7 Doc: Restored E2EE spec 2020-09-05 00:13:49 +01:00
Caleb John 30e0d69a74 Desktop: Load Codemirror css in index.html (#3673) 2020-09-05 00:02:20 +01:00
Caleb John 652816fd26 Desktop: Change codemirror default home and end to be visual line based (#3672) 2020-09-05 00:01:06 +01:00
Laurent Cozic 3a33e5f416 Electron release v1.0.242 2020-09-04 22:46:26 +01:00
Laurent Cozic 277dac5512 Desktop: Fixes sync target upgrade issue when custom TLS settings are used 2020-09-04 22:46:09 +01:00
Laurent Cozic 81d97d9f9d Update website 2020-09-04 19:34:43 +01:00
Laurent Cozic a4873cd40d Android release v1.0.340 2020-09-04 19:22:28 +01:00
Laurent Cozic 20cb2daf43 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-09-04 19:15:04 +01:00
Laurent Cozic e5b5250a91 Tools: Make it easier to create Android pre-releases 2020-09-04 19:11:46 +01:00
Laurent Cozic db7d617e2b Update website 2020-09-04 19:06:36 +01:00
Laurent Cozic a627884876 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-09-04 18:31:01 +01:00
Laurent Cozic ed30d09e07 ios-v10.0.53 2020-09-04 18:30:43 +01:00
Laurent Cozic 179e3f9aee Electron release v1.0.241 2020-09-04 18:25:04 +01:00
Laurent Cozic a67aedba35 Tools: Fixed changelog script 2020-09-04 18:24:53 +01:00
Laurent Cozic 199c411a7d CLI v1.0.167 2020-09-04 18:17:24 +01:00
Laurent Cozic 5cd7bb5bdb Electron release v1.0.240 2020-09-04 18:01:38 +01:00
Laurent Cozic 30b8f5e2aa Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-09-04 18:01:05 +01:00
Laurent Cozic 44f2842820 Android release v1.0.339-3 2020-09-04 17:28:18 +01:00
Laurent Cozic 569355a318 Mobile: Add startup screen to show progress of db migration 2020-09-04 17:07:57 +01:00
Laurent Cozic 8464e16d5d Doc: Added note on how to update Katex or Mermaid package 2020-09-02 22:54:58 +01:00
Laurent Cozic 874c1e3e82 Fixing mermaid script 2020-09-02 22:51:16 +01:00
Laurent Cozic 2530ecfc86 All: Fixes #3664: Fixed Katex font rendering 2020-09-02 22:48:24 +01:00
Laurent Cozic 6b49f1dfcc Desktop: Fixes #3618: Fix links within Mermaid documents 2020-09-02 22:44:24 +01:00
Laurent Cozic b1af25ea18 Electron release v1.0.239 2020-09-01 22:26:52 +01:00
Laurent Cozic 3086007a9c Electron release v1.0.238 2020-09-01 22:25:42 +01:00
Laurent Cozic bdfb6b97f5 Desktop: Fixes #3645: Improved handling of startup errors, and prevent window from being invisible when upgrading sync target 2020-09-01 22:25:23 +01:00
Laurent Cozic c01219e6be Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-09-01 20:55:12 +01:00
Laurent Cozic 743c11c279 Android release v1.0.338 2020-08-30 20:42:09 +01:00
Laurent Cozic 11646d1c21 Desktop: Added more logging info to sync target upgrade process 2020-08-29 19:12:02 +01:00
Laurent Cozic 797f4a9669 Electron release v1.0.237 2020-08-29 11:28:12 +01:00
Laurent Cozic 815775ad8f Linux: Fixes #3645: Prevent lock when restarting app 2020-08-29 11:27:13 +01:00
Eduardo Kimmel 085109288f All: Translation: Update pt_BR.po (#3668)
Updated some pt_br translations.
2020-08-28 17:43:30 -04:00
Alexander KnyazevandAlexander Knyazev c6cf0f3ba5 All: Translation: Update ru_RU.po (#3666)
Co-authored-by: Alexander Knyazev <a.knyazev@omprussia.ru>
2020-08-28 17:42:31 -04:00
rnbastos f9bdce7e24 All: Translation: Update pt_BR.po (#3642)
Update pt_BR translation.
2020-08-28 17:41:29 -04:00
Kourosh f8cf4db5db All: Translation: Update fa.po (#3637) 2020-08-28 17:40:33 -04:00
Laurent Cozic 14e6ae373f Electron release v1.0.236 2020-08-28 09:49:48 +01:00
jonath92 39bffd2790 Regression: Fixed OneDrive synchronisation (#3674) 2020-08-28 09:46:41 +01:00
Laurent f3cb903901 Doc: Mentioned that we don't accept pull requests created using an automated tool 2020-08-20 11:55:13 +01:00
Laurent Cozic abad1883bc Merge branch 'master' into dev 2020-08-19 14:30:36 +01:00
Laurent Cozic bb8770a967 Update website 2020-08-19 14:29:49 +01:00
Laurent Cozic c903947704 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-08-19 00:12:33 +01:00
Naveen M V 82e96840e9 All: When searching, weight notes using Okapi BM25 score (#3454) 2020-08-18 23:53:28 +01:00
Laurent Cozic e190d90832 Desktop: Fixed restarting portable app 2020-08-18 23:51:23 +01:00
Caleb John 0ae8d454ad Focus the editor whenever the user toggles the visible panes (#3646) 2020-08-18 22:26:57 +01:00
Laurent Cozic 77c7f966cf Electron release v1.0.235 2020-08-18 21:56:50 +01:00
Laurent Cozic 619fa1d607 Revert "Desktop, Mobile: Add support for media player for video and audio files"
Reverted as it now attempts to render note links as images. See:

https://discourse.joplinapp.org/t/joplin-for-windows-1-0-234-bug/10558?u=laurent

This reverts commit 13280ce1b3.
2020-08-18 21:52:00 +01:00
Laurent Cozic 157736ff7e Desktop: Fixed regression: Several menu items did not work anymore 2020-08-18 21:45:22 +01:00
Naveen M V 0b57d906f8 Doc: Update search engine doc (#3614)
Add documentation for search filters
2020-08-18 00:15:00 +01:00
Laurent Cozic 86bfdb5c79 Android release v1.0.337 2020-08-17 21:48:37 +01:00
Laurent Cozic fc7d34f79c Electron release v1.0.234 2020-08-17 21:44:40 +01:00
Matias Laporte 9d962f0328 All: Translation: Update es_ES.po (#3629) 2020-08-13 20:38:30 -04:00
Laurent Cozic 79e1a33b28 Fixed tabs 2020-08-08 10:21:25 +01:00
Caleb John 0eedae1f62 Desktop: Fixed vim mode issue where status bar was not visible (#3606) 2020-08-08 10:15:01 +01:00
Caleb John e1144c098e Desktop: Fixed scrolling issue when editing while searching in CodeMirror (#3593)
Only scroll to search matchs when the user changes the search or
explicitly changes the search index
https://discourse.joplinapp.org/t/codemirror-feedback/10397
2020-08-08 10:11:55 +01:00
Caleb John 5c5cb0f781 Desktop: Implement scroll-past-end on CodeMirror editor (#3589) 2020-08-08 09:55:10 +01:00
Laurent Cozic 656615b571 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-08-08 00:35:55 +01:00
jonath92 799a9e810d All: Resolves #1266: Add support for OneDrive for Business (#3433) 2020-08-08 00:35:30 +01:00
R3dError aa147bbcdc Desktop: Added attach file to menu bar (#3540) 2020-08-08 00:32:49 +01:00
alexchee 8c7a24282c All: Fixes #3591: Fixed sync fetching issue (#3599) 2020-08-08 00:30:11 +01:00
Naveen M V f99f3f8a6d All: Resolves #1877: Add search filters (#3213) 2020-08-08 00:13:21 +01:00
Laurent Cozic 29fbafdfff Mobile: Fixed issue when creating new notebook and going back 2020-08-07 02:03:33 +00:00
Laurent Cozic 3253146dae Merge branch 'master' into dev 2020-08-06 15:30:00 +01:00
Laurent Cozic 7e4ac0fd73 Tools: Add complexity rule 2020-08-05 23:16:51 +00:00
Laurent Cozic 60d77cb5ea ios-v10.0.52 2020-08-05 15:57:21 +01:00
Laurent Cozic 8299164964 Android release v1.0.336 2020-08-05 00:18:42 +01:00
Laurent Cozic 488393e75d Mobile: Fixes #3597: Fixed regression when saving a new notebook 2020-08-05 00:08:11 +01:00
Laurent Cozic c19f8c8035 Mobile: Fixes #3597: Fixed regression when saving a new notebook 2020-08-05 00:07:55 +01:00
Laurent Cozic b0b55718cc Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-08-04 23:00:38 +01:00
Laurent Cozic 5ade9ff2f6 Tools: Updated eslint and clarified comma-dangle rule 2020-08-04 23:00:11 +01:00
Allan Jacquet-Cretides e4cfb518ba Desktop: Resolves #3569: Show full folder name in mouse-over pop-up text (#3590) 2020-08-04 22:45:32 +01:00
Laurent Cozic 89864de1ff Tools: Added eslint radix rule 2020-08-04 22:36:50 +01:00
Laurent ee2a474611 Update .travis.yml 2020-08-04 22:31:59 +01:00
Caleb John 223caca0f5 Desktop: Enable CodeMirror keybindings (#3545) 2020-08-02 16:26:26 +01:00
Laurent Cozic ee358f70dd Desktop: Add version info to error screen 2020-08-02 16:21:30 +01:00
Laurent Cozic 0718828d60 Merge branch 'master' into dev 2020-08-02 16:12:11 +01:00
Laurent Cozic 97c752a39e Fixed config 2020-08-02 13:16:41 +01:00
Laurent 0c147236a3 All: Add mechanism to lock and upgrade sync targets (#3524) 2020-08-02 12:28:50 +01:00
Anjula Karunarathne 88f22fabf7 Desktop: Added KeymapService to manage keyboard shortcuts (#3252) 2020-08-02 12:26:55 +01:00
Jess cc8c200826 Desktop: Resolves #3529: Fix AppImage Icon (#3570) 2020-08-02 12:17:51 +01:00
Laurent 4485947b0f Desktop: Fixed copy, cut and paste in Rich Text editor (#3565) 2020-08-02 12:16:42 +01:00
Roman Musin 9f1a877f96 Android: remove hardcoded portrait screen orientation for share activity (#3418) 2020-08-02 12:14:56 +01:00
Laurent CozicandBryan 13280ce1b3 Desktop, Mobile: Add support for media player for video and audio files
Co-authored-by: Bryan <bryan.r.gerlach@gmail.com>
2020-08-02 12:03:49 +01:00
Caleb John 7f73931530 Desktop: Fixes #3560: Modify the CodeMirror linter plugin to fix katex (#3582)
Rewrite the joplin mode to manually handle parsing.

This gives us more control over katex parsing and the ability to upgrade
in the future
2020-08-02 11:33:59 +01:00
Caleb John bbfed9bca8 Desktop: Codemirror: clean up list indent code (#3581) 2020-08-01 19:09:52 +01:00
Caleb John bab29cd582 Desktop: Toggle Editor rather than setting split mode on search (#3561) 2020-08-01 19:07:52 +01:00
Laurent Cozic baea44cbd6 Fixed mobile version build 2020-08-01 18:56:56 +01:00
Laurent Cozic ab0538df43 All: Security: Apply latest package security updates 2020-08-01 18:56:26 +01:00
Laurent Cozic 58d4a69053 Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-08-01 18:21:42 +01:00
Laurent 9147b3061a Desktop: Hide completed to-dos in GotoAnything (#3580) 2020-08-01 18:17:40 +01:00
Laurent 1b0102f62c Update stale.yml 2020-08-01 18:15:17 +01:00
j-krl 471631933b CLI: Resolves #3217: Added link navigation shortcuts (#3275) 2020-08-01 17:57:45 +01:00
Laurent cd761932c1 Merge branch 'dev' into cli-shortcuts-ts 2020-08-01 17:50:03 +01:00
j-krl fd568b5300 interop unnecessary 2020-06-18 15:50:19 -06:00
j-krl f19f148297 word choice 2020-05-25 20:18:13 -06:00
j-krl 79c6eb5503 Added to docs and rewrote LinkSelector as ts file 2020-05-25 20:14:38 -06:00
j-krl 1204bed80a Merge branch 'cli-shortcuts-ts' of https://github.com/j-krl/joplin into cli-shortcuts-ts 2020-05-25 20:10:04 -06:00
j-krl dc91b1f7d6 Added to docs and rewrote LinkSelector as ts file 2020-05-25 20:09:53 -06:00
j-krl 66dc2b33de Added to docs and rewrote LinkSelector as ts file 2020-05-25 20:00:16 -06:00
j-krl 811942d45e Merge branch 'cli-shortcuts' of https://github.com/j-krl/joplin into cli-shortcuts 2020-05-24 03:20:44 -06:00
j-krl dbde239a25 formatting 2020-05-24 03:20:05 -06:00
j-krl ab3ba3e2ca formatting 2020-05-24 03:11:45 -06:00
j-krl 79e77b5a10 removed personal comments 2020-05-24 02:44:24 -06:00
j-krl c3395133c9 automatic page scrolling, docs updated 2020-05-24 02:38:14 -06:00
j-krl b1d073cdeb next/prev link index is buggy on page change 2020-05-23 21:09:22 -06:00
j-krl 25d723b373 switched to class based implementation 2020-05-23 07:23:55 -06:00
j-krl e9783d050b I'm going to have to rewrite this entire thing... 2020-05-22 15:26:42 -06:00
j-krl e1be1ead86 big link doesn't work but regular link does 2020-05-22 06:39:08 -06:00
j-krl b129bed69f installed open package 2020-05-22 06:06:28 -06:00
j-krl 265ad50ce4 app 2020-05-22 05:50:56 -06:00
j-krl 3e2570601f app-gui 2020-05-22 05:48:27 -06:00
259 changed files with 11866 additions and 5164 deletions

No files matched your search

+15
View File
@@ -61,6 +61,9 @@ Modules/TinyMCE/IconPack/postinstall.js
Modules/TinyMCE/langs/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/commands/focusElement.js
ElectronClient/commands/startExternalEditing.js
ElectronClient/commands/stopExternalEditing.js
@@ -131,6 +134,7 @@ ElectronClient/gui/NoteList/commands/focusElementNoteList.js
ElectronClient/gui/NoteListItem.js
ElectronClient/gui/NoteToolbar/NoteToolbar.js
ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/Root_UpgradeSyncTarget.js
ElectronClient/gui/ShareNoteDialog.js
ElectronClient/gui/SideBar/commands/focusElementSideBar.js
ReactNativeClient/lib/AsyncActionQueue.js
@@ -138,6 +142,7 @@ ReactNativeClient/lib/checkPermissions.js
ReactNativeClient/lib/commands/historyBackward.js
ReactNativeClient/lib/commands/historyForward.js
ReactNativeClient/lib/commands/synchronize.js
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
@@ -152,14 +157,24 @@ ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.node.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriverBase.js
ReactNativeClient/lib/services/KeymapService.js
ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
ReactNativeClient/lib/services/synchronizer/migrations/1.js
ReactNativeClient/lib/services/synchronizer/migrations/2.js
ReactNativeClient/lib/services/synchronizer/utils/types.js
ReactNativeClient/lib/services/UndoRedoService.js
ReactNativeClient/lib/ShareExtension.js
ReactNativeClient/lib/shareHandler.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/setUpQuickActions.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
+66 -55
View File
@@ -4,9 +4,9 @@ module.exports = {
'es6': true,
'node': true,
},
"parser": "@typescript-eslint/parser",
'parser': '@typescript-eslint/parser',
'extends': ['eslint:recommended'],
"settings": {
'settings': {
'react': {
'version': '16.12',
},
@@ -37,33 +37,38 @@ module.exports = {
},
'parserOptions': {
'ecmaVersion': 2018,
"ecmaFeatures": {
"jsx": true,
},
"sourceType": "module",
'ecmaFeatures': {
'jsx': true,
},
'sourceType': 'module',
},
'rules': {
// -------------------------------
// Code correctness
// -------------------------------
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-unused-vars": "error",
"@typescript-eslint/no-unused-vars": "error",
"no-constant-condition": 0,
"no-prototype-builtins": 0,
'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error',
'no-unused-vars': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'no-constant-condition': 0,
'no-prototype-builtins': 0,
// This error is always a false positive so far since it detects
// possible race conditions in contexts where we know it cannot happen.
"require-atomic-updates": 0,
"prefer-const": ["error"],
"no-var": ["error"],
"no-new-func": ["error"],
"import/prefer-default-export": ["error"],
"import/first": ["error"],
"no-array-constructor": ["error"],
'require-atomic-updates': 0,
'prefer-const': ['error'],
'no-var': ['error'],
'no-new-func': ['error'],
'import/prefer-default-export': ['error'],
'import/first': ['error'],
'no-array-constructor': ['error'],
'radix': ['error'],
// Warn only for now because fixing everything would take too much
// refactoring, but new code should try to stick to it.
'complexity': ['warn', { max: 10 }],
// Checks rules of Hooks
"react-hooks/rules-of-hooks": "error",
'react-hooks/rules-of-hooks': 'error',
// Checks effect dependencies
// Disable because of this: https://github.com/facebook/react/issues/16265
// "react-hooks/exhaustive-deps": "warn",
@@ -71,43 +76,49 @@ module.exports = {
// -------------------------------
// Formatting
// -------------------------------
"space-in-parens": ["error", "never"],
"space-infix-ops": ["error"],
"curly": ["error", "multi-line", "consistent"],
"semi": ["error", "always"],
"eol-last": ["error", "always"],
"quotes": ["error", "single"],
"indent": ["error", "tab"],
"comma-dangle": ["error", "always-multiline"],
"no-trailing-spaces": "error",
"linebreak-style": ["error", "unix"],
"prefer-template": ["error"],
"template-curly-spacing": ["error", "never"],
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": ["error", "never"],
"key-spacing": ["error", {
"beforeColon": false,
"afterColon": true,
"mode": "strict"
'space-in-parens': ['error', 'never'],
'space-infix-ops': ['error'],
'curly': ['error', 'multi-line', 'consistent'],
'semi': ['error', 'always'],
'eol-last': ['error', 'always'],
'quotes': ['error', 'single'],
'indent': ['error', 'tab'],
'comma-dangle': ['error', {
'arrays': 'always-multiline',
'objects': 'always-multiline',
'imports': 'always-multiline',
'exports': 'always-multiline',
'functions': 'never',
}],
"block-spacing": ["error"],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"no-spaced-func": ["error"],
"func-call-spacing": ["error"],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
'no-trailing-spaces': 'error',
'linebreak-style': ['error', 'unix'],
'prefer-template': ['error'],
'template-curly-spacing': ['error', 'never'],
'object-curly-spacing': ['error', 'always'],
'array-bracket-spacing': ['error', 'never'],
'key-spacing': ['error', {
'beforeColon': false,
'afterColon': true,
'mode': 'strict',
}],
"multiline-comment-style": ["error", "separate-lines"],
"space-before-blocks": "error",
"spaced-comment": ["error", "always"],
"keyword-spacing": ["error", { "before": true, "after": true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
'no-spaced-func': ['error'],
'func-call-spacing': ['error'],
'space-before-function-paren': ['error', {
'anonymous': 'never',
'named': 'never',
'asyncArrow': 'always',
}],
'multiline-comment-style': ['error', 'separate-lines'],
'space-before-blocks': 'error',
'spaced-comment': ['error', 'always'],
'keyword-spacing': ['error', { 'before': true, 'after': true }],
},
"plugins": [
"react",
"@typescript-eslint",
"react-hooks",
"import",
'plugins': [
'react',
'@typescript-eslint',
'react-hooks',
'import',
],
};
+1 -1
View File
@@ -9,7 +9,7 @@ exemptLabels:
- "upstream"
- "backlog"
- "high"
- "full-spec"
- "spec"
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
+15
View File
@@ -52,6 +52,9 @@ Tools/commit_hook.txt
*.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/commands/focusElement.js
ElectronClient/commands/startExternalEditing.js
ElectronClient/commands/stopExternalEditing.js
@@ -122,6 +125,7 @@ ElectronClient/gui/NoteList/commands/focusElementNoteList.js
ElectronClient/gui/NoteListItem.js
ElectronClient/gui/NoteToolbar/NoteToolbar.js
ElectronClient/gui/ResourceScreen.js
ElectronClient/gui/Root_UpgradeSyncTarget.js
ElectronClient/gui/ShareNoteDialog.js
ElectronClient/gui/SideBar/commands/focusElementSideBar.js
ReactNativeClient/lib/AsyncActionQueue.js
@@ -129,6 +133,7 @@ ReactNativeClient/lib/checkPermissions.js
ReactNativeClient/lib/commands/historyBackward.js
ReactNativeClient/lib/commands/historyForward.js
ReactNativeClient/lib/commands/synchronize.js
ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js
ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js
@@ -143,14 +148,24 @@ ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.mobile.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.node.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriverBase.js
ReactNativeClient/lib/services/KeymapService.js
ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
ReactNativeClient/lib/services/synchronizer/migrations/1.js
ReactNativeClient/lib/services/synchronizer/migrations/2.js
ReactNativeClient/lib/services/synchronizer/utils/types.js
ReactNativeClient/lib/services/UndoRedoService.js
ReactNativeClient/lib/ShareExtension.js
ReactNativeClient/lib/shareHandler.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js
ReactNativeClient/setUpQuickActions.js
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
+2 -1
View File
@@ -9,6 +9,7 @@ rvm: 2.3.3
branches:
only:
- master
- dev
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
matrix:
@@ -69,7 +70,7 @@ script:
# and that would break the desktop release.
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
cd CliClient
npm run test
npm run test-ci
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

+4
View File
@@ -87,6 +87,10 @@ It still requires you to quit the application each time you want it to rebuild,
2. Switch to the Electron app and <kbd>cmd</kbd>+<kbd>Q</kbd> to quit it.
3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm start"`, and hit enter).
# Updating Markdown renderer packages
The Markdown renderer is located under ReactNativeClient/lib/joplin-renderer. Whenever updating one of its dependencies, such as Mermaid or Katex, please run `npm run buildAssets` to make sure all assets such as fonts or CSS files are deployed correctly.
# Troubleshooting
Please read for the [Build Troubleshooting Document](https://github.com/laurent22/joplin/blob/master/readme/build_troubleshooting.md) for various tips on how to get the build working.
+1
View File
@@ -36,6 +36,7 @@ If you want to start contributing to the project's code, please follow these gui
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
- All the applications share the same backend (database, synchronisation, settings, models, business logic, etc.) so if you change something in the backend in one app, makes sure it still work in the other apps. Usually it does, but keep this in mind.
- Pull requests that make many changes using an automated tool, like for spell fixing, styling, etc. will not be accepted. An exception would be if the changes have been discussed in the forum and someone has agreed to review **and test** the pull request.
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
+6 -6
View File
@@ -1,13 +1,13 @@
module.exports = {
"overrides": [
'overrides': [
{
"files": ["tests/**/*.js"],
'files': ['tests/**/*.js'],
'rules': {
// Ignore all unused function arguments, because in some
// case they are kept to indicate the function signature.
"no-unused-vars": ["error", { "argsIgnorePattern": ".*" }],
"@typescript-eslint/no-unused-vars": 0,
}
'no-unused-vars': ['error', { 'argsIgnorePattern': '.*' }],
'@typescript-eslint/no-unused-vars': 0,
},
},
],
};
};
+2
View File
@@ -20,4 +20,6 @@ out.txt
linkToLocal.sh
yarn-error.log
tests/support/dropbox-auth.txt
tests/support/nextcloud-auth.json
tests/support/onedrive-auth.txt
build/
+134
View File
@@ -0,0 +1,134 @@
const open = require('open');
interface LinkStoreEntry {
link: string;
noteX: number;
noteY: number;
}
class LinkSelector {
noteId_: string;
scrollTop_: number;
renderedText_: string;
currentLinkIndex_: number;
linkStore_: LinkStoreEntry[];
linkRegex_: RegExp;
constructor() {
this.noteId_ = null;
this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling
this.renderedText_ = null;
this.currentLinkIndex_ = null;
this.linkStore_ = null;
this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g;
}
get link(): string | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].link;
}
get noteX(): number | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteX;
}
get noteY(): number | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteY;
}
findLinks(renderedText: string): LinkStoreEntry[] {
const newLinkStore: LinkStoreEntry[] = [];
const lines: string[] = renderedText.split('\n');
for (let i = 0; i < lines.length; i++) {
const matches = [...lines[i].matchAll(this.linkRegex_)];
matches.forEach((_e, n) => {
newLinkStore.push(
{
link: matches[n][0],
noteX: matches[n].index,
noteY: i,
}
);
});
}
return newLinkStore;
}
updateText(renderedText: string): void {
this.currentLinkIndex_ = null;
this.renderedText_ = renderedText;
this.linkStore_ = this.findLinks(this.renderedText_);
}
updateNote(textWidget: any): void {
this.noteId_ = textWidget.noteId;
this.scrollTop_ = textWidget.scrollTop_;
this.updateText(textWidget.renderedText_);
}
scrollWidget(textWidget: any): void {
if (this.currentLinkIndex_ === null) return;
const noteY = this.linkStore_[this.currentLinkIndex_].noteY;
let viewBoxMin = textWidget.scrollTop_ + 1;
let viewBoxMax = viewBoxMin + textWidget.innerHeight - 2;
if (noteY < viewBoxMin) {
for (; noteY < viewBoxMin; textWidget.pageUp()) {
viewBoxMin = textWidget.scrollTop_;
viewBoxMax = viewBoxMin + textWidget.innerHeight;
}
return;
} else if (noteY > viewBoxMax) {
for (; noteY > viewBoxMax; textWidget.pageDown()) {
viewBoxMin = textWidget.scrollTop_;
viewBoxMax = viewBoxMin + textWidget.innerHeight;
}
return;
}
return;
}
changeLink(textWidget: any, offset: number): void | null {
if (textWidget.noteId !== this.noteId_) {
this.updateNote(textWidget);
this.changeLink(textWidget, offset);
return;
}
if (textWidget.renderedText_ !== this.renderedText_) {
this.updateText(textWidget.renderedText_);
this.changeLink(textWidget, offset);
return;
}
if (textWidget.scrollTop_ !== this.scrollTop_) this.scrollTop_ = textWidget.scrollTop_;
if (!this.linkStore_.length) return null;
let offsetMod = (offset + this.currentLinkIndex_) % this.linkStore_.length;
if (this.currentLinkIndex_ === null) {
if (offsetMod < 0) this.currentLinkIndex_ = this.linkStore_.length + offsetMod;
else if (!offsetMod) this.currentLinkIndex_ = 0;
else this.currentLinkIndex_ = offsetMod - 1;
return;
}
if (offsetMod < 0) offsetMod = this.linkStore_.length + offsetMod;
this.currentLinkIndex_ = offsetMod;
return;
}
openLink(textWidget: any): void {
if (textWidget.noteId !== this.noteId_) return;
if (textWidget.renderedText_ !== this.renderedText_) return;
if (textWidget.scrollTop_ !== this.scrollTop_) return;
open(this.linkStore_[this.currentLinkIndex_].link);
}
}
export default LinkSelector;
+28
View File
@@ -33,6 +33,8 @@ const FolderListWidget = require('./gui/FolderListWidget.js');
const NoteListWidget = require('./gui/NoteListWidget.js');
const StatusBarWidget = require('./gui/StatusBarWidget.js');
const ConsoleWidget = require('./gui/ConsoleWidget.js');
const LinkSelector = require('./LinkSelector.js').default;
class AppGui {
constructor(app, store, keymap) {
@@ -74,6 +76,8 @@ class AppGui {
this.currentShortcutKeys_ = [];
this.lastShortcutKeyTime_ = 0;
this.linkSelector_ = new LinkSelector();
// Recurrent sync is setup only when the GUI is started. In
// a regular command it's not necessary since the process
// exits right away.
@@ -455,6 +459,30 @@ class AppGui {
} else {
this.stdout(_('Please select the note or notebook to be deleted first.'));
}
} else if (cmd === 'next_link' || cmd === 'previous_link') {
const noteText = this.widget('noteText');
noteText.render();
if (cmd === 'next_link') this.linkSelector_.changeLink(noteText, 1);
else this.linkSelector_.changeLink(noteText, -1);
this.linkSelector_.scrollWidget(noteText);
const cursorOffsetX = this.widget('mainWindow').width - noteText.innerWidth - 8;
const cursorOffsetY = 1 - noteText.scrollTop_;
if (this.linkSelector_.link) {
this.term_.moveTo(
this.linkSelector_.noteX + cursorOffsetX,
this.linkSelector_.noteY + cursorOffsetY
);
setTimeout(() => this.term_.term().inverse(this.linkSelector_.link), 50);
}
} else if (cmd === 'open_link') {
if (this.widget('noteText').hasFocus) {
this.linkSelector_.openLink(this.widget('noteText'));
}
} else if (cmd === 'toggle_console') {
if (!this.consoleIsShown()) {
this.showConsole();
+3
View File
@@ -324,6 +324,9 @@ class Application extends BaseApplication {
{ keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' },
{ keys: ['ENTER'], type: 'function', command: 'activate' },
{ keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' },
{ keys: ['n'], type: 'function', command: 'next_link' },
{ keys: ['b'], type: 'function', command: 'previous_link' },
{ keys: ['o'], type: 'function', command: 'open_link' },
{ keys: [' '], command: 'todo toggle $n' },
{ keys: ['tc'], type: 'function', command: 'toggle_console' },
{ keys: ['tm'], type: 'function', command: 'toggle_metadata' },
+14
View File
@@ -2,6 +2,7 @@ const yargParser = require('yargs-parser');
const { _ } = require('lib/locale.js');
const { time } = require('lib/time-utils.js');
const stringPadding = require('string-padding');
const { Logger } = require('lib/logger.js');
const cliUtils = {};
@@ -245,4 +246,17 @@ cliUtils.redrawDone = function() {
redrawStarted_ = false;
};
cliUtils.stdoutLogger = function(stdout) {
const stdoutFn = (...s) => stdout(s.join(' '));
const logger = new Logger();
logger.addTarget('console', { console: {
info: stdoutFn,
warn: stdoutFn,
error: stdoutFn,
} });
return logger;
};
module.exports = { cliUtils };
+41 -7
View File
@@ -11,6 +11,7 @@ const md5 = require('md5');
const locker = require('proper-lockfile');
const fs = require('fs-extra');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const MigrationHandler = require('lib/services/synchronizer/MigrationHandler').default;
class Command extends BaseCommand {
constructor() {
@@ -29,7 +30,10 @@ class Command extends BaseCommand {
}
options() {
return [['--target <target>', _('Sync to provided target (defaults to sync.target config value)')]];
return [
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
['--upgrade', _('Upgrade the sync target to the latest version.')],
];
}
static lockFile(filePath) {
@@ -148,12 +152,8 @@ class Command extends BaseCommand {
const syncTarget = reg.syncTarget(this.syncTargetId_);
if (!(await syncTarget.isAuthenticated())) {
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
app().gui().showConsole();
app().gui().maximizeConsole();
const authDone = await this.doAuth();
if (!authDone) return cleanUp();
@@ -176,6 +176,34 @@ class Command extends BaseCommand {
if (!sync) throw new Error(_('Cannot initialise synchroniser.'));
if (args.options.upgrade) {
let migrationError = null;
try {
const migrationHandler = new MigrationHandler(
sync.api(),
sync.lockHandler(),
Setting.value('appType'),
Setting.value('clientId')
);
migrationHandler.setLogger(cliUtils.stdoutLogger(this.stdout.bind(this)));
await migrationHandler.upgrade();
} catch (error) {
migrationError = error;
}
if (!migrationError) {
Setting.setValue('sync.upgradeState', Setting.SYNC_UPGRADE_STATE_IDLE);
await Setting.saveAll();
}
if (migrationError) throw migrationError;
return cleanUp();
}
this.stdout(_('Starting synchronisation...'));
const contextKey = `sync.${this.syncTargetId_}.context`;
@@ -210,6 +238,12 @@ class Command extends BaseCommand {
throw error;
}
if (Setting.value('sync.upgradeState') > Setting.SYNC_UPGRADE_STATE_IDLE) {
this.stdout(`/!\\ ${_('Sync target must be upgraded! Run `%s` to proceed.', 'sync --upgrade')}`);
app().gui().showConsole();
app().gui().maximizeConsole();
}
cleanUp();
}
+2
View File
@@ -38,6 +38,8 @@ tasks.prepareTestBuild = {
'lib/',
'locales/',
'node_modules/',
'*.ts',
'*.tsx',
],
});
+62 -86
View File
@@ -18,9 +18,11 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3.1\n"
"X-Generator: Poedit 2.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: CliClient/app/command-cp.js:13
msgid ""
@@ -110,9 +112,9 @@ msgid "Do not ask for confirmation."
msgstr "No requiere confirmación."
#: CliClient/app/command-import.js:27
#, fuzzy, javascript-format
#, javascript-format
msgid "Output format: %s"
msgstr "Formato de origen: %s"
msgstr "Formato de destino: %s"
#: CliClient/app/command-import.js:47 ElectronClient/gui/ImportScreen.min.js:69
#, javascript-format
@@ -373,7 +375,6 @@ msgid "Synchronisation target: %s (%s)"
msgstr "Destino de la sincronización: %s (%s)"
#: CliClient/app/command-sync.js:177
#, fuzzy
msgid "Cannot initialise synchroniser."
msgstr "No se puede inicializar sincronizador."
@@ -560,7 +561,6 @@ msgstr ""
"segundo plano y estarán disponibles en breve."
#: CliClient/app/gui/NoteWidget.js:50
#, fuzzy
msgid "You may also type `status` for more information."
msgstr "También puede escribir `status` para obtener más información."
@@ -813,7 +813,6 @@ msgid "Cancel"
msgstr "Cancelar"
#: ElectronClient/plugins/GotoAnything.min.js:454
#, fuzzy
msgid ""
"Type a note title or part of its content to jump to it. Or type # followed "
"by a tag name, or @ followed by a notebook name."
@@ -832,7 +831,7 @@ msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr "Exportando el formato de «%s» a «%s». Por favor espere..."
#: ElectronClient/InteropServiceHelper.js:164
#, fuzzy, javascript-format
#, javascript-format
msgid "Could not export notes: %s"
msgstr "No se pudo exportar las notas: %s"
@@ -944,7 +943,7 @@ msgid "Delete"
msgstr "Eliminar"
#: ElectronClient/gui/SideBar/SideBar.min.js:276
#, fuzzy, javascript-format
#, javascript-format
msgid "Remove tag \"%s\" and its descendant tags from all notes?"
msgstr "Desea borrar las etiquetas pertenecientes a «%s» de todas las notas?"
@@ -1016,7 +1015,6 @@ msgstr "texto destacado"
#: ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/CodeMirror.js:145
#: ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.js:261
#, fuzzy
msgid "emphasised text"
msgstr "texto resaltado"
@@ -1076,7 +1074,6 @@ msgstr ""
"la nota."
#: ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/plugins/lists.js:2151
#, fuzzy
msgid "Checkbox list"
msgstr "Casilla"
@@ -1095,11 +1092,11 @@ msgstr "Adjuntar archivo"
#: ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js:535
msgid "Code Block"
msgstr ""
msgstr "Bloque de Código"
#: ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js:544
msgid "Inline Code"
msgstr ""
msgstr "Código Integrado"
#: ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js:559
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:80
@@ -1109,11 +1106,10 @@ msgstr "Introduce fecha"
#: ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js:903
msgid "Drop notes or files here"
msgstr ""
msgstr "Suelta notas o archivos aquí"
#: ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js:903
#: ElectronClient/gui/MainScreen/MainScreen.min.js:401
#, fuzzy
msgid "Code View"
msgstr "Código"
@@ -1123,6 +1119,8 @@ msgid ""
"Please wait for all attachments to be downloaded and decrypted. You may also "
"switch to %s to edit the note."
msgstr ""
"Por favor espere a que todos los adjuntos hayan sido descargados y "
"descifrados. Puede también cambiar a %s para editar la nota."
#: ElectronClient/gui/NoteEditor/utils/useMessageHandler.js:70
#: ElectronClient/gui/NoteText.min.js:833
@@ -1133,7 +1131,7 @@ msgstr "Ha habido un error descargando este adjunto:"
#: ElectronClient/gui/NoteText.min.js:835
#: ReactNativeClient/lib/services/ResourceEditWatcher.js:146
msgid "This attachment is not downloaded or not decrypted yet"
msgstr "Este adjunto no ha sido descargado o desencriptado todavía"
msgstr "Este adjunto no ha sido descargado o descifrado todavía"
#: ElectronClient/gui/NoteEditor/utils/contextMenu.js:39
#: ElectronClient/gui/NoteText.min.js:778
@@ -1147,7 +1145,7 @@ msgstr "Guardar como..."
#: ElectronClient/gui/NoteEditor/utils/contextMenu.js:65
msgid "Reveal file in folder"
msgstr ""
msgstr "Mostrar archivo en directorio"
#: ElectronClient/gui/NoteEditor/utils/contextMenu.js:73
#: ElectronClient/gui/NoteText.min.js:797
@@ -1307,21 +1305,18 @@ msgid "Set the password"
msgstr "Establecer la contraseña"
#: ElectronClient/gui/MainScreen/MainScreen.min.js:331
#, fuzzy
msgid "One of your master keys use an obsolete encryption method."
msgstr "Una o más llaves maestras necesitan una contraseña."
msgstr "Una de tus llaves maestras usa un método de encripción obsoleto."
#: ElectronClient/gui/MainScreen/MainScreen.min.js:343
#, fuzzy
msgid ""
"The default encryption method has been changed, you should re-encrypt your "
"data."
msgstr ""
"El método de cifrado predeterminado se ha cambiado por uno más seguro y se "
"recomienda que lo aplique a sus datos."
"El método de cifrado predeterminado ha sido cambiado, se recomienda re-"
"cifrar los datos."
#: ElectronClient/gui/MainScreen/MainScreen.min.js:348
#, fuzzy
msgid "More info"
msgstr "Más información"
@@ -1435,9 +1430,8 @@ msgstr "Imprimir"
#: ElectronClient/gui/MainScreen/commands/print.js:25
#: ElectronClient/gui/NoteText.min.js:1321
#, fuzzy
msgid "Only one note can be printed at a time."
msgstr "Solo una nota puede ser impresa o exportada a PDF a la vez."
msgstr "Solo una nota puede ser impresa a la vez."
#: ElectronClient/gui/NoteToolbar/NoteToolbar.js:32
#: ElectronClient/gui/NoteText.min.js:1656
@@ -1558,7 +1552,6 @@ msgid "Options"
msgstr "Opciones"
#: ElectronClient/gui/Root.min.js:93
#, fuzzy
msgid "Note attachments"
msgstr "Adjuntos"
@@ -1607,7 +1600,7 @@ msgstr "Ordenar notas por"
#: ElectronClient/gui/NoteList.min.js:147
#: ElectronClient/gui/NoteList/NoteList.min.js:152
#, fuzzy, javascript-format
#, javascript-format
msgid ""
"To manually sort the notes, the sort order must be changed to \"%s\" in the "
"menu \"%s\" > \"%s\""
@@ -1617,9 +1610,8 @@ msgstr ""
#: ElectronClient/gui/NoteList.min.js:148
#: ElectronClient/gui/NoteList/NoteList.min.js:153
#, fuzzy
msgid "Do it now"
msgstr "Obtenerlo ahora en:"
msgstr "Hazlo ahora"
#: ElectronClient/gui/NoteList.min.js:452
#: ElectronClient/gui/NoteList/NoteList.min.js:425
@@ -1678,7 +1670,6 @@ msgid "Icon"
msgstr "Icono"
#: ElectronClient/gui/FolderPropertiesDialog.min.js:272
#, fuzzy
msgid "Notebook properties"
msgstr "Propiedades de libreta"
@@ -1705,9 +1696,8 @@ msgid "Edit in external editor"
msgstr "Editar con un editor externo"
#: ElectronClient/gui/NoteText.min.js:1852
#, fuzzy
msgid "Content Properties"
msgstr "Propiedades de nota"
msgstr "Propiedades del Contenido"
#: ElectronClient/gui/EncryptionConfigScreen.min.js:95
#: ReactNativeClient/lib/components/screens/encryption-config.js:125
@@ -1719,7 +1709,6 @@ msgid "Master keys that need upgrading"
msgstr "Claves maestras que necesitan actualización"
#: ElectronClient/gui/EncryptionConfigScreen.min.js:149
#, fuzzy
msgid ""
"The following master keys use an out-dated encryption algorithm and it is "
"recommended to upgrade them. The upgraded master key will still be able to "
@@ -1735,7 +1724,7 @@ msgstr "Actualizar"
#: ElectronClient/gui/EncryptionConfigScreen.min.js:181
msgid "Re-encrypt data"
msgstr "Encriptar datos nuevamente"
msgstr "Cifrar datos nuevamente"
#: ElectronClient/gui/EncryptionConfigScreen.min.js:183
msgid ""
@@ -1755,7 +1744,7 @@ msgstr ""
"cifrado obsoleto."
#: ElectronClient/gui/EncryptionConfigScreen.min.js:185
#, fuzzy, javascript-format
#, javascript-format
msgid ""
"In order to do so, your entire data set will have to be encrypted and "
"synchronised, so it is best to run it overnight.\n"
@@ -1771,8 +1760,8 @@ msgid ""
"\n"
"Important: you only need to run this ONCE on one device."
msgstr ""
"Para hacerlo, todo sus datos deberán estar encriptados y sincronizados, por "
"lo que es mejor ejecutarlo durante la noche.\n"
"Para hacerlo, todo sus datos deberán ser cifrados y sincronizados, por lo "
"que es mejor ejecutarlo durante la noche.\n"
"\n"
"Para empezar, siga estas instrucciones:\n"
"\n"
@@ -1803,7 +1792,6 @@ msgstr "Seleccione a dónde se debería exportar el estado de sincronización"
#: ElectronClient/gui/StatusScreen.min.js:71
#: ReactNativeClient/lib/components/screens/status.js:112
#, fuzzy
msgid "Retry All"
msgstr "Reintentar todos"
@@ -1855,14 +1843,13 @@ msgid "Viewer"
msgstr "Visor"
#: ElectronClient/gui/NoteContentPropertiesDialog.js:107
#, fuzzy
msgid "Statistics"
msgstr "Estadísticas..."
msgstr "Estadísticas"
#: ElectronClient/gui/NoteContentPropertiesDialog.js:111
#, javascript-format
msgid "Read time: %s min"
msgstr ""
msgstr "Tiempo de lectura: %s minutos"
#: ElectronClient/gui/NoteContentPropertiesDialog.js:112
#: ElectronClient/gui/ShareNoteDialog.js:175
@@ -1933,11 +1920,8 @@ msgid "Usage"
msgstr "Uso"
#: ElectronClient/gui/NoteList/commands/focusElementNoteList.js:15
#, fuzzy
msgid "Note list"
msgstr ""
"Historial\n"
" de notas"
msgstr "Historial de notas"
#: ElectronClient/gui/Header/commands/focusSearch.js:15
msgid "Search in all the notes"
@@ -1952,9 +1936,8 @@ msgid "Size"
msgstr "Tamaño"
#: ElectronClient/gui/ResourceScreen.js:39
#, fuzzy
msgid "Action"
msgstr "Localización"
msgstr "Acción"
#: ElectronClient/gui/ResourceScreen.js:42
#: ReactNativeClient/lib/path-utils.js:65
@@ -1964,9 +1947,9 @@ msgid "Untitled"
msgstr "Sin título"
#: ElectronClient/gui/ResourceScreen.js:93
#, fuzzy, javascript-format
#, javascript-format
msgid "Delete attachment \"%s\"?"
msgstr "¿Borrar nota «%s»?"
msgstr "¿Borrar adjunto «%s»?"
#: ElectronClient/gui/ResourceScreen.js:139
msgid ""
@@ -1979,9 +1962,8 @@ msgstr ""
"ellos, ya que no pueden ser recuperados después."
#: ElectronClient/gui/ResourceScreen.js:140
#, fuzzy
msgid "Please wait..."
msgstr "Comprobando... Por favor espere."
msgstr "Por favor espere..."
#: ElectronClient/gui/ResourceScreen.js:142
msgid "No resources!"
@@ -2052,7 +2034,6 @@ msgid "Insert template"
msgstr "Insertar plantilla"
#: ElectronClient/app.js:541
#, fuzzy
msgid "Open template directory"
msgstr "Abrir directorio de plantillas"
@@ -2061,7 +2042,6 @@ msgid "Refresh templates"
msgstr "Actualizar plantillas"
#: ElectronClient/app.js:574
#, fuzzy
msgid "Note attachments..."
msgstr "Adjuntos..."
@@ -2220,7 +2200,6 @@ msgstr ""
"con terceros."
#: ReactNativeClient/lib/registry.js:156
#, fuzzy
msgid ""
"Could not synchronise with OneDrive.\n"
"\n"
@@ -2231,10 +2210,10 @@ msgid ""
msgstr ""
"No se ha podido sincronizar con OneDrive.\n"
"\n"
"Este error suele ocurrir al utilizar OneDrive for Business. Este producto no "
"está soportado.\n"
"Este error suele ocurrir al utilizar OneDrive for Business, que "
"desafortunadamente no puede ser soportado.\n"
"\n"
"Podría considerar utilizar una cuenta Personal de OneDrive."
"Por favor considere utilizar una cuenta Personal de OneDrive."
#: ReactNativeClient/lib/logger.js:178
#, javascript-format
@@ -2347,7 +2326,6 @@ msgid "Aritim Dark"
msgstr ""
#: ReactNativeClient/lib/models/Setting.js:58
#, fuzzy
msgid "OLED Dark"
msgstr "Oscuro"
@@ -2500,7 +2478,7 @@ msgstr "Auto-emparejar llaves, paréntesis, comillas, etc."
#: ReactNativeClient/lib/models/Setting.js:395
msgid "Use CodeMirror as the code editor (WARNING: BETA)."
msgstr ""
msgstr "Utilizar CodeMirror como editor de código (PRECAUCIÓN: BETA)"
#: ReactNativeClient/lib/models/Setting.js:397
#: ReactNativeClient/lib/models/Setting.js:415
@@ -2550,9 +2528,8 @@ msgid "Enable Fountain syntax support"
msgstr "Habilitar el soporte de sintaxis de Fountain"
#: ReactNativeClient/lib/models/Setting.js:468
#, fuzzy
msgid "Enable Mermaid diagrams support"
msgstr "Habilitar el soporte de tipógrafo"
msgstr "Habilitar el soporte de diagramas de Mermaid"
#: ReactNativeClient/lib/models/Setting.js:470
msgid "Enable ==mark== syntax"
@@ -2629,13 +2606,12 @@ msgid "Editor font family"
msgstr "Editor de familia de fuente"
#: ReactNativeClient/lib/models/Setting.js:550
#, fuzzy
msgid ""
"This should be a *monospace* font or some elements will render incorrectly. "
"If the font is incorrect or empty, it will default to a generic monospace "
"font."
msgstr ""
"Esta debe ser una fuente *monoespaciada* o no funcionará correctamente. Si "
"Ésta debe ser una fuente *monoespaciada* o no funcionará correctamente. Si "
"la fuente es incorrecta o vacía, se configurará una fuente monoespaciada "
"genérica."
@@ -2808,12 +2784,11 @@ msgstr "Mantener historial de la nota durante"
#: ReactNativeClient/lib/models/Setting.js:741
msgid "Notebook list growth factor"
msgstr ""
msgstr "Factor de crecimiento de la lista de libretas"
#: ReactNativeClient/lib/models/Setting.js:743
#: ReactNativeClient/lib/models/Setting.js:756
#: ReactNativeClient/lib/models/Setting.js:769
#, fuzzy
msgid ""
"The factor property sets how the item will grow or shrink to fit the "
"available space in its container with respect to the other items. Thus an "
@@ -2828,11 +2803,11 @@ msgstr ""
#: ReactNativeClient/lib/models/Setting.js:754
msgid "Note list growth factor"
msgstr ""
msgstr "Factor de crecimiento de la lista de notas"
#: ReactNativeClient/lib/models/Setting.js:767
msgid "Note area growth factor"
msgstr ""
msgstr "Factor de crecimiento del área de la nota"
#: ReactNativeClient/lib/models/Setting.js:919
#, javascript-format
@@ -2865,14 +2840,13 @@ msgstr "Aplicación"
#: ReactNativeClient/lib/models/Setting.js:1254
msgid "Encryption"
msgstr "Encriptación"
msgstr "Cifrado"
#: ReactNativeClient/lib/models/Setting.js:1255
msgid "Web Clipper"
msgstr "Web Clipper"
#: ReactNativeClient/lib/models/Setting.js:1260
#, fuzzy
msgid ""
"These plugins enhance the Markdown renderer with additional features. Please "
"note that, while these features might be useful, they are not standard "
@@ -2908,30 +2882,32 @@ msgid "Downloaded"
msgstr "Descargado"
#: ReactNativeClient/lib/models/Resource.js:371
#, fuzzy, javascript-format
#, javascript-format
msgid "Attachment conflict: \"%s\""
msgstr "Adjuntos"
msgstr "Conflicto en los adjuntos: \"%s\""
#: ReactNativeClient/lib/models/Resource.js:372
#, fuzzy, javascript-format
#, javascript-format
msgid ""
"There was a [conflict](%s) on the attachment below.\n"
"\n"
"%s"
msgstr "Ha habido un error descargando este adjunto:"
msgstr ""
"Ha habido un [conflicto](%s) descargando este adjunto:.\n"
"\n"
"%s"
#: ReactNativeClient/lib/models/Tag.js:384
#, fuzzy
msgid "Cannot move tag to this location."
msgstr "No se puede mover la libreta a este lugar"
msgstr "No se puede mover la etiqueta a este lugar."
#: ReactNativeClient/lib/models/Tag.js:429
msgid "Tag name cannot start or end with a `/`."
msgstr ""
msgstr "El nombre de la etiqueta no puede empezar o terminar con `/`."
#: ReactNativeClient/lib/models/Tag.js:431
msgid "Tag name cannot contain `//`."
msgstr ""
msgstr "El nombre de la etiqueta no puede contener `//`."
#: ReactNativeClient/lib/models/Tag.js:482
#, javascript-format
@@ -3104,11 +3080,11 @@ msgstr ""
msgid ""
"Please confirm that you would like to re-encrypt your complete database."
msgstr ""
"Por favor, confirme que desea volver a encriptar su base de datos completa."
"Por favor, confirme que desea volver a cifrar su base de datos completa."
#: ReactNativeClient/lib/components/shared/encryption-config-shared.js:37
msgid "Your data is going to be re-encrypted and synced again."
msgstr "Sus datos van a ser encriptados y sincronizados nuevamente."
msgstr "Sus datos van a ser cifrados y sincronizados nuevamente."
#: ReactNativeClient/lib/components/shared/encryption-config-shared.js:47
msgid ""
@@ -3381,7 +3357,7 @@ msgstr "No hay elementos con el ID %s"
#: ReactNativeClient/lib/components/screens/note.js:180
msgid "This attachment is not downloaded or not decrypted yet."
msgstr "El adjunto no ha sido descargado o desencriptado todavía."
msgstr "El adjunto no ha sido descargado o descifrado todavía."
#: ReactNativeClient/lib/components/screens/note.js:184
#, javascript-format
@@ -3554,10 +3530,9 @@ msgstr "Sistema de archivos"
#: ReactNativeClient/lib/commands/historyForward.js:16
msgid "Forward"
msgstr ""
msgstr "Avanzar"
#: ReactNativeClient/lib/commands/synchronize.js:17
#, fuzzy
msgid "Synchronize"
msgstr "Sincronizar"
@@ -3617,7 +3592,7 @@ msgstr "Adjuntos"
#: ReactNativeClient/lib/services/report.js:190
msgid "Downloaded and decrypted"
msgstr "Descargado y desencriptado"
msgstr "Descargado y descifrado"
#: ReactNativeClient/lib/services/report.js:190
#: ReactNativeClient/lib/services/report.js:191
@@ -3628,7 +3603,7 @@ msgstr "%s: %d"
#: ReactNativeClient/lib/services/report.js:191
msgid "Downloaded and encrypted"
msgstr "Descargado y desencriptado"
msgstr "Descargado y cifrado"
#: ReactNativeClient/lib/services/report.js:204
msgid "Attachments that could not be downloaded"
@@ -3665,7 +3640,7 @@ msgstr "Borrar: %d"
#: ReactNativeClient/lib/services/report.js:236
msgid "Folders"
msgstr "Carpetas"
msgstr "Directorios"
#: ReactNativeClient/lib/services/report.js:244
#, javascript-format
@@ -3725,9 +3700,10 @@ msgid "Directory"
msgstr "Directorio"
#: ReactNativeClient/lib/services/InteropService.js:174
#, fuzzy, javascript-format
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\" and output \"%s\""
msgstr "No se puede cargar el módulo «%s» para el formato «%s»"
msgstr ""
"No se puede cargar el módulo «%s» para el formato «%s» y destino \"%s\""
#: ReactNativeClient/lib/services/InteropService.js:232
#, javascript-format
+591 -617
View File
File diff suppressed because it is too large. Load diff
+17 -17
View File
@@ -14,8 +14,10 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3.1\n"
"X-Generator: Poedit 2.4.1\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: CliClient/app/command-cp.js:13
msgid ""
@@ -822,7 +824,7 @@ msgstr "Exportando para \"%s\" com o formato \"%s\". Por favor, aguarde..."
#: ElectronClient/InteropServiceHelper.js:164
#, javascript-format
msgid "Could not export notes: %s"
msgstr "Não foi possível exportar a nota: %s"
msgstr "Não foi possível exportar notas: %s"
#: ElectronClient/checkForUpdates.js:138
msgid "Current version is up-to-date."
@@ -932,9 +934,9 @@ msgid "Delete"
msgstr "Excluir"
#: ElectronClient/gui/SideBar/SideBar.min.js:276
#, fuzzy, javascript-format
#, javascript-format
msgid "Remove tag \"%s\" and its descendant tags from all notes?"
msgstr "Remover a tag \"%s\" de todas as notas?"
msgstr "Remover a tag \"%s\" e seus descendentes de todas as notas?"
#: ElectronClient/gui/SideBar/SideBar.min.js:278
#: ElectronClient/gui/SideBar.min.js:292
@@ -1837,7 +1839,7 @@ msgstr "Estatísticas"
#: ElectronClient/gui/NoteContentPropertiesDialog.js:111
#, javascript-format
msgid "Read time: %s min"
msgstr ""
msgstr "Tempo de leitura: %s min"
#: ElectronClient/gui/NoteContentPropertiesDialog.js:112
#: ElectronClient/gui/ShareNoteDialog.js:175
@@ -2214,7 +2216,7 @@ msgstr "Nível ID desconhecido: %s"
#: ReactNativeClient/lib/SyncTargetAmazonS3.js:28
msgid "AWS S3"
msgstr ""
msgstr "AWS S3"
#: ReactNativeClient/lib/SyncTargetDropbox.js:25
msgid "Dropbox"
@@ -2358,15 +2360,15 @@ msgstr "Senha do WebDAV"
#: ReactNativeClient/lib/models/Setting.js:189
msgid "AWS S3 bucket"
msgstr ""
msgstr "AWS S3 bucket"
#: ReactNativeClient/lib/models/Setting.js:200
msgid "AWS key"
msgstr ""
msgstr "AWS key"
#: ReactNativeClient/lib/models/Setting.js:210
msgid "AWS secret"
msgstr ""
msgstr "AWS secret"
#: ReactNativeClient/lib/models/Setting.js:224
msgid "Attachment download behaviour"
@@ -2609,12 +2611,12 @@ msgstr "Atualizar automaticamente o aplicativo"
#: ReactNativeClient/lib/models/Setting.js:596
msgid "Get pre-releases when checking for updates"
msgstr "Obter pre-releases quando for checar atualizações"
msgstr "Obter pre-lançamentos quando checar por atualizações"
#: ReactNativeClient/lib/models/Setting.js:596
#, javascript-format
msgid "See the pre-release page for more details: %s"
msgstr "Ver página de pre-release para mais detalhes: %s"
msgstr "Ver página de pre-lançamentos para mais detalhes: %s"
#: ReactNativeClient/lib/models/Setting.js:604
msgid "Synchronisation interval"
@@ -2697,7 +2699,7 @@ msgstr "Modo do Teclado"
#: ReactNativeClient/lib/models/Setting.js:650
msgid "Default"
msgstr "Default"
msgstr "Padrão"
#: ReactNativeClient/lib/models/Setting.js:651
msgid "Emacs"
@@ -2881,17 +2883,16 @@ msgstr ""
"%s"
#: ReactNativeClient/lib/models/Tag.js:384
#, fuzzy
msgid "Cannot move tag to this location."
msgstr "Não é possível mover a nota para este local"
msgstr "Não é possível mover a tag para este local"
#: ReactNativeClient/lib/models/Tag.js:429
msgid "Tag name cannot start or end with a `/`."
msgstr ""
msgstr "O nome da tag não pode começar ou terminar com `/`."
#: ReactNativeClient/lib/models/Tag.js:431
msgid "Tag name cannot contain `//`."
msgstr ""
msgstr "O nome da tag não pode conter `//`."
#: ReactNativeClient/lib/models/Tag.js:482
#, javascript-format
@@ -3514,7 +3515,6 @@ msgid "Forward"
msgstr "Encaminhar"
#: ReactNativeClient/lib/commands/synchronize.js:17
#, fuzzy
msgid "Synchronize"
msgstr "Sincronizar"
+32 -32
View File
@@ -24,8 +24,8 @@ msgid ""
"Duplicates the notes matching <note> to [notebook]. If no notebook is "
"specified the note is duplicated in the current notebook."
msgstr ""
"Дублирует заметки соответствующие выражению <note> в блокнот [notebook]. "
"Если блокнот не указан - заметки дублируются в текущем блокноте."
"Дублирует заметки, соответствующие выражению <note>, в блокнот [notebook]. "
"Если блокнот не указан, заметки дублируются в текущем блокноте."
#: CliClient/app/command-cp.js:24 CliClient/app/command-cp.js:27
#: CliClient/app/command-import.js:34 CliClient/app/command-mv.js:22
@@ -75,7 +75,7 @@ msgid ""
msgstr ""
"Отображает только элементы заданного типа(ов). Поддерживаемые типы: `n` для "
"заметок, `t` для задач или `nt` для заметок и задач (напр. `-tt` выведет "
"только задачи, в то время как `-ttd` выведет и заметки и задачи)."
"только задачи, в то время как `-ttd` выведет и заметки, и задачи)."
#: CliClient/app/command-ls.js:31
msgid "Either \"text\" or \"json\""
@@ -230,7 +230,7 @@ msgstr "Расшифровано элементов: %d"
#, javascript-format
msgid "Skipped items: %d (use --retry-failed-items to retry decrypting them)"
msgstr ""
"Пропущено элементов: %d (используйте команду --retry-failed-items чтобы "
"Пропущено элементов: %d (используйте команду --retry-failed-items, чтобы "
"повторить попытку расшифровки)"
#: CliClient/app/command-e2ee.js:68
@@ -273,7 +273,7 @@ msgstr "Создает новый блокнот."
#: CliClient/app/command-mv.js:14
msgid "Moves the notes matching <note> to [notebook]."
msgstr ""
"Перемещает заметки соответствующие выражению <note> в блокнот [notebook]."
"Перемещает заметки, соответствующие выражению <note>, в блокнот [notebook]."
#: CliClient/app/command-config.js:13
msgid ""
@@ -282,12 +282,12 @@ msgid ""
"current configuration."
msgstr ""
"Выводит или задает значение параметра конфигурации. Если значение [value] не "
"указано - выведет текущее значение параметра [name]. Если не указаны ни имя "
"[name], ни значение [value] - выведет всю текущую конфигурацию."
"указано, выведет текущее значение параметра [name]. Если не указаны ни имя "
"[name], ни значение [value], выведет всю текущую конфигурацию."
#: CliClient/app/command-config.js:18
msgid "Also displays unset and hidden config variables."
msgstr "Также выводит не установленные и скрытые параметры конфигурации."
msgstr "Также выводит неустановленные и скрытые параметры конфигурации."
#: CliClient/app/command-config.js:79
#, javascript-format
@@ -364,7 +364,7 @@ msgid ""
"operation."
msgstr ""
"Файл блокировки уже существует. Если вы уверены, что синхронизация не "
"выполняется - вы можете вручную удалить файл блокировки \"%s\" и возобновить "
"выполняется, вы можете вручную удалить файл блокировки \"%s\" и возобновить "
"операцию."
#: CliClient/app/command-sync.js:175
@@ -555,7 +555,7 @@ msgid ""
msgstr ""
"Один или несколько элементов зашифрованы. Для расшифровки может "
"потребоваться мастер-пароль. Для начала расшифровки введите `e2ee decrypt`. "
"Если мастер-пароль уже был введен - зашифрованные элементы уже "
"Если мастер-пароль уже был введен, зашифрованные элементы уже "
"расшифровываются в фоновом режиме и вскоре будут доступны."
#: CliClient/app/gui/NoteWidget.js:50
@@ -647,7 +647,7 @@ msgid ""
msgstr ""
"В любой команде можно ссылаться на заметку или блокнот по названию или ID, "
"либо используя ярлыки `$n` или `$b`, указывающие на текущую заметку или "
"блокнот соответственно. С помощью `$c` можно ссылаться на текущий выбранный "
"блокнот, соответственно. С помощью `$c` можно ссылаться на текущий выбранный "
"элемент."
#: CliClient/app/command-help.js:79
@@ -664,7 +664,7 @@ msgstr ""
#: CliClient/app/command-help.js:81
msgid "To maximise/minimise the console, press \"tc\"."
msgstr "Чтобы развернуть/свернуть консоль используйте комбинацию \"tc\"."
msgstr "Чтобы развернуть/свернуть консоль, используйте комбинацию \"tc\"."
#: CliClient/app/command-help.js:82
msgid "To enter command line mode, press \":\""
@@ -678,7 +678,7 @@ msgstr "Для выхода из режима командной строки н
msgid ""
"For the list of keyboard shortcuts and config options, type `help keymap`"
msgstr ""
"Чтобы получить список сочетаний клавиш и настроек конфигурации введите `help "
"Чтобы получить список сочетаний клавиш и настроек конфигурации, введите `help "
"keymap`"
#: CliClient/app/command-todo.js:14
@@ -690,7 +690,7 @@ msgid ""
msgstr ""
"<todo-command> может принимать значения \"toggle\" или \"clear\". \"toggle\" "
"переключает статус выбранной задачи (с завершенной на незавершенную и "
"наоборот). При применении к обычной заметке - заметка преобразуется в "
"наоборот). При применении к обычной заметке она преобразуется в "
"задачу. \"clear\" преобразует выбранную задачу в обычную заметку."
#: CliClient/app/command-exit.js:11
@@ -815,7 +815,7 @@ msgid ""
"Type a note title or part of its content to jump to it. Or type # followed "
"by a tag name, or @ followed by a notebook name."
msgstr ""
"Введите название заметки чтобы перейти к ней, либо введите #имя_метки или "
"Введите название заметки, чтобы перейти к ней, либо введите #имя_метки или "
"@имя_блокнота."
#: ElectronClient/plugins/GotoAnything.min.js:493
@@ -1117,7 +1117,7 @@ msgid ""
"Please wait for all attachments to be downloaded and decrypted. You may also "
"switch to %s to edit the note."
msgstr ""
"Пожалуйста дождитесь окончания загрузки и расшифровки всех вложений. Вы "
"Пожалуйста, дождитесь окончания загрузки и расшифровки всех вложений. Вы "
"можете переключиться на %s для редактирования заметки."
#: ElectronClient/gui/NoteEditor/utils/useMessageHandler.js:70
@@ -1218,7 +1218,7 @@ msgstr "Заголовок"
#: ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js:75
#: ElectronClient/gui/NoteText.min.js:1770
msgid "Horizontal Rule"
msgstr "Горизотальный разделитель"
msgstr "Горизонтальный разделитель"
#: ElectronClient/gui/NoteEditor/commands/focusElementNoteTitle.js:15
msgid "Note title"
@@ -1762,7 +1762,7 @@ msgstr ""
"\n"
"1. Синхронизируйте все ваши устройства.\n"
"2. Нажмите “%s”.\n"
"3. Ждите завершения. Во время работы избегайте изменения заметок на других "
"3. Ждите завершения. Во время работы не изменяйте заметки на других "
"устройствах, чтобы избежать конфликтов.\n"
"4. После завершения синхронизации на этом устройстве синхронизируйте все "
"остальные устройства и ждите завершения.\n"
@@ -1782,7 +1782,7 @@ msgstr "Шифрование:"
#: ElectronClient/gui/StatusScreen.min.js:33
msgid "Please select where the sync status should be exported to"
msgstr ""
"Пожалуйста, выберите куда должен быть экспортирован статус синхронизации"
"Пожалуйста, выберите, куда должен быть экспортирован статус синхронизации"
#: ElectronClient/gui/StatusScreen.min.js:71
#: ReactNativeClient/lib/components/screens/status.js:112
@@ -2194,7 +2194,7 @@ msgstr ""
"Откройте следующую ссылку в вашем браузере для аутентификации приложения. "
"Приложением будет создан отдельный каталог \"Apps/Joplin\", в котором будет "
"происходить работа с файлами. У приложения не будет доступа ни к каким-либо "
"файлам за пределами этого каталога ни к каким-либо другим личным данным. "
"файлам за пределами этого каталога, ни к каким-либо другим личным данным. "
"Никакая информация не будет передана третьим лицам."
#: ReactNativeClient/lib/registry.js:156
@@ -2391,9 +2391,9 @@ msgid ""
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
"the attachments are downloaded whether you open the note or not."
msgstr ""
"В режиме “Ручной” вложения загружаются толко если на них кликнуть. В режиме "
"В режиме “Ручной” вложения загружаются, только если на них кликнуть. В режиме "
"“Автоматически” вложения загружаются при открытии заметки. В режиме “Всегда” "
"вложения загружаются не зависимо от того была заметка открыта или нет."
"вложения загружаются вне зависимости от того, была заметка открыта или нет."
#: ReactNativeClient/lib/models/Setting.js:228
msgid "Always"
@@ -2433,11 +2433,11 @@ msgstr "Автоматическая смена темы в соответств
#: ReactNativeClient/lib/models/Setting.js:327
msgid "Preferred light theme"
msgstr "Предпочитемая светлая тема"
msgstr "Предпочитаемая светлая тема"
#: ReactNativeClient/lib/models/Setting.js:341
msgid "Preferred dark theme"
msgstr "Предпочитемая тёмная тема"
msgstr "Предпочитаемая тёмная тема"
#: ReactNativeClient/lib/models/Setting.js:346
msgid "Show note counts"
@@ -2526,7 +2526,7 @@ msgstr "Включить поддержку синтаксиса Fountain"
#: ReactNativeClient/lib/models/Setting.js:468
msgid "Enable Mermaid diagrams support"
msgstr "Включить поддержку Mermaid диаграм"
msgstr "Включить поддержку диаграмм Mermaid"
#: ReactNativeClient/lib/models/Setting.js:470
msgid "Enable ==mark== syntax"
@@ -2609,7 +2609,7 @@ msgid ""
"font."
msgstr ""
"Для корректного отображения требуется задать *моноширинный* шрифт. Если "
"шрифт не был задан или выбран некорректный шрифт будет использован шрифт по "
"шрифт не был задан или выбран некорректный шрифт, будет использован шрифт по "
"умолчанию."
#: ReactNativeClient/lib/models/Setting.js:589
@@ -2663,7 +2663,7 @@ msgid ""
"If none is provided it will try to auto-detect the default editor."
msgstr ""
"Команда запуска внешнего текстового редактора (может включать аргументы "
"командной строки). Если команда не задана - будет произведена попытка "
"командной строки). Если команда не задана, будет произведена попытка "
"автоматического определения редактора по умолчанию."
#: ReactNativeClient/lib/models/Setting.js:623
@@ -2734,7 +2734,7 @@ msgid ""
"changes before clicking on \"Check synchronisation configuration\"."
msgstr ""
"Разделенный запятыми список путей к файлам сертификатов (поддерживаются как "
"каталоги так и абсолютные пути к отдельным файлам). Например: /my/cert_dir, /"
"каталоги, так и абсолютные пути к отдельным файлам). Например: /my/cert_dir, /"
"other/custom.pem. Обратите внимание, что если вы вносите изменения в "
"настройки TLS, вы должны сохранить внесенные изменения перед нажатием на "
"\"Проверить настройки синхронизации\"."
@@ -2793,7 +2793,7 @@ msgid ""
"item with a factor of 2 will take twice as much space as an item with a "
"factor of 1.Restart app to see changes."
msgstr ""
"Параметр \"фактор\" отвечает за то, как элемент будет расти или уменьшаться "
"Параметр \"фактор\" отвечает за то, как элемент будет расти или уменьшаться, "
"чтобы поместиться в свободное пространство контейнера. Элемент с фактором 2 "
"займёт в 2 раза больше места, чем элемент с фактором 1. Перезапустите "
"приложение, чтобы изменения вступили в силу."
@@ -2854,7 +2854,7 @@ msgid ""
"the WYSIWYG editor."
msgstr ""
"Данные плагины расширяют отрисовщик Markdown доп. функциями. Учтите, что эти "
"функции не являются стандартными для Markdown и большонство из них будет "
"функции не являются стандартными для Markdown и большинство из них будет "
"работать только в Joplin. Кроме того, некоторые из них *несовместимы* с "
"редактором WYSIWYG. При открытии заметки с такими плагинами в WYSIWYG "
"форматирование будет утеряно. Ниже отмечены плагины, совместимые с "
@@ -3235,7 +3235,7 @@ msgstr "Только ошибки"
#: ReactNativeClient/lib/components/screens/config.js:50
msgid "Warning"
msgstr "Предупрежение"
msgstr "Предупреждение"
#: ReactNativeClient/lib/components/screens/config.js:50
msgid ""
@@ -3744,7 +3744,7 @@ msgstr ""
#~ "to continue?"
#~ msgstr ""
#~ "Отключение шифрования означает, что *все* ваши заметки и вложения будут "
#~ "расшифрованы и отправлены в незашифрованом виде к цели синхронизации. Вы "
#~ "расшифрованы и отправлены в незашифрованном виде к цели синхронизации. Вы "
#~ "хотите продолжить?"
#~ msgid "Master Keys"
+7 -5
View File
@@ -14,7 +14,9 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 2.3.1\n"
"X-Generator: Poedit 2.4.1\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: CliClient/app/command-cp.js:13
msgid ""
@@ -2221,7 +2223,7 @@ msgstr "Bilinmeyen ID seviyesi: %s"
#: ReactNativeClient/lib/SyncTargetAmazonS3.js:28
msgid "AWS S3"
msgstr ""
msgstr "AWS S3"
#: ReactNativeClient/lib/SyncTargetDropbox.js:25
msgid "Dropbox"
@@ -2364,15 +2366,15 @@ msgstr "WebDAV şifresi"
#: ReactNativeClient/lib/models/Setting.js:189
msgid "AWS S3 bucket"
msgstr ""
msgstr "AWS S3 deposu"
#: ReactNativeClient/lib/models/Setting.js:200
msgid "AWS key"
msgstr ""
msgstr "AWS anahtarı"
#: ReactNativeClient/lib/models/Setting.js:210
msgid "AWS secret"
msgstr ""
msgstr "AWS gizli anahtarı"
#: ReactNativeClient/lib/models/Setting.js:224
msgid "Attachment download behaviour"
+93 -408
View File
@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.0.166",
"version": "1.0.167",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -69,14 +69,6 @@
"json-schema-traverse": "^0.3.0"
}
},
"ansi-align": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
"integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
"requires": {
"string-width": "^2.0.0"
}
},
"ansi-colors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
@@ -551,20 +543,6 @@
}
}
},
"boxen": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
"integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
"requires": {
"ansi-align": "^2.0.0",
"camelcase": "^4.0.0",
"chalk": "^2.0.1",
"cli-boxes": "^1.0.0",
"string-width": "^2.0.0",
"term-size": "^1.2.0",
"widest-line": "^2.0.0"
}
},
"brace-expansion": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
@@ -658,11 +636,6 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
},
"capture-stack-trace": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
"integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw=="
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -800,9 +773,9 @@
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"readable-stream": {
"version": "3.6.0",
@@ -829,11 +802,6 @@
}
}
},
"cli-boxes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
},
"cliss": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/cliss/-/cliss-0.0.2.tgz",
@@ -1096,19 +1064,6 @@
"typedarray": "^0.0.6"
}
},
"configstore": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz",
"integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==",
"requires": {
"dot-prop": "^4.1.0",
"graceful-fs": "^4.1.2",
"make-dir": "^1.0.0",
"unique-string": "^1.0.0",
"write-file-atomic": "^2.0.0",
"xdg-basedir": "^3.0.0"
}
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -1143,14 +1098,6 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"create-error-class": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
"integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
"requires": {
"capture-stack-trace": "^1.0.0"
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -1175,11 +1122,6 @@
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"crypto-random-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
"integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
},
"css": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
@@ -1448,19 +1390,6 @@
"domhandler": "^3.0.0"
}
},
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
"requires": {
"is-obj": "^1.0.0"
}
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
"duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@@ -1676,32 +1605,6 @@
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
},
"execa": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
"integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
"requires": {
"cross-spawn": "^5.0.1",
"get-stream": "^3.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
},
"dependencies": {
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"requires": {
"lru-cache": "^4.0.1",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
}
}
},
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -2766,11 +2669,6 @@
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
"integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g="
},
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
},
"get-value": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -2855,14 +2753,6 @@
"object.defaults": "^1.1.0"
}
},
"global-dirs": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
"integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
"requires": {
"ini": "^1.3.4"
}
},
"global-modules": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
@@ -2912,24 +2802,6 @@
"sparkles": "^1.0.0"
}
},
"got": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
"integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
"requires": {
"create-error-class": "^3.0.0",
"duplexer3": "^0.1.4",
"get-stream": "^3.0.0",
"is-redirect": "^1.0.0",
"is-retry-allowed": "^1.0.0",
"is-stream": "^1.0.0",
"lowercase-keys": "^1.0.0",
"safe-buffer": "^5.0.1",
"timed-out": "^4.0.0",
"unzip-response": "^2.0.1",
"url-parse-lax": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@@ -3350,16 +3222,6 @@
"file-type": "^4.1.0"
}
},
"import-lazy": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
"integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM="
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -3604,6 +3466,11 @@
}
}
},
"is-docker": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz",
"integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ=="
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -3629,26 +3496,12 @@
"is-extglob": "^2.1.1"
}
},
"is-installed-globally": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
"integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
"requires": {
"global-dirs": "^0.1.0",
"is-path-inside": "^1.0.0"
}
},
"is-negated-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
"integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=",
"dev": true
},
"is-npm": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
"integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ="
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
@@ -3667,19 +3520,6 @@
}
}
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
},
"is-path-inside": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
"integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
"requires": {
"path-is-inside": "^1.0.1"
}
},
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -3688,11 +3528,6 @@
"isobject": "^3.0.1"
}
},
"is-redirect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
},
"is-relative": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz",
@@ -3701,11 +3536,6 @@
"is-unc-path": "^0.1.1"
}
},
"is-retry-allowed": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
"integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
@@ -3741,6 +3571,14 @@
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz",
"integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw="
},
"is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"requires": {
"is-docker": "^2.0.0"
}
},
"is2": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/is2/-/is2-0.0.9.tgz",
@@ -3945,10 +3783,37 @@
"prebuild-install": "5.3.3"
},
"dependencies": {
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"nan": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
},
"prebuild-install": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
"integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^3.0.3",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
}
}
}
},
@@ -3983,14 +3848,6 @@
"es6-weak-map": "^2.0.1"
}
},
"latest-version": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
"integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
"requires": {
"package-json": "^4.0.0"
}
},
"lazyness": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/lazyness/-/lazyness-1.1.1.tgz",
@@ -4092,9 +3949,9 @@
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
},
"lodash-es": {
"version": "4.17.4",
@@ -4134,11 +3991,6 @@
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
"integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
},
"lowercase-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
},
"lowlight": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.9.2.tgz",
@@ -4155,22 +4007,6 @@
}
}
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
},
"dependencies": {
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}
}
},
"magicli": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.8.tgz",
@@ -4182,14 +4018,6 @@
"inspect-property": "0.0.6"
}
},
"make-dir": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
"integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
"requires": {
"pify": "^3.0.0"
}
},
"make-iterator": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
@@ -4422,11 +4250,6 @@
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"minipass": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.1.tgz",
@@ -4463,13 +4286,25 @@
}
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "0.0.8"
"minimist": "^1.2.5"
},
"dependencies": {
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
}
}
},
"mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
@@ -4729,14 +4564,6 @@
"npm-bundled": "^1.0.1"
}
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
"requires": {
"path-key": "^2.0.0"
}
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
@@ -4911,6 +4738,15 @@
"wrappy": "1"
}
},
"open": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/open/-/open-7.1.0.tgz",
"integrity": "sha512-lLPI5KgOwEYCDKXf4np7y1PBEkj7HYIyP2DY8mVDRnx0VIIu6bNrRB0R66TuO7Mack6EnTNLm4uvcl1UoklTpA==",
"requires": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
}
},
"optionator": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
@@ -4968,11 +4804,6 @@
"os-tmpdir": "^1.0.0"
}
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
@@ -4994,17 +4825,6 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
},
"package-json": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
"integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
"requires": {
"got": "^6.7.1",
"registry-auth-token": "^3.0.1",
"registry-url": "^3.0.3",
"semver": "^5.1.0"
}
},
"param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
@@ -5092,9 +4912,9 @@
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
"patch-package": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.0.tgz",
"integrity": "sha512-HWlQflaBBMjLBfOWomfolF8aqsFDeNbSNro1JDUgYqnVvPM5OILJ9DQdwIRiKmGaOsmHvhkl1FYkvv1I9r2ZJw==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.2.tgz",
"integrity": "sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg==",
"requires": {
"@yarnpkg/lockfile": "^1.1.0",
"chalk": "^2.4.2",
@@ -5107,8 +4927,7 @@
"rimraf": "^2.6.3",
"semver": "^5.6.0",
"slash": "^2.0.0",
"tmp": "^0.0.33",
"update-notifier": "^2.5.0"
"tmp": "^0.0.33"
},
"dependencies": {
"chalk": {
@@ -5145,9 +4964,9 @@
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"rimraf": {
"version": "2.7.1",
@@ -5180,11 +4999,6 @@
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-is-inside": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
"integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM="
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
@@ -5281,14 +5095,14 @@
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
"prebuild-install": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
"integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==",
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.5.tgz",
"integrity": "sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"minimist": "^1.2.3",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
@@ -5303,9 +5117,9 @@
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
}
}
},
@@ -5314,11 +5128,6 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prepend-http": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
},
"pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
@@ -5347,11 +5156,6 @@
"retry": "^0.10.0"
}
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"psl": {
"version": "1.1.29",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
@@ -5440,9 +5244,9 @@
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
}
}
},
@@ -5556,23 +5360,6 @@
"safe-regex": "^1.1.0"
}
},
"registry-auth-token": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
"integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
"requires": {
"rc": "^1.1.6",
"safe-buffer": "^5.0.1"
}
},
"registry-url": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
"integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
"requires": {
"rc": "^1.0.1"
}
},
"relateurl": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
@@ -5910,14 +5697,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
},
"semver-diff": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
"integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
"requires": {
"semver": "^5.0.3"
}
},
"semver-greatest-satisfied-range": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz",
@@ -6428,11 +6207,6 @@
"is-utf8": "^0.2.0"
}
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
@@ -6524,12 +6298,12 @@
}
},
"tar-fs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz",
"integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz",
"integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==",
"requires": {
"chownr": "^1.1.1",
"mkdirp": "^0.5.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.0.0"
}
@@ -6602,14 +6376,6 @@
"rimraf": "~2.6.2"
}
},
"term-size": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
"integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
"requires": {
"execa": "^0.7.0"
}
},
"terminal-kit": {
"version": "1.31.2",
"resolved": "https://registry.npmjs.org/terminal-kit/-/terminal-kit-1.31.2.tgz",
@@ -6683,11 +6449,6 @@
"integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=",
"dev": true
},
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
"integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
},
"tkwidgets": {
"version": "0.5.26",
"resolved": "https://registry.npmjs.org/tkwidgets/-/tkwidgets-0.5.26.tgz",
@@ -6976,14 +6737,6 @@
"through2-filter": "^3.0.0"
}
},
"unique-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
"integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
"requires": {
"crypto-random-string": "^1.0.0"
}
},
"universalify": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
@@ -7035,49 +6788,12 @@
}
}
},
"unzip-response": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
"integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c="
},
"upath": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
"integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
"dev": true
},
"update-notifier": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
"integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
"requires": {
"boxen": "^1.2.1",
"chalk": "^2.0.1",
"configstore": "^3.0.0",
"import-lazy": "^2.1.0",
"is-ci": "^1.0.10",
"is-installed-globally": "^0.1.0",
"is-npm": "^1.0.0",
"latest-version": "^3.0.0",
"semver-diff": "^2.0.0",
"xdg-basedir": "^3.0.0"
},
"dependencies": {
"ci-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
"integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A=="
},
"is-ci": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
"integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
"requires": {
"ci-info": "^1.5.0"
}
}
}
},
"upper-case": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
@@ -7128,14 +6844,6 @@
"requires-port": "^1.0.0"
}
},
"url-parse-lax": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
"integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
"requires": {
"prepend-http": "^1.0.1"
}
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@@ -7336,14 +7044,6 @@
"string-width": "^1.0.2 || 2"
}
},
"widest-line": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
"integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
"requires": {
"string-width": "^2.1.1"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@@ -7372,26 +7072,11 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"write-file-atomic": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
"integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
"requires": {
"graceful-fs": "^4.1.11",
"imurmurhash": "^0.1.4",
"signal-exit": "^3.0.2"
}
},
"ws": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
},
"xdg-basedir": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ="
},
"xml-name-validator": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
+5 -3
View File
@@ -4,7 +4,8 @@
"license": "MIT",
"author": "Laurent Cozic",
"scripts": {
"test": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --config=tests/support/jasmine.json",
"test": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --fail-fast=true --config=tests/support/jasmine.json",
"test-ci": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --config=tests/support/jasmine.json",
"postinstall": "npm run build && patch-package --patch-dir ../patches",
"build": "gulp build",
"start": "gulp build -L && node 'build/main.js' --stack-trace-enabled --log-level debug --env dev"
@@ -27,7 +28,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.0.166",
"version": "1.0.167",
"bin": {
"joplin": "./main.js"
},
@@ -85,7 +86,8 @@
"node-emoji": "^1.8.1",
"node-fetch": "^1.7.1",
"node-persist": "^2.1.0",
"patch-package": "^6.2.0",
"open": "^7.0.4",
"patch-package": "^6.2.2",
"promise": "^7.1.1",
"proper-lockfile": "^2.0.1",
"query-string": "4.3.4",
-2
View File
@@ -4,8 +4,6 @@ const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-
const { shim } = require('lib/shim');
const { enexXmlToHtml } = require('lib/import-enex-html-gen.js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
process.on('unhandledRejection', (reason, p) => {
console.warn('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
-2
View File
@@ -12,8 +12,6 @@ const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
-2
View File
@@ -13,8 +13,6 @@ const { shim } = require('lib/shim');
const HtmlToHtml = require('lib/joplin-renderer/HtmlToHtml');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
-2
View File
@@ -13,8 +13,6 @@ const { shim } = require('lib/shim');
const HtmlToMd = require('lib/HtmlToMd');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
-2
View File
@@ -14,8 +14,6 @@ const MdToHtml = require('lib/joplin-renderer/MdToHtml');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
const { themeStyle } = require('lib/theme');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
+96 -87
View File
@@ -9,118 +9,127 @@ const { shim } = require('lib/shim.js');
const fs = require('fs-extra');
const Setting = require('lib/models/Setting.js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
let api = null;
const api = null;
// NOTE: These tests work with S3 and memory driver, but not
// with other targets like file system or Nextcloud.
// All this is tested in an indirect way in tests/synchronizer
// anyway.
// We keep the file here as it could be useful as a spec for
// what calls a sync target should support, but it would
// need to be fixed first.
// To test out an FileApi implementation:
// * add a SyncTarget for your driver in `test-utils.js`
// * set `syncTargetId_` to your New SyncTarget:
// `const syncTargetId_ = SyncTargetRegistry.nameToId('memory');`
describe('fileApi', function() {
// describe('fileApi', function() {
beforeEach(async (done) => {
api = new fileApi();
api.clearRoot();
done();
});
// beforeEach(async (done) => {
// api = new fileApi();
// api.clearRoot();
// done();
// });
describe('list', function() {
it('should return items with relative path', asyncTest(async () => {
await api.mkdir('.subfolder');
await api.put('1', 'something on root 1');
await api.put('.subfolder/1', 'something subfolder 1');
await api.put('.subfolder/2', 'something subfolder 2');
await api.put('.subfolder/3', 'something subfolder 3');
sleep(0.8);
// describe('list', function() {
// it('should return items with relative path', asyncTest(async () => {
// await api.mkdir('.subfolder');
// await api.put('1', 'something on root 1');
// await api.put('.subfolder/1', 'something subfolder 1');
// await api.put('.subfolder/2', 'something subfolder 2');
// await api.put('.subfolder/3', 'something subfolder 3');
// sleep(0.8);
const response = await api.list('.subfolder');
const items = response.items;
expect(items.length).toBe(3);
expect(items[0].path).toBe('1');
}));
// const response = await api.list('.subfolder');
// const items = response.items;
// expect(items.length).toBe(3);
// expect(items[0].path).toBe('1');
// expect(items[0].updated_time).toMatch(/^\d+$/); // make sure it's using epoch timestamp
// }));
it('should default to only files on root directory', asyncTest(async () => {
await api.mkdir('.subfolder');
await api.put('.subfolder/1', 'something subfolder 1');
await api.put('file1', 'something 1');
await api.put('file2', 'something 2');
sleep(0.6);
// it('should default to only files on root directory', asyncTest(async () => {
// await api.mkdir('.subfolder');
// await api.put('.subfolder/1', 'something subfolder 1');
// await api.put('file1', 'something 1');
// await api.put('file2', 'something 2');
// sleep(0.6);
const response = await api.list();
expect(response.items.length).toBe(2);
}));
}); // list
// const response = await api.list();
// expect(response.items.length).toBe(2);
// }));
// }); // list
describe('delete', function() {
it('should not error if file does not exist', asyncTest(async () => {
const hasThrown = await checkThrowAsync(async () => await api.delete('nonexistant_file'));
expect(hasThrown).toBe(false);
}));
// describe('delete', function() {
// it('should not error if file does not exist', asyncTest(async () => {
// const hasThrown = await checkThrowAsync(async () => await api.delete('nonexistant_file'));
// expect(hasThrown).toBe(false);
// }));
it('should delete specific file given full path', asyncTest(async () => {
await api.mkdir('deleteDir');
await api.put('deleteDir/1', 'something 1');
await api.put('deleteDir/2', 'something 2');
sleep(0.4);
// it('should delete specific file given full path', asyncTest(async () => {
// await api.mkdir('deleteDir');
// await api.put('deleteDir/1', 'something 1');
// await api.put('deleteDir/2', 'something 2');
// sleep(0.4);
await api.delete('deleteDir/1');
let response = await api.list('deleteDir');
expect(response.items.length).toBe(1);
response = await api.list('deleteDir/1');
expect(response.items.length).toBe(0);
}));
}); // delete
// await api.delete('deleteDir/1');
// let response = await api.list('deleteDir');
// expect(response.items.length).toBe(1);
// response = await api.list('deleteDir/1');
// expect(response.items.length).toBe(0);
// }));
// }); // delete
describe('get', function() {
it('should return null if object does not exist', asyncTest(async () => {
const response = await api.get('nonexistant_file');
expect(response).toBe(null);
}));
// describe('get', function() {
// it('should return null if object does not exist', asyncTest(async () => {
// const response = await api.get('nonexistant_file');
// expect(response).toBe(null);
// }));
it('should return UTF-8 encoded string by default', asyncTest(async () => {
await api.put('testnote.md', 'something 2');
// it('should return UTF-8 encoded string by default', asyncTest(async () => {
// await api.put('testnote.md', 'something 2');
const response = await api.get('testnote.md');
expect(response).toBe('something 2');
}));
// const response = await api.get('testnote.md');
// expect(response).toBe('something 2');
// }));
it('should return a Response object and writes file to options.path, if options.target is "file"', asyncTest(async () => {
const localFilePath = `${Setting.value('tempDir')}/${uuid.create()}.md`;
await api.put('testnote.md', 'something 2');
sleep(0.2);
// it('should return a Response object and writes file to options.path, if options.target is "file"', asyncTest(async () => {
// const localFilePath = `${Setting.value('tempDir')}/${uuid.create()}.md`;
// await api.put('testnote.md', 'something 2');
// sleep(0.2);
const response = await api.get('testnote.md', { target: 'file', path: localFilePath });
expect(typeof response).toBe('object');
// expect(response.path).toBe(localFilePath);
expect(fs.existsSync(localFilePath)).toBe(true);
expect(fs.readFileSync(localFilePath, 'utf8')).toBe('something 2');
}));
}); // get
// const response = await api.get('testnote.md', { target: 'file', path: localFilePath });
// expect(typeof response).toBe('object');
// // expect(response.path).toBe(localFilePath);
// expect(fs.existsSync(localFilePath)).toBe(true);
// expect(fs.readFileSync(localFilePath, 'utf8')).toBe('something 2');
// }));
// }); // get
describe('put', function() {
it('should create file to remote path and content', asyncTest(async () => {
await api.put('putTest.md', 'I am your content');
sleep(0.2);
// describe('put', function() {
// it('should create file to remote path and content', asyncTest(async () => {
// await api.put('putTest.md', 'I am your content');
// sleep(0.2);
const response = await api.get('putTest.md');
expect(response).toBe('I am your content');
}));
// const response = await api.get('putTest.md');
// expect(response).toBe('I am your content');
// }));
it('should upload file in options.path to remote path, if options.source is "file"', asyncTest(async () => {
const localFilePath = `${Setting.value('tempDir')}/${uuid.create()}.md`;
fs.writeFileSync(localFilePath, 'I am the local file.');
// it('should upload file in options.path to remote path, if options.source is "file"', asyncTest(async () => {
// const localFilePath = `${Setting.value('tempDir')}/${uuid.create()}.md`;
// fs.writeFileSync(localFilePath, 'I am the local file.');
await api.put('testfile', 'ignore me', { source: 'file', path: localFilePath });
sleep(0.2);
// await api.put('testfile', 'ignore me', { source: 'file', path: localFilePath });
// sleep(0.2);
const response = await api.get('testfile');
expect(response).toBe('I am the local file.');
}));
}); // put
// const response = await api.get('testfile');
// expect(response).toBe('I am the local file.');
// }));
// }); // put
});
// });
+155
View File
@@ -0,0 +1,155 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const filterParser = require('lib/services/searchengine/filterParser.js').default;
// import filterParser from 'lib/services/searchengine/filterParser.js';
const makeTerm = (name, value, negated) => { return { name, value, negated }; };
describe('filterParser should be correct filter for keyword', () => {
it('title', () => {
const searchString = 'title: something';
expect(filterParser(searchString)).toContain(makeTerm('title', 'something', false));
});
it('negated title', () => {
const searchString = '-title: something';
expect(filterParser(searchString)).toContain(makeTerm('title', 'something', true));
});
it('body', () => {
const searchString = 'body:something';
expect(filterParser(searchString)).toContain(makeTerm('body', 'something', false));
});
it('negated body', () => {
const searchString = '-body:something';
expect(filterParser(searchString)).toContain(makeTerm('body', 'something', true));
});
it('title and body', () => {
const searchString = 'title:testTitle body:testBody';
expect(filterParser(searchString)).toContain(makeTerm('title', 'testTitle', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'testBody', false));
});
it('title with multiple words', () => {
const searchString = 'title:"word1 word2" body:testBody';
expect(filterParser(searchString)).toContain(makeTerm('title', 'word1', false));
expect(filterParser(searchString)).toContain(makeTerm('title', 'word2', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'testBody', false));
});
it('body with multiple words', () => {
const searchString = 'title:testTitle body:"word1 word2"';
expect(filterParser(searchString)).toContain(makeTerm('title', 'testTitle', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'word1', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'word2', false));
});
it('single word text', () => {
const searchString = 'joplin';
expect(filterParser(searchString)).toContain(makeTerm('text', '"joplin"', false));
});
it('multi word text', () => {
const searchString = 'scott joplin';
expect(filterParser(searchString)).toContain(makeTerm('text', '"scott"', false));
expect(filterParser(searchString)).toContain(makeTerm('text', '"joplin"', false));
});
it('negated word text', () => {
const searchString = 'scott -joplin';
expect(filterParser(searchString)).toContain(makeTerm('text', '"scott"', false));
expect(filterParser(searchString)).toContain(makeTerm('text', '"joplin"', true));
});
it('phrase text search', () => {
const searchString = '"scott joplin"';
expect(filterParser(searchString)).toContain(makeTerm('text', '"scott joplin"', false));
});
it('multi word body', () => {
const searchString = 'body:"foo bar"';
expect(filterParser(searchString)).toContain(makeTerm('body', 'foo', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'bar', false));
});
it('negated tag queries', () => {
const searchString = '-tag:mozart';
expect(filterParser(searchString)).toContain(makeTerm('tag', 'mozart', true));
});
it('created after', () => {
const searchString = 'created:20151218'; // YYYYMMDD
expect(filterParser(searchString)).toContain(makeTerm('created', '20151218', false));
});
it('created before', () => {
const searchString = '-created:20151218'; // YYYYMMDD
expect(filterParser(searchString)).toContain(makeTerm('created', '20151218', true));
});
it('any', () => {
const searchString = 'any:1 tag:123';
expect(filterParser(searchString)).toContain(makeTerm('any', '1', false));
expect(filterParser(searchString)).toContain(makeTerm('tag', '123', false));
});
it('wildcard tags', () => {
let searchString = 'tag:*';
expect(filterParser(searchString)).toContain(makeTerm('tag', '%', false));
searchString = '-tag:*';
expect(filterParser(searchString)).toContain(makeTerm('tag', '%', true));
searchString = 'tag:bl*sphemy';
expect(filterParser(searchString)).toContain(makeTerm('tag', 'bl%sphemy', false));
});
it('wildcard notebooks', () => {
const searchString = 'notebook:my*notebook';
expect(filterParser(searchString)).toContain(makeTerm('notebook', 'my%notebook', false));
});
it('wildcard MIME types', () => {
const searchString = 'resource:image/*';
expect(filterParser(searchString)).toContain(makeTerm('resource', 'image/%', false));
});
it('sourceurl', () => {
let searchString = 'sourceurl:https://www.google.com';
expect(filterParser(searchString)).toContain(makeTerm('sourceurl', 'https://www.google.com', false));
searchString = 'sourceurl:https://www.google.com -sourceurl:https://www.facebook.com';
expect(filterParser(searchString)).toContain(makeTerm('sourceurl', 'https://www.google.com', false));
expect(filterParser(searchString)).toContain(makeTerm('sourceurl', 'https://www.facebook.com', true));
});
it('handle invalid filters', () => {
let searchString = 'titletitle:123';
expect(() => filterParser(searchString)).toThrow(new Error('Invalid filter: titletitle'));
searchString = 'invalid:abc';
expect(() => filterParser(searchString)).toThrow(new Error('Invalid filter: invalid'));
searchString = ':abc';
expect(() => filterParser(searchString)).toThrow(new Error('Invalid filter: '));
searchString = 'type:blah';
expect(() => filterParser(searchString)).toThrow(new Error('The value of filter "type" must be "note" or "todo"'));
searchString = '-type:note';
expect(() => filterParser(searchString)).toThrow(new Error('type can\'t be negated'));
searchString = 'iscompleted:blah';
expect(() => filterParser(searchString)).toThrow(new Error('The value of filter "iscompleted" must be "1" or "0"'));
searchString = '-notebook:n1';
expect(() => filterParser(searchString)).toThrow(new Error('notebook can\'t be negated'));
searchString = '-iscompleted:1';
expect(() => filterParser(searchString)).toThrow(new Error('iscompleted can\'t be negated'));
});
});
+1 -1
View File
@@ -4,7 +4,7 @@ require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, revisionService, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const SearchEngine = require('lib/services/SearchEngine');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const ResourceService = require('lib/services/ResourceService');
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
const Note = require('lib/models/Note');
-2
View File
@@ -10,8 +10,6 @@ const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
@@ -19,8 +19,6 @@ process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built
let service = null;
describe('services_EncryptionService', function() {
@@ -15,8 +15,6 @@ const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
@@ -11,8 +11,6 @@ const Resource = require('lib/models/Resource.js');
const Note = require('lib/models/Note.js');
const { shim } = require('lib/shim.js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
const exportDir = `${__dirname}/export`;
process.on('unhandledRejection', (reason, p) => {
+319
View File
@@ -0,0 +1,319 @@
require('app-module-path').addPath(__dirname);
const KeymapService = require('lib/services/KeymapService').default;
const keymapService = KeymapService.instance();
describe('services_KeymapService', () => {
describe('validateAccelerator', () => {
it('should identify valid Accelerators', () => {
const testCases = {
darwin: [
'F4',
'Cmd+F9',
'Option+Z',
'Option+Shift+F',
'Ctrl+Option+U',
'Option+Shift+Cmd+F9',
'Ctrl+Shift+Z',
'Option+Shift+Cmd+B',
],
linux: [
'F4',
'Ctrl+F9',
'Alt+Shift+F',
'Shift+U',
'Ctrl+Shift+T',
'Ctrl+Alt+Shift+Z',
'Alt+E',
'Alt+Shift+F9',
],
};
Object.entries(testCases).forEach(([platform, accelerators]) => {
keymapService.initialize(platform);
accelerators.forEach(accelerator => {
expect(() => keymapService.validateAccelerator(accelerator)).not.toThrow();
});
});
});
it('should identify invalid Accelerators', () => {
const testCases = {
darwin: [
'',
'A+Z',
'Cmd',
'Ctrl+',
'Option+Cmd',
'Ctrl+Shift',
'Cmd+H+A',
'Option+Shift+Cmd+J+O+P',
'Opptionn+F9',
'Ctrl+Shiftt+X',
'Cmd+Option+Shoft+T',
],
win32: [
'+',
'B+F4',
'Ctrl+',
'Ctrl+Shift+',
'Cmd+Alt',
'Ctrl+Shift+Alt',
'Cmd+H+A',
'Ctrl+Shift+Alt+J+O+P',
'Contrl+F9',
'Controller+Shift+X',
'Cmd+Option+Shoft+T',
],
};
Object.entries(testCases).forEach(([platform, accelerators]) => {
keymapService.initialize(platform);
accelerators.forEach(accelerator => {
expect(() => keymapService.validateAccelerator(accelerator)).toThrow();
});
});
});
});
describe('getAccelerator', () => {
beforeEach(() => keymapService.initialize());
it('should return the platform-specific default Accelerator', () => {
keymapService.initialize('darwin');
expect(keymapService.getAccelerator('newNote')).toEqual('Cmd+N');
expect(keymapService.getAccelerator('synchronize')).toEqual('Cmd+S');
expect(keymapService.getAccelerator('textSelectAll')).toEqual('Cmd+A');
expect(keymapService.getAccelerator('textBold')).toEqual('Cmd+B');
keymapService.initialize('linux');
expect(keymapService.getAccelerator('newNote')).toEqual('Ctrl+N');
expect(keymapService.getAccelerator('synchronize')).toEqual('Ctrl+S');
keymapService.initialize('win32');
expect(keymapService.getAccelerator('textSelectAll')).toEqual('Ctrl+A');
expect(keymapService.getAccelerator('textBold')).toEqual('Ctrl+B');
});
if ('should throw when an invalid command is requested', () => {
expect(() => keymapService.getAccelerator('totallyNonExistentCommand')).toThrow();
});
});
describe('setAccelerator', () => {
beforeEach(() => keymapService.initialize());
it('should update the Accelerator', () => {
keymapService.initialize('darwin');
const testCases_Darwin = [
{ command: 'newNote', accelerator: 'Ctrl+Option+Shift+N' },
{ command: 'synchronize', accelerator: 'F11' },
{ command: 'textBold', accelerator: 'Shift+F5' },
{ command: 'showLocalSearch', accelerator: 'Option+Cmd+S' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: null /* Disabled */ },
{ command: 'focusElementNoteTitle', accelerator: 'Option+Shift+Cmd+T' },
{ command: 'focusElementNoteBody', accelerator: 'Ctrl+Option+Shift+Cmd+B' },
];
testCases_Darwin.forEach(({ command, accelerator }) => {
keymapService.setAccelerator(command, accelerator);
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
keymapService.initialize('linux');
const testCases_Linux = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: 'F15' },
{ command: 'textBold', accelerator: 'Shift+F5' },
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: null /* Disabled */ },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+T' },
{ command: 'focusElementNoteBody', accelerator: 'Ctrl+Alt+Shift+B' },
];
testCases_Linux.forEach(({ command, accelerator }) => {
keymapService.setAccelerator(command, accelerator);
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
});
});
describe('resetAccelerator', () => {
beforeEach(() => keymapService.initialize());
it('should reset the Accelerator', () => {
const testCases = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: null /* Disabled */ },
{ command: 'textBold', accelerator: 'Shift+F5' },
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+T' },
{ command: 'focusElementNoteBody', accelerator: 'Ctrl+Alt+Shift+B' },
];
testCases.forEach(({ command, accelerator }) => {
// Remember the default Accelerator value
const prevAccelerator = keymapService.getAccelerator(command);
// Update the Accelerator,
keymapService.setAccelerator(command, accelerator);
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
// and then reset it..
keymapService.resetAccelerator(command);
expect(keymapService.getAccelerator(command)).toEqual(prevAccelerator);
});
});
});
describe('setKeymap', () => {
beforeEach(() => keymapService.initialize());
it('should update the keymap', () => {
keymapService.initialize('darwin');
const customKeymap_Darwin = [
{ command: 'newNote', accelerator: 'Option+Shift+Cmd+N' },
{ command: 'synchronize', accelerator: 'F11' },
{ command: 'textBold', accelerator: 'Shift+F5' },
{ command: 'showLocalSearch', accelerator: 'Ctrl+Option+S' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Option+P' },
{ command: 'help', accelerator: null /* Disabled */ },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Option+Shift+T' },
{ command: 'focusElementNoteBody', accelerator: 'Option+Shift+Cmd+B' },
{ command: 'focusElementSideBar', accelerator: 'Shift+Cmd+L' /* Default of focusElementNoteList */ },
{ command: 'focusElementNoteList', accelerator: 'Shift+Cmd+S' /* Default of focusElementSideBar */ },
];
expect(() => keymapService.setKeymap(customKeymap_Darwin)).not.toThrow();
customKeymap_Darwin.forEach(({ command, accelerator }) => {
// Also check if keymap is updated or not
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
keymapService.initialize('win32');
const customKeymap_Win32 = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: 'F11' },
{ command: 'textBold', accelerator: 'Shift+F5' },
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'help', accelerator: null /* Disabled */ },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+T' },
{ command: 'focusElementNoteBody', accelerator: 'Ctrl+Alt+Shift+B' },
{ command: 'focusElementSideBar', accelerator: 'Ctrl+Shift+L' /* Default of focusElementNoteList */ },
{ command: 'focusElementNoteList', accelerator: 'Ctrl+Shift+S' /* Default of focusElementSideBar */ },
];
expect(() => keymapService.setKeymap(customKeymap_Win32)).not.toThrow();
customKeymap_Win32.forEach(({ command, accelerator }) => {
// Also check if keymap is updated or not
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
});
it('should throw when the required properties are missing', () => {
const customKeymaps = [
[
{ commmmmand: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' },
{ command: 'print', accelerator: 'Alt+P' },
],
[
{ accelerator: 'Alt+P' },
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
],
[
{ command: 'showLocalSearch', accel: 'Ctrl+Alt+S' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
],
[
{ command: 'print' },
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
],
];
for (let i = 0; i < customKeymaps.length; i++) {
const customKeymap = customKeymaps[i];
expect(() => keymapService.setKeymap(customKeymap)).toThrow();
}
});
it('should throw when the provided Accelerators are invalid', () => {
// Only one test case is provided since KeymapService.validateAccelerator() is already tested
const customKeymap = [
{ command: 'gotoAnything', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+J+O+P+L+I+N' },
];
expect(() => keymapService.setKeymap(customKeymap)).toThrow();
});
it('should throw when the provided commands are invalid', () => {
const customKeymap = [
{ command: 'totallyInvalidCommand', accelerator: 'Ctrl+Shift+G' },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+J' },
];
expect(() => keymapService.setKeymap(customKeymap)).toThrow();
});
it('should throw when duplicate accelerators are provided', () => {
const customKeymaps_Darwin = [
[
{ command: 'showLocalSearch', accelerator: 'Option+Cmd+S' /* Duplicate */ },
{ command: 'gotoAnything', accelerator: 'Option+Cmd+S' /* Duplicate */ },
{ command: 'print', accelerator: 'Option+P' },
],
[
{ command: 'showLocalSearch', accelerator: 'Option+Cmd+S' },
{ command: 'print', accelerator: 'Cmd+G' /* Default of gotoAnything */ },
{ command: 'focusElementNoteTitle', accelerator: 'Option+Shift+Cmd+J' },
],
];
for (let i = 0; i < customKeymaps_Darwin.length; i++) {
const customKeymap = customKeymaps_Darwin[i];
const defaultAccelerators = customKeymap.map(({ command }) => keymapService.getAccelerator(command));
expect(() => keymapService.setKeymap(customKeymap)).toThrow();
// All items should be reset to default values
for (let j = 0; j < customKeymap.length; j++) {
expect(keymapService.getAccelerator(customKeymap[j].command)).toEqual(defaultAccelerators[j]);
}
}
const customKeymaps_Linux = [
[
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' /* Duplicate */ },
{ command: 'print', accelerator: 'Alt+P' },
{ command: 'gotoAnything', accelerator: 'Ctrl+Alt+S' /* Duplicate */ },
],
[
{ command: 'showLocalSearch', accelerator: 'Ctrl+Alt+S' },
{ command: 'print', accelerator: 'Ctrl+G' /* Default of gotoAnything */ },
{ command: 'focusElementNoteTitle', accelerator: 'Ctrl+Alt+Shift+J' },
],
];
for (let i = 0; i < customKeymaps_Linux.length; i++) {
const customKeymap = customKeymaps_Linux[i];
const defaultAccelerators = customKeymap.map(({ command }) => keymapService.getAccelerator(command));
expect(() => keymapService.setKeymap(customKeymap)).toThrow();
for (let j = 0; j < customKeymap.length; j++) {
expect(keymapService.getAccelerator(customKeymap[j].command)).toEqual(defaultAccelerators[j]);
}
}
});
});
});
-2
View File
@@ -9,8 +9,6 @@ process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
function setupStore() {
const store = KvStore.instance();
store.setDb(db());
+1 -3
View File
@@ -17,14 +17,12 @@ const fs = require('fs-extra');
const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
const SearchEngine = require('lib/services/SearchEngine');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
function exportDir() {
return `${__dirname}/export`;
}
+112 -27
View File
@@ -5,7 +5,7 @@ require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const SearchEngine = require('lib/services/SearchEngine');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');
const ItemChange = require('lib/models/ItemChange');
const Setting = require('lib/models/Setting');
@@ -16,6 +16,39 @@ process.on('unhandledRejection', (reason, p) => {
let engine = null;
const IDF = (N, n) => Math.max(Math.log((N - n + 0.5) / (n + 0.5)), 0);
const frequency = (word, string) => {
const re = new RegExp(`\\b(${word})\\b`, 'g');
return (string.match(re) || []).length;
};
const calculateScore = (searchString, notes) => {
const K1 = 1.2;
const B = 0.75;
const freqTitle = notes.map(note => frequency(searchString, note.title));
const notesWithWord = freqTitle.filter(count => count !== 0).length;
const numTokens = notes.map(note => note.title.split(' ').length);
const avgTokens = Math.round(numTokens.reduce((a, b) => a + b, 0) / notes.length);
let titleBM25 = new Array(notes.length).fill(-1);
if (avgTokens != 0) {
for (let i = 0; i < notes.length; i++) {
titleBM25[i] = IDF(notes.length, notesWithWord) * ((freqTitle[i] * (K1 + 1)) / (freqTitle[i] + K1 * (1 - B + B * (numTokens[i] / avgTokens))));
}
}
const scores = [];
for (let i = 0; i < notes.length; i++) {
if (freqTitle[i]) scores.push(titleBM25[i]);
}
scores.sort().reverse();
return scores;
};
describe('services_SearchEngine', function() {
beforeEach(async (done) => {
@@ -79,17 +112,92 @@ describe('services_SearchEngine', function() {
}));
it('should order search results by relevance (1)', asyncTest(async () => {
it('should order search results by relevance BM25', asyncTest(async () => {
// BM25 is based on term frequency - inverse document frequency
// The tf–idf value increases proportionally to the number of times a word appears in the document
// and is offset by the number of documents in the corpus that contain the word, which helps to adjust
// for the fact that some words appear more frequently in general.
// BM25 returns weight zero for search term which occurs in more than half the notes.
// So terms that are abundant in all notes to have zero relevance w.r.t BM25.
const n1 = await Note.save({ title: 'abcd efgh' }); // 3
const n2 = await Note.save({ title: 'abcd aaaaa abcd abcd' }); // 1
const n2 = await Note.save({ title: 'abcd efgh abcd abcd' }); // 1
const n3 = await Note.save({ title: 'abcd aaaaa bbbb eeee abcd' }); // 2
const n4 = await Note.save({ title: 'xyz xyz' });
const n5 = await Note.save({ title: 'xyz xyz xyz xyz' });
const n6 = await Note.save({ title: 'xyz xyz xyz xyz xyz xyz' });
const n7 = await Note.save({ title: 'xyz xyz xyz xyz xyz xyz' });
const n8 = await Note.save({ title: 'xyz xyz xyz xyz xyz xyz xyz xyz' });
await engine.syncTables();
const rows = await engine.search('abcd');
let rows = await engine.search('abcd');
expect(rows[0].id).toBe(n2.id);
expect(rows[1].id).toBe(n3.id);
expect(rows[2].id).toBe(n1.id);
rows = await engine.search('abcd efgh');
expect(rows[0].id).toBe(n1.id); // shorter note; also 'efgh' is more rare than 'abcd'.
expect(rows[1].id).toBe(n2.id);
}));
it('should correctly weigh notes using BM25', asyncTest(async () => {
const noteData = [
{
title: 'abc test2 test2',
},
{
title: 'foo foo',
},
{
title: 'dead beef',
},
{
title: 'test2 bar',
},
{
title: 'blah blah abc',
},
];
const n0 = await Note.save(noteData[0]);
const n1 = await Note.save(noteData[1]);
const n2 = await Note.save(noteData[2]);
const n3 = await Note.save(noteData[3]);
const n4 = await Note.save(noteData[4]);
await engine.syncTables();
let searchString = 'abc';
let scores = calculateScore(searchString, noteData);
let rows = await engine.search(searchString);
expect(rows[0].weight).toEqual(scores[0]);
expect(rows[1].weight).toEqual(scores[1]);
// console.log(rows);
// console.log(scores);
searchString = 'test2';
scores = calculateScore(searchString, noteData);
rows = await engine.search(searchString);
// console.log(rows);
// console.log(scores);
expect(rows[0].weight).toEqual(scores[0]);
expect(rows[1].weight).toEqual(scores[1]);
searchString = 'foo';
scores = calculateScore(searchString, noteData);
rows = await engine.search(searchString);
// console.log(rows);
// console.log(scores);
expect(rows[0].weight).toEqual(scores[0]);
}));
it('should tell where the results are found', asyncTest(async () => {
@@ -118,28 +226,6 @@ describe('services_SearchEngine', function() {
}
}));
it('should order search results by relevance (2)', asyncTest(async () => {
// 1
const n1 = await Note.save({ title: 'abcd efgh', body: 'XX abcd XX efgh' });
// 4
const n2 = await Note.save({ title: 'abcd aaaaa bbbb eeee efgh' });
// 3
const n3 = await Note.save({ title: 'abcd aaaaa efgh' });
// 2
const n4 = await Note.save({ title: 'blablablabla blabla bla abcd X efgh' });
// 5
const n5 = await Note.save({ title: 'occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh' });
await engine.syncTables();
const rows = await engine.search('abcd efgh');
expect(rows[0].id).toBe(n1.id);
expect(rows[1].id).toBe(n4.id);
expect(rows[2].id).toBe(n3.id);
expect(rows[3].id).toBe(n2.id);
expect(rows[4].id).toBe(n5.id);
}));
it('should order search results by relevance (last updated first)', asyncTest(async () => {
let rows;
@@ -389,5 +475,4 @@ describe('services_SearchEngine', function() {
expect((await engine.search('"- [ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(1);
expect((await engine.search('"[ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(2);
}));
});
+752
View File
@@ -0,0 +1,752 @@
/* eslint-disable no-unused-vars */
/* eslint prefer-const: 0*/
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');
const Folder = require('lib/models/Folder');
const Tag = require('lib/models/Tag');
const ItemChange = require('lib/models/ItemChange');
const Setting = require('lib/models/Setting');
const Resource = require('lib/models/Resource.js');
const { shim } = require('lib/shim');
const ResourceService = require('lib/services/ResourceService.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
let engine = null;
const ids = (array) => array.map(a => a.id);
// For pretty printing.
// See https://stackoverflow.com/questions/23676459/karma-jasmine-pretty-printing-object-comparison/26324116
// jasmine.pp = function(obj) {
// return JSON.stringify(obj, undefined, 2);
// };
describe('services_SearchFilter', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
engine = new SearchEngine();
engine.setDb(db());
done();
});
it('should return note matching title', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd', body: 'body 1' });
const n2 = await Note.save({ title: 'efgh', body: 'body 2' });
await engine.syncTables();
rows = await engine.search('title: abcd');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
}));
it('should return note matching negated title', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd', body: 'body 1' });
const n2 = await Note.save({ title: 'efgh', body: 'body 2' });
await engine.syncTables();
rows = await engine.search('-title: abcd');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n2.id);
}));
it('should return note matching body', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd', body: 'body1' });
const n2 = await Note.save({ title: 'efgh', body: 'body2' });
await engine.syncTables();
rows = await engine.search('body: body1');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
}));
it('should return note matching negated body', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd', body: 'body1' });
const n2 = await Note.save({ title: 'efgh', body: 'body2' });
await engine.syncTables();
rows = await engine.search('-body: body1');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n2.id);
}));
it('should return note matching title containing multiple words', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd xyz', body: 'body1' });
const n2 = await Note.save({ title: 'efgh ijk', body: 'body2' });
await engine.syncTables();
rows = await engine.search('title: "abcd xyz"');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
}));
it('should return note matching body containing multiple words', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd', body: 'ho ho ho' });
const n2 = await Note.save({ title: 'efgh', body: 'foo bar' });
await engine.syncTables();
rows = await engine.search('body: "foo bar"');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n2.id);
}));
it('should return note matching title AND body', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd', body: 'ho ho ho' });
const n2 = await Note.save({ title: 'efgh', body: 'foo bar' });
await engine.syncTables();
rows = await engine.search('title: efgh body: "foo bar"');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n2.id);
rows = await engine.search('title: abcd body: "foo bar"');
expect(rows.length).toBe(0);
}));
it('should return note matching title OR body', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abcd', body: 'ho ho ho' });
const n2 = await Note.save({ title: 'efgh', body: 'foo bar' });
await engine.syncTables();
rows = await engine.search('any:1 title: abcd body: "foo bar"');
expect(rows.length).toBe(2);
expect(rows.map(r=>r.id)).toContain(n1.id);
expect(rows.map(r=>r.id)).toContain(n2.id);
rows = await engine.search('any:1 title: wxyz body: "blah blah"');
expect(rows.length).toBe(0);
}));
it('should return notes matching text', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'foo beef', body: 'dead bar' });
const n2 = await Note.save({ title: 'bar efgh', body: 'foo dog' });
const n3 = await Note.save({ title: 'foo ho', body: 'ho ho ho' });
await engine.syncTables();
// Interpretation: Match with notes containing foo in title/body and bar in title/body
// Note: This is NOT saying to match notes containing foo bar in title/body
rows = await engine.search('foo bar');
expect(rows.length).toBe(2);
expect(rows.map(r=>r.id)).toContain(n1.id);
expect(rows.map(r=>r.id)).toContain(n2.id);
rows = await engine.search('foo -bar');
expect(rows.length).toBe(1);
expect(rows.map(r=>r.id)).toContain(n3.id);
rows = await engine.search('foo efgh');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n2.id);
rows = await engine.search('zebra');
expect(rows.length).toBe(0);
}));
it('should return notes matching any negated text', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abc', body: 'def' });
const n2 = await Note.save({ title: 'def', body: 'ghi' });
const n3 = await Note.save({ title: 'ghi', body: 'jkl' });
await engine.syncTables();
rows = await engine.search('any:1 -abc -ghi');
expect(rows.length).toBe(3);
expect(rows.map(r=>r.id)).toContain(n1.id);
expect(rows.map(r=>r.id)).toContain(n2.id);
expect(rows.map(r=>r.id)).toContain(n3.id);
}));
it('should return notes matching any negated title', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abc', body: 'def' });
const n2 = await Note.save({ title: 'def', body: 'ghi' });
const n3 = await Note.save({ title: 'ghi', body: 'jkl' });
await engine.syncTables();
rows = await engine.search('any:1 -title:abc -title:ghi');
expect(rows.length).toBe(3);
expect(rows.map(r=>r.id)).toContain(n1.id);
expect(rows.map(r=>r.id)).toContain(n2.id);
expect(rows.map(r=>r.id)).toContain(n3.id);
}));
it('should return notes matching any negated body', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'abc', body: 'def' });
const n2 = await Note.save({ title: 'def', body: 'ghi' });
const n3 = await Note.save({ title: 'ghi', body: 'jkl' });
await engine.syncTables();
rows = await engine.search('any:1 -body:xyz -body:ghi');
expect(rows.length).toBe(3);
expect(rows.map(r=>r.id)).toContain(n1.id);
expect(rows.map(r=>r.id)).toContain(n2.id);
expect(rows.map(r=>r.id)).toContain(n3.id);
}));
it('should support phrase search', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'foo beef', body: 'bar dog' });
const n2 = await Note.save({ title: 'bar efgh', body: 'foo dog' });
await engine.syncTables();
rows = await engine.search('"bar dog"');
expect(rows.length).toBe(1);
expect(rows[0].id).toBe(n1.id);
}));
it('should support prefix search', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'foo beef', body: 'bar dog' });
const n2 = await Note.save({ title: 'bar efgh', body: 'foo dog' });
await engine.syncTables();
rows = await engine.search('"bar*"');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
}));
it('should support filtering by tags', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'But I would', body: 'walk 500 miles' });
const n2 = await Note.save({ title: 'And I would', body: 'walk 500 more' });
const n3 = await Note.save({ title: 'Just to be', body: 'the man who' });
const n4 = await Note.save({ title: 'walked a thousand', body: 'miles to fall' });
const n5 = await Note.save({ title: 'down at your', body: 'door' });
await Tag.setNoteTagsByTitles(n1.id, ['Da', 'da', 'lat', 'da']);
await Tag.setNoteTagsByTitles(n2.id, ['Da', 'da', 'lat', 'da']);
await engine.syncTables();
rows = await engine.search('tag:*');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('-tag:*');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n3.id);
expect(ids(rows)).toContain(n4.id);
expect(ids(rows)).toContain(n5.id);
}));
it('should support filtering by tags', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'peace talks', body: 'battle ground' });
const n2 = await Note.save({ title: 'mouse', body: 'mister' });
const n3 = await Note.save({ title: 'dresden files', body: 'harry dresden' });
await Tag.setNoteTagsByTitles(n1.id, ['tag1', 'tag2']);
await Tag.setNoteTagsByTitles(n2.id, ['tag2', 'tag3']);
await Tag.setNoteTagsByTitles(n3.id, ['tag3', 'tag4']);
await engine.syncTables();
rows = await engine.search('tag:tag2');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('tag:tag2 tag:tag3');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('any:1 tag:tag1 tag:tag2 tag:tag3');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('tag:tag2 tag:tag3 tag:tag4');
expect(rows.length).toBe(0);
rows = await engine.search('-tag:tag2');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('-tag:tag2 -tag:tag3');
expect(rows.length).toBe(0);
rows = await engine.search('-tag:tag2 -tag:tag3');
expect(rows.length).toBe(0);
rows = await engine.search('any:1 -tag:tag2 -tag:tag3');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by notebook', asyncTest(async () => {
let rows;
const folder0 = await Folder.save({ title: 'notebook0' });
const folder1 = await Folder.save({ title: 'notebook1' });
const notes0 = await createNTestNotes(5, folder0);
const notes1 = await createNTestNotes(5, folder1);
await engine.syncTables();
rows = await engine.search('notebook:notebook0');
expect(rows.length).toBe(5);
expect(ids(rows).sort()).toEqual(ids(notes0).sort());
}));
it('should support filtering by nested notebook', asyncTest(async () => {
let rows;
const folder0 = await Folder.save({ title: 'notebook0' });
const folder00 = await Folder.save({ title: 'notebook00', parent_id: folder0.id });
const folder1 = await Folder.save({ title: 'notebook1' });
const notes0 = await createNTestNotes(5, folder0);
const notes00 = await createNTestNotes(5, folder00);
const notes1 = await createNTestNotes(5, folder1);
await engine.syncTables();
rows = await engine.search('notebook:notebook0');
expect(rows.length).toBe(10);
expect(ids(rows).sort()).toEqual(ids(notes0.concat(notes00)).sort());
}));
it('should support filtering by multiple notebooks', asyncTest(async () => {
let rows;
const folder0 = await Folder.save({ title: 'notebook0' });
const folder00 = await Folder.save({ title: 'notebook00', parent_id: folder0.id });
const folder1 = await Folder.save({ title: 'notebook1' });
const folder2 = await Folder.save({ title: 'notebook2' });
const notes0 = await createNTestNotes(5, folder0);
const notes00 = await createNTestNotes(5, folder00);
const notes1 = await createNTestNotes(5, folder1);
const notes2 = await createNTestNotes(5, folder2);
await engine.syncTables();
rows = await engine.search('notebook:notebook0 notebook:notebook1');
expect(rows.length).toBe(15);
expect(ids(rows).sort()).toEqual(ids(notes0).concat(ids(notes00).concat(ids(notes1))).sort());
}));
it('should support filtering by created date', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I made this on', body: 'May 20 2020', user_created_time: Date.parse('2020-05-20') });
const n2 = await Note.save({ title: 'I made this on', body: 'May 19 2020', user_created_time: Date.parse('2020-05-19') });
const n3 = await Note.save({ title: 'I made this on', body: 'May 18 2020', user_created_time: Date.parse('2020-05-18') });
await engine.syncTables();
rows = await engine.search('created:20200520');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('created:20200519');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('-created:20200519');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by between two dates', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'January 01 2020', body: 'January 01 2020', user_created_time: Date.parse('2020-01-01') });
const n2 = await Note.save({ title: 'February 15 2020', body: 'February 15 2020', user_created_time: Date.parse('2020-02-15') });
const n3 = await Note.save({ title: 'March 25 2019', body: 'March 25 2019', user_created_time: Date.parse('2019-03-25') });
const n4 = await Note.save({ title: 'March 01 2018', body: 'March 01 2018', user_created_time: Date.parse('2018-03-01') });
await engine.syncTables();
rows = await engine.search('created:20200101 -created:20200220');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('created:201901 -created:202002');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n3.id);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('created:2018 -created:2019');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n4.id);
}));
it('should support filtering by created with smart value: day', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I made this', body: 'today', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'day'), 10) });
const n2 = await Note.save({ title: 'I made this', body: 'yesterday', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'day'), 10) });
const n3 = await Note.save({ title: 'I made this', body: 'day before yesterday', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'day'), 10) });
await engine.syncTables();
rows = await engine.search('created:day-0');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('created:day-1');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('created:day-2');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by created with smart value: week', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I made this', body: 'this week', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'week'), 10) });
const n2 = await Note.save({ title: 'I made this', body: 'the week before', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'week'), 10) });
const n3 = await Note.save({ title: 'I made this', body: 'before before week', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'week'), 10) });
await engine.syncTables();
rows = await engine.search('created:week-0');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('created:week-1');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('created:week-2');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by created with smart value: month', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I made this', body: 'this month', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'month'), 10) });
const n2 = await Note.save({ title: 'I made this', body: 'the month before', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'month'), 10) });
const n3 = await Note.save({ title: 'I made this', body: 'before before month', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'month'), 10) });
await engine.syncTables();
rows = await engine.search('created:month-0');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('created:month-1');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('created:month-2');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by created with smart value: year', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I made this', body: 'this year', user_created_time: parseInt(time.goBackInTime(Date.now(), 0, 'year'), 10) });
const n2 = await Note.save({ title: 'I made this', body: 'the year before', user_created_time: parseInt(time.goBackInTime(Date.now(), 1, 'year'), 10) });
const n3 = await Note.save({ title: 'I made this', body: 'before before year', user_created_time: parseInt(time.goBackInTime(Date.now(), 2, 'year'), 10) });
await engine.syncTables();
rows = await engine.search('created:year-0');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('created:year-1');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('created:year-2');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by updated date', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I updated this on', body: 'May 20 2020', updated_time: Date.parse('2020-05-20'), user_updated_time: Date.parse('2020-05-20') }, { autoTimestamp: false });
const n2 = await Note.save({ title: 'I updated this on', body: 'May 19 2020', updated_time: Date.parse('2020-05-19'), user_updated_time: Date.parse('2020-05-19') }, { autoTimestamp: false });
await engine.syncTables();
rows = await engine.search('updated:20200520');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('updated:20200519');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
}));
it('should support filtering by updated with smart value: day', asyncTest(async () => {
let rows;
const today = parseInt(time.goBackInTime(Date.now(), 0, 'day'), 10);
const yesterday = parseInt(time.goBackInTime(Date.now(), 1, 'day'), 10);
const dayBeforeYesterday = parseInt(time.goBackInTime(Date.now(), 2, 'day'), 10);
const n1 = await Note.save({ title: 'I made this', body: 'today', updated_time: today, user_updated_time: today }, { autoTimestamp: false });
const n11 = await Note.save({ title: 'I also made this', body: 'today', updated_time: today, user_updated_time: today }, { autoTimestamp: false });
const n2 = await Note.save({ title: 'I made this', body: 'yesterday', updated_time: yesterday, user_updated_time: yesterday }, { autoTimestamp: false });
const n3 = await Note.save({ title: 'I made this', body: 'day before yesterday', updated_time: dayBeforeYesterday ,user_updated_time: dayBeforeYesterday }, { autoTimestamp: false });
await engine.syncTables();
rows = await engine.search('updated:day-0');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n11.id);
rows = await engine.search('updated:day-1');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n11.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('updated:day-2');
expect(rows.length).toBe(4);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n11.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by type todo', asyncTest(async () => {
let rows;
const t1 = await Note.save({ title: 'This is a ', body: 'todo', is_todo: 1 });
const t2 = await Note.save({ title: 'This is another', body: 'todo but completed', is_todo: 1, todo_completed: 1590085027710 });
const t3 = await Note.save({ title: 'This is NOT a ', body: 'todo' });
await engine.syncTables();
rows = await engine.search('type:todo');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(t1.id);
expect(ids(rows)).toContain(t2.id);
rows = await engine.search('any:1 type:todo');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(t1.id);
expect(ids(rows)).toContain(t2.id);
rows = await engine.search('iscompleted:1');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(t2.id);
rows = await engine.search('iscompleted:0');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(t1.id);
}));
it('should support filtering by type note', asyncTest(async () => {
let rows;
const t1 = await Note.save({ title: 'This is a ', body: 'todo', is_todo: 1 });
const t2 = await Note.save({ title: 'This is another', body: 'todo but completed', is_todo: 1, todo_completed: 1590085027710 });
const t3 = await Note.save({ title: 'This is NOT a ', body: 'todo' });
await engine.syncTables();
rows = await engine.search('type:note');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(t3.id);
}));
it('should support filtering by latitude, longitude, altitude', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: 'I made this', body: 'this week', latitude: 12.97, longitude: 88.88, altitude: 69.96 });
const n2 = await Note.save({ title: 'I made this', body: 'the week before', latitude: 42.11, longitude: 77.77, altitude: 42.00 });
const n3 = await Note.save({ title: 'I made this', body: 'before before week', latitude: 82.01, longitude: 66.66, altitude: 13.13 });
await engine.syncTables();
rows = await engine.search('latitude:13.5');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('-latitude:40');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('latitude:13 -latitude:80');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('altitude:13.5');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('-altitude:80.12');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('longitude:70 -longitude:80');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('latitude:20 longitude:50 altitude:40');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('any:1 latitude:20 longitude:50 altitude:40');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
}));
it('should support filtering by resource MIME type', asyncTest(async () => {
let rows;
const service = new ResourceService();
// console.log(testImagePath)
const folder1 = await Folder.save({ title: 'folder1' });
let n1 = await Note.save({ title: 'I have a picture', body: 'Im awesome', parent_id: folder1.id });
const n2 = await Note.save({ title: 'Boring note 1', body: 'I just have text', parent_id: folder1.id });
const n3 = await Note.save({ title: 'Boring note 2', body: 'me too', parent_id: folder1.id });
let n4 = await Note.save({ title: 'A picture?', body: 'pfff, I have a pdf', parent_id: folder1.id });
await engine.syncTables();
// let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
n1 = await shim.attachFileToNote(n1, `${__dirname}/../tests/support/photo.jpg`);
// const resource1 = (await Resource.all())[0];
n4 = await shim.attachFileToNote(n4, `${__dirname}/../tests/support/welcome.pdf`);
await service.indexNoteResources();
rows = await engine.search('resource:image/jpeg');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('resource:image/*');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('resource:application/pdf');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n4.id);
rows = await engine.search('-resource:image/jpeg');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
expect(ids(rows)).toContain(n4.id);
rows = await engine.search('any:1 resource:application/pdf resource:image/jpeg');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n4.id);
}));
it('should ignore dashes in a word', asyncTest(async () => {
const n0 = await Note.save({ title: 'doesnotwork' });
const n1 = await Note.save({ title: 'does not work' });
const n2 = await Note.save({ title: 'does-not-work' });
const n3 = await Note.save({ title: 'does_not_work' });
await engine.syncTables();
let rows = await engine.search('does-not-work');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('does not work');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('"does not work"');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('title:does-not-work');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('doesnotwork');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n0.id);
}));
it('should support filtering by sourceurl', asyncTest(async () => {
const n0 = await Note.save({ title: 'n0', source_url: 'https://discourse.joplinapp.org' });
const n1 = await Note.save({ title: 'n1', source_url: 'https://google.com' });
const n2 = await Note.save({ title: 'n2', source_url: 'https://reddit.com' });
const n3 = await Note.save({ title: 'n3', source_url: 'https://joplinapp.org' });
await engine.syncTables();
let rows = await engine.search('sourceurl:https://joplinapp.org');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('sourceurl:https://google.com');
expect(rows.length).toBe(1);
expect(ids(rows)).toContain(n1.id);
rows = await engine.search('any:1 sourceurl:https://google.com sourceurl:https://reddit.com');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
rows = await engine.search('-sourceurl:https://google.com');
expect(rows.length).toBe(3);
expect(ids(rows)).toContain(n0.id);
expect(ids(rows)).toContain(n2.id);
expect(ids(rows)).toContain(n3.id);
rows = await engine.search('sourceurl:*joplinapp.org');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n0.id);
expect(ids(rows)).toContain(n3.id);
}));
});
@@ -10,8 +10,6 @@
// console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
// });
// jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
// describe('services_UndoRedoService', function() {
// beforeEach(async (done) => {
-2
View File
@@ -11,8 +11,6 @@ const Tag = require('lib/models/Tag');
const NoteTag = require('lib/models/NoteTag');
const { shim } = require('lib/shim');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
+1 -1
View File
@@ -4,6 +4,6 @@
"*.js",
"!test-utils.js"
],
"stopSpecOnExpectationFailure": true,
"stopSpecOnExpectationFailure": false,
"random": true
}
@@ -0,0 +1 @@
JED0100002205a1a0987e82cc400c90582492f814c23c00137c{"iv":"4qbO48bUQNxEbyYaMlBjhA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"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 truncated
@@ -0,0 +1 @@
JED0100002205a1a0987e82cc400c90582492f814c23c00137c{"iv":"MXV+gJwY5jpGvZjFBN0asg==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"jENfbwpckHR6HR+DXKjrREBjbiFZwN2msNqabC4ecwq82bOAEU8V7sDSM8uoasnozoDyexqHCkxaFBixeyXSOKj6HvrHddv4jD/QAVXKY9aqwmSzoHiG7KHl0U4MONUCfsGSi3JRhreAjeeJx0FEKS+9w6tNV40JH+tAL9faomftCOu+bkHG9lzZ8Ccj8UIJUNgtoFNqQWsmQcRciquVLJ+yAjiEH0l0+ccp2b4a12ZsML90woiXM0YEptn+5eSiL8wqPVzm77MSDq+GffA41LotYMzGRD51Cwbdz/o0ZeJZUG0jzNZ4JLGXGEN2QYFeXyPevRz/dG4qgjytoNaejRXAxEbr1/CUPg4YvyjuZm86Dt3flWeHEJNZpc8UuQkyc1hGliD8WDujSfC8cAqLDaB4VQuHM/c7NxVxycProcQQA4wy6z0zy2rOxTmpOFSamYKpdoXLAg+qoWvxYZuJ8BnXJDezQhjslR7+AvhrAlisWEZbXBLo+D490ECEFapEGs41RacOVzlnjQHH6qZg1QY/316S3xyKcMepSSWX3/UUXvcXkrqAJkUoeupkhakcTx6or5hgTkihCMuLfkfY5lc+dM8c9GR6PDXcUgu7LCa2f/DwbQIAfhNgzaR6fKO3XYYJo5cIFbSCMXa7v/Hr3wxi/SnRaizAfpbOIC8rXoVjn2RmkxpZEI2WvH5FsXGepW/++mxL4zuRx62YlIe05CbbN8mD2TaiNvR6eL4Mxovtmu7wxoMrBHRR27+UziOZX/cFr0rHeWsf6vHun8asu1bG0itnGZRa2ByuHUHr3druCDIGeEBJJkV/jAqwv8rOFonCmZKo6IP1/vFMsLDKUOsEArUN7ZOQEAIgsURLWCPGpwSySKD35iPMeoT3PriSuluV/GHb6L4E6TnwpX6wMPYXyFbB0WY9HbHvlhHaaHmihpujm8rYrudFDJJuoEG5lT2B2Ja9xcp/+Ec4y6HDV2zZ8yFgOSjLxZF58vcKrK+F8AE1quF1h2sO20YqX0RDJ5qjpdKOCXh7utIykYFgTvVmgkZFHsYQcWeDwi/WJfz0ok5QEgKcWlFF+5O5dZIafB7DVBxB+N7eH0pMYiD9QNF6QaONm3+dN9Q7ZVud6CfhkPWIEbARzpcMfxUZ8h5WeFX4CVQHy2LvqM+aN9LlV+zDwIHtlBL8CW7Y3hBxfhnI17zPRfcWg07O5Qu7vJuRXfab5VGotK9CZ4mz4PJaHDvBXKGgKb+D8c4LSUjgtxB5pgtV6OlDJ5QqHq3Sa/gOScsujMEEzvzPOpXswYLbYsxGCsOkvLn3OpI1sqpTiBsEFy4Xv3aF6aoWUcWwevItPqrtdrMf+cvutPJSiWO3dOgWf4XaQHJath6KOC5rPWIJCFwYZFbNplxQfuHLJQ6VYGHT3zNNzbNK57LUqnfowCRZDe7tRsgqO5/BBoSIdcd7s4zhO+W/s8xcJz7CvGF1zPUWdv7RgyQlNpzUbOjzQxMC67xe4oCpSqfmp9dt2UXWHJqKwo0V4/dgYqZoXQX2Wsfpx9LooG4Achi9xdcjkLhCvYAyvw5pSWwtqziAfV3eW/khPGnVDU/5jET6MlJ0EM3tnI2MAbX0heJ31eIaBn29PBayRImXM7ApxXuU/o2l9EZLQyFehBpDrhvAZpB05qWyW6Ll+01b2ivK96gk4A846CoHmvdP0I4PuR9gjikeqk94K34ngH9eMVH8lXxLfcD4BeEZyUuMLBeCw8hqC5dOCaTno3J0b4t+Fs1Y8Wa9tEr7yCApr/rA5hYjQyfJAxd+vSDrpf/2sAFxNqC+99o33Uf9iB0h3gFTK3g9vc/+6i3FE6Y2gZnMkjHFJBSN0JCeJ30EE5fY0Mc0nyFs8eChoHo0zEMtELKAdZigUmk2lrXrK/AqiM7efFsq3puNupY2SZ+02VbAPXNI6UcOv3rMzD3vYXuyUSv/SUmCYwtRnhhCryHnJcxd++NeiGro28/dzveCyrxCrbxL/cd3xUbY0i8+SMZeaLfWvDv8HZuNgcTuYB2kN38UGId6BYKcKVbfK2mLihLEEV6MvqbdwuhItBsCOaOY4P94GIz3Ne3eg/FzNJz0k4E+srvYnOb7tKvXkfT3DhtAU25Ma8ms88Nak1uHO45cMxQxdFQW9tZ5RzxRrjG90iF0OGkxkBeSFuPPryLA76W+vOg98xf98Ve8y52sKMR4biMmYxIfg3YGv/kgSlDcHtXq0rWyQW3QQCthUPD10SNJm9UBZKmpkpBeyuT0940CW4A/EMSyCSjIyQxqc3M5pB9v1LKwpQXdT8craVFyb/JLrMn6eEPXEGbc35i8JDi9dV9GgOWIwbmUi2PS4i4AeWpMytgW5ln5tL/U8Od0Fpe3pc5VTpkhdpKF+hinZMYo+FZl8d76eQ8ZHPLW7GwViEtQpOQe1wjPH+6i9YguvzV89NxHwLeotHxg7C064/k4LTBGNwrVUVcw0vq1saRJwyJuhLIWqNpVZb7PYruGwlkfZRIRXOYapx/CTIvv8iy6D7LGSK+0ctoAgKrO094ae0LzaZfvdZ9rCeF8cl4HHNDDHwqtBxMtWjbRCdw1CwL5wlDhNamXGbTpWLDFchRcBS4OhiTGAWkhmsxbs3PvOHOxognMn1QdyOoe4Pa3TgTVSPpKGWiVxtaPeZ1u2tLgHxOWS3DH+S01LNZiGKA/0PjjqxWhbOynqG32uLJzrYem4aq+EdvDey5FeMtGbMcGKn+zdtRKWfKQocVV4J2p1/z1GuqE50hG2zcbDoxxy7wGXzRYpqRXnVY0r07ROm1QrJWfSOUjnym6U1P9dDQ14N/OHY62vqg5Bq4UYraHRIoabDoz+Gp/fQZo4af6QYhf03g3al8xdTrObjKi5wMB6zRGlf5FYop9fi6nECL3CMp9la38Ovbg/O8Kx/5yVaBzGSK0JtgrpJD+1gThL041rXH6e2tUnvzHk65Bi6fbeL1XfE6kTrz3VYPL0+F3I+oL0IMCdosS9vDExGiMiOQjhVuTz28LpjA0ZrVAwCexwpFMkLd9+78hfB/M68GzZM/UfhvmBFvHK05hQ/1ooirsKtydt780/Iiyun1BEnQQb4/903sNLCVMciR/Hg6hBybO1gTZjMYy/i3/UI3iMzhIymcf2iRvWFbNeeVEH+t5LQLhTN3LdRwvuqzHSB5YNekzE9cYE//RbUtx/tnCKD6i1wQmZV0dHigetD3a3PJLPVS/86x9EKenvOj4ibl6kXCLvnbw2h7FdpSdPtp0XbL7nJwkPveyeIgjPrDC8NoTPO7XhJOFypHiX7pN2jKV7U5vnp7dW9Py9GMUTticrlUjZFvp3YY1SaphK0NnATf0HMim0emYxq1xfVpsWaO+q4a5BwlSb3vtCIrxPI7uHW4rG2O258oiAUrrCos7Mp6U64+bNvQ0iqxGIbE9qErGyPHO0fLcoPhc6Ym1uz1aVBaxInKOGFGy3xceYu5Mfc9KMAUxaxXqgGYrm/sfEt7PBQE7SVlr3wEZ0NHEhdx1L5fmLYsET+dBEfQwH6xGR66W7HMZxUziAXIJ/oi2KKuCL29GpmIjzmzaGmPqFp+LU4A05GT2RvN5fvmWO8JPqeh8ePcbKECGgWC3E+8/UHcpO0Cg2qtjufsCFwD1S/jv+M3JYemEjtcmaLbUraFFkn/j9u8+V4gP9rrOTOCLoYokLaFvdBDINY3ruBJR7h2SxKtwA4KEi68bCibPpfUN9gEKVLrZDCSjokOZahTxSnjGhUqzu3KKptSrSb9+JBBVkVPcUquWlwk292rKH8TY2hH7HiGiwUSqDFMpnGxCAGhiKlV36zP8nLbBZX3NZpucKzeMwi6ABRu9s8lZFaJrtwrGwFQe4NRyKxOd7j3VMRdUPSmjSoJcPIRu+mc8eAR0eqCdnYKwkzQeQcN1fH5UNJ80PMSf2EWZwHzyt2x58mHS0V8YwlfNeJjYqLW7cC+d4sJNiCwREmWLeFwuWGQ3418XRthGjchaeaNSBl/anT3dqZoItJAW07B/m8A0gfBZVQZOsYDWQG8XG4AqcYJzDtqu7UYwiVVg62dYppo4xO/ZMidxWejRiFHgjOtVzfkrVNB0i+q30oCSVOVeOBaVZChI57AulpD793OV8f+H1Ozd/VkGoI4hFHghS+5BwaE2itxmeYce/2d+Vc4kLSe7WIjrC4Nbt6os7VvXAcF+DyB2vZet+yYE7sCnaVb4e8WO1bqHUtBMiHZ7W0P2A9kTNBuJ2M0bO+N/616LEFgu4DjyLvBWnedbFIHgFExfHO9l4USlJKpvKpd4DQNw8oN9HYU2SJtksCNvEZRuCUBhxvNwApuLr/dkkSqaDW5ZnXzWi9yQxS83KQkl7SiZ6dXRlBzxLUDo+PpmkD3Rtnvatch10crweOUHdnLWpQUV6GgnorD5j2XQ63J2npwAVxkPjNrYVJeu1mmSe1wPCWB27B5+fLX7ZLwvpC+eD7+6jkyb8eLgwsZeGMoZgBMu9KMOcMXD8WZ9MHz6rFrXUi6E2g9V9Xd8r8ehOC7aJFnmi1p/J7w+HaL3ngFBWW9HH7vj+M9hUVToM0tbLdY1jGj64/flZU4vpmvKWbzVpmIrAg5TKowBc8lyxP2ze9dkixZppos7QVKR8KpZE4SxoHF4XZvWDbbP2yZbZH6HDPGFb8E9+E83KlwYUK7LIKo3brVbJ++DIm6Kwys12dpDZFiX9HYXXnZcQaHxJRvL1lkxV1P4sucy5bBACvIFD71x06TQmi1ZVYc66IRpMKZM/eSY8CYjJEobbtklTgCE6ccKQtRPD+WA8q2klXLine truncated
@@ -0,0 +1 @@
2020-07-16: In the new sync format, the version number is stored in /info.json. However, for backward compatibility, we need to keep the old version.txt file here, otherwise old clients will automatically recreate it, and assume a sync target version 1. So we keep it here but set its value to "2", so that old clients know that they need to be upgraded. This directory can be removed after a year or so, once we are confident that all clients have been upgraded to recent versions.
@@ -0,0 +1,24 @@
id: 04c4e932fe3c4c4a9450c09208bd6c21
parent_id: 3d675395b5cd4d1e9d7ca4f045f41493
created_time:
updated_time: 2020-07-25T10:55:20.793Z
is_conflict:
latitude:
longitude:
altitude:
author:
source_url:
is_todo:
todo_due:
todo_completed:
source:
source_application:
application_data:
order:
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000470{"iv":"gJXa88pt5ZzaYAlD4ZCSkA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"8jM7A4Kx9RfLOHz/uriiE3r5zz6AwnUNVaHFiiF3Sl5La7spsW0hAzoAJuUHncbIE9Rks6RH5g+k7Y4J5zxlpHlsbXYFzvYyh706kemlwg/k/lDPkMK47BPPYICcCCOeUvc+IXAjgcr52RX29yuBYhqQDyJVhFgkaEmbYYmYXbomZrzOWeXF4O0nqVFt7sawFUg3Vu0+gESRK5SV24OL6l9Qw6rRh+kwCO0E8piKq31CDrx3741E3X2LOR2jw6i+EIs/pyPMRDPWg8o3YOF5Ka1u4NkMXM14pSc/VYFiJoR2kDh4alXKIaJaJeIRjQ/rWNz42s3eT/GpoIRxPKwfG5DwD8xT5Ns5HJ0aD2E3es++BjOSeeqi8MkeCdyHLtgIQB9FaILLMtzGu6lUwP6VlxJ7HJhvtA1T+tfKMlTrzNYxJCcKGg2IUU+Qv+8LnSjybIia5weKj58emRNAXyVAWT6CQupoe1c3XxM6hPInggOZkghQOV4rpMuKniZ3DMIuddgFpdXtD8q4pemQkXMQfRtUafyqz69zH9Bkn4RS7yEROfRfqi5TrAUTGnTyPnyAQdyORLHCNBJBheHMa+s37LSe0ZBYmGxhnl1VMWjvUatIZphF7EUlseFC3jzxMv5fYsi8BbmpyLoqH56Odogc6e+ToPuBvRuVdIBHXfT4k5B62+KkWAmedBkjwJIgp6EWaaTGmVwwBL/xWgkKL87qsMmMn+Obj/e/3cDBQm0ubg8gvsWiGsA0cL0jBM3QKMgTb1D123zG2s8DWdIoSSByjwW4sVkb9hH6v3RCPoOyxV1JubcuDsbGnnrL9hhmx/5ahHnoHhUD2vYVLsI5xRmktEUpBbf9BcMY6RGhUXWM40djLLQuTAbYFF49o9X233BlbQrQndEnF6mhy3vLSotJdeGc8PWbSenicXaqJm/2U2beCAeQ5TgcDg5hlru1JitD2TIvJuv5e7Z1UFXSc/P2TEFsIP549bCt7gDpJl8="}
encryption_applied: 1
markup_language:
is_shared:
type_: 1
@@ -0,0 +1,24 @@
id: 26b9c0dc3ff146ed99031e259bc1240b
parent_id: 3d675395b5cd4d1e9d7ca4f045f41493
created_time:
updated_time: 2020-07-25T10:55:20.807Z
is_conflict:
latitude:
longitude:
altitude:
author:
source_url:
is_todo:
todo_due:
todo_completed:
source:
source_application:
application_data:
order:
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000470{"iv":"4LKOBhs4VTm2kEFjWsE66g==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"T4CSUbg3Ma11ffHKa8t6Zfu42DYcKGv6+p87c/V4wXsnHEaLFk2vV9iZg0CDOpAfOZaOCBrWSUsUI3FYIWjIEg8Wb2GsIrYz5i03B68yAjjN4hDtulyuLAlFzQUWw6xyPwaCebHUZHekearKFL/sQl2aNClxsKz/uT1/g27ANiilywFxp+GwQ/MxI0YhR7Qdr7Ri6YbySOSVExInQqMY2ERsWhgLQpR0NFSZLVIi3wJmlG4PR4uAxJgHu29Z2tvaMjW/lKUbCaqt5vsbP4qw1BqgNjt8it0qLkf9TwW3y4sxMCXI5zo5cdlwbMEcuDxvU2zfDBfWHgnIIekSR3csOTBswpQ/csXIKyZomlP+7ys7liGtoDssCrbH8YBwGW1KkzBmaKYhP9Q1JOWC8/HWI/YBkSWX0AQIhkwUjPVY+WgzfKUFD4XpUaKm9xmSCfj67GqSFUlB0MhTer1aFj4L3T/YOSZQevaUwmcnJzRpAmArhEAnHlrm4DO5AqubrAd88hPGWiCo5Ud9PYsLBeDiRJaQroA0VCOuzGLTyq5p/YfPedNBI08cN5cLuKc8bx5W4HBp6ZYv6oDkkbR9eZQyY2qbvkZoOQDlG9QKrenvIprrtRgFCO9NUfEYzxApSpyzVRfBv4LFxRTlpjihaGxKcA77U7xfqszFxJNyQj49hK8/SWR+/m4TOmqMRxwWm638t/NYz1MhLU2Vee9tA847ITVbZCgpxKRCVSJytYuHi8gshrTom/zjJPMJxoZnQpoWRB9nEbd2s+Tqev7U5lLPiMwjBzwNNnRLKkI2EFRxIWhOxSUwZuxGSIzhb8dAC1T6/ngwvtFTmYZxx3/XYZnsZO47IeYUbHb9SPbUSUXoQeU9rNDekcWusfrbod9Y7ON0aHoiytX54xLFOTUXyFLY7OeGM0C6QeumvOLTJ/WLHj6ReBB0vVipJULfNW5r4GEhD3Cf8uMf99mqXIYOFpBJj/WQesO72R9PjYrjk7c="}
encryption_applied: 1
markup_language:
is_shared:
type_: 1
@@ -0,0 +1,24 @@
id: 3a8eaf72f62847689176a952a0b321a0
parent_id: aa7dca873bdc47beaa9465e04610619d
created_time:
updated_time: 2020-07-25T10:55:20.782Z
is_conflict:
latitude:
longitude:
altitude:
author:
source_url:
is_todo:
todo_due:
todo_completed:
source:
source_application:
application_data:
order:
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0004c8{"iv":"iOtmBH1egyWEQ4howS5ssQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"BbJdyFskBpsOLSHMogZbAF4GgPJsY07EwoQjmiZl2WDT6QezrTnLQ4BsYjMLiLHphtjs3MqxK2p11cfQLPY4fAoVywQeTLFjMyncWsnAFZaDnCvZ/VCwbI3yTi/XOVgQ342AAIZFQ47tAkNFmH6CEgLK+g9BW2fcBxHcnky70XfyE7FI3HNl/d0qGIBWViv6LJKl5F4ywbky75/Myb029g3u+TQvmxZXDWjbDewWFdvIj82KdR12XSf+oQJQpeuwR2yHioX8qjTC4/3bLeQ9CumCRHrrSpxTqjCcvMH3w3+OnfMnmT83MxQa+mQJzMhZCaFbeEw5Mvbs1lgcyhsCC6yWDmgTFwhfJfz6KqZxtmkqiLt+E26ej8nd344DaEJ2GSHrbpkgIgkzYJndtiFGWnHnMoKKKsgAyOcTB85mQRvwZqsx+StKGkdNkqfbSQhtspi3XWr6TchjJgE2sqchWMZqyk6HSGVNVROptJZELPyEBDqG+vq7JrHPEagUCGUYBHEL2ZfKIy6ecU31Q7Hmpgpqyct/iRKr8mKrCLPbrUGiL27Yov7Mn8+MDwnpyGSw8tGYG29LCOADAkbSHl8dtYsF7dQl0VULipdwVv5KRO0ZFzacIu5/ciw8mx79wfZSVcKy06YYvSsGOYuyvRxXarftPBc9abVhSL7OLfIxwaNJH5GBus97fEcaWPTMTIoZuj7qRWsR5vjG7DQ1PWlWh/5+zNQ7mP5SANJCIjTEu5138hGhOc54sMrP7HPRfzEVIgiX6fOQ9lyJGwn65nn7SRrlsIiVLeT4Hna2y8mcXRw6p+J1WiqZyn5Sq8MyhBx8Q/Uc3IMVwZd7auOnL2BLr2SG+9z0YHUr5xPR9WADQiPw/d6GoXs9Jo0mzqAhFjX6WKjMo0RYeQVCAOnIvm7XTz65pmwVIxqI++nN3RB3e8HQCnS/ERBiSuBb5cEylsP1NV0Mzp7JSJKrPiRF6TJED4pJ+HHScRV4Sg0hrrZiF/ZkxJ+VGEcgCr8IT/QrMesHjXPaZ5f3ov8Amz57Eq7WLaNP+4lOxzyy8AjYaPdBlswx47PN412Yna7a0qG11l4="}
encryption_applied: 1
markup_language:
is_shared:
type_: 1
@@ -0,0 +1,10 @@
id: 3d675395b5cd4d1e9d7ca4f045f41493
created_time:
updated_time: 2020-07-25T10:55:20.632Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000280{"iv":"bbWuqdBgF3f4nSVt1RXDZA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"2nlg8ecuDKCTHuAVnU7JqpJ774cjEb5OC+HVImvBQe1sVBxK4Cypf31LLXYX7Kz9T+QZkB7EQSnH42+zMlYWLJgQzqLcd4OglSjwnfdFf4puH5xIk6mlYhCNu+H9nvYYa4FQJTXXqAi0Bp2fj7ta0uMePGxKUOZVB/tZ3YPS+rFo+4/C82SoTKY5JDPjEgtrF7ZaokRV51K/LMufT3ApZKcKp4TnK3lm0TCGcUqebONdk0jtbcDQnCy/HmM8P4UZGx/Hcw4dDcrwwSrtvJQrp0sIQ0XwMQwr2VLeK40rS4TAEQ/SeodQcN4K6kkSOS6IEDxps0s/742HZJUDfIfJ95zvBySqsIFXEvMGepwIEB35Vkt9uSzJDoCeAPd1tzI3EE1bEtJ66AuAGv3bZ6EbBiH/PszED1gMxEvmHscoOvZRzHn73SkhPLO/4n1VJhjNJgj0E9KdlLl0k2Mla9E6veIGDhgqdIjWcC+Z+g0ctpRoILRi8zi6nnSH"}
encryption_applied: 1
parent_id:
is_shared:
type_: 2
@@ -0,0 +1,24 @@
id: 49e1777d9c17439fb612cce85700d16d
parent_id: 673415563b2f4db2aae8665ddf9fbc67
created_time:
updated_time: 2020-07-25T10:55:20.924Z
is_conflict:
latitude:
longitude:
altitude:
author:
source_url:
is_todo:
todo_due:
todo_completed:
source:
source_application:
application_data:
order:
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0004c8{"iv":"OWv0KZHC+nkbL4+rXmhV4Q==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"L0eNB9OR+DkJ5qatAYN+UlY8+GVQBZJoV438yV76JqGmBibDZEiJmVdhgv9A8pwWCfN+vUP8Kd2DIPCtSgn3d0Hs5ZTJmK78TqkiF7ax/gE7Ri4Ys8ri58Ct2gZ9LTaK+EybJypqjtHrO87dHxyLrROcONuI+m+5JpBoPFkyDor7U1pcM1P0C4VvYhR5zxIRiDEuWL/lVinoogC4OulkhkWBOJl0OCF28WeKGAwGnjV/S0EjF7FDlSXWytHuMAIfk7UkVmlSAFDOwxabVBjuVYmpxxLCTHR/okaJRiZob+1ykZfT+Tvv2Y27zC8Kffarg1t5ZZhcA7vJbl8p5gLhEM7XJtgwaQ27jr8cJVdO8UsjjVoQQf1U/E2mZSS5KPFzma6w0vG33RvoZ37jzPq50v+jCPivt+zLda5mwV0s6E1Lf+M2eWVaOpIpxHiHYGUdbZRPGB7+ZbCmcXbGLezpsIIb/+AYbnE2Tj5Ygj66OBLttJXbaJpvni5IrXaeEkXrPT4ouIMkB1nUeSvEGG6tXCkQv2k2bSddFUQEkRXl22J8U7gMlbdvbh0G8W7bRvLNzME/vtxN+A4IlY0QW20Q4bEmX41B9z9SDdkrjNCydmejN8LxHCTWSl9jHtxD5NSzaRXgtDrOoQgdPQ0zMgXbe81Tsw3Dgf7DIGa7/+kTn5NBi+iqV9l0y9diRRJ9BtHChM9jdwcG2yk14iuSflX8MR5ALDd//USiodRZhHsuDWfDSS1/a7OzJhZCDGXICAnNmrWUwA/JfuXHEiGTsZ/Hq04vfDNHyX/ywM+g7uCjjyPiU2EtuiMwhVORwms7fwFeolQvu8JvG41LWttWo5UJBCEo9DZu1Ikzutv8Qa9xuMKTFCFnIXFtiMruMEGw8paANubodB1RnI2q5AHEHGO3HIo90DFDdcrpiWHv6XOblKhlXe4Q8nS7wMxcHK/P4AWsgRQc/UnkzRsk86HOl0pS09d/ZJ6VR7wOFC4/xb+YLcZgvAlS81Grs3SsXzwlPAH1z59qUxGkzAwO6BlbmmassiqArHetYJC1jje5dQBNfWUFDmGkCKblXgfkm47Oub4="}
encryption_applied: 1
markup_language:
is_shared:
type_: 1
@@ -0,0 +1,10 @@
id: 51799b7f7fda4bff84954305f707ca72
created_time:
updated_time: 2020-07-25T10:55:20.634Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0002b0{"iv":"VMKkOgt/Zhpw0WSe4YNy8Q==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"j0+JoccW00HDUH0jgeLYIhj6VTYbSYgoSncwpFVBPK+yRXwpefjfL9KKmCBNBNibOd2TvJRh1Tk20BRwr2U3kZK31Ytwnpeq7dMuYZX/rkSpJRZ5QvJ2lwJbPCT5kg1Cv/5kE4zD7bhWD7k8u4cWKTdrz69tsqWWBj6lCXD92ns4Iq85btKvGBT/ExvXFsvOjG1rKKzWua1WDkMXKyS0yRutHZ7GrlgJve2QMvoh28OMCPFyNb7Z4/gzCZjMe44MfCCRIIEyyi1332DSSyXMsgGZuyUwQKNDC3+exlK0sfA4pKZhkT9uBNp4jRGehjxmwq+a/olX4H2qdKkpAjR1UznxXCCyRfew6arokr1K8ZT1TC2DJzM8jc7wrt7hX8FTCY57YO9+jJuhdlVVY4S6YkwfFwRTvC8NQSuBWrydU5IwvNABICr4o1IAB/gfA0CTVI3Ua4DcETh1m/7ptUtTM7vDQJ4+Xb+KzhOm1c7mYCothLPxRU/A/CjoGAwzAIFNM92YCi5UPl/aP9P836trbm52XGggtrb8Ofe9uOA="}
encryption_applied: 1
parent_id: 3d675395b5cd4d1e9d7ca4f045f41493
is_shared:
type_: 2
@@ -0,0 +1,14 @@
id: 5fcd3813d8ec4fb29d2a9d08da81a0a0
mime:
filename:
created_time:
updated_time: 2020-07-25T10:55:20.921Z
user_created_time:
user_updated_time:
file_extension:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000308{"iv":"3Rz70DM1fx/t+x96IgfbVA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"O0o0M5yISa/BJT40Wddy9F2HD00jgI4z8w0tY/BsuDQQHLZ/bJJMJg3qZvXkcWEHrBr/ki2TNOSvV4xCWG3Q2IbbLQjp2lpoBGRULtkzGqKrCtQL/oJhlbYACv3Au2b0nhgozeW6Uj+24aT6a9lTnDlvOvhLbmKP3wjNbMqzYot6Df6Fz/s4ZgUbBqYlTJBkvfu9d2eH0NPPfHc6ZzUMRjk9tOPCQjKnl2f7lfg46YKDqM5VbQR3oE68jlFm5ufmeVVgAE2CZAkv2b1dE/ouh2EKYalVBkrPjOmQuw0MEW4sam0wDy6IlxINxY42ldYL0ASDN53hIzLK3UcL6igz1QhnWTtY7fSHp2OROvVD23Yq7XNORXjWq9C5+99Tp0hryunUZEwTAP60IpfECbi16RyOcs+u07B+uJ1VdJ2uWJ6261XSTTONU5M/jrQA6e9RQoyVsmuE2CmNzv/X1esDVu0JhX92MgadQ0529WN51vqKQ0toR7FMazKZXDFLLh6iWrwz8yPZCRLJOgFF1wnojk86CaZ0Z5bvZDdGAdgJC+ttYWqQI1TPd8CYdpO0K5Sjcb/mmRdmO3hThJ0z4qj+FBrwzCceNgeJPbm/yWhp4h2LLQGvYW0PlxV53neDB5pw"}
encryption_applied: 1
encryption_blob_encrypted:
size:
is_shared:
type_: 4
@@ -0,0 +1,10 @@
id: 673415563b2f4db2aae8665ddf9fbc67
created_time:
updated_time: 2020-07-25T10:55:20.815Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000280{"iv":"kCJkRvCMeK+RKb41oOeAtA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"7Fzu0jh8AejqVH/SjqFIRZGBGA4pMMc0l6v6vJWvN+YXe2FesG+xlpmIeq1EB52h5F/WumQnvEaJWSISK2TA/VygkkUy0xqLlwNsLQmdfvMapJDnT5XXprwqdMgMg9LZ0isjpi8pqvYBSUk+Bzbm2RvuCosUxNEv8deJalYoDNyBajjhZIYSyAeH+2yMs5tQv/AqgEuUy3AOQIr6fvk1DlKcSt4FjIFnw+jFudT6djIrUHlnq2LnKT9zxYVfh1XBLXHCQna0df3aGlMG37DUzlxHX2VNt2Gp9CXqGY2PXuVAA6DcBtAMjy7BKh+nESGhWPbLSfXk53EOfmm5Iis9drqZRoCl4rjZoioehQv7kSpUMPq4712yKTIFYnlOIPzlCBPdlOnWfdcGcwq3qitbtOmZNW4qvLWTlwkRo0H1W7DmfEASUFheqIWCqBJuJ6Wk2ONmFPtGPWutIabYUVsZKAQpK6qYd3arR5t3plq/jJfMFrrG/7edKFEM"}
encryption_applied: 1
parent_id:
is_shared:
type_: 2
@@ -0,0 +1,11 @@
id: 6d05a0cf086043129720b5da35210efd
note_id: 26b9c0dc3ff146ed99031e259bc1240b
tag_id: 8a17074d4ec24de7b5a1aa666f7d8b38
created_time:
updated_time: 2020-07-25T10:55:20.809Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0002d8{"iv":"2Vou/WCQoWwTOzfTBzaPTQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"AZ9IJMctvBFEfDaN7rIcdZGHBvquxEx0W9l0VstK3Gmwv98eiUhajXAQD3MsfCM7JyZPjh17RdDIcqmGxDyY7GMJflFOTRYXaOyOE+e/nT/pvbo9TCegkYO7HWq108I7Ndt4VOLmbe99O/glsE8J5G93qaiqR15fL8sET346T+kVkuBEoWBoxYZ0W5eNSdtU3TEl9ieOsmJ3vx+xt+85+6HGsrB/HXLaEi1rHKklUUp8EDUavh3BiAunHr315oqedJbdbV8VlwehJvtYaZvyivvshsVuiJLA6QRgKHwbM8DFeVNdDiq5E5LNBsoNnAQzskqwSjt3E7ONkQMv65hRaTFE/DHisJ+IC261J/0wYXCWOKxF+8ybTKPc3XqRzuMZZkEAgOmW48OBVsjU72pNpx2e53HwiLABRlQp2a/0MVfZrI+dVjGLP0Azgifj0IJJeAhDqJN6nmZHTxhlkv9kDH4e3508qKpX/+EB0EHVo06nO/0kB+hbR5FyyQhedRbOMclfkbsiNRZfjg1vpYEtHPF5u4pqyT+atHDOh3q8Hdku9uzL9QGa0KUxylZG98B218sVOgUfQeqVKA=="}
encryption_applied: 1
is_shared:
type_: 6
@@ -0,0 +1,24 @@
id: 7ac37541e8404239babdaf1d3fa39c90
parent_id: aa7dca873bdc47beaa9465e04610619d
created_time:
updated_time: 2020-07-25T10:55:20.792Z
is_conflict:
latitude:
longitude:
altitude:
author:
source_url:
is_todo:
todo_due:
todo_completed:
source:
source_application:
application_data:
order:
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000470{"iv":"bhbCBStJc0FV0QfurbZZRw==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"vYNW1c21bdKGNLhcpPlu/WwRPGZC3kfn2ZJLJj0Q5jxsLw+ZFCBvZlYMfgW5KP14PjhmQZhaCrzfJn35uLaO7E3OZUnH5jZnHyGpcL7GE78hml+odtVJrPsjTltMLKx3TXtcza3WPQjGPJyL/ekU+YPI5+tt/DcgHofzDJ8K8vvHyf7/OQ4oXkK5K2AnL0mzLNqaY2swuZsuHASGX9KBKAQ6EzJqLvd2Fm459im8kTY3v1oaLKWfchQHYDPkvXVjzXCgAsRrjUY1b4MdcBODoWk8Hs7AxqLGdbDcYMjs4T3tUjJ+8g+osZU1hMYEl9B180RBt3/ZEn03SrvZsGn7kQ5ILYes8Og0AZ4R0WVmwABOObTYy+qgg/2dSuveyDs5hsBVcVYnQOAC8lYEodhOu1XZsEEJxh+h3fvzSN8grd5fL5F7BbZ50Elz0Ba3YJFh2yWWLT5AR7yxsj+CSxoCZAiAEm97KgeeA9exY89o9EuLt7bq+VuGFNPRppldHCF5si/u9CmR6Dv50QVRmMdNU3AjyVRNESXQ/BxhwGluAOKd0dV65hjf96TufN+BhVKOrDl/ztTkt57buNPx+8rWNOUodJSu2zJIvajfPWCSbWAMCGinjD5uy95SsNuyiUZulS1NadR+eKVA9oZURzcf1ZHDIJ2oyWhoNW5dmhtZyQ5vmT7IrmE/PQRKmTwEi/2rYF+EI8NFxWdZ/e20le9UM67LwWfkzqDY2I9ZJC1Xxu8gG/FeEkP7qADhEF/QR7u+WvIfCISksTl4XjyA+pfhsRgU14D9vqRmkaMgrU0SOqDusQdC932Htzr9Aun8eLjATBsZJ57zqEcaiaXVl5aZb7yic5iVXi9KYis84ijFr0vP0QfNpxIjBigvDGWO/xTcGme8pkoMyjXCuOhs6uc/7KoBhO4V+ZrsvDAF0QQVq6l0tl//cUmCPkSlHhcVIT3fDm7Ry4GuNy6gik6q0xhNMr8d98Q4rqxajgn/bBo="}
encryption_applied: 1
markup_language:
is_shared:
type_: 1
@@ -0,0 +1,10 @@
id: 8a17074d4ec24de7b5a1aa666f7d8b38
created_time:
updated_time: 2020-07-25T10:55:20.801Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c00027c{"iv":"+bwkkcvF8+iRUp42297fAQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"GuuoQYblFgQ9OlYr+A4UVDNSjq6k/sFgE9kW6VNbee4LFSYFmxLtkRFgmoCJe97yopB/JLDPVyn+InOpEAQvRyGFbgku/yElAy1v/PEL3VKST3QezhpNHB+qgvUUPIqkkFtmo67CtvUsSD20txIPPbm+y2dabjaoY+Xi0TE4XYAMUjCDR2vozWrgtMylETyV7xzwWw991YM/HufWlktjsNKiRQvanEhda5z4xaIIk42zhqnqHfnhwDAvy1eTrfIy3giTgB0bPNYxY0ZvlFARfNuHF1owzKpBB38lHGvW77CK1elBsDdkzCKD4stm5LOlUHuQ6w6vPLuGbpcSJJZk2vaeer6ydoG8BPG+xUQofesL5QXpAx1CWqXsH+o7j0NBHclR4lic2JiVMHBhKIRmHvM+a1EzqlT/YL46Bh9P6dCbPqLCKrBJxOJwozj8DfCVG/xMLrCn11vA09lq395gQlH1qJ1c6XQ6vexC/iuDB7US8KarYpns"}
encryption_applied: 1
is_shared:
parent_id:
type_: 5
@@ -0,0 +1,10 @@
id: 8d4de93d82e2468baa8e539d10d67510
created_time:
updated_time: 2020-07-25T10:55:20.813Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000280{"iv":"oope5TviAxbb3opHGHqzWw==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"UML4deeatwKX3QkbH57smGVgvQxzXk/My6rkupu9AhP4W4g7J6iFKs30Xbt9sLdrBaHsshI4d7Tp44YA/5fASSHosHr2DjSLH9AZnQ35eCGKjHaXKMXPWAdQwSfCM6mB6z2BXFA2GU0Ign0GfJvSwTNZo4WIG7s+pjPG8puaONlvJ0E6dPkZJoEM8IGrQku0N6yvnQu82HlVNWUJs/bi4Rd7leegPDYoMowC31xmF0uK09afBlPd2UixL+4qnq9bLiIaF4UKYlE+Cd0G+1tTjPklebRxhfdkDxdJVOImrjWdba8h2gRZak3Gm+i6ZLM5H6doZyWUBs/EpTOU2TOh80+Gmd4DdOdJ6yVASND5hsY5QbjpGJ/6M7RoastJPB9HSM+5mSDU51oSeqIEjrcgvr2j0XB5IGc8zY6Y1Ebgg5VAV2Ds7p1Avak3Jb+R3bF9oA2ZyrrxK+LJfc0T31ZgvwzDEqvPwt7cHosjwjNrjLiakyhekFDE1+ee"}
encryption_applied: 1
parent_id:
is_shared:
type_: 2
@@ -0,0 +1,8 @@
id: a1a0987e82cc400c90582492f814c23c
created_time: 2020-07-25T10:55:20.995Z
updated_time: 2020-07-25T10:55:20.995Z
source_application: net.cozic.joplintest-cli
encryption_method: 4
checksum:
content: {"iv":"lAWEbGcNeJKdyBtAFJhCJg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"L0lL3nxewe0=","ct":"ZonX+RPsUjs7KznJXZb2HA3WDPc5vBnqcAU2FSphHIYYP6FEjPSmiGbAmCcScs2WsOkSm6t+4Elt5nu44XPxj7PR/O5JkpOlP2WNrIMovS7dhpm2fFhvnywMeYfVYJGvOvaOxUsAnbTjNayUxIYFKoKEB6o3pmp9Msu8ZESc46mnnjbL7VDuiCWIVp/PCf9hK4l1Id8hLAy7Xd2Jl+ToItD7LYWkuA8+xEa6WvW3l7tEyPeAYKQGze3GqL3p+R1E6bXZ4XV7K193JlYwDvDSORq5eGR290FmjaGk0twuSerzx33YJFNfGpYx2YBEqYD9JeHmEabDgBn2sbZRbCNVRsEx7YwAvUzgAH92Vpzw9m59zCdKVxEBekkNUfOAk9ylDhl/1qnYmHd6bpnO8ruU0NrYV0cAfpgwIkD01stmGo3Nq0w5hAS2qtpZBvHickJR+3nb/G+HPCrnHvQVjUZo/7PRfnTuqdF+ZxRM/ENxL8s545x60ugJLOFUC4KU3VmkAt3YdmamgoS8O/7t8pR/vgY3Ll9DbYSDfL91w1mgZ0fo+XYI+Y/cgDpNhiTwcWSn8QjyXPBIO5Y0Rv1t6Sh+G8VOhf90riUI7RJG4t9Y69YEyqbbdsDLCcAbTxPL950guHz9na3KQq2ALiflPIMgKjvD9PAJqx3Cm/aoTFUZiah3tJ4vmBxpMg=="}
type_: 9
@@ -0,0 +1,10 @@
id: a2fc1b9ae0d04c8bbeab6d299d0193cc
created_time:
updated_time: 2020-07-25T10:55:20.785Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c00027c{"iv":"SR6yWBcxM5ngXZzQPnrfcg==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"GkMaEQSVNWpYVstVT+viNzY0b6Ol3QzdgthJ4xIYEdLGZpaLCbBoN1bcYK6VVm8HLegUvH4HjeKlGQQEgX8nhwIaI/DGpWp5KMK+4d7QDSbz/JrDz8mJYpMPIgaTVd/elAs/aojUoJC7ZMEqIiZJi08xkiZeMxj5sFk3Bp+t7Fdg9q/HGTKIoxZBRKHke2j5IiZhLmaeI1soFksi6UmpofejXJB3e/U6p91j2dZVOuI/XpV4aI+Qqut5sU6nWB/kM1/DgTmSJGwYcMmx87zUqLxTs0efWOOuMH+598PTYTACW8QGXx1ErjeiiT9XnXo3yte5Bq2OhLf/Bs2YfkToCejQ7pnLOmVRFX26M8CQzhOqVm9lGxP2eB+ArgHXuUwEFWFDQaNI4LmwzXoXDQLA2HWxCEI5tfFZMD2ge9uTU0TBQ2cvVd282R0kM++LwaNdcDNFjPkQLp6G8Cbyql34sksiTPlshXHF86yoYKxpHLdfyBQ4uE96"}
encryption_applied: 1
is_shared:
parent_id:
type_: 5
@@ -0,0 +1,11 @@
id: a807b6e7d6594567934938dbfcd2bcdf
note_id: 04c4e932fe3c4c4a9450c09208bd6c21
tag_id: a2fc1b9ae0d04c8bbeab6d299d0193cc
created_time:
updated_time: 2020-07-25T10:55:20.795Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0002d8{"iv":"EPjRvnAQokTnBFvw2ZeRkQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"xBFojIoEUsUNbGAu8CibWcTn7Pn8N/zC6UkIxla9Gul8STudN/HLxdX5zBnlLzIiXvumAt/OtRQ8aOjmGxrpPudPH220Shd/eH7jLfh59PxpDNWRrUZLwO1rxUozJjpFAJsqiHGurmsLjtWzMM2p1zeoSRbIacb8X/Sk3BiD3mnnbBNOHoSbV3Rli+mBRgs5SkWqJSvwHbFzjJIk7vv5Uavkwchh21EFOrGwsNlYby5/JIZTz84e3JjXOR0pVs3D33cZM1nSv0ijnJwlLS6Aty+UGfGZl0pq1FdB+Rw9BVAge2+eptKfzmN+bfUEWTIEfKbPWnaokoAa8Jcs9qlJHIqCAAmYRs20iHggjliPzYQ0JO/YXlfsQlpqPS4r+vrEt8V2caCXG0+VLV2XcmJgFjU1oZuXuunJNlD9iobxdgSjxDbmEdYZ/FzT6BjUpTVXbjZLEChq3tUz2PB+JLqK4uNM7J9ZPASW2/M5CH6NdmESk7gafjYM70TQJ8AFsDQNZ3xuA9/kw4xvkW4s8PniELPhMgbSbOlI8ACWP0UReMgvi8h8/fxNc9JILEizuXgN9Vv5Ya32rT8CDw=="}
encryption_applied: 1
is_shared:
type_: 6
@@ -0,0 +1,10 @@
id: aa7dca873bdc47beaa9465e04610619d
created_time:
updated_time: 2020-07-25T10:55:20.635Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0002b0{"iv":"/hdpjX6UaArAOXSsk6W8eg==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"7qX8Fym/M6iCMLeN1M1NuTgPzBKFlleRtwTdGAJCNR6s88rgC4XYlT0AA/mAJ28FDw61JO29ZnVQTLulyuAhmsNtxY6Bar005sLHlEhX/s+/LRyfdLKhzVWVMuKvGeg9F8omkDVDjE9oOcx2DqM+wV9kQe7d2sDWYpu/fEk7T9MNBWI+vodqfw3iNLOiQWzQq5Xrqy7lpb/fj4sSgtKGZouSkbhcckjaKYl5IpblfZffnRF5T770Our6ufID3kTFAorqD4Zg6lY4SUKLrWWAptPv4/wQtBa4J5mQ0XUy3OSSjh4EP70H2js2NrDouKFsaRB2KyKk0/JuMmnMhuajyb9ozIDGwWEmi9zKZZ5FrvqLCKY53hhqxyQ8BMOrrHm8T/GVmfFMKN2ZkO8eLdoTETx8qqyErB9oHg6FsKCJS+943BcsrEuDHaSKUFTV+y5JNiEHIg+hBqMwc53z5mNgylP5eYqvje+t8zo0ZCXPJaMArlSohSQOE2pga9b2k8pwtczIQk1ZL36CX0WcDJVe5ir+c8NtMHG7omHolaU="}
encryption_applied: 1
parent_id: 3d675395b5cd4d1e9d7ca4f045f41493
is_shared:
type_: 2
@@ -0,0 +1,14 @@
id: bda6d120223140afbe7f03ef1d876400
mime:
filename:
created_time:
updated_time: 2020-07-25T10:55:20.778Z
user_created_time:
user_updated_time:
file_extension:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c000308{"iv":"zyEI38ZCb2UGn3cy0Ejjjg==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"vqxf3Ul0liOt86X6uiY3/tyGGpv1nb7Nhj/xX7j+BBrUCeVJEG49cxUGwH94ZCKI1LDnFW1wiiItsdmZySyZZXTmcaVmw3lIA3hDRmS6QaSevRM8dSg1m7zMArXVGKwHPAhfysZF5wtS5rMf4cZSlr4ib5w064Ux8LqYEYS7IIDqczt4qORZUrzwigx2V54426w4PsUD4xzUI4K5PIdBewWinaRsmZ/Dwf4tLHJa0B3BYT/j4T0Iuznw+1O6JXUZc3G12lYLDPL9NAmX7BQ+aTzjCrvrbAq+ew0Sefhzb3kUxp6VM9QBbD9sPWSE0L3rr97t0smJYA8SVPBcwmiPRU6tsNx6ISc0/pQMe6KxZNQVid2/K1/e3OmkFItI5y20PXRs9IuUhRhylQ6/2dL6MR1d514PA3BFQyfsq1EBL8waO0p9A3qMFxNkpgrxail/Q21XbSaojTA9oW2+o3e7jMecwUjVlR2kvZG+by+FcE4SwZz09nBVvFEU4hmRDnKkRYNwnB5WbvVGsfa+gn5uj3HTFY/XIgK9160oGSncKVGkoG9wpoQ+JhKqhwjBZ0rIA/fOaD02ndEMpKlxoBjIPAtXTS/3rRYIypvajiI1qc/2FYgBCl1PvbWle1yOFsXT"}
encryption_applied: 1
encryption_blob_encrypted:
size:
is_shared:
type_: 4
@@ -0,0 +1,11 @@
id: ce59e12313e84ae299b6166068755253
note_id: 49e1777d9c17439fb612cce85700d16d
tag_id: 8a17074d4ec24de7b5a1aa666f7d8b38
created_time:
updated_time: 2020-07-25T10:55:20.926Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0002d8{"iv":"h2tVkjGBWm/rXhSD6tyneQ==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"l41akNAnKt7pRrD5evL4ebPZJ+9wgV1bian4MPJUiUsK63AXJspadGbgMEZ0lrBDody4dC1lauUKdiDc3CFL715xGyscvMR9LNhqHqNWqg2PPMTLbwPThNi8X0OkaeH5tftpxBElD3eYvWRyoSy8mqytzkmaqDdgGBHZQcaqjHcB8f8B7gnNgT2EAhsHHe+KsN/AwD4v1CzJAs2ODrGZK6Bx76doZzH5vRFZ4gyYHoo0N0Ec9AaSl2UtiPH171GD4i5J7r3Puvif1sFZk4EPTfyfpJ+xxwWMM/10a6o9T4kIWOiFTFPi0o/l0ocD4ghUNX2FKxrfRtCcQk+D5qlRXhrTaJDj6SFOAAkkseddotgtvU+y44kagu47XKV+frEjAW6HMznpZBTrJ3OVLXf33uvOLVLM5c1RDiiBj20kNQ0m4+XuGAsXxrcxFj/uAzCO48Q1AvaLE3xlRP6rs+h6+JP8CAmmd5Grxm4Y73ww8IcJyzfajiQj6siScgNRw6LVY7lI8MV6UwxI0fah8MsqcSS2ZMWx/DJpqlbuoRhLWVeBLp3LNq1VT78LAZVqJsn6LesUSCeZd/tXeQ=="}
encryption_applied: 1
is_shared:
type_: 6
@@ -0,0 +1,11 @@
id: ed20d91ef4e64fc0910088112c077188
note_id: 3a8eaf72f62847689176a952a0b321a0
tag_id: a2fc1b9ae0d04c8bbeab6d299d0193cc
created_time:
updated_time: 2020-07-25T10:55:20.788Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0002d8{"iv":"WIWEiOpZBFro4/AXC+mtcg==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"GREDeqYxU2c9LLKu7WNzDhaCMyZRmo8eL6E1Kx/0rvHT7A8tI1ZK/yTSNvfLcVWU164pxvkCtRB6fPij8dVA6P1uLcdhH6ztuvekCcHsb2PPdOtX4Hytc3X5Hi0oHvgVTnMnmY00XpODR22M0LPUE0/OoJYajTGM5Cx93cieTUB+qyxyyIHGBhjhw8vsI/CIjpvZIogOQDm4D6ptg1gmFhDrJx04C8tfZxpMI7iszBTyfsnIOBtRsbeONnqHxcOweIHkIMRKQlBcIRg0yM8vm0Ft1wyQzRg0B/KOTcfctdV1yUTnY3txVTb5o29tH829fyYKF0q2rLtqlVNfN56gaHgpkXC/6szW+UNkCmJI/v+EgkorfBB7B8NPH7v3zVw01u9QWuETi8RKjcMo7ZUMdMDqQYi4vjqSsmWtfAfs5/uUG260nS+cu3OxNJm1UtdIJ+l2e1e36EwbHbCiiKRF9deQaLWBmm43ZmqoukpmX9FFzjv78s2JbeFnDKc5tiw7BkYU9yETv7DgC7f7T8D9xDQDJGELkXbzzNBGoqniWFgWJc7Akt+Gof3uKZUIQO8lRKjTOpgxP2+7/A=="}
encryption_applied: 1
is_shared:
type_: 6
@@ -0,0 +1,11 @@
id: f58c1af04627410da55d9c771b28bece
note_id: 04c4e932fe3c4c4a9450c09208bd6c21
tag_id: 8a17074d4ec24de7b5a1aa666f7d8b38
created_time:
updated_time: 2020-07-25T10:55:20.803Z
user_created_time:
user_updated_time:
encryption_cipher_text: JED0100002205a1a0987e82cc400c90582492f814c23c0002d8{"iv":"j4Au5F1MRKUgSevr56iisw==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"HvRLpScsoN1juxw18ostkjkjcxO+VWaprzz15PzE+3LU0KfoPqo0g2GgVPpbmp9dNyuyv+akfj5u5/buMZipiyGteEOa5WxoJ16KJ9qIYjv+kxu9kcfteDhHP6gTz1Mc0DfQRlLfZ5EbcpYQwkcNvdF71t8JsH5QwqA27P/wk5TKZDM/gd641zL92tNViAM4dZws2FDeWvgb4xRU3L7tfSBVoR5DXBKPl5syNrr8m2prdolydWms+RZuQQnFWIMn0jIKucSx56YEwcmCsAdsOLpNd8/MLqHfUOafrCQYDd9QIWEbNz509wWoXiu/Cjl49B+xx0ACa5z/Ey0yBQAwibPLMBq2yAQgxo6SWG00reOkGKQmxYIkD7mQa87zUtsCRWPebFohDV2LfAbbPFScsqNsH2wNhVXJZJ4JcOKMUR1R4hx66P158wOn9VY1Rf3zyJujiqzAhGivdvMQ2qp1TyAoiA8ibPizZIEPh0oyYf7EoZf1mIO/hyFMfs31xSbfdfXhUn57BCgVkndA7NRT2xoEdqBgymLMp2z7ll7F8xjMEG+8wUlAWNXzNDjhzPza2D2s8Co9pqgs9g=="}
encryption_applied: 1
is_shared:
type_: 6
@@ -0,0 +1 @@
{"version":2}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1 @@
2020-07-16: In the new sync format, the version number is stored in /info.json. However, for backward compatibility, we need to keep the old version.txt file here, otherwise old clients will automatically recreate it, and assume a sync target version 1. So we keep it here but set its value to "2", so that old clients know that they need to be upgraded. This directory can be removed after a year or so, once we are confident that all clients have been upgraded to recent versions.
@@ -0,0 +1,16 @@
photo.jpg
id: 006a89df4de64a22b4b1fa71f87fd258
mime: image/jpeg
filename:
created_time: 2020-07-25T10:55:18.547Z
updated_time: 2020-07-25T10:55:18.547Z
user_created_time: 2020-07-25T10:55:18.547Z
user_updated_time: 2020-07-25T10:55:18.547Z
file_extension: jpg
encryption_cipher_text:
encryption_applied: 0
encryption_blob_encrypted: 0
size: 2720
is_shared: 0
type_: 4
@@ -0,0 +1,11 @@
id: 106ec766eba54715b19dc899e1de6906
note_id: bf551517ef7c40be9477168677d0b77a
tag_id: b684a65012c74c508b891935ecf2f5b1
created_time: 2020-07-25T10:55:18.556Z
updated_time: 2020-07-25T10:55:18.556Z
user_created_time: 2020-07-25T10:55:18.556Z
user_updated_time: 2020-07-25T10:55:18.556Z
encryption_cipher_text:
encryption_applied: 0
is_shared: 0
type_: 6
@@ -0,0 +1,11 @@
id: 23d35df6c34848ec86c42c3194051ecc
note_id: a91bf5ddf3a749d2be010e9a04e5a1cc
tag_id: b684a65012c74c508b891935ecf2f5b1
created_time: 2020-07-25T10:55:18.439Z
updated_time: 2020-07-25T10:55:18.439Z
user_created_time: 2020-07-25T10:55:18.439Z
user_updated_time: 2020-07-25T10:55:18.439Z
encryption_cipher_text:
encryption_applied: 0
is_shared: 0
type_: 6
@@ -0,0 +1,28 @@
note1
![photo.jpg](:/6f60ca35b0e4423fb49f9e097449fd99)
id: 2a914b3fb8fb43819b976eb4e5be80e3
parent_id: 2fa39884ba3b47a489dae93dc20021f2
created_time: 2020-07-25T10:55:18.127Z
updated_time: 2020-07-25T10:55:18.403Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin
source_application: net.cozic.joplintest-cli
application_data:
order: 1595674518127
user_created_time: 2020-07-25T10:55:18.127Z
user_updated_time: 2020-07-25T10:55:18.403Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 0
type_: 1
@@ -0,0 +1,12 @@
subFolder2
id: 2fa39884ba3b47a489dae93dc20021f2
created_time: 2020-07-25T10:55:18.125Z
updated_time: 2020-07-25T10:55:18.125Z
user_created_time: 2020-07-25T10:55:18.125Z
user_updated_time: 2020-07-25T10:55:18.125Z
encryption_cipher_text:
encryption_applied: 0
parent_id: c4e45cadb2e84beb801980155a707e21
is_shared: 0
type_: 2
@@ -0,0 +1,12 @@
subFolder1
id: 352dcd65cd6e4b09a93669378b8e2b50
created_time: 2020-07-25T10:55:18.122Z
updated_time: 2020-07-25T10:55:18.122Z
user_created_time: 2020-07-25T10:55:18.122Z
user_updated_time: 2020-07-25T10:55:18.122Z
encryption_cipher_text:
encryption_applied: 0
parent_id: c4e45cadb2e84beb801980155a707e21
is_shared: 0
type_: 2
@@ -0,0 +1,11 @@
id: 38341af5a8764d4d9318f58f778e3240
note_id: 45867f53ece54da38eed83288882e374
tag_id: 6cb91bb296ee458589eea0256ada06fa
created_time: 2020-07-25T10:55:18.425Z
updated_time: 2020-07-25T10:55:18.425Z
user_created_time: 2020-07-25T10:55:18.425Z
user_updated_time: 2020-07-25T10:55:18.425Z
encryption_cipher_text:
encryption_applied: 0
is_shared: 0
type_: 6
@@ -0,0 +1,12 @@
folder3
id: 40f117103de1405586b4289a55e0ea22
created_time: 2020-07-25T10:55:18.443Z
updated_time: 2020-07-25T10:55:18.443Z
user_created_time: 2020-07-25T10:55:18.443Z
user_updated_time: 2020-07-25T10:55:18.443Z
encryption_cipher_text:
encryption_applied: 0
parent_id:
is_shared: 0
type_: 2
@@ -0,0 +1,26 @@
note3
id: 45867f53ece54da38eed83288882e374
parent_id: c4e45cadb2e84beb801980155a707e21
created_time: 2020-07-25T10:55:18.424Z
updated_time: 2020-07-25T10:55:18.424Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin
source_application: net.cozic.joplintest-cli
application_data:
order: 1595674518424
user_created_time: 2020-07-25T10:55:18.424Z
user_updated_time: 2020-07-25T10:55:18.424Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 0
type_: 1
@@ -0,0 +1,11 @@
id: 50fdc4447c334b00a4dde44344aceb25
note_id: 45867f53ece54da38eed83288882e374
tag_id: b684a65012c74c508b891935ecf2f5b1
created_time: 2020-07-25T10:55:18.434Z
updated_time: 2020-07-25T10:55:18.434Z
user_created_time: 2020-07-25T10:55:18.434Z
user_updated_time: 2020-07-25T10:55:18.434Z
encryption_cipher_text:
encryption_applied: 0
is_shared: 0
type_: 6
@@ -0,0 +1,11 @@
id: 567486477f4249d38feadf6c5ec6e03d
note_id: 2a914b3fb8fb43819b976eb4e5be80e3
tag_id: 6cb91bb296ee458589eea0256ada06fa
created_time: 2020-07-25T10:55:18.416Z
updated_time: 2020-07-25T10:55:18.416Z
user_created_time: 2020-07-25T10:55:18.416Z
user_updated_time: 2020-07-25T10:55:18.416Z
encryption_cipher_text:
encryption_applied: 0
is_shared: 0
type_: 6
@@ -0,0 +1,12 @@
tag1
id: 6cb91bb296ee458589eea0256ada06fa
created_time: 2020-07-25T10:55:18.411Z
updated_time: 2020-07-25T10:55:18.411Z
user_created_time: 2020-07-25T10:55:18.411Z
user_updated_time: 2020-07-25T10:55:18.411Z
encryption_cipher_text:
encryption_applied: 0
is_shared: 0
parent_id:
type_: 5
@@ -0,0 +1,16 @@
photo.jpg
id: 6f60ca35b0e4423fb49f9e097449fd99
mime: image/jpeg
filename:
created_time: 2020-07-25T10:55:18.397Z
updated_time: 2020-07-25T10:55:18.397Z
user_created_time: 2020-07-25T10:55:18.397Z
user_updated_time: 2020-07-25T10:55:18.397Z
file_extension: jpg
encryption_cipher_text:
encryption_applied: 0
encryption_blob_encrypted: 0
size: 2720
is_shared: 0
type_: 4
@@ -0,0 +1,12 @@
folder2
id: 7ae4083db8e64328a4d3ccab6279c6bf
created_time: 2020-07-25T10:55:18.442Z
updated_time: 2020-07-25T10:55:18.442Z
user_created_time: 2020-07-25T10:55:18.442Z
user_updated_time: 2020-07-25T10:55:18.442Z
encryption_cipher_text:
encryption_applied: 0
parent_id:
is_shared: 0
type_: 2
@@ -0,0 +1,26 @@
note4
id: a91bf5ddf3a749d2be010e9a04e5a1cc
parent_id: c4e45cadb2e84beb801980155a707e21
created_time: 2020-07-25T10:55:18.437Z
updated_time: 2020-07-25T10:55:18.437Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin
source_application: net.cozic.joplintest-cli
application_data:
order: 1595674518437
user_created_time: 2020-07-25T10:55:18.437Z
user_updated_time: 2020-07-25T10:55:18.437Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 0
type_: 1
@@ -0,0 +1,12 @@
tag2
id: b684a65012c74c508b891935ecf2f5b1
created_time: 2020-07-25T10:55:18.432Z
updated_time: 2020-07-25T10:55:18.432Z
user_created_time: 2020-07-25T10:55:18.432Z
user_updated_time: 2020-07-25T10:55:18.432Z
encryption_cipher_text:
encryption_applied: 0
is_shared: 0
parent_id:
type_: 5
@@ -0,0 +1,28 @@
note5
![photo.jpg](:/006a89df4de64a22b4b1fa71f87fd258)
id: bf551517ef7c40be9477168677d0b77a
parent_id: 40f117103de1405586b4289a55e0ea22
created_time: 2020-07-25T10:55:18.444Z
updated_time: 2020-07-25T10:55:18.551Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin
source_application: net.cozic.joplintest-cli
application_data:
order: 1595674518444
user_created_time: 2020-07-25T10:55:18.444Z
user_updated_time: 2020-07-25T10:55:18.551Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 0
type_: 1
@@ -0,0 +1,12 @@
folder1
id: c4e45cadb2e84beb801980155a707e21
created_time: 2020-07-25T10:55:18.120Z
updated_time: 2020-07-25T10:55:18.120Z
user_created_time: 2020-07-25T10:55:18.120Z
user_updated_time: 2020-07-25T10:55:18.120Z
encryption_cipher_text:
encryption_applied: 0
parent_id:
is_shared: 0
type_: 2
@@ -0,0 +1,26 @@
note2
id: edd3cb394ada4d389c01c2bdca09b3ef
parent_id: 2fa39884ba3b47a489dae93dc20021f2
created_time: 2020-07-25T10:55:18.422Z
updated_time: 2020-07-25T10:55:18.422Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 0
todo_due: 0
todo_completed: 0
source: joplin
source_application: net.cozic.joplintest-cli
application_data:
order: 1595674518422
user_created_time: 2020-07-25T10:55:18.422Z
user_updated_time: 2020-07-25T10:55:18.422Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 0
type_: 1
@@ -0,0 +1 @@
{"version":2}
+12 -1
View File
@@ -5,9 +5,13 @@ const Setting = require('lib/models/Setting');
const Folder = require('lib/models/Folder');
const Note = require('lib/models/Note');
const Tag = require('lib/models/Tag');
const Resource = require('lib/models/Resource');
const markdownUtils = require('lib/markdownUtils');
const {shim} = require('lib/shim');
const fs = require('fs-extra');
const snapshotBaseDir = `${__dirname}/../../tests/support/syncTargetSnapshots`;
const testData = {
folder1: {
subFolder1: {},
@@ -96,6 +100,12 @@ async function checkTestData(data) {
await recurseCheck(data);
}
async function deploySyncTargetSnapshot(syncTargetType, syncVersion) {
const sourceDir = `${snapshotBaseDir}/${syncVersion}/${syncTargetType}`;
await fs.remove(syncDir);
await fs.copy(sourceDir, syncDir);
}
async function main(syncTargetType) {
const validSyncTargetTypes = ['normal', 'e2ee'];
if (!validSyncTargetTypes.includes(syncTargetType)) throw new Error('Sync target type must be: ' + validSyncTargetTypes.join(', '));
@@ -112,7 +122,7 @@ async function main(syncTargetType) {
await synchronizer().start();
if (!Setting.value('syncVersion')) throw new Error('syncVersion is not set');
const destDir = `${__dirname}/../../tests/support/syncTargetSnapshots/${Setting.value('syncVersion')}/${syncTargetType}`;
const destDir = `${snapshotBaseDir}/${Setting.value('syncVersion')}/${syncTargetType}`;
await fs.mkdirp(destDir); // Create intermediate directories
await fs.remove(destDir);
await fs.mkdirp(destDir);
@@ -125,4 +135,5 @@ module.exports = {
checkTestData,
main,
testData,
deploySyncTargetSnapshot,
};
Binary file not shown.
File diff suppressed because it is too large. Load diff
+201
View File
@@ -0,0 +1,201 @@
import LockHandler, { LockType, LockHandlerOptions, Lock } from 'lib/services/synchronizer/LockHandler';
require('app-module-path').addPath(__dirname);
const { isNetworkSyncTarget, asyncTest, fileApi, setupDatabaseAndSynchronizer, synchronizer, switchClient, msleep, expectThrow, expectNotThrow } = require('test-utils.js');
process.on('unhandledRejection', (reason:any, p:any) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
// For tests with memory of file system we can use low intervals to make the tests faster.
// However if we use such low values with network sync targets, some calls might randomly fail with
// ECONNRESET and similar errors (Dropbox or OneDrive migth also throttle). Also we can't use a
// low lock TTL value because the lock might expire between the time it's written and the time it's checked.
// For that reason we add this multiplier for non-memory sync targets.
const timeoutMultipler = isNetworkSyncTarget() ? 100 : 1;
let lockHandler_:LockHandler = null;
function newLockHandler(options:LockHandlerOptions = null):LockHandler {
return new LockHandler(fileApi(), options);
}
function lockHandler():LockHandler {
if (lockHandler_) return lockHandler_;
lockHandler_ = new LockHandler(fileApi());
return lockHandler_;
}
describe('synchronizer_LockHandler', function() {
beforeEach(async (done:Function) => {
// logger.setLevel(Logger.LEVEL_WARN);
lockHandler_ = null;
await setupDatabaseAndSynchronizer(1);
await setupDatabaseAndSynchronizer(2);
await switchClient(1);
await synchronizer().start(); // Need to sync once to setup the sync target and allow locks to work
// logger.setLevel(Logger.LEVEL_DEBUG);
done();
});
it('should acquire and release a sync lock', asyncTest(async () => {
await lockHandler().acquireLock(LockType.Sync, 'mobile', '123456');
const locks = await lockHandler().locks(LockType.Sync);
expect(locks.length).toBe(1);
expect(locks[0].type).toBe(LockType.Sync);
expect(locks[0].clientId).toBe('123456');
expect(locks[0].clientType).toBe('mobile');
await lockHandler().releaseLock(LockType.Sync, 'mobile', '123456');
expect((await lockHandler().locks(LockType.Sync)).length).toBe(0);
}));
it('should not use files that are not locks', asyncTest(async () => {
await fileApi().put('locks/desktop.ini', 'a');
await fileApi().put('locks/exclusive.json', 'a');
await fileApi().put('locks/garbage.json', 'a');
await fileApi().put('locks/sync_mobile_72c4d1b7253a4475bfb2f977117d26ed.json', 'a');
const locks = await lockHandler().locks(LockType.Sync);
expect(locks.length).toBe(1);
}));
it('should allow multiple sync locks', asyncTest(async () => {
await lockHandler().acquireLock(LockType.Sync, 'mobile', '111');
await switchClient(2);
await lockHandler().acquireLock(LockType.Sync, 'mobile', '222');
expect((await lockHandler().locks(LockType.Sync)).length).toBe(2);
{
await lockHandler().releaseLock(LockType.Sync, 'mobile', '222');
const locks = await lockHandler().locks(LockType.Sync);
expect(locks.length).toBe(1);
expect(locks[0].clientId).toBe('111');
}
}));
it('should auto-refresh a lock', asyncTest(async () => {
const handler = newLockHandler({ autoRefreshInterval: 100 * timeoutMultipler });
const lock = await handler.acquireLock(LockType.Sync, 'desktop', '111');
const lockBefore = await handler.activeLock(LockType.Sync, 'desktop', '111');
handler.startAutoLockRefresh(lock, () => {});
await msleep(500 * timeoutMultipler);
const lockAfter = await handler.activeLock(LockType.Sync, 'desktop', '111');
expect(lockAfter.updatedTime).toBeGreaterThan(lockBefore.updatedTime);
handler.stopAutoLockRefresh(lock);
}));
it('should call the error handler when lock has expired while being auto-refreshed', asyncTest(async () => {
const handler = newLockHandler({
lockTtl: 50 * timeoutMultipler,
autoRefreshInterval: 200 * timeoutMultipler,
});
const lock = await handler.acquireLock(LockType.Sync, 'desktop', '111');
let autoLockError:any = null;
handler.startAutoLockRefresh(lock, (error:any) => {
autoLockError = error;
});
await msleep(250 * timeoutMultipler);
expect(autoLockError.code).toBe('lockExpired');
handler.stopAutoLockRefresh(lock);
}));
it('should not allow sync locks if there is an exclusive lock', asyncTest(async () => {
await lockHandler().acquireLock(LockType.Exclusive, 'desktop', '111');
await expectThrow(async () => {
await lockHandler().acquireLock(LockType.Sync, 'mobile', '222');
}, 'hasExclusiveLock');
}));
it('should not allow exclusive lock if there are sync locks', asyncTest(async () => {
const lockHandler = newLockHandler({ lockTtl: 1000 * 60 * 60 });
await lockHandler.acquireLock(LockType.Sync, 'mobile', '111');
await lockHandler.acquireLock(LockType.Sync, 'mobile', '222');
await expectThrow(async () => {
await lockHandler.acquireLock(LockType.Exclusive, 'desktop', '333');
}, 'hasSyncLock');
}));
it('should allow exclusive lock if the sync locks have expired', asyncTest(async () => {
const lockHandler = newLockHandler({ lockTtl: 500 * timeoutMultipler });
await lockHandler.acquireLock(LockType.Sync, 'mobile', '111');
await lockHandler.acquireLock(LockType.Sync, 'mobile', '222');
await msleep(600 * timeoutMultipler);
await expectNotThrow(async () => {
await lockHandler.acquireLock(LockType.Exclusive, 'desktop', '333');
});
}));
it('should decide what is the active exclusive lock', asyncTest(async () => {
const lockHandler = newLockHandler();
{
const lock1:Lock = { type: LockType.Exclusive, clientId: '1', clientType: 'd' };
const lock2:Lock = { type: LockType.Exclusive, clientId: '2', clientType: 'd' };
await lockHandler.saveLock_(lock1);
await msleep(100);
await lockHandler.saveLock_(lock2);
const activeLock = await lockHandler.activeLock(LockType.Exclusive);
expect(activeLock.clientId).toBe('1');
}
}));
// it('should not have race conditions', asyncTest(async () => {
// const lockHandler = newLockHandler();
// const clients = [];
// for (let i = 0; i < 20; i++) {
// clients.push({
// id: 'client' + i,
// type: 'desktop',
// });
// }
// for (let loopIndex = 0; loopIndex < 1000; loopIndex++) {
// const promises:Promise<void | Lock>[] = [];
// for (let clientIndex = 0; clientIndex < clients.length; clientIndex++) {
// const client = clients[clientIndex];
// promises.push(
// lockHandler.acquireLock(LockType.Exclusive, client.type, client.id).catch(() => {})
// );
// // if (gotLock) {
// // await msleep(100);
// // const locks = await lockHandler.locks(LockType.Exclusive);
// // console.info('=======================================');
// // console.info(locks);
// // lockHandler.releaseLock(LockType.Exclusive, client.type, client.id);
// // }
// // await msleep(500);
// }
// const result = await Promise.all(promises);
// const locks = result.filter((lock:any) => !!lock);
// expect(locks.length).toBe(1);
// const lock:Lock = locks[0] as Lock;
// const allLocks = await lockHandler.locks();
// console.info('================================', allLocks);
// lockHandler.releaseLock(LockType.Exclusive, lock.clientType, lock.clientId);
// }
// }));
});
@@ -0,0 +1,159 @@
import LockHandler from 'lib/services/synchronizer/LockHandler';
import MigrationHandler from 'lib/services/synchronizer/MigrationHandler';
// To create a sync target snapshot for the current syncVersion:
// - In test-utils, set syncTargetName_ to "filesystem"
// - Then run:
// gulp buildTests -L && node tests-build/support/createSyncTargetSnapshot.js normal && node tests-build/support/createSyncTargetSnapshot.js e2ee
require('app-module-path').addPath(__dirname);
const { asyncTest, setSyncTargetName, fileApi, synchronizer, decryptionWorker, encryptionService, setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow } = require('test-utils.js');
const { deploySyncTargetSnapshot, testData, checkTestData } = require('./support/syncTargetUtils');
const Setting = require('lib/models/Setting');
const MasterKey = require('lib/models/MasterKey');
const specTimeout = 60000 * 10; // Nextcloud tests can be slow
let lockHandler_:LockHandler = null;
let migrationHandler_:MigrationHandler = null;
function lockHandler():LockHandler {
if (lockHandler_) return lockHandler_;
lockHandler_ = new LockHandler(fileApi());
return lockHandler_;
}
function migrationHandler(clientId:string = 'abcd'):MigrationHandler {
if (migrationHandler_) return migrationHandler_;
migrationHandler_ = new MigrationHandler(fileApi(), lockHandler(), 'desktop', clientId);
return migrationHandler_;
}
interface MigrationTests {
[key:string]: Function;
}
const migrationTests:MigrationTests = {
2: async function() {
const items = (await fileApi().list('', { includeHidden: true })).items;
expect(items.filter((i:any) => i.path === '.resource' && i.isDir).length).toBe(1);
expect(items.filter((i:any) => i.path === 'locks' && i.isDir).length).toBe(1);
expect(items.filter((i:any) => i.path === 'temp' && i.isDir).length).toBe(1);
expect(items.filter((i:any) => i.path === 'info.json' && !i.isDir).length).toBe(1);
const versionForOldClients = await fileApi().get('.sync/version.txt');
expect(versionForOldClients).toBe('2');
},
};
let previousSyncTargetName:string = '';
describe('synchronizer_MigrationHandler', function() {
beforeEach(async (done:Function) => {
// To test the migrations, we have to use the filesystem sync target
// because the sync target snapshots are plain files. Eventually
// it should be possible to copy a filesystem target to memory
// but for now that will do.
previousSyncTargetName = setSyncTargetName('filesystem');
lockHandler_ = null;
migrationHandler_ = null;
await setupDatabaseAndSynchronizer(1);
await setupDatabaseAndSynchronizer(2);
await switchClient(1);
done();
});
afterEach(async (done:Function) => {
setSyncTargetName(previousSyncTargetName);
done();
});
it('should not allow syncing if the sync target is out-dated', asyncTest(async () => {
await synchronizer().start();
await fileApi().put('info.json', `{"version":${Setting.value('syncVersion') - 1}}`);
await expectThrow(async () => await migrationHandler().checkCanSync(), 'outdatedSyncTarget');
}), specTimeout);
it('should not allow syncing if the client is out-dated', asyncTest(async () => {
await synchronizer().start();
await fileApi().put('info.json', `{"version":${Setting.value('syncVersion') + 1}}`);
await expectThrow(async () => await migrationHandler().checkCanSync(), 'outdatedClient');
}), specTimeout);
for (const migrationVersionString in migrationTests) {
const migrationVersion = Number(migrationVersionString);
it(`should migrate (${migrationVersion})`, asyncTest(async () => {
await deploySyncTargetSnapshot('normal', migrationVersion - 1);
const info = await migrationHandler().fetchSyncTargetInfo();
expect(info.version).toBe(migrationVersion - 1);
// Now, migrate to the new version
await migrationHandler().upgrade(migrationVersion);
// Verify that it has been upgraded
const newInfo = await migrationHandler().fetchSyncTargetInfo();
expect(newInfo.version).toBe(migrationVersion);
await migrationTests[migrationVersion]();
// Now sync with that upgraded target
await synchronizer().start();
// Check that the data has not been altered
await expectNotThrow(async () => await checkTestData(testData));
// Check what happens if we switch to a different client and sync
await switchClient(2);
Setting.setConstant('syncVersion', migrationVersion);
await synchronizer().start();
await expectNotThrow(async () => await checkTestData(testData));
}), specTimeout);
it(`should migrate (E2EE) (${migrationVersion})`, asyncTest(async () => {
// First create some test data that will be used to validate
// that the migration didn't alter any data.
await deploySyncTargetSnapshot('e2ee', migrationVersion - 1);
// Now, migrate to the new version
Setting.setConstant('syncVersion', migrationVersion);
await migrationHandler().upgrade(migrationVersion);
// Verify that it has been upgraded
const newInfo = await migrationHandler().fetchSyncTargetInfo();
expect(newInfo.version).toBe(migrationVersion);
await migrationTests[migrationVersion]();
// Now sync with that upgraded target
await synchronizer().start();
// Decrypt the data
const masterKey = (await MasterKey.all())[0];
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
await encryptionService().loadMasterKeysFromSettings();
await decryptionWorker().start();
// Check that the data has not been altered
await expectNotThrow(async () => await checkTestData(testData));
// Check what happens if we switch to a different client and sync
await switchClient(2);
Setting.setConstant('syncVersion', migrationVersion);
await synchronizer().start();
// Should throw because data hasn't been decrypted yet
await expectThrow(async () => await checkTestData(testData));
// Enable E2EE and decrypt
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
await encryptionService().loadMasterKeysFromSettings();
await decryptionWorker().start();
// Should not throw because data is decrypted
await expectNotThrow(async () => await checkTestData(testData));
}), specTimeout);
}
});
+143 -30
View File
@@ -21,6 +21,7 @@ const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav.js');
const { FileApiDriverDropbox } = require('lib/file-api-driver-dropbox.js');
const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js');
const { FileApiDriverAmazonS3 } = require('lib/file-api-driver-amazon-s3.js');
const BaseService = require('lib/services/BaseService.js');
const { FsDriverNode } = require('lib/fs-driver-node.js');
@@ -43,6 +44,7 @@ const ResourceFetcher = require('lib/services/ResourceFetcher.js');
const KvStore = require('lib/services/KvStore.js');
const WebDavApi = require('lib/WebDavApi');
const DropboxApi = require('lib/DropboxApi');
const { OneDriveApi } = require('lib/onedrive-api');
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
const KeychainServiceDriver = require('lib/services/keychain/KeychainServiceDriver.node').default;
const KeychainServiceDriverDummy = require('lib/services/keychain/KeychainServiceDriver.dummy').default;
@@ -50,14 +52,15 @@ const md5 = require('md5');
const S3 = require('aws-sdk/clients/s3');
const databases_ = [];
const synchronizers_ = [];
let synchronizers_ = [];
const synchronizerContexts_ = {};
const fileApis_ = {};
const encryptionServices_ = [];
const revisionServices_ = [];
const decryptionWorkers_ = [];
const resourceServices_ = [];
const resourceFetchers_ = [];
const kvStores_ = [];
let fileApi_ = null;
let currentClient_ = 1;
// The line `process.on('unhandledRejection'...` in all the test files is going to
@@ -88,16 +91,39 @@ SyncTargetRegistry.addClass(SyncTargetNextcloud);
SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
// const syncTargetId_ = SyncTargetRegistry.nameToId("nextcloud");
const syncTargetId_ = SyncTargetRegistry.nameToId('memory');
// const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem');
// const syncTargetId_ = SyncTargetRegistry.nameToId('dropbox');
// const syncTargetId_ = SyncTargetRegistry.nameToId('amazon_s3');
let syncTargetName_ = '';
let syncTargetId_ = null;
let sleepTime = 0;
let isNetworkSyncTarget_ = false;
function syncTargetName() {
return syncTargetName_;
}
function setSyncTargetName(name) {
if (name === syncTargetName_) return syncTargetName_;
const previousName = syncTargetName_;
syncTargetName_ = name;
syncTargetId_ = SyncTargetRegistry.nameToId(syncTargetName_);
sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;// 400;
isNetworkSyncTarget_ = ['nextcloud', 'dropbox', 'onedrive', 'amazon_s3'].includes(syncTargetName_);
synchronizers_ = [];
return previousName;
}
setSyncTargetName('memory');
// setSyncTargetName('nextcloud');
// setSyncTargetName('dropbox');
// setSyncTargetName('onedrive');
// setSyncTargetName('amazon_s3');
console.info(`Testing with sync target: ${syncTargetName_}`);
const syncDir = `${__dirname}/../tests/sync`;
const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;// 400;
console.info(`Testing with sync target: ${SyncTargetRegistry.idToName(syncTargetId_)}`);
let defaultJasmineTimeout = 90 * 1000;
if (isNetworkSyncTarget_) defaultJasmineTimeout = 60 * 1000 * 10;
if (typeof jasmine !== 'undefined') jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultJasmineTimeout;
const dbLogger = new Logger();
dbLogger.addTarget('console');
@@ -129,6 +155,10 @@ function syncTargetId() {
return syncTargetId_;
}
function isNetworkSyncTarget() {
return isNetworkSyncTarget_;
}
function sleep(n) {
return new Promise((resolve, reject) => {
setTimeout(() => {
@@ -137,6 +167,14 @@ function sleep(n) {
});
}
function msleep(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, ms);
});
}
function currentClientId() {
return currentClient_;
}
@@ -252,9 +290,11 @@ async function setupDatabaseAndSynchronizer(id = null, options = null) {
if (!synchronizers_[id]) {
const SyncTargetClass = SyncTargetRegistry.classById(syncTargetId_);
const syncTarget = new SyncTargetClass(db(id));
await initFileApi();
syncTarget.setFileApi(fileApi());
syncTarget.setLogger(logger);
synchronizers_[id] = await syncTarget.synchronizer();
synchronizerContexts_[id] = null;
}
encryptionServices_[id] = new EncryptionService();
@@ -278,6 +318,19 @@ function synchronizer(id = null) {
return synchronizers_[id];
}
// This is like calling synchronizer.start() but it handles the
// complexity of passing around the sync context depending on
// the client.
async function synchronizerStart(id = null, extraOptions = null) {
if (id === null) id = currentClient_;
const context = synchronizerContexts_[id];
const options = Object.assign({}, extraOptions);
if (context) options.context = context;
const newContext = await synchronizer(id).start(options);
synchronizerContexts_[id] = newContext;
return newContext;
}
function encryptionService(id = null) {
if (id === null) id = currentClient_;
return encryptionServices_[id];
@@ -331,44 +384,67 @@ async function loadEncryptionMasterKey(id = null, useExisting = false) {
return masterKey;
}
function fileApi() {
if (fileApi_) return fileApi_;
async function initFileApi() {
if (fileApis_[syncTargetId_]) return;
let fileApi = null;
if (syncTargetId_ == SyncTargetRegistry.nameToId('filesystem')) {
fs.removeSync(syncDir);
fs.mkdirpSync(syncDir, 0o755);
fileApi_ = new FileApi(syncDir, new FileApiDriverLocal());
fileApi = new FileApi(syncDir, new FileApiDriverLocal());
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('memory')) {
fileApi_ = new FileApi('/root', new FileApiDriverMemory());
fileApi = new FileApi('/root', new FileApiDriverMemory());
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('nextcloud')) {
const options = {
baseUrl: () => 'http://nextcloud.local/remote.php/dav/files/admin/JoplinTest',
username: () => 'admin',
password: () => '123456',
};
const api = new WebDavApi(options);
fileApi_ = new FileApi('', new FileApiDriverWebDav(api));
const options = require(`${__dirname}/../tests/support/nextcloud-auth.json`);
const api = new WebDavApi({
baseUrl: () => options.baseUrl,
username: () => options.username,
password: () => options.password,
});
fileApi = new FileApi('', new FileApiDriverWebDav(api));
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('dropbox')) {
// To get a token, go to the App Console:
// https://www.dropbox.com/developers/apps/
// Then select "JoplinTest" and click "Generated access token"
const api = new DropboxApi();
const authTokenPath = `${__dirname}/support/dropbox-auth.txt`;
const authToken = fs.readFileSync(authTokenPath, 'utf8');
if (!authToken) throw new Error(`Dropbox auth token missing in ${authTokenPath}`);
api.setAuthToken(authToken);
fileApi_ = new FileApi('', new FileApiDriverDropbox(api));
fileApi = new FileApi('', new FileApiDriverDropbox(api));
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('onedrive')) {
// To get a token, open the URL below, then copy the *complete*
// redirection URL in onedrive-auth.txt. Keep in mind that auth data
// only lasts 1h for OneDrive.
// https://login.live.com/oauth20_authorize.srf?client_id=f1e68e1e-a729-4514-b041-4fdd5c7ac03a&scope=files.readwrite,offline_access&response_type=token&redirect_uri=https://joplinapp.org
const { parameters, setEnvOverride } = require('lib/parameters.js');
Setting.setConstant('env', 'dev');
setEnvOverride('test');
const config = parameters().oneDriveTest;
const api = new OneDriveApi(config.id, config.secret, false);
const authData = fs.readFileSync(`${__dirname}/support/onedrive-auth.txt`, 'utf8');
const urlInfo = require('url-parse')(authData, true);
const auth = require('querystring').parse(urlInfo.hash.substr(1));
api.setAuth(auth);
const appDir = await api.appDirectory();
fileApi = new FileApi(appDir, new FileApiDriverOneDrive(api));
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('amazon_s3')) {
const amazonS3CredsPath = `${__dirname}/support/amazon-s3-auth.json`;
const amazonS3Creds = require(amazonS3CredsPath);
if (!amazonS3Creds || !amazonS3Creds.accessKeyId) throw new Error(`AWS auth JSON missing in ${amazonS3CredsPath} format should be: { "accessKeyId": "", "secretAccessKey": "", "bucket": "mybucket"}`);
const api = new S3({ accessKeyId: amazonS3Creds.accessKeyId, secretAccessKey: amazonS3Creds.secretAccessKey, s3UseArnRegion: true });
fileApi_ = new FileApi('', new FileApiDriverAmazonS3(api, amazonS3Creds.bucket));
fileApi = new FileApi('', new FileApiDriverAmazonS3(api, amazonS3Creds.bucket));
}
fileApi.setLogger(logger);
fileApi.setSyncTargetId(syncTargetId_);
fileApi.requestRepeatCount_ = isNetworkSyncTarget_ ? 1 : 0;
fileApi_.setLogger(logger);
fileApi_.setSyncTargetId(syncTargetId_);
fileApi_.requestRepeatCount_ = 0;
return fileApi_;
fileApis_[syncTargetId_] = fileApi;
}
function fileApi() {
return fileApis_[syncTargetId_];
}
function objectsEqual(o1, o2) {
@@ -390,6 +466,41 @@ async function checkThrowAsync(asyncFn) {
return hasThrown;
}
async function expectThrow(asyncFn, errorCode = undefined) {
let hasThrown = false;
let thrownError = null;
try {
await asyncFn();
} catch (error) {
hasThrown = true;
thrownError = error;
}
if (!hasThrown) {
expect('not throw').toBe('throw', 'Expected function to throw an error but did not');
} else if (thrownError.code !== errorCode) {
console.error(thrownError);
expect(`error code: ${thrownError.code}`).toBe(`error code: ${errorCode}`);
} else {
expect(true).toBe(true);
}
}
async function expectNotThrow(asyncFn) {
let thrownError = null;
try {
await asyncFn();
} catch (error) {
thrownError = error;
}
if (thrownError) {
expect(thrownError.message).toBe('', 'Expected function not to throw an error but it did');
} else {
expect(true).toBe(true);
}
}
function checkThrow(fn) {
let hasThrown = false;
try {
@@ -427,13 +538,15 @@ function asyncTest(callback) {
}
async function allSyncTargetItemsEncrypted() {
const list = await fileApi().list();
const list = await fileApi().list('', { includeDirs: false });
const files = list.items;
let totalCount = 0;
let encryptedCount = 0;
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (!BaseItem.isSystemPath(file.path)) continue;
const remoteContentString = await fileApi().get(file.path);
const remoteContent = await BaseItem.unserialize(remoteContentString);
const ItemClass = BaseItem.itemClass(remoteContent);
@@ -585,4 +698,4 @@ class TestApp extends BaseApplication {
}
}
module.exports = { syncDir, kvStore, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
module.exports = { synchronizerStart, syncTargetName, setSyncTargetName, syncDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, TestApp };
+66
View File
@@ -0,0 +1,66 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const timeUtils = require('../../ReactNativeClient/lib/time-utils');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('timeUtils', function() {
beforeEach(async (done) => {
done();
});
it('should go back in time', asyncTest(async () => {
let startDate = new Date('3 Aug 2020');
let endDate = new Date('2 Aug 2020');
expect(time.goBackInTime(startDate, 1, 'day')).toBe(endDate.getTime().toString());
// We're always subtracting time from the beginning of the current period.
startDate = new Date('3 Aug 2020 07:30:20');
expect(time.goBackInTime(startDate, 1, 'day')).toBe(endDate.getTime().toString());
startDate = new Date('11 Aug 2020');
endDate = new Date('9 Aug 2020'); // week start;
expect(time.goBackInTime(startDate, 0, 'week')).toBe(endDate.getTime().toString());
startDate = new Date('02 Feb 2020');
endDate = new Date('01 Jan 2020');
expect(time.goBackInTime(startDate, 1, 'month')).toBe(endDate.getTime().toString());
startDate = new Date('19 September 2020');
endDate = new Date('01 Jan 1997');
expect(time.goBackInTime(startDate, 23, 'year')).toBe(endDate.getTime().toString());
}));
it('should go forward in time', asyncTest(async () => {
let startDate = new Date('2 Aug 2020');
let endDate = new Date('3 Aug 2020');
expect(time.goForwardInTime(startDate, 1, 'day')).toBe(endDate.getTime().toString());
startDate = new Date('2 Aug 2020 07:30:20');
expect(time.goForwardInTime(startDate, 1, 'day')).toBe(endDate.getTime().toString());
startDate = new Date('9 Aug 2020');
endDate = new Date('9 Aug 2020'); // week start;
expect(time.goForwardInTime(startDate, 0, 'week')).toBe(endDate.getTime().toString());
startDate = new Date('02 Jan 2020');
endDate = new Date('01 Feb 2020');
expect(time.goForwardInTime(startDate, 1, 'month')).toBe(endDate.getTime().toString());
startDate = new Date('19 September 1997');
endDate = new Date('01 Jan 2020');
expect(time.goForwardInTime(startDate, 23, 'year')).toBe(endDate.getTime().toString());
}));
});
+3 -2
View File
@@ -8,9 +8,10 @@ const { ipcMain } = require('electron');
class ElectronAppWrapper {
constructor(electronApp, env, profilePath) {
constructor(electronApp, env, profilePath, isDebugMode) {
this.electronApp_ = electronApp;
this.env_ = env;
this.isDebugMode_ = isDebugMode;
this.profilePath_ = profilePath;
this.win_ = null;
this.willQuitApp_ = false;
@@ -41,7 +42,7 @@ class ElectronAppWrapper {
createWindow() {
// Set to true to view errors if the application does not start
const debugEarlyBugs = this.env_ === 'dev';
const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_;
const windowStateKeeper = require('electron-window-state');
Loaded 100 of 259 files, more files were not shown because too many files have changed in this diff. Show more