mirror of
https://github.com/laurent22/joplin.git
synced 2026-11-03 07:20:48 +00:00
Compare commits
119
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96475f82b2 | ||
|
|
e720c91f72 | ||
|
|
771acbe463 | ||
|
|
a5e4c13759 | ||
|
|
0998fc0ad7 | ||
|
|
d5f3e860b9 | ||
|
|
4e624f7db5 | ||
|
|
5b697b7e16 | ||
|
|
62c3945b8f | ||
|
|
f8a3f509e7 | ||
|
|
30e0d69a74 | ||
|
|
652816fd26 | ||
|
|
3a33e5f416 | ||
|
|
277dac5512 | ||
|
|
81d97d9f9d | ||
|
|
a4873cd40d | ||
|
|
20cb2daf43 | ||
|
|
e5b5250a91 | ||
|
|
db7d617e2b | ||
|
|
a627884876 | ||
|
|
ed30d09e07 | ||
|
|
179e3f9aee | ||
|
|
a67aedba35 | ||
|
|
199c411a7d | ||
|
|
5cd7bb5bdb | ||
|
|
30b8f5e2aa | ||
|
|
44f2842820 | ||
|
|
569355a318 | ||
|
|
8464e16d5d | ||
|
|
874c1e3e82 | ||
|
|
2530ecfc86 | ||
|
|
6b49f1dfcc | ||
|
|
b1af25ea18 | ||
|
|
3086007a9c | ||
|
|
bdfb6b97f5 | ||
|
|
c01219e6be | ||
|
|
743c11c279 | ||
|
|
11646d1c21 | ||
|
|
797f4a9669 | ||
|
|
815775ad8f | ||
|
|
085109288f | ||
|
|
c6cf0f3ba5 | ||
|
|
f9bdce7e24 | ||
|
|
f8cf4db5db | ||
|
|
14e6ae373f | ||
|
|
39bffd2790 | ||
|
|
f3cb903901 | ||
|
|
abad1883bc | ||
|
|
bb8770a967 | ||
|
|
c903947704 | ||
|
|
82e96840e9 | ||
|
|
e190d90832 | ||
|
|
0ae8d454ad | ||
|
|
77c7f966cf | ||
|
|
619fa1d607 | ||
|
|
157736ff7e | ||
|
|
0b57d906f8 | ||
|
|
86bfdb5c79 | ||
|
|
fc7d34f79c | ||
|
|
9d962f0328 | ||
|
|
79e1a33b28 | ||
|
|
0eedae1f62 | ||
|
|
e1144c098e | ||
|
|
5c5cb0f781 | ||
|
|
656615b571 | ||
|
|
799a9e810d | ||
|
|
aa147bbcdc | ||
|
|
8c7a24282c | ||
|
|
f99f3f8a6d | ||
|
|
29fbafdfff | ||
|
|
3253146dae | ||
|
|
7e4ac0fd73 | ||
|
|
60d77cb5ea | ||
|
|
8299164964 | ||
|
|
488393e75d | ||
|
|
c19f8c8035 | ||
|
|
b0b55718cc | ||
|
|
5ade9ff2f6 | ||
|
|
e4cfb518ba | ||
|
|
89864de1ff | ||
|
|
ee2a474611 | ||
|
|
223caca0f5 | ||
|
|
ee358f70dd | ||
|
|
0718828d60 | ||
|
|
97c752a39e | ||
|
|
0c147236a3 | ||
|
|
88f22fabf7 | ||
|
|
cc8c200826 | ||
|
|
4485947b0f | ||
|
|
9f1a877f96 | ||
|
|
13280ce1b3 | ||
|
|
7f73931530 | ||
|
|
bbfed9bca8 | ||
|
|
bab29cd582 | ||
|
|
baea44cbd6 | ||
|
|
ab0538df43 | ||
|
|
58d4a69053 | ||
|
|
9147b3061a | ||
|
|
1b0102f62c | ||
|
|
471631933b | ||
|
|
cd761932c1 | ||
|
|
fd568b5300 | ||
|
|
f19f148297 | ||
|
|
79c6eb5503 | ||
|
|
1204bed80a | ||
|
|
dc91b1f7d6 | ||
|
|
66dc2b33de | ||
|
|
811942d45e | ||
|
|
dbde239a25 | ||
|
|
ab3ba3e2ca | ||
|
|
79e77b5a10 | ||
|
|
c3395133c9 | ||
|
|
b1d073cdeb | ||
|
|
25d723b373 | ||
|
|
e9783d050b | ||
|
|
e1be1ead86 | ||
|
|
b129bed69f | ||
|
|
265ad50ce4 | ||
|
|
3e2570601f |
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 |
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -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/
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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 };
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ tasks.prepareTestBuild = {
|
||||
'lib/',
|
||||
'locales/',
|
||||
'node_modules/',
|
||||
'*.ts',
|
||||
'*.tsx',
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
+62
-86
@@ -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
File diff suppressed because it is too large.
Load diff
+17
-17
@@ -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
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Generated
+93
-408
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
});
|
||||
// });
|
||||
@@ -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'));
|
||||
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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());
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
|
||||
});
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"*.js",
|
||||
"!test-utils.js"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": true,
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": true
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
JED0100002205a1a0987e82cc400c90582492f814c23c00137c{"iv":"4qbO48bUQNxEbyYaMlBjhA==","v":1,"iter":101,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Gyo7bQeqz2w=","ct":"N0NAMWtm3txzTZWG3x4rMvoqbI4Hjl+0i9EQTrigVnaq6zEvexlO6p0tnFiu/C0TGXyqx6fTctequ3vKJL/xMZQBAafW8fs+9/C0/bVupUvfmACxmTOhkBJclyNQn6KQLQrwyeRXGF3myLHjdB33482BPznYvn+NTuryJ8cS8jmDrW9+6d9nTrqKlwzxIs3vkY1f51mGgBS9itJLd+J3+gwfWu/FFuZa+auwzsMHZsEspm8jDwHnh0nWVSPO5Ufo6VzpnvJy+ieuRLW1aoZuRSFWIVb3s1BpWAZSW2vtLqo2Jgak8CpJLJ+kZu2XoT/nHyafpkJh/cRzjnehyyU58Bq0U0sgjKO3ToyY6IsJONtNLgS2selD6JNnGFiIByj5JTKr0cbwx5u9eLv4rX2VddA+FS0NWV36U3iCuMMZf9ar+1gyxjasjcsqXu8hDRB4esOD3QoFrGyHRDyv0DA8vSEMajfrqRGTtjb5b0vEjPIq2pRbdtkEMf357O71cVuEPStVsdMuFtZcSJsSeUR2uowA1Z6mKCzC9ACA+nif4dwiasNHRDslVKsMEKiXsdqUXEYUTjvcLHwyGIYggzEbfZdv/usz1ALWKhd9NNpekDOtv5jEXy+v19OKnFKXLIPU/pShsIqFMQ730eDS8cPJooPvxSsntFVKNt8LxbQYWdGGfu4F2dIUcjvmCfb4fDNp3KpwVsL0CXR/IGLavP9Ct4exijHSHrEKvXEEAXP8OIWppbvEyxaXkwG/N1g8cheeZeLkv8jvpYFWGLzVLZl9/PnNvhuxkDqImD918BLTKMIdunratnBd+58hWAG70hGWain/NbnC8wyvIlE+jjb39pQYNSUZ4/5eiIbNNb8AB68fqjw1ljDqqR7n61C1mjUtoOjzH6phOAlIfmRhYI0doVs1vuOZAaaATR1GiRolGMrXumjM0XYWNuVZ4WkfPSxLT5NkdHTDfSeR9JVO400Fe0XkuJ8K9/IxOMdTZ8F9t0f4+yXac31Ws1qTn454DDH9jK5UF5SUGpTrTAYMrNoq0wl/CPLmcO6W6wxm1KlU3rZ8XqVO62e6bLE7Sppy5+U+gD2pVGFvyf6Xz0hB6zkA60GhH986+MezPU6SjMThZJtIc0mMTRDPPO7RuZioMkuCdkwvcswAszQKr7E4xreRnNOQ8XPeYut9qV5uStXv59If8pKHD9TqgQUrX1cn97XyF9zptwwTjBtfj9tAQhhepVQlrnKK2278gH8KiwV7qCiUQ92PD4QF/Qm0fZMF7TsrGo0AlPf9HSCXsq+Umipikd6Pi02nwu7SwqQ6v+5qJE1GBVWK6gMYvcqp8/vnPvTXMII+GAkHya4Wi+80nxGRRcnhwXcVavM3M6D5Y/BCQoOnbmcSmlBSpiSIH2vD9egdBrhuEqilFHwAa7OQaEaRCGqpuhrGhhLv4EKdBc65z63AVh3rR+bY/NsHNOeFVraNStiXOozIlxqitEYc6aFgTpVsPFBbdW66hcanNGAhb9XpxZXBqGqnrLNXE1WdMyCYt+qoltYkPfawyBTSsOMR+ReNA0t+mWtl+2iTlzc2WK+6DQkMBNt0+5IbE6YoyTLh91s6LFoVvxqqRjIS8T9UiS/VV8ncD8Jkdgk5GecXpMV7Hu/XO3fUjZ16HGdyweybCyPVWbpR8e9TSPhl6si+ViboeNV7xN0G+ktmIeSNg2lkdBNS9wrjwfyj1HTiv8YS6wLl2+XpDI5ypcYnNlGO86YgsQhneEA2Bs+h0GlEP1o/uTnPDhi+rWPtoRfCr0wOOoYNogjwMkPSYRO01zokJsPu+nhocJk2WqMOE0OAM3tcyIUx2tLfxvVfiE+Yg/ZTyYK6DSs3u1Xn4kHEIP3+QIf7MD5zD8TaD9wHDj3JNMz4D1In4S+rzJweS/Om6cAC4ML5IKRY791hfItFI+Ujo01RevdKoh1EdE8j4H+8we0xpL/nbXM2wX3ja0WdwhG4n0Ix4qZWfi2dUYFVseU5ZR8EPLwXezxERp8EILIlWxSXHf1jlk49PL4Ko7hHrtEx+Iu6jiIn7Ys5FMDkS469/ZOLKs2ANicYhx/JSecRfwqP2AzbELaCytyP0G2L58i4crzD3IPdE+lwrgqBrgqvRgdUCuwQRF4JC+jku+i1rmADv3JpBNd/FfcmkKJZHn2Va8dsHr7pZG3Anmxb2hE2s88T62Hyv+F1CFBLMtUkb0aQ1wA9aHIsU0D3qdboTF1IQw4Wmin8jmqd5HJMeCASb8nnot/ounk42AAVLBiJ3vYX1rTjy0MD+CZ8Bn+Vj0ztCays3IY3G1NYD6zRCmd0rD+GmSzLy5JcGS3QzZZ1squV0F6xL2ZD4grOIwxeBrCgVCKj2uJSq7TB5UPTZ37wgcHYQzqpC2zmEf2Mrp6i/7ShwI448KYnrcZ4t/VokjGS3Z7AeE7+i39CdL2MUXJtEGQ6ftdUJF9fQu4jfrgZ+/sTrIdG5FGeZqEW5vMn9JIggThgCln+SIsS7abSnRCH6jGYthaV9ju0DCB3QcolCvZ0Poqw80N+fRDwPC0wUfoq5Ile7vEQ+Pe4wBMNngqbesL/fQeutSOMNrfJ1R7ZQ+QOq7ATlF4JCUvdqCvWTsYsMP9tkV1qMSmMaBXbStUAvKgscHbakG0O05C49TaW4+v8OLhgIXQ8kYUdo5V7YvRpPa4LfKCRdELNw1HLFuJO/ICzuzEMppiydMKILTXmr79DDvNv4rpnjcFmUG/DOD7k6ZCinbDW4wBi1e9PABFTvr5Q9iTfo1XwRq2VhGmGO602LmVYs+o+jfUw9/bgKsTKqBbexLM+Bk1dWz6Cs4p+rvIe/kPsjDsxbt/twgcqR/4VNzN3IOh+I9SiaAPTLXbKFouGbdqJSRgQFx1V/qT0aGnHybGliZRyAwT94CQCmPpIz2e2Ydy3UP1yKzKa3n1GrfEj0tq591sUzns2Kz2CTnrVM4rNF7XuzSbLGRiHDpz6g0N5NxI4N4xdF2TtNNGfzpNs79rOioRm9G/PKcbuUi4a8mAkINvU5XxXUGMAPaFddHfe38ZLmfJ7EHhwIokpkr2DF1ZcPEqzhHmFJHuSrvqvqR6El8bLEf4JCcR/RYaEFpUttwyn+Nixsg+FwNJrD7kEG2HUIR/qW0DEY41L7BPh5EfWNpgV/fXYR1I3U701GxBRW5jMF1vnPdC11coXjyJUWkD6EjHXaoEI9aTtPDUZ8PeoGkbCfIaSPPoj+J+g55ZfIRWV4aEEuYTqmdRfppYujX8iAldOBQrX69W7xRzm/JWsGaj19I2QR0/wRHX4fKP6kmqEOm7aKe7hyqr86xnrkn2/eSv+O0GEkE4Ygldvx6M5FaFeYLYVrPUT062qK9Pb8pWcpb0r0ZW/YAYX3S7bsVFds0JIVtTkwl6ibGASWMX2a/JDjx5gKOzPR1pAwUMgBXemJcA/Lzc1Mb05MdDie+u/Y5lO7YijzHTDzRl13AT0UI05YB3nAnnHeZbSJ2oxtdkXcrc74/MBz7AjKHfAakWuRb99Za3+koqctnjigi8ckLk08RtP7B4+PDWMtUUqeXrC4NSplegcmjyFP5DwKn74MfUt2P9/q321AVOsUlkSx+jR5F9C7dzYEUCb8v+guTCXjjxs28OBVsk2EAFohmudrBUIkfaS6NmJsU2H1H9O26SsZ4wGqj1cdLCNY0VPz8pDjvgINopbmKiO3SmVjhBmtEzwfUyhp79d8q/zDEF7osUPW5wtxowRaPIqSyPh1LXNKQn4n3JwAlPFROMgyQ5Hsp/yyzUlfJXWskWnOsbO4XD9KgIIt39YVOBnhXfzwLfWmynUKbzctIeb8oESFHdEkACxXjz3rW1oFXUWZu86mtO0jmkIMUKMI2ahq4N1kwYHg9vagMXx7MQzitLaHHM7zsYljwWs5zNtRBh1g3bd12WRszPMKHYfysIrHhIY4F/kyPWisSVmqmmXQkgBHNQIFQ9rJdkfj+4QNwOnCCS/JOvZWA3U80XlmZJnc36L3OqrlvY1adBob7ZV0E58o9hBQre6QbFcSwVD8inITfLoIohBz/a0p8Y8pzUc4X2wH+qck2CKgNFsv6BajMhf/0Sa4lGi9zotMegjQWh/TiuCQ2k6ZEBrSwYtPX8wqa+A6RYbVQ8eu40Mf1aTGBMX/YHPWCV8udtlOQx4I+9R+mnQnliNlzy9k8y6xtznFQ4b4qm4c+fUfQLOJ10Etm0xiiosnaXms1ypkxpkHNBMXs+zYSH2uAZxEwlcrCMIW9/IQGsDVMZENZf3J2F4iHtnjd0/jTUjDsWgvzjy24vETYv3ob7Wwz3BTcZfWK6PoylSi/9wAhuK3UDikPTaRy5PCFXfujR4jJmGe/TIsFyLIsQw8WDPGU6oQGAAPXg6Aq7ULiR/0kWbxfgdTZ1dBVhMwh3uPXDiYL84Gmipzc+2EMV9XKF5IFV1b81EzbBAseTaH8JbRS+6gjxXItair28JAf6bS5mnqKs/ODrlL/LMDWakj61CbtNZAWFs866Fl1OQBwqhmBIU8UNRutZf6E+W6TPmM7GmERSo9uABMio4W2NghfZbPkf5XAHBI3wo7E0jXwIM5k0FEZhy3uhIMMv200kGyft2LT6O9rGxSCo8KQHtvZmnV7P6wsWdMZvu53sm8nmX+kFADI3SOtxGVWdZtma7u0GyqjjR1TGn7kq+JUKzjMxKBH/QRFUFEFLCZgkV3HV8nbZTRNptd4ISk12xPWq1wBO1qTSrtF2FV0qtvM+Jov7nALc5qS+8YyalA7VXIF6oPl7yvb+i1fMPn8Ce9fZPJjJsRTLine truncated
|
||||
+1
@@ -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 @@
|
||||
2
|
||||
+24
@@ -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
|
||||
+24
@@ -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
|
||||
+24
@@ -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
|
||||
+10
@@ -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
|
||||
+24
@@ -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
|
||||
+10
@@ -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
|
||||
+14
@@ -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
|
||||
+10
@@ -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
|
||||
+11
@@ -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
|
||||
+24
@@ -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
|
||||
+10
@@ -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
|
||||
+10
@@ -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
|
||||
+10
@@ -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
|
||||
+11
@@ -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
|
||||
+10
@@ -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
|
||||
+14
@@ -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
|
||||
+11
@@ -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
|
||||
+11
@@ -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
|
||||
+11
@@ -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}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
BIN
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 @@
|
||||
2
|
||||
+16
@@ -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
|
||||
+11
@@ -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
|
||||
+11
@@ -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
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
note1
|
||||
|
||||

|
||||
|
||||
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
|
||||
+12
@@ -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
|
||||
+12
@@ -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
|
||||
+11
@@ -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
|
||||
+12
@@ -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
|
||||
+26
@@ -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
|
||||
+11
@@ -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
|
||||
+11
@@ -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
|
||||
+12
@@ -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
|
||||
+16
@@ -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
|
||||
+12
@@ -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
|
||||
+26
@@ -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
|
||||
+12
@@ -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
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
note5
|
||||
|
||||

|
||||
|
||||
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
|
||||
+12
@@ -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
|
||||
+26
@@ -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}
|
||||
@@ -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.
+176
-212
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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 };
|
||||
@@ -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());
|
||||
}));
|
||||
|
||||
});
|
||||
@@ -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
Reference in new issue
Block a user