Compare commits

..
Author SHA1 Message Date
Laurent Cozic e81427a1f2 Merge branch 'dev' into sync_batch_upload 2021-06-18 11:50:41 +01:00
Laurent Cozic 958e9163b6 All: Batch upload during initial sync 2021-06-17 12:45:34 +01:00
40 changed files with 233 additions and 718 deletions

No files matched your search

-9
View File
@@ -872,9 +872,6 @@ packages/lib/eventManager.js.map
packages/lib/file-api-driver-joplinServer.d.ts
packages/lib/file-api-driver-joplinServer.js
packages/lib/file-api-driver-joplinServer.js.map
packages/lib/file-api-driver-memory.d.ts
packages/lib/file-api-driver-memory.js
packages/lib/file-api-driver-memory.js.map
packages/lib/file-api-driver.test.d.ts
packages/lib/file-api-driver.test.js
packages/lib/file-api-driver.test.js.map
@@ -1394,12 +1391,6 @@ packages/lib/services/spellChecker/SpellCheckerService.js.map
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.d.ts
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js.map
packages/lib/services/synchronizer/ItemUploader.d.ts
packages/lib/services/synchronizer/ItemUploader.js
packages/lib/services/synchronizer/ItemUploader.js.map
packages/lib/services/synchronizer/ItemUploader.test.d.ts
packages/lib/services/synchronizer/ItemUploader.test.js
packages/lib/services/synchronizer/ItemUploader.test.js.map
packages/lib/services/synchronizer/LockHandler.d.ts
packages/lib/services/synchronizer/LockHandler.js
packages/lib/services/synchronizer/LockHandler.js.map
-9
View File
@@ -858,9 +858,6 @@ packages/lib/eventManager.js.map
packages/lib/file-api-driver-joplinServer.d.ts
packages/lib/file-api-driver-joplinServer.js
packages/lib/file-api-driver-joplinServer.js.map
packages/lib/file-api-driver-memory.d.ts
packages/lib/file-api-driver-memory.js
packages/lib/file-api-driver-memory.js.map
packages/lib/file-api-driver.test.d.ts
packages/lib/file-api-driver.test.js
packages/lib/file-api-driver.test.js.map
@@ -1380,12 +1377,6 @@ packages/lib/services/spellChecker/SpellCheckerService.js.map
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.d.ts
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
packages/lib/services/spellChecker/SpellCheckerServiceDriverBase.js.map
packages/lib/services/synchronizer/ItemUploader.d.ts
packages/lib/services/synchronizer/ItemUploader.js
packages/lib/services/synchronizer/ItemUploader.js.map
packages/lib/services/synchronizer/ItemUploader.test.d.ts
packages/lib/services/synchronizer/ItemUploader.test.js
packages/lib/services/synchronizer/ItemUploader.test.js.map
packages/lib/services/synchronizer/LockHandler.d.ts
packages/lib/services/synchronizer/LockHandler.js
packages/lib/services/synchronizer/LockHandler.js.map
+9 -14
View File
@@ -33,7 +33,6 @@ class Command extends BaseCommand {
return [
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
['--upgrade', _('Upgrade the sync target to the latest version.')],
['--use-lock <value>', 'Disable local locks that prevent multiple clients from synchronizing at the same time (Default = 1)'],
];
}
@@ -125,21 +124,17 @@ class Command extends BaseCommand {
const lockFilePath = `${require('os').tmpdir()}/synclock_${md5(escape(Setting.value('profileDir')))}`; // https://github.com/pvorb/node-md5/issues/41
if (!(await fs.pathExists(lockFilePath))) await fs.writeFile(lockFilePath, 'synclock');
const useLock = args.options.useLock !== 0;
try {
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
if (useLock) {
try {
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
this.releaseLockFn_ = await Command.lockFile(lockFilePath);
} catch (error) {
if (error.code == 'ELOCKED') {
const msg = _('Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at "%s" and resume the operation.', error.file);
this.stdout(msg);
return;
}
throw error;
this.releaseLockFn_ = await Command.lockFile(lockFilePath);
} catch (error) {
if (error.code == 'ELOCKED') {
const msg = _('Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at "%s" and resume the operation.', error.file);
this.stdout(msg);
return;
}
throw error;
}
const cleanUp = () => {
@@ -8,46 +8,25 @@ joplin.plugins.register({
iconName: 'fas fa-music',
});
await joplin.settings.registerSettings({
'myCustomSetting': {
value: 123,
type: SettingItemType.Int,
section: 'myCustomSection',
public: true,
label: 'My Custom Setting',
},
await joplin.settings.registerSetting('myCustomSetting', {
value: 123,
type: SettingItemType.Int,
section: 'myCustomSection',
public: true,
label: 'My Custom Setting',
});
'multiOptionTest': {
value: 'en',
type: SettingItemType.String,
section: 'myCustomSection',
isEnum: true,
public: true,
label: 'Multi-options test',
options: {
'en': 'English',
'fr': 'French',
'es': 'Spanish',
},
},
'mySecureSetting': {
value: 'hunter2',
type: SettingItemType.String,
section: 'myCustomSection',
public: true,
secure: true,
label: 'My Secure Setting',
},
'myFileSetting': {
value: 'abcd',
type: SettingItemType.String,
section: 'myCustomSection',
public: true,
label: 'My file setting',
description: 'This setting will be saved to settings.json',
['storage' as any]: 2, // Should be `storage: SettingStorage.File`
await joplin.settings.registerSetting('multiOptionTest', {
value: 'en',
type: SettingItemType.String,
section: 'myCustomSection',
isEnum: true,
public: true,
label: 'Multi-options test',
options: {
'en': 'English',
'fr': 'French',
'es': 'Spanish',
},
});
@@ -68,11 +47,7 @@ joplin.plugins.register({
iconName: 'fas fa-drum',
execute: async () => {
const value = await joplin.settings.value('myCustomSetting');
console.info('Current value is: ' + value);
const secureValue = await joplin.settings.value('mySecureSetting');
console.info('Secure value is: ' + secureValue);
const fileValue = await joplin.settings.value('myFileSetting');
console.info('Setting in file is: ' + fileValue);
alert('Current value is: ' + value);
},
});
@@ -5,9 +5,6 @@ set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../../../../.."
API_BASE_URL="https://test.joplincloud.com"
# API_BASE_URL="http://api.joplincloud.local:22300"
COMMANDS=($(echo $1 | tr "," "\n"))
PROFILE_DIR=~/.config/joplindev-testperf
@@ -19,7 +16,7 @@ for CMD in "${COMMANDS[@]}"
do
if [[ $CMD == "createUsers" ]]; then
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' $API_BASE_URL/api/debug
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
# elif [[ $CMD == "createData" ]]; then
@@ -35,7 +32,7 @@ do
rm -rf "$PROFILE_DIR"
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 9" >> "$CMD_FILE"
echo "config sync.9.path $API_BASE_URL" >> "$CMD_FILE"
echo "config sync.9.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.9.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.9.password 123456" >> "$CMD_FILE"
@@ -54,5 +51,5 @@ done
cd "$ROOT_DIR/packages/app-cli"
npm start -- --profile "$PROFILE_DIR" batch "$CMD_FILE"
npm start -- --profile "$PROFILE_DIR" import ~/Desktop/Joplin_17_06_2021.jex
# npm start -- --profile "$PROFILE_DIR" import ~/Desktop/Tout_18_06_2021.jex
npm start -- --profile "$PROFILE_DIR" sync --use-lock 1
npm start -- --profile "$PROFILE_DIR" sync
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.1.3",
"version": "2.0.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.1.3",
"version": "2.1.0",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
+1 -1
View File
@@ -4,5 +4,5 @@
# It could be used to develop plugins too.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/settings"
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/external_assets"
npm i --prefix="$PLUGIN_PATH" && npm start -- --dev-plugins "$PLUGIN_PATH"
+1 -1
View File
@@ -604,4 +604,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 6f558aa823c0fd07348253cc01996e4ee3de75c4
COCOAPODS: 1.8.4
COCOAPODS: 1.10.1
+1 -1
View File
@@ -1,5 +1,5 @@
module.exports = {
hash:"6608023b8053b48e0eec248644475e33", files: {
hash:"45572502e8b0a8e9b85192de7291684c", files: {
'highlight.js/atom-one-dark-reasonable.css': { data: require('./highlight.js/atom-one-dark-reasonable.css.base64.js'), mime: 'text/css', encoding: 'base64' },
'highlight.js/atom-one-light.css': { data: require('./highlight.js/atom-one-light.css.base64.js'), mime: 'text/css', encoding: 'base64' },
'katex/fonts/KaTeX_AMS-Regular.woff2': { data: require('./katex/fonts/KaTeX_AMS-Regular.woff2.base64.js'), mime: 'application/octet-stream', encoding: 'base64' },
@@ -1 +1 @@
module.exports = `IWZ1bmN0aW9uKHQsZSl7Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzJiYib2JqZWN0Ij09dHlwZW9mIG1vZHVsZT9tb2R1bGUuZXhwb3J0cz1lKCk6ImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoW10sZSk6Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzP2V4cG9ydHMubWVybWFpZD1lKCk6dC5tZXJtYWlkPWUoKX0oInVuZGVmaW5lZCIhPXR5cGVvZiBzZWxmP3NlbGY6dGhpcywoZnVuY3Rpb24oKXtyZXR1cm4gZnVuY3Rpb24odCl7dmFyIGU9e307ZnVuY3Rpb24gbihyKXtpZihlW3JdKXJldHVybiBlW3JdLmV4cG9ydHM7dmFyIGk9ZVtyXT17aTpyLGw6ITEsZXhwb3J0czp7fX07cmV0dXJuIHRbcl0uY2FsbChpLmV4cG9ydHMsaSxpLmV4cG9ydHMsbiksaS5sPSEwLGkuZXhwb3J0c31yZXR1cm4gbi5tPXQsbi5jPWUsbi5kPWZ1bmN0aW9uKHQsZSxyKXtuLm8odCxlKXx8T2JqZWN0LmRlZmluZVByb3BlcnR5KHQsZSx7ZW51bWVyYWJsZTohMCxnZXQ6cn0pfSxuLnI9ZnVuY3Rpb24odCl7InVuZGVmaW5lZCIhPXR5cGVvZiBTeW1ib2wmJlN5bWJvbC50b1N0cmluZ1RhZyYmT2JqZWN0LmRlZmluZVByb3BlcnR5KHQsU3ltYm9sLnRvU3RyaW5nVGFnLHt2YWx1ZToiTW9kdWxlIn0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KX0sbi50PWZ1bmN0aW9uKHQsZSl7aWYoMSZlJiYodD1uKHQpKSw4JmUpcmV0dXJuIHQ7aWYoNCZlJiYib2JqZWN0Ij09dHlwZW9mIHQmJnQmJnQuX19lc01vZHVsZSlyZXR1cm4gdDt2YXIgcj1PYmplY3QuY3JlYXRlKG51bGwpO2lmKG4ucihyKSxPYmplY3QuZGVmaW5lUHJvcGVydHkociwiZGVmYXVsdCIse2VudW1lcmFibGU6ITAsdmFsdWU6dH0pLDImZSYmInN0cmluZyIhPXR5cGVvZiB0KWZvcih2YXIgaSBpbiB0KW4uZChyLGksZnVuY3Rpb24oZSl7cmV0dXJuIHRbZV19LmJpbmQobnVsbCxpKSk7cmV0dXJuIHJ9LG4ubj1mdW5jdGlvbih0KXt2YXIgZT10JiZ0Ll9fZXNNb2R1bGU/ZnVuY3Rpb24oKXtyZXR1cm4gdC5kZWZhdWx0fTpmdW5jdGlvbigpe3JldHVybiB0fTtyZXR1cm4gbi5kKGUsImEiLGUpLGV9LG4ubz1mdW5jdGlvbih0LGUpe3JldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodCxlKX0sbi5wPSIiLG4obi5zPTM4NCl9KFtmdW5jdGlvbih0LGUsbil7InVzZSBzdHJpY3QiO24ucihlKTt2YXIgcj1mdW5jdGlvbih0LGUpe3JldHVybiB0PGU/LTE6dD5lPzE6dD49ZT8wOk5hTn0saT1mdW5jdGlvbih0KXt2YXIgZTtyZXR1cm4gMT09PXQubGVuZ3RoJiYoZT10LHQ9ZnVuY3Rpb24odCxuKXtyZXR1cm4gcihlKHQpLG4pfSkse2xlZnQ6ZnVuY3Rpb24oZSxuLHIsaSl7Zm9yKG51bGw9PXImJihyPTApLG51bGw9PWkmJihpPWUubGVuZ3RoKTtyPGk7KXt2YXIgYT1yK2k+Pj4xO3QoZVthXSxuKTwwP3I9YSsxOmk9YX1yZXR1cm4gcn0scmlnaHQ6ZnVuY3Rpb24oZSxuLHIsaSl7Zm9yKG51bGw9PXImJihyPTApLG51bGw9PWkmJihpPWUubGVuZ3RoKTtyPGk7KXt2YXIgYT1yK2k+Pj4xO3QoZVthXSxuKT4wP2k9YTpyPWErMX1yZXR1cm4gcn19fTt2YXIgYT1pKHIpLG89YS5yaWdodCxzPWEubGVmdCxjPW8sdT1mdW5jdGlvbih0LGUpe251bGw9PWUmJihlPWwpO2Zvcih2YXIgbj0wLHI9dC5sZW5ndGgtMSxpPXRbMF0sYT1uZXcgQXJyYXkocjwwPzA6cik7bjxyOylhW25dPWUoaSxpPXRbKytuXSk7cmV0dXJuIGF9O2Z1bmN0aW9uIGwodCxlKXtyZXR1cm5bdCxlXX12YXIgaD1mdW5jdGlvbih0LGUsbil7dmFyIHIsaSxhLG8scz10Lmxlbmd0aCxjPWUubGVuZ3RoLHU9bmV3IEFycmF5KHMqYyk7Zm9yKG51bGw9PW4mJihuPWwpLHI9YT0wO3I8czsrK3IpZm9yKG89dFtyXSxpPTA7aTxjOysraSwrK2EpdVthXT1uKG8sZVtpXSk7cmV0dXJuIHV9LGY9ZnVuY3Rpb24odCxlKXtyZXR1cm4gZTx0Py0xOmU+dD8xOmU+PXQ/MDpOYU59LGQ9ZnVuY3Rpb24odCl7cmV0dXJuIG51bGw9PT10P05hTjordH0scD1mdW5jdGlvbih0LGUpe3ZhciBuLHIsaT10Lmxlbmd0aCxhPTAsbz0tMSxzPTAsYz0wO2lmKG51bGw9PWUpZm9yKDsrK288aTspaXNOYU4obj1kKHRbb10pKXx8KGMrPShyPW4tcykqKG4tKHMrPXIvKythKSkpO2Vsc2UgZm9yKDsrK288aTspaXNOYU4obj1kKGUodFtvXSxvLHQpKSl8fChjKz0ocj1uLXMpKihuLShzKz1yLysrYSkpKTtpZihhPjEpcmV0dXJuIGMvKGEtMSl9LHk9ZnVuY3Rpb24odCxlKXt2YXIgbj1wKHQsZSk7cmV0dXJuIG4/TWF0aC5zcXJ0KG4pOm59LGc9ZnVuY3Rpb24odCxlKXt2YXIgbixyLGksYT10Lmxlbmd0aCxvPS0xO2lmKG51bGw9PWUpe2Zvcig7KytvPGE7KWlmKG51bGwhPShuPXRbb10pJiZuPj1uKWZvcihyPWk9bjsrK288YTspbnVsbCE9KG49dFtvXSkmJihyPm4mJihyPW4pLGk8biYmKGk9bikpfWVsc2UgZm9yKDsrK288YTspaWYobnVsbCE9KG49ZSh0W29dLG8sdCkpJiZuPj1uKWZvcihyPWk9bjsrK288YTspbnVsbCE9KG49ZSh0W29dLG8sdCkpJiYocj5uJiYocj1uKSxpPG4mJihpPW4pKTtyZXR1cm5bcixpXX0sdj1BcnJheS5wcm90b3R5cGUsbT12LnNsaWNlLGI9di5tYXAseD1mdW5jdGlvbih0KXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gdH19LF89ZnVuY3Rpb24odCl7cmV0dXJuIHR9LGs9ZnVuY3Rpb24odCxlLG4pe3Q9K3QsZT0rZSxuPShpPWFyZ3VtZW50cy5sZW5ndGgpPDI/KGU9dCx0PTAsMSk6aTwzPzE6K247Zm9yKHZhciByPS0xLGk9MHxNYXRoLm1heCgwLE1hdGguY2VpbCgoZS10KS9uKSksYT1uZXcgQXJyYXkoaSk7KytyPGk7KWFbcl09dCtyKm47cmV0dXJuIGF9LHc9TWF0aC5zcXJ0KDUwKSxFPU1hdGguc3FydCgxMCksVD1NYXRoLnNxcnQoMiksQz1mdW5jdGlvbih0LGUsbil7dmFyIHIsaSxhLG8scz0tMTtpZihuPStuLCh0PSt0KT09PShlPStlKSYmbj4wKXJldHVyblt0XTtpZigocj1lPHQpJiYoaT10LHQ9ZSxlPWkpLDA9PT0obz1TKHQsZSxuKSl8fCFpc0Zpbml0ZShvKSlyZXR1cm5bXTtpZihvPjApZm9yKHQ9TWF0aC5jZWlsKHQvbyksZT1NYXRoLmZsb29yKGUvbyksYT1uZXcgQXJyYXkoaT1NYXRoLmNlaWwoZS10KzEpKTsrK3M8aTspYVtzXT0odCtzKSpvO2Vsc2UgZm9yKHQ9TWF0aC5mbG9vcih0Km8pLGU9TWF0aC5jZWlsKGUqbyksYT1uZXcgQXJyYXkoaT1NYXRoLmNlaWwodC1lKzEpKTsrK3M8aTspYVtzXT0odC1zKS9vO3JldHVybiByJiZhLnJldmVyc2UoKSxhfTtmdW5jdGlvbiBTKHQsZSxuKXt2YXIgcj0oZS10KS9NYXRoLm1heCgwLG4pLGk9TWF0aC5mbG9vcihNYXRoLmxvZyhyKS9NYXRoLkxOMTApLGE9ci9NYXRoLnBvdygxMCxpKTtyZXR1cm4gaT49MD8oYT49dz8xMDphPj1FPzU6YT49VD8yOjEpKk1hdGgucG93KDEwLGkpOi1NYXRoLnBvdygxMCwtaSkvKGE+PXc/MTA6YT49RT81OmE+PVQ/MjoxKX1mdW5jdGlvbiBBKHQsZSxuKXt2YXIgcj1NYXRoLmFicyhlLXQpL01hdGgubWF4KDAsbiksaT1NYXRoLnBvdygxMCxNYXRoLmZsb29yKE1hdGgubG9nKHIpL01hdGguTE4xMCkpLGE9ci9pO3JldHVybiBhPj13P2kqPTEwOmE+PUU/aSo9NTphPj1UJiYoaSo9MiksZTx0Py1pOml9dmFyIE09ZnVuY3Rpb24odCl7cmV0dXJuIE1hdGguY2VpbChNYXRoLmxvZyh0Lmxlbmd0aCkvTWF0aC5MTjIpKzF9LE89ZnVuY3Rpb24oKXt2YXIgdD1fLGU9ZyxuPU07ZnVuY3Rpb24gcihyKXt2YXIgaSxhLG89ci5sZW5ndGgscz1uZXcgQXJyYXkobyk7Zm9yKGk9MDtpPG87KytpKXNbaV09dChyW2ldL Line truncated
module.exports = `IWZ1bmN0aW9uKHQsZSl7Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzJiYib2JqZWN0Ij09dHlwZW9mIG1vZHVsZT9tb2R1bGUuZXhwb3J0cz1lKCk6ImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoW10sZSk6Im9iamVjdCI9PXR5cGVvZiBleHBvcnRzP2V4cG9ydHMubWVybWFpZD1lKCk6dC5tZXJtYWlkPWUoKX0oInVuZGVmaW5lZCIhPXR5cGVvZiBzZWxmP3NlbGY6dGhpcywoZnVuY3Rpb24oKXtyZXR1cm4gZnVuY3Rpb24odCl7dmFyIGU9e307ZnVuY3Rpb24gbihyKXtpZihlW3JdKXJldHVybiBlW3JdLmV4cG9ydHM7dmFyIGk9ZVtyXT17aTpyLGw6ITEsZXhwb3J0czp7fX07cmV0dXJuIHRbcl0uY2FsbChpLmV4cG9ydHMsaSxpLmV4cG9ydHMsbiksaS5sPSEwLGkuZXhwb3J0c31yZXR1cm4gbi5tPXQsbi5jPWUsbi5kPWZ1bmN0aW9uKHQsZSxyKXtuLm8odCxlKXx8T2JqZWN0LmRlZmluZVByb3BlcnR5KHQsZSx7ZW51bWVyYWJsZTohMCxnZXQ6cn0pfSxuLnI9ZnVuY3Rpb24odCl7InVuZGVmaW5lZCIhPXR5cGVvZiBTeW1ib2wmJlN5bWJvbC50b1N0cmluZ1RhZyYmT2JqZWN0LmRlZmluZVByb3BlcnR5KHQsU3ltYm9sLnRvU3RyaW5nVGFnLHt2YWx1ZToiTW9kdWxlIn0pLE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0LCJfX2VzTW9kdWxlIix7dmFsdWU6ITB9KX0sbi50PWZ1bmN0aW9uKHQsZSl7aWYoMSZlJiYodD1uKHQpKSw4JmUpcmV0dXJuIHQ7aWYoNCZlJiYib2JqZWN0Ij09dHlwZW9mIHQmJnQmJnQuX19lc01vZHVsZSlyZXR1cm4gdDt2YXIgcj1PYmplY3QuY3JlYXRlKG51bGwpO2lmKG4ucihyKSxPYmplY3QuZGVmaW5lUHJvcGVydHkociwiZGVmYXVsdCIse2VudW1lcmFibGU6ITAsdmFsdWU6dH0pLDImZSYmInN0cmluZyIhPXR5cGVvZiB0KWZvcih2YXIgaSBpbiB0KW4uZChyLGksZnVuY3Rpb24oZSl7cmV0dXJuIHRbZV19LmJpbmQobnVsbCxpKSk7cmV0dXJuIHJ9LG4ubj1mdW5jdGlvbih0KXt2YXIgZT10JiZ0Ll9fZXNNb2R1bGU/ZnVuY3Rpb24oKXtyZXR1cm4gdC5kZWZhdWx0fTpmdW5jdGlvbigpe3JldHVybiB0fTtyZXR1cm4gbi5kKGUsImEiLGUpLGV9LG4ubz1mdW5jdGlvbih0LGUpe3JldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodCxlKX0sbi5wPSIiLG4obi5zPTM4Myl9KFtmdW5jdGlvbih0LGUsbil7InVzZSBzdHJpY3QiO24ucihlKTt2YXIgcj1mdW5jdGlvbih0LGUpe3JldHVybiB0PGU/LTE6dD5lPzE6dD49ZT8wOk5hTn0saT1mdW5jdGlvbih0KXt2YXIgZTtyZXR1cm4gMT09PXQubGVuZ3RoJiYoZT10LHQ9ZnVuY3Rpb24odCxuKXtyZXR1cm4gcihlKHQpLG4pfSkse2xlZnQ6ZnVuY3Rpb24oZSxuLHIsaSl7Zm9yKG51bGw9PXImJihyPTApLG51bGw9PWkmJihpPWUubGVuZ3RoKTtyPGk7KXt2YXIgYT1yK2k+Pj4xO3QoZVthXSxuKTwwP3I9YSsxOmk9YX1yZXR1cm4gcn0scmlnaHQ6ZnVuY3Rpb24oZSxuLHIsaSl7Zm9yKG51bGw9PXImJihyPTApLG51bGw9PWkmJihpPWUubGVuZ3RoKTtyPGk7KXt2YXIgYT1yK2k+Pj4xO3QoZVthXSxuKT4wP2k9YTpyPWErMX1yZXR1cm4gcn19fTt2YXIgYT1pKHIpLG89YS5yaWdodCxzPWEubGVmdCxjPW8sdT1mdW5jdGlvbih0LGUpe251bGw9PWUmJihlPWwpO2Zvcih2YXIgbj0wLHI9dC5sZW5ndGgtMSxpPXRbMF0sYT1uZXcgQXJyYXkocjwwPzA6cik7bjxyOylhW25dPWUoaSxpPXRbKytuXSk7cmV0dXJuIGF9O2Z1bmN0aW9uIGwodCxlKXtyZXR1cm5bdCxlXX12YXIgaD1mdW5jdGlvbih0LGUsbil7dmFyIHIsaSxhLG8scz10Lmxlbmd0aCxjPWUubGVuZ3RoLHU9bmV3IEFycmF5KHMqYyk7Zm9yKG51bGw9PW4mJihuPWwpLHI9YT0wO3I8czsrK3IpZm9yKG89dFtyXSxpPTA7aTxjOysraSwrK2EpdVthXT1uKG8sZVtpXSk7cmV0dXJuIHV9LGY9ZnVuY3Rpb24odCxlKXtyZXR1cm4gZTx0Py0xOmU+dD8xOmU+PXQ/MDpOYU59LGQ9ZnVuY3Rpb24odCl7cmV0dXJuIG51bGw9PT10P05hTjordH0scD1mdW5jdGlvbih0LGUpe3ZhciBuLHIsaT10Lmxlbmd0aCxhPTAsbz0tMSxzPTAsYz0wO2lmKG51bGw9PWUpZm9yKDsrK288aTspaXNOYU4obj1kKHRbb10pKXx8KGMrPShyPW4tcykqKG4tKHMrPXIvKythKSkpO2Vsc2UgZm9yKDsrK288aTspaXNOYU4obj1kKGUodFtvXSxvLHQpKSl8fChjKz0ocj1uLXMpKihuLShzKz1yLysrYSkpKTtpZihhPjEpcmV0dXJuIGMvKGEtMSl9LGc9ZnVuY3Rpb24odCxlKXt2YXIgbj1wKHQsZSk7cmV0dXJuIG4/TWF0aC5zcXJ0KG4pOm59LHk9ZnVuY3Rpb24odCxlKXt2YXIgbixyLGksYT10Lmxlbmd0aCxvPS0xO2lmKG51bGw9PWUpe2Zvcig7KytvPGE7KWlmKG51bGwhPShuPXRbb10pJiZuPj1uKWZvcihyPWk9bjsrK288YTspbnVsbCE9KG49dFtvXSkmJihyPm4mJihyPW4pLGk8biYmKGk9bikpfWVsc2UgZm9yKDsrK288YTspaWYobnVsbCE9KG49ZSh0W29dLG8sdCkpJiZuPj1uKWZvcihyPWk9bjsrK288YTspbnVsbCE9KG49ZSh0W29dLG8sdCkpJiYocj5uJiYocj1uKSxpPG4mJihpPW4pKTtyZXR1cm5bcixpXX0sdj1BcnJheS5wcm90b3R5cGUsbT12LnNsaWNlLGI9di5tYXAseD1mdW5jdGlvbih0KXtyZXR1cm4gZnVuY3Rpb24oKXtyZXR1cm4gdH19LF89ZnVuY3Rpb24odCl7cmV0dXJuIHR9LGs9ZnVuY3Rpb24odCxlLG4pe3Q9K3QsZT0rZSxuPShpPWFyZ3VtZW50cy5sZW5ndGgpPDI/KGU9dCx0PTAsMSk6aTwzPzE6K247Zm9yKHZhciByPS0xLGk9MHxNYXRoLm1heCgwLE1hdGguY2VpbCgoZS10KS9uKSksYT1uZXcgQXJyYXkoaSk7KytyPGk7KWFbcl09dCtyKm47cmV0dXJuIGF9LHc9TWF0aC5zcXJ0KDUwKSxFPU1hdGguc3FydCgxMCksVD1NYXRoLnNxcnQoMiksQz1mdW5jdGlvbih0LGUsbil7dmFyIHIsaSxhLG8scz0tMTtpZihuPStuLCh0PSt0KT09PShlPStlKSYmbj4wKXJldHVyblt0XTtpZigocj1lPHQpJiYoaT10LHQ9ZSxlPWkpLDA9PT0obz1TKHQsZSxuKSl8fCFpc0Zpbml0ZShvKSlyZXR1cm5bXTtpZihvPjApZm9yKHQ9TWF0aC5jZWlsKHQvbyksZT1NYXRoLmZsb29yKGUvbyksYT1uZXcgQXJyYXkoaT1NYXRoLmNlaWwoZS10KzEpKTsrK3M8aTspYVtzXT0odCtzKSpvO2Vsc2UgZm9yKHQ9TWF0aC5mbG9vcih0Km8pLGU9TWF0aC5jZWlsKGUqbyksYT1uZXcgQXJyYXkoaT1NYXRoLmNlaWwodC1lKzEpKTsrK3M8aTspYVtzXT0odC1zKS9vO3JldHVybiByJiZhLnJldmVyc2UoKSxhfTtmdW5jdGlvbiBTKHQsZSxuKXt2YXIgcj0oZS10KS9NYXRoLm1heCgwLG4pLGk9TWF0aC5mbG9vcihNYXRoLmxvZyhyKS9NYXRoLkxOMTApLGE9ci9NYXRoLnBvdygxMCxpKTtyZXR1cm4gaT49MD8oYT49dz8xMDphPj1FPzU6YT49VD8yOjEpKk1hdGgucG93KDEwLGkpOi1NYXRoLnBvdygxMCwtaSkvKGE+PXc/MTA6YT49RT81OmE+PVQ/MjoxKX1mdW5jdGlvbiBBKHQsZSxuKXt2YXIgcj1NYXRoLmFicyhlLXQpL01hdGgubWF4KDAsbiksaT1NYXRoLnBvdygxMCxNYXRoLmZsb29yKE1hdGgubG9nKHIpL01hdGguTE4xMCkpLGE9ci9pO3JldHVybiBhPj13P2kqPTEwOmE+PUU/aSo9NTphPj1UJiYoaSo9MiksZTx0Py1pOml9dmFyIE09ZnVuY3Rpb24odCl7cmV0dXJuIE1hdGguY2VpbChNYXRoLmxvZyh0Lmxlbmd0aCkvTWF0aC5MTjIpKzF9LE89ZnVuY3Rpb24oKXt2YXIgdD1fLGU9eSxuPU07ZnVuY3Rpb24gcihyKXt2YXIgaSxhLG89ci5sZW5ndGgscz1uZXcgQXJyYXkobyk7Zm9yKGk9MDtpPG87KytpKXNbaV09dChyW2ldL Line truncated
+1 -1
View File
@@ -1,7 +1,7 @@
const BaseSyncTarget = require('./BaseSyncTarget').default;
const Setting = require('./models/Setting').default;
const { FileApi } = require('./file-api.js');
const FileApiDriverMemory = require('./file-api-driver-memory').default;
const { FileApiDriverMemory } = require('./file-api-driver-memory.js');
const Synchronizer = require('./Synchronizer').default;
class SyncTargetMemory extends BaseSyncTarget {
+23 -49
View File
@@ -19,8 +19,7 @@ import EncryptionService from './services/EncryptionService';
import JoplinError from './JoplinError';
import ShareService from './services/share/ShareService';
import TaskQueue from './TaskQueue';
import ItemUploader from './services/synchronizer/ItemUploader';
import { FileApi } from './file-api';
import { preUploadItems, serializeAndUploadItem } from './services/synchronizer/uploadUtils';
const { sprintf } = require('sprintf-js');
const { Dirnames } = require('./services/synchronizer/utils/types');
@@ -28,18 +27,6 @@ interface RemoteItem {
id: string;
path?: string;
type_?: number;
isDeleted?: boolean;
// This the time when the file was created on the server. It is used for
// example for the locking mechanim or any file that's not an actual Joplin
// item.
updated_time?: number;
// This is the time that corresponds to the actual Joplin item updated_time
// value. A note is always uploaded with a delay so the server updated_time
// value will always be ahead. However for synchronising we need to know the
// exact Joplin item updated_time value.
jop_updated_time?: number;
}
function isCannotSyncError(error: any): boolean {
@@ -63,7 +50,7 @@ export default class Synchronizer {
public static verboseMode: boolean = true;
private db_: any;
private api_: FileApi;
private api_: any;
private appType_: string;
private logger_: Logger = new Logger();
private state_: string = 'idle';
@@ -87,7 +74,7 @@ export default class Synchronizer {
public dispatch: Function;
public constructor(db: any, api: FileApi, appType: string) {
constructor(db: any, api: any, appType: string) {
this.db_ = db;
this.api_ = api;
this.appType_ = appType;
@@ -97,8 +84,6 @@ export default class Synchronizer {
this.progressReport_ = {};
this.dispatch = function() {};
this.apiCall = this.apiCall.bind(this);
}
state() {
@@ -316,11 +301,11 @@ export default class Synchronizer {
return '';
}
private async apiCall(fnName: string, ...args: any[]) {
async apiCall(fnName: string, ...args: any[]) {
if (this.syncTargetIsLocked_) throw new JoplinError('Sync target is locked - aborting API call', 'lockError');
try {
const output = await (this.api() as any)[fnName](...args);
const output = await this.api()[fnName](...args);
return output;
} catch (error) {
const lockStatus = await this.lockErrorStatus_();
@@ -378,7 +363,7 @@ export default class Synchronizer {
this.dispatch({ type: 'SYNC_STARTED' });
eventManager.emit('syncStart');
this.logSyncOperation('starting', null, null, `Starting synchronisation to target ${syncTargetId}... supportsAccurateTimestamp = ${this.api().supportsAccurateTimestamp}; supportsMultiPut = ${this.api().supportsMultiPut} [${synchronizationId}]`);
this.logSyncOperation('starting', null, null, `Starting synchronisation to target ${syncTargetId}... [${synchronizationId}]`);
const handleCannotSyncItem = async (ItemClass: any, syncTargetId: any, item: any, cannotSyncReason: string, itemLocation: any = null) => {
await ItemClass.saveSyncDisabled(syncTargetId, item, cannotSyncReason, itemLocation);
@@ -405,7 +390,11 @@ export default class Synchronizer {
// correctly so as to share/unshare the right items.
await Folder.updateAllShareIds();
const itemUploader = new ItemUploader(this.api(), this.apiCall);
const uploadQueue = new TaskQueue('syncUpload', this.logger());
const uploadItem = (path: string, content: any) => {
return this.apiCall('put', path, content);
};
let errorToThrow = null;
let syncLock = null;
@@ -458,7 +447,7 @@ export default class Synchronizer {
const result = await BaseItem.itemsThatNeedSync(syncTargetId);
const locals = result.items;
await itemUploader.preUploadItems(result.items.filter((it: any) => result.neverSyncedItemIds.includes(it.id)));
await preUploadItems(uploadItem, uploadQueue, result.items.filter((it: any) => result.neverSyncedItemIds.includes(it.id)));
for (let i = 0; i < locals.length; i++) {
if (this.cancelling()) break;
@@ -608,7 +597,7 @@ export default class Synchronizer {
let canSync = true;
try {
if (this.testingHooks_.indexOf('notesRejectedByTarget') >= 0 && local.type_ === BaseModel.TYPE_NOTE) throw new JoplinError('Testing rejectedByTarget', 'rejectedByTarget');
await itemUploader.serializeAndUploadItem(ItemClass, path, local);
await serializeAndUploadItem(uploadItem, uploadQueue, ItemClass, path, local);
} catch (error) {
if (error && error.code === 'rejectedByTarget') {
await handleCannotSyncItem(ItemClass, syncTargetId, local, error.message);
@@ -638,6 +627,7 @@ export default class Synchronizer {
// above also doesn't use it because it fetches the whole remote object and read the
// more reliable 'updated_time' property. Basically remote.updated_time is deprecated.
// await this.api().setTimestamp(path, local.updated_time);
await ItemClass.saveSyncTime(syncTargetId, local, local.updated_time);
}
} else if (action == 'itemConflict') {
@@ -782,27 +772,16 @@ export default class Synchronizer {
logger: this.logger(),
});
const remotes: RemoteItem[] = listResult.items;
const remotes = listResult.items;
this.logSyncOperation('fetchingTotal', null, null, 'Fetching delta items from sync target', remotes.length);
const remoteIds = remotes.map(r => BaseItem.pathToId(r.path));
const locals = await BaseItem.loadItemsByIds(remoteIds);
for (const remote of remotes) {
if (this.cancelling()) break;
let needsToDownload = true;
if (this.api().supportsAccurateTimestamp) {
const local = locals.find(l => l.id === BaseItem.pathToId(remote.path));
if (local && local.updated_time === remote.jop_updated_time) needsToDownload = false;
}
if (needsToDownload) {
this.downloadQueue_.push(remote.path, async () => {
return this.apiCall('get', remote.path);
});
}
this.downloadQueue_.push(remote.path, async () => {
return this.apiCall('get', remote.path);
});
}
for (let i = 0; i < remotes.length; i++) {
@@ -824,10 +803,9 @@ export default class Synchronizer {
};
const path = remote.path;
const remoteId = BaseItem.pathToId(path);
let action = null;
let reason = '';
let local = locals.find(l => l.id === remoteId);
let local = await BaseItem.loadItemByPath(path);
let ItemClass = null;
let content = null;
@@ -846,14 +824,10 @@ export default class Synchronizer {
action = 'deleteLocal';
reason = 'remote has been deleted';
} else {
if (this.api().supportsAccurateTimestamp && remote.jop_updated_time === local.updated_time) {
// Nothing to do, and no need to fetch the content
} else {
content = await loadContent();
if (content && content.updated_time > local.updated_time) {
action = 'updateLocal';
reason = 'remote is more recent than local';
}
content = await loadContent();
if (content && content.updated_time > local.updated_time) {
action = 'updateLocal';
reason = 'remote is more recent than local';
}
}
}
+1 -16
View File
@@ -1,7 +1,5 @@
import { MultiPutItem } from './file-api';
import JoplinError from './JoplinError';
import JoplinServerApi from './JoplinServerApi';
import Setting from './models/Setting';
import { trimSlashes } from './path-utils';
// All input paths should be in the format: "path/to/file". This is converted to
@@ -33,14 +31,6 @@ export default class FileApiDriverJoplinServer {
return this.api_;
}
public get supportsMultiPut() {
return Setting.value('featureFlag.syncMultiPut');
}
public get supportsAccurateTimestamp() {
return Setting.value('featureFlag.syncAccurateTimestamps');
}
public requestRepeatCount() {
return 3;
}
@@ -49,8 +39,7 @@ export default class FileApiDriverJoplinServer {
const output = {
path: rootPath ? path.substr(rootPath.length + 1) : path,
updated_time: md.updated_time,
jop_updated_time: md.jop_updated_time,
isDir: false,
isDir: false, // !!md.is_directory,
isDeleted: isDeleted,
};
@@ -185,10 +174,6 @@ export default class FileApiDriverJoplinServer {
}
}
public async multiPut(items: MultiPutItem[], options: any = null) {
return this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);
}
public async delete(path: string) {
return this.api().exec('DELETE', this.apiFilePath_(path));
}
@@ -1,18 +1,14 @@
import time from './time';
const time = require('./time').default;
const fs = require('fs-extra');
import { basicDelta, MultiPutItem } from './file-api';
export default class FileApiDriverMemory {
private items_: any[];
private deletedItems_: any[];
const { basicDelta } = require('./file-api');
class FileApiDriverMemory {
constructor() {
this.items_ = [];
this.deletedItems_ = [];
}
encodeContent_(content: any) {
encodeContent_(content) {
if (content instanceof Buffer) {
return content.toString('base64');
} else {
@@ -20,31 +16,23 @@ export default class FileApiDriverMemory {
}
}
public get supportsMultiPut() {
return true;
}
public get supportsAccurateTimestamp() {
return true;
}
decodeContent_(content: any) {
decodeContent_(content) {
return Buffer.from(content, 'base64').toString('utf-8');
}
itemIndexByPath(path: string) {
itemIndexByPath(path) {
for (let i = 0; i < this.items_.length; i++) {
if (this.items_[i].path == path) return i;
}
return -1;
}
itemByPath(path: string) {
itemByPath(path) {
const index = this.itemIndexByPath(path);
return index < 0 ? null : this.items_[index];
}
newItem(path: string, isDir = false) {
newItem(path, isDir = false) {
const now = time.unixMs();
return {
path: path,
@@ -55,18 +43,18 @@ export default class FileApiDriverMemory {
};
}
stat(path: string) {
stat(path) {
const item = this.itemByPath(path);
return Promise.resolve(item ? Object.assign({}, item) : null);
}
async setTimestamp(path: string, timestampMs: number): Promise<any> {
async setTimestamp(path, timestampMs) {
const item = this.itemByPath(path);
if (!item) return Promise.reject(new Error(`File not found: ${path}`));
item.updated_time = timestampMs;
}
async list(path: string) {
async list(path) {
const output = [];
for (let i = 0; i < this.items_.length; i++) {
@@ -89,7 +77,7 @@ export default class FileApiDriverMemory {
});
}
async get(path: string, options: any) {
async get(path, options) {
const item = this.itemByPath(path);
if (!item) return Promise.resolve(null);
if (item.isDir) return Promise.reject(new Error(`${path} is a directory, not a file`));
@@ -105,13 +93,13 @@ export default class FileApiDriverMemory {
return output;
}
async mkdir(path: string) {
async mkdir(path) {
const index = this.itemIndexByPath(path);
if (index >= 0) return;
this.items_.push(this.newItem(path, true));
}
async put(path: string, content: any, options: any = null) {
async put(path, content, options = null) {
if (!options) options = {};
if (options.source === 'file') content = await fs.readFile(options.path);
@@ -121,38 +109,13 @@ export default class FileApiDriverMemory {
const item = this.newItem(path, false);
item.content = this.encodeContent_(content);
this.items_.push(item);
return item;
} else {
this.items_[index].content = this.encodeContent_(content);
this.items_[index].updated_time = time.unixMs();
return this.items_[index];
}
}
public async multiPut(items: MultiPutItem[], options: any = null) {
const output: any = {
items: {},
};
for (const item of items) {
try {
const processedItem = await this.put(`/root/${item.name}`, item.body, options);
output.items[item.name] = {
item: processedItem,
error: null,
};
} catch (error) {
output.items[item.name] = {
item: null,
error: error,
};
}
}
return output;
}
async delete(path: string) {
async delete(path) {
const index = this.itemIndexByPath(path);
if (index >= 0) {
const item = Object.assign({}, this.items_[index]);
@@ -163,10 +126,10 @@ export default class FileApiDriverMemory {
}
}
async move(oldPath: string, newPath: string): Promise<any> {
async move(oldPath, newPath) {
const sourceItem = this.itemByPath(oldPath);
if (!sourceItem) return Promise.reject(new Error(`Path not found: ${oldPath}`));
await this.delete(newPath); // Overwrite if newPath already exists
this.delete(newPath); // Overwrite if newPath already exists
sourceItem.path = newPath;
}
@@ -174,8 +137,8 @@ export default class FileApiDriverMemory {
this.items_ = [];
}
async delta(path: string, options: any = null) {
const getStatFn = async (path: string) => {
async delta(path, options = null) {
const getStatFn = async path => {
const output = this.items_.slice();
for (let i = 0; i < output.length; i++) {
const item = Object.assign({}, output[i]);
@@ -193,3 +156,5 @@ export default class FileApiDriverMemory {
this.items_ = [];
}
}
module.exports = { FileApiDriverMemory };
+6 -30
View File
@@ -11,11 +11,6 @@ const Mutex = require('async-mutex').Mutex;
const logger = Logger.create('FileApi');
export interface MultiPutItem {
name: string;
body: string;
}
function requestCanBeRepeated(error: any) {
const errorCode = typeof error === 'object' && error.code ? error.code : null;
@@ -86,26 +81,6 @@ class FileApi {
if (this.driver_.initialize) return this.driver_.initialize(this.fullPath(''));
}
// This can be true if the driver implements uploading items in batch. Will
// probably only be supported by Joplin Server.
public get supportsMultiPut(): boolean {
return !!this.driver().supportsMultiPut;
}
// This can be true when the sync target timestamps (updated_time) provided
// in the delta call are guaranteed to be accurate. That requires
// explicitely setting the timestamp, which is not done anymore on any sync
// target as it wasn't accurate (for example, the file system can't be
// relied on, and even OneDrive for some reason doesn't guarantee that the
// timestamp you set is what you get back).
//
// The only reliable one at the moment is Joplin Server since it reads the
// updated_time property directly from the item (it unserializes it
// server-side).
public get supportsAccurateTimestamp(): boolean {
return !!this.driver().supportsAccurateTimestamp;
}
async fetchRemoteDateOffset_() {
const tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;
const startTime = Date.now();
@@ -276,6 +251,12 @@ class FileApi {
if (!output) return output;
output.path = path;
return output;
// return this.driver_.stat(this.fullPath(path)).then((output) => {
// if (!output) return output;
// output.path = path;
// return output;
// });
}
// Returns UTF-8 encoded string by default, or a Response if `options.target = 'file'`
@@ -296,11 +277,6 @@ class FileApi {
return tryAndRepeat(() => this.driver_.put(this.fullPath(path), content, options), this.requestRepeatCount());
}
public async multiPut(items: MultiPutItem[], options: any = null) {
if (!this.driver().supportsMultiPut) throw new Error('Multi PUT not supported');
return tryAndRepeat(() => this.driver_.multiPut(items, options), this.requestRepeatCount());
}
delete(path: string) {
logger.debug(`delete ${this.fullPath(path)}`);
return tryAndRepeat(() => this.driver_.delete(this.fullPath(path)), this.requestRepeatCount());
+1 -1
View File
@@ -403,7 +403,7 @@ export default class BaseItem extends BaseModel {
return this.shareService_;
}
public static async serializeForSync(item: BaseItemEntity): Promise<string> {
public static async serializeForSync(item: BaseItemEntity) {
const ItemClass = this.itemClass(item);
const shownKeys = ItemClass.fieldNames();
shownKeys.push('type_');
+2 -25
View File
@@ -1242,21 +1242,6 @@ class Setting extends BaseModel {
appTypes: ['desktop'],
storage: SettingStorage.Database,
},
'featureFlag.syncAccurateTimestamps': {
value: false,
type: SettingItemType.Bool,
public: false,
storage: SettingStorage.File,
},
'featureFlag.syncMultiPut': {
value: false,
type: SettingItemType.Bool,
public: false,
storage: SettingStorage.File,
},
};
this.metadata_ = Object.assign(this.metadata_, this.customMetadata_);
@@ -1363,18 +1348,10 @@ class Setting extends BaseModel {
}
// Low-level method to load a setting directly from the database. Should not be used in most cases.
public static async loadOne(key: string): Promise<CacheItem> {
public static async loadOne(key: string) {
if (this.keyStorage(key) === SettingStorage.File) {
const fromFile = await this.fileHandler.load();
return {
key,
value: fromFile[key],
};
} else if (this.settingMetadata(key).secure) {
return {
key,
value: await this.keychainService().password(`setting.${key}`),
};
return fromFile[key];
} else {
return this.modelSelectOne('SELECT * FROM settings WHERE key = ?', [key]);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/lib",
"version": "2.1.0",
"version": "2.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -65,7 +65,6 @@ export default class JoplinSettings {
if ('minimum' in setting) internalSettingItem.minimum = setting.minimum;
if ('maximum' in setting) internalSettingItem.maximum = setting.maximum;
if ('step' in setting) internalSettingItem.step = setting.step;
if ('storage' in setting) internalSettingItem.storage = setting.storage;
await Setting.registerSetting(this.namespacedKey(key), internalSettingItem);
}
@@ -334,11 +334,6 @@ export enum SettingItemType {
Button = 6,
}
export enum SettingStorage {
Database = 1,
File = 2,
}
// Redefine a simplified interface to mask internal details
// and to remove function calls as they would have to be async.
export interface SettingItem {
@@ -398,11 +393,6 @@ export interface SettingItem {
minimum?: number;
maximum?: number;
step?: number;
/**
* Either store the setting in the database or in settings.json. Defaults to database.
*/
storage?: SettingStorage;
}
export interface SettingSection {
@@ -1,167 +0,0 @@
import { FileApi } from '../../file-api';
import BaseItem from '../../models/BaseItem';
import Note from '../../models/Note';
import { expectNotThrow, expectThrow, setupDatabaseAndSynchronizer, switchClient } from '../../testing/test-utils';
import time from '../../time';
import ItemUploader, { ApiCallFunction } from './ItemUploader';
interface ApiCall {
name: string;
args: any[];
}
function clearArray(a: any[]) {
a.splice(0, a.length);
}
function newFakeApi(): FileApi {
return { supportsMultiPut: true } as any;
}
function newFakeApiCall(callRecorder: ApiCall[], itemBodyCallback: Function = null): ApiCallFunction {
const apiCall = async (callName: string, ...args: any[]): Promise<any> => {
callRecorder.push({ name: callName, args });
if (callName === 'multiPut') {
const [batch] = args;
const output: any = { items: {} };
for (const item of batch) {
if (itemBodyCallback) {
output.items[item.name] = itemBodyCallback(item);
} else {
output.items[item.name] = {
item: item.body,
error: null,
};
}
}
return output;
}
};
return apiCall;
}
describe('synchronizer_ItemUplader', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await setupDatabaseAndSynchronizer(2);
await switchClient(1);
done();
});
it('should batch uploads and use the cache afterwards', (async () => {
const callRecorder: ApiCall[] = [];
const itemUploader = new ItemUploader(newFakeApi(), newFakeApiCall(callRecorder));
const notes = [
await Note.save({ title: '1' }),
await Note.save({ title: '2' }),
];
await itemUploader.preUploadItems(notes);
// There should be only one call to "multiPut" because the items have
// been batched.
expect(callRecorder.length).toBe(1);
expect(callRecorder[0].name).toBe('multiPut');
clearArray(callRecorder);
// Now if we try to upload the item it shouldn't call the API because it
// will use the cached item.
await itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[0]), notes[0]);
expect(callRecorder.length).toBe(0);
// Now try to process a note that hasn't been cached. In that case, it
// should call "PUT" directly.
const note3 = await Note.save({ title: '3' });
await itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(note3), note3);
expect(callRecorder.length).toBe(1);
expect(callRecorder[0].name).toBe('put');
}));
it('should not batch upload if the items are over the batch size limit', (async () => {
const callRecorder: ApiCall[] = [];
const itemUploader = new ItemUploader(newFakeApi(), newFakeApiCall(callRecorder));
itemUploader.maxBatchSize = 1;
const notes = [
await Note.save({ title: '1' }),
await Note.save({ title: '2' }),
];
await itemUploader.preUploadItems(notes);
expect(callRecorder.length).toBe(0);
}));
it('should not use the cache if the note has changed since the pre-upload', (async () => {
const callRecorder: ApiCall[] = [];
const itemUploader = new ItemUploader(newFakeApi(), newFakeApiCall(callRecorder));
const notes = [
await Note.save({ title: '1' }),
await Note.save({ title: '2' }),
];
await itemUploader.preUploadItems(notes);
clearArray(callRecorder);
await itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[0]), notes[0]);
expect(callRecorder.length).toBe(0);
await time.msleep(1);
notes[1] = await Note.save({ title: '22' }),
await itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[1]), notes[1]);
expect(callRecorder.length).toBe(1);
}));
it('should respect the max batch size', (async () => {
const callRecorder: ApiCall[] = [];
const itemUploader = new ItemUploader(newFakeApi(), newFakeApiCall(callRecorder));
const notes = [
await Note.save({ title: '1' }),
await Note.save({ title: '2' }),
await Note.save({ title: '3' }),
];
const noteSize = BaseItem.systemPath(notes[0]).length + (await Note.serializeForSync(notes[0])).length;
itemUploader.maxBatchSize = noteSize * 2;
// It should send two batches - one with two notes, and the second with
// only one note.
await itemUploader.preUploadItems(notes);
expect(callRecorder.length).toBe(2);
expect(callRecorder[0].args[0].length).toBe(2);
expect(callRecorder[1].args[0].length).toBe(1);
}));
it('should rethrow error for items within the batch', (async () => {
const callRecorder: ApiCall[] = [];
const notes = [
await Note.save({ title: '1' }),
await Note.save({ title: '2' }),
await Note.save({ title: '3' }),
];
// Simulates throwing an error on note 2
const itemBodyCallback = (item: any): any => {
if (item.name === BaseItem.systemPath(notes[1])) {
return { error: new Error('Could not save item'), item: null };
} else {
return { error: null, item: item.body };
}
};
const itemUploader = new ItemUploader(newFakeApi(), newFakeApiCall(callRecorder, itemBodyCallback));
await itemUploader.preUploadItems(notes);
await expectNotThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[0]), notes[0]));
await expectThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[1]), notes[1]));
await expectNotThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[2]), notes[2]));
}));
});
@@ -1,110 +0,0 @@
import { ModelType } from '../../BaseModel';
import { FileApi, MultiPutItem } from '../../file-api';
import Logger from '../../Logger';
import BaseItem, { ItemThatNeedSync } from '../../models/BaseItem';
const logger = Logger.create('ItemUploader');
export type ApiCallFunction = (fnName: string, ...args: any[])=> Promise<any>;
interface BatchItem extends MultiPutItem {
localItemUpdatedTime: number;
}
export default class ItemUploader {
private api_: FileApi;
private apiCall_: ApiCallFunction;
private preUploadedItems_: Record<string, any> = {};
private preUploadedItemUpdatedTimes_: Record<string, number> = {};
private maxBatchSize_ = 1 * 1024 * 1024; // 1MB;
public constructor(api: FileApi, apiCall: ApiCallFunction) {
this.api_ = api;
this.apiCall_ = apiCall;
}
public get maxBatchSize() {
return this.maxBatchSize_;
}
public set maxBatchSize(v: number) {
this.maxBatchSize_ = v;
}
public async serializeAndUploadItem(ItemClass: any, path: string, local: ItemThatNeedSync) {
const preUploadItem = this.preUploadedItems_[path];
if (preUploadItem) {
if (this.preUploadedItemUpdatedTimes_[path] !== local.updated_time) {
// Normally this should be rare as it can only happen if the
// item has been changed between the moment it was pre-uploaded
// and the moment where it's being processed by the
// synchronizer. It could happen for example for a note being
// edited just at the same time. In that case, we proceed with
// the regular upload.
logger.warn(`Pre-uploaded item updated_time has changed. It is going to be re-uploaded again: ${path} (From ${this.preUploadedItemUpdatedTimes_[path]} to ${local.updated_time})`);
} else {
if (preUploadItem.error) throw new Error(preUploadItem.error.message ? preUploadItem.error.message : 'Unknown pre-upload error');
return;
}
}
const content = await ItemClass.serializeForSync(local);
await this.apiCall_('put', path, content);
}
public async preUploadItems(items: ItemThatNeedSync[]) {
if (!this.api_.supportsMultiPut) return;
const itemsToUpload: BatchItem[] = [];
for (const local of items) {
// For resources, additional logic is necessary - in particular the blob
// should be uploaded before the metadata, so we can't batch process.
if (local.type_ === ModelType.Resource) continue;
const ItemClass = BaseItem.itemClass(local);
itemsToUpload.push({
name: BaseItem.systemPath(local),
body: await ItemClass.serializeForSync(local),
localItemUpdatedTime: local.updated_time,
});
}
let batchSize = 0;
let currentBatch: BatchItem[] = [];
const uploadBatch = async (batch: BatchItem[]) => {
for (const batchItem of batch) {
this.preUploadedItemUpdatedTimes_[batchItem.name] = batchItem.localItemUpdatedTime;
}
const response = await this.apiCall_('multiPut', batch);
this.preUploadedItems_ = {
...this.preUploadedItems_,
...response.items,
};
};
while (itemsToUpload.length) {
const itemToUpload = itemsToUpload.pop();
const itemSize = itemToUpload.name.length + itemToUpload.body.length;
// Although it should be rare, if the item itself is above the
// batch max size, we skip it. In that case it will be uploaded the
// regular way when the synchronizer calls `serializeAndUploadItem()`
if (itemSize > this.maxBatchSize) continue;
if (batchSize + itemSize > this.maxBatchSize) {
await uploadBatch(currentBatch);
batchSize = itemSize;
currentBatch = [itemToUpload];
} else {
batchSize += itemSize;
currentBatch.push(itemToUpload);
}
}
if (currentBatch.length) await uploadBatch(currentBatch);
}
}
@@ -0,0 +1,30 @@
import { ModelType } from '../../BaseModel';
import BaseItem, { ItemThatNeedSync } from '../../models/BaseItem';
import TaskQueue from '../../TaskQueue';
type UploadItem = (path: string, content: any)=> Promise<any>;
export async function serializeAndUploadItem(uploadItem: UploadItem, uploadQueue: TaskQueue, ItemClass: any, path: string, local: ItemThatNeedSync) {
if (uploadQueue && uploadQueue.taskExists(path)) {
return uploadQueue.taskResult(path);
}
const content = await ItemClass.serializeForSync(local);
return uploadItem(path, content);
}
export async function preUploadItems(uploadItem: UploadItem, uploadQueue: TaskQueue, items: ItemThatNeedSync[]) {
for (const local of items) {
// For resources, additional logic is necessary - in particular the blob
// should be uploaded before the metadata, so we can't batch process.
if (local.type_ === ModelType.Resource) continue;
const ItemClass = BaseItem.itemClass(local);
const path = BaseItem.systemPath(local);
uploadQueue.push(path, async () => {
await serializeAndUploadItem(uploadItem, null, ItemClass, path, local);
});
}
await uploadQueue.waitForAll();
}
+1 -1
View File
@@ -29,7 +29,7 @@ import Revision from '../models/Revision';
import MasterKey from '../models/MasterKey';
import BaseItem from '../models/BaseItem';
const { FileApi } = require('../file-api.js');
const FileApiDriverMemory = require('../file-api-driver-memory').default;
const { FileApiDriverMemory } = require('../file-api-driver-memory.js');
const { FileApiDriverLocal } = require('../file-api-driver-local.js');
const { FileApiDriverWebDav } = require('../file-api-driver-webdav.js');
const { FileApiDriverDropbox } = require('../file-api-driver-dropbox.js');
+5 -5
View File
@@ -1,4 +1,4 @@
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}("undefined"!=typeof self?self:this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=384)}([function(t,e,n){"use strict";n.r(e);var r=function(t,e){return t<e?-1:t>e?1:t>=e?0:NaN},i=function(t){var e;return 1===t.length&&(e=t,t=function(t,n){return r(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r<i;){var a=r+i>>>1;t(e[a],n)<0?r=a+1:i=a}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r<i;){var a=r+i>>>1;t(e[a],n)>0?i=a:r=a+1}return r}}};var a=i(r),o=a.right,s=a.left,c=o,u=function(t,e){null==e&&(e=l);for(var n=0,r=t.length-1,i=t[0],a=new Array(r<0?0:r);n<r;)a[n]=e(i,i=t[++n]);return a};function l(t,e){return[t,e]}var h=function(t,e,n){var r,i,a,o,s=t.length,c=e.length,u=new Array(s*c);for(null==n&&(n=l),r=a=0;r<s;++r)for(o=t[r],i=0;i<c;++i,++a)u[a]=n(o,e[i]);return u},f=function(t,e){return e<t?-1:e>t?1:e>=t?0:NaN},d=function(t){return null===t?NaN:+t},p=function(t,e){var n,r,i=t.length,a=0,o=-1,s=0,c=0;if(null==e)for(;++o<i;)isNaN(n=d(t[o]))||(c+=(r=n-s)*(n-(s+=r/++a)));else for(;++o<i;)isNaN(n=d(e(t[o],o,t)))||(c+=(r=n-s)*(n-(s+=r/++a)));if(a>1)return c/(a-1)},y=function(t,e){var n=p(t,e);return n?Math.sqrt(n):n},g=function(t,e){var n,r,i,a=t.length,o=-1;if(null==e){for(;++o<a;)if(null!=(n=t[o])&&n>=n)for(r=i=n;++o<a;)null!=(n=t[o])&&(r>n&&(r=n),i<n&&(i=n))}else for(;++o<a;)if(null!=(n=e(t[o],o,t))&&n>=n)for(r=i=n;++o<a;)null!=(n=e(t[o],o,t))&&(r>n&&(r=n),i<n&&(i=n));return[r,i]},v=Array.prototype,m=v.slice,b=v.map,x=function(t){return function(){return t}},_=function(t){return t},k=function(t,e,n){t=+t,e=+e,n=(i=arguments.length)<2?(e=t,t=0,1):i<3?1:+n;for(var r=-1,i=0|Math.max(0,Math.ceil((e-t)/n)),a=new Array(i);++r<i;)a[r]=t+r*n;return a},w=Math.sqrt(50),E=Math.sqrt(10),T=Math.sqrt(2),C=function(t,e,n){var r,i,a,o,s=-1;if(n=+n,(t=+t)===(e=+e)&&n>0)return[t];if((r=e<t)&&(i=t,t=e,e=i),0===(o=S(t,e,n))||!isFinite(o))return[];if(o>0)for(t=Math.ceil(t/o),e=Math.floor(e/o),a=new Array(i=Math.ceil(e-t+1));++s<i;)a[s]=(t+s)*o;else for(t=Math.floor(t*o),e=Math.ceil(e*o),a=new Array(i=Math.ceil(t-e+1));++s<i;)a[s]=(t-s)/o;return r&&a.reverse(),a};function S(t,e,n){var r=(e-t)/Math.max(0,n),i=Math.floor(Math.log(r)/Math.LN10),a=r/Math.pow(10,i);return i>=0?(a>=w?10:a>=E?5:a>=T?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=w?10:a>=E?5:a>=T?2:1)}function A(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),a=r/i;return a>=w?i*=10:a>=E?i*=5:a>=T&&(i*=2),e<t?-i:i}var M=function(t){return Math.ceil(Math.log(t.length)/Math.LN2)+1},O=function(){var t=_,e=g,n=M;function r(r){var i,a,o=r.length,s=new Array(o);for(i=0;i<o;++i)s[i]=t(r[i],i,r);var u=e(s),l=u[0],h=u[1],f=n(s,l,h);Array.isArray(f)||(f=A(l,h,f),f=k(Math.ceil(l/f)*f,h,f));for(var d=f.length;f[0]<=l;)f.shift(),--d;for(;f[d-1]>h;)f.pop(),--d;var p,y=new Array(d+1);for(i=0;i<=d;++i)(p=y[i]=[]).x0=i>0?f[i-1]:l,p.x1=i<d?f[i]:h;for(i=0;i<o;++i)l<=(a=s[i])&&a<=h&&y[c(f,a,0,d)].push(r[i]);return y}return r.value=function(e){return arguments.length?(t="function"==typeof e?e:x(e),r):t},r.domain=function(t){return arguments.length?(e="function"==typeof t?t:x([t[0],t[1]]),r):e},r.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?x(m.call(t)):x(t),r):n},r},B=function(t,e,n){if(null==n&&(n=d),r=t.length){if((e=+e)<=0||r<2)return+n(t[0],0,t);if(e>=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,a=Math.floor(i),o=+n(t[a],a,t);return o+(+n(t[a+1],a+1,t)-o)*(i-a)}},N=function(t,e,n){return t=b.call(t,d).sort(r),Math.ceil((n-e)/(2*(B(t,.75)-B(t,.25))*Math.pow(t.length,-1/3)))},D=function(t,e,n){return Math.ceil((n-e)/(3.5*y(t)*Math.pow(t.length,-1/3)))},L=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a<i;)if(null!=(n=t[a])&&n>=n)for(r=n;++a<i;)null!=(n=t[a])&&n>r&&(r=n)}else for(;++a<i;)if(null!=(n=e(t[a],a,t))&&n>=n)for(r=n;++a<i;)null!=(n=e(t[a],a,t))&&n>r&&(r=n);return r},I=function(t,e)Line truncated
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}("undefined"!=typeof self?self:this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=383)}([function(t,e,n){"use strict";n.r(e);var r=function(t,e){return t<e?-1:t>e?1:t>=e?0:NaN},i=function(t){var e;return 1===t.length&&(e=t,t=function(t,n){return r(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r<i;){var a=r+i>>>1;t(e[a],n)<0?r=a+1:i=a}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r<i;){var a=r+i>>>1;t(e[a],n)>0?i=a:r=a+1}return r}}};var a=i(r),o=a.right,s=a.left,c=o,u=function(t,e){null==e&&(e=l);for(var n=0,r=t.length-1,i=t[0],a=new Array(r<0?0:r);n<r;)a[n]=e(i,i=t[++n]);return a};function l(t,e){return[t,e]}var h=function(t,e,n){var r,i,a,o,s=t.length,c=e.length,u=new Array(s*c);for(null==n&&(n=l),r=a=0;r<s;++r)for(o=t[r],i=0;i<c;++i,++a)u[a]=n(o,e[i]);return u},f=function(t,e){return e<t?-1:e>t?1:e>=t?0:NaN},d=function(t){return null===t?NaN:+t},p=function(t,e){var n,r,i=t.length,a=0,o=-1,s=0,c=0;if(null==e)for(;++o<i;)isNaN(n=d(t[o]))||(c+=(r=n-s)*(n-(s+=r/++a)));else for(;++o<i;)isNaN(n=d(e(t[o],o,t)))||(c+=(r=n-s)*(n-(s+=r/++a)));if(a>1)return c/(a-1)},g=function(t,e){var n=p(t,e);return n?Math.sqrt(n):n},y=function(t,e){var n,r,i,a=t.length,o=-1;if(null==e){for(;++o<a;)if(null!=(n=t[o])&&n>=n)for(r=i=n;++o<a;)null!=(n=t[o])&&(r>n&&(r=n),i<n&&(i=n))}else for(;++o<a;)if(null!=(n=e(t[o],o,t))&&n>=n)for(r=i=n;++o<a;)null!=(n=e(t[o],o,t))&&(r>n&&(r=n),i<n&&(i=n));return[r,i]},v=Array.prototype,m=v.slice,b=v.map,x=function(t){return function(){return t}},_=function(t){return t},k=function(t,e,n){t=+t,e=+e,n=(i=arguments.length)<2?(e=t,t=0,1):i<3?1:+n;for(var r=-1,i=0|Math.max(0,Math.ceil((e-t)/n)),a=new Array(i);++r<i;)a[r]=t+r*n;return a},w=Math.sqrt(50),E=Math.sqrt(10),T=Math.sqrt(2),C=function(t,e,n){var r,i,a,o,s=-1;if(n=+n,(t=+t)===(e=+e)&&n>0)return[t];if((r=e<t)&&(i=t,t=e,e=i),0===(o=S(t,e,n))||!isFinite(o))return[];if(o>0)for(t=Math.ceil(t/o),e=Math.floor(e/o),a=new Array(i=Math.ceil(e-t+1));++s<i;)a[s]=(t+s)*o;else for(t=Math.floor(t*o),e=Math.ceil(e*o),a=new Array(i=Math.ceil(t-e+1));++s<i;)a[s]=(t-s)/o;return r&&a.reverse(),a};function S(t,e,n){var r=(e-t)/Math.max(0,n),i=Math.floor(Math.log(r)/Math.LN10),a=r/Math.pow(10,i);return i>=0?(a>=w?10:a>=E?5:a>=T?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=w?10:a>=E?5:a>=T?2:1)}function A(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),a=r/i;return a>=w?i*=10:a>=E?i*=5:a>=T&&(i*=2),e<t?-i:i}var M=function(t){return Math.ceil(Math.log(t.length)/Math.LN2)+1},O=function(){var t=_,e=y,n=M;function r(r){var i,a,o=r.length,s=new Array(o);for(i=0;i<o;++i)s[i]=t(r[i],i,r);var u=e(s),l=u[0],h=u[1],f=n(s,l,h);Array.isArray(f)||(f=A(l,h,f),f=k(Math.ceil(l/f)*f,h,f));for(var d=f.length;f[0]<=l;)f.shift(),--d;for(;f[d-1]>h;)f.pop(),--d;var p,g=new Array(d+1);for(i=0;i<=d;++i)(p=g[i]=[]).x0=i>0?f[i-1]:l,p.x1=i<d?f[i]:h;for(i=0;i<o;++i)l<=(a=s[i])&&a<=h&&g[c(f,a,0,d)].push(r[i]);return g}return r.value=function(e){return arguments.length?(t="function"==typeof e?e:x(e),r):t},r.domain=function(t){return arguments.length?(e="function"==typeof t?t:x([t[0],t[1]]),r):e},r.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?x(m.call(t)):x(t),r):n},r},D=function(t,e,n){if(null==n&&(n=d),r=t.length){if((e=+e)<=0||r<2)return+n(t[0],0,t);if(e>=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,a=Math.floor(i),o=+n(t[a],a,t);return o+(+n(t[a+1],a+1,t)-o)*(i-a)}},N=function(t,e,n){return t=b.call(t,d).sort(r),Math.ceil((n-e)/(2*(D(t,.75)-D(t,.25))*Math.pow(t.length,-1/3)))},B=function(t,e,n){return Math.ceil((n-e)/(3.5*g(t)*Math.pow(t.length,-1/3)))},L=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a<i;)if(null!=(n=t[a])&&n>=n)for(r=n;++a<i;)null!=(n=t[a])&&n>r&&(r=n)}else for(;++a<i;)if(null!=(n=e(t[a],a,t))&&n>=n)for(r=n;++a<i;)null!=(n=e(t[a],a,t))&&n>r&&(r=n);return r},P=function(t,e)Line truncated
/**
* @license
* Copyright (c) 2012-2013 Chris Pettitt
@@ -21,12 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
t.exports={graphlib:n(312),dagre:n(154),intersect:n(369),render:n(371),util:n(14),version:n(383)}},function(t,e,n){"use strict";var r=n(4),i=n(20).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=u*o/s,r=u):(o<0&&(c=-c),n=c,r=c*s/o);return{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e,n){t.exports={graphlib:n(20),layout:n(313),debug:n(367),util:{time:n(8).time,notime:n(8).notime},version:n(368)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(173),i=n(174),a=n(175),o={channel:r.default,lang:i.default,unit:a.default};e.default=o},function(t,e){var n,r,i=t.exports={};function a(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(t){n=a}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(t){r=o}}();var c,u=[],l=!1,h=-1;function f(){l&&c&&(l=!1,c.length?u=c.concat(u):h=-1,u.length&&d())}function d(){if(!l){var t=s(f);l=!0;for(var e=u.length;e;){for(c=u,u=[];++h<e;)c&&c[h].run();h=-1,e=u.length}c=null,l=!1,function(t){if(r===clearTimeout)return clearTimeout(t);if((r===o||!r)&&clearTimeout)return r=clearTimeout,clearTimeout(t);try{r(t)}catch(e){try{return r.call(null,t)}catch(e){return r.call(this,t)}}}(t)}}function p(t,e){this.fun=t,this.array=e}function y(){}i.nextTick=function(t){var e=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)e[n-1]=arguments[n];u.push(new p(t,e)),1!==u.length||l||s(d)},p.prototype.run=function(){this.fun.apply(null,this.array)},i.title="browser",i.browser=!0,i.env={},i.argv=[],i.version="",i.versions={},i.on=y,i.addListener=y,i.once=y,i.off=y,i.removeListener=y,i.removeAllListeners=y,i.emit=y,i.prependListener=y,i.prependOnceListener=y,i.listeners=function(t){return[]},i.binding=function(t){throw new Error("process.binding is not supported")},i.cwd=function(){return"/"},i.chdir=function(t){throw new Error("process.chdir is not supported")},i.umask=function(){return 0}},function(t,e,n){var r;try{r={clone:n(200),constant:n(87),each:n(88),filter:n(129),has:n(94),isArray:n(5),isEmpty:n(277),isFunction:n(38),isUndefined:n(140),keys:n(30),map:n(141),reduce:n(143),size:n(280),transform:n(286),union:n(287),values:n(148)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;returLine truncated
t.exports={graphlib:n(311),dagre:n(153),intersect:n(368),render:n(370),util:n(12),version:n(382)}},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=u*o/s,r=u):(o<0&&(c=-c),n=c,r=c*s/o);return{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(173),i=n(174),a=n(175),o={channel:r.default,lang:i.default,unit:a.default};e.default=o},function(t,e,n){var r;try{r={clone:n(199),constant:n(86),each:n(87),filter:n(128),has:n(93),isArray:n(5),isEmpty:n(276),isFunction:n(37),isUndefined:n(139),keys:n(30),map:n(140),reduce:n(142),size:n(279),transform:n(285),union:n(286),values:n(147)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){var r=n(43);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return a(t.v)+":"+a(t.w)+":"+a(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function a(t){return t?String(t).replace(i,"\\:"):""}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,7],n=[1,6],r=[1,14],i=[1,25],a=[1,28],o=[1,26],s=[1,27],c=[1,29],u=[1,30],l=[1,31],h=[1,33],f=[1,34],d=[1,35],p=[10,19],g=[1,47],y=[1,48],v=[1,49],m=[1,50],b=[1,51],x=[1,52],_=[10,19,25,32,33,41,44,45,46,47,48,49],k=[10,19,23,25,32,33,37,41,44,45,46,47,48,49,66,67,68],w=[10,13,17,19],E=[41,66,67,68],T=[41,48,49,66,67,68],C=[41,44,45,46,47,66,67,68],S=[10,19,25],A=[1,81],M={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,graphConfig:6,openDirective:7,typeDirective:8,closeDirective:9,NEWLINE:10,":":11,argDirective:12,open_directive:13,type_directive:14,arg_directive:15,close_directive:16,CLASS_DIAGRAM:17,statements:18,EOF:19,statement:20,className:21,alphaNumToken:22,GENERICTYPE:23,relationStatement:24,LABEL:25,classStatement:26,methodStatement:27,annotationStatement:28,clickStatement:29,cssClassStatement:30,CLASLine truncated
/*!
* Wait for document loaded before starting the execution
*/
window.addEventListener("load",(function(){ms()}),!1);var bs={startOnLoad:!0,htmlLabels:!0,mermaidAPI:vs,parse:vs.parse,render:vs.render,init:function(){var t,e,n=this,r=vs.getConfig();arguments.length>=2?(
window.addEventListener("load",(function(){Go()}),!1);var qo={startOnLoad:!0,htmlLabels:!0,mermaidAPI:Ho,parse:Ho.parse,render:Ho.render,init:function(){var t,e,n=this,r=Ho.getConfig();arguments.length>=2?(
/*! sequence config was passed as #1 */
void 0!==arguments[0]&&(bs.sequenceConfig=arguments[0]),t=arguments[1]):t=arguments[0],"function"==typeof arguments[arguments.length-1]?(e=arguments[arguments.length-1],c.debug("Callback function found")):void 0!==r.mermaid&&("function"==typeof r.mermaid.callback?(e=r.mermaid.callback,c.debug("Callback function found")):c.debug("No Callback function found")),t=void 0===t?document.querySelectorAll(".mermaid"):"string"==typeof t?document.querySelectorAll(t):t instanceof window.Node?[t]:t,c.debug("Start On Load before: "+bs.startOnLoad),void 0!==bs.startOnLoad&&(c.debug("Start On Load inner: "+bs.startOnLoad),vs.updateSiteConfig({startOnLoad:bs.startOnLoad})),void 0!==bs.ganttConfig&&vs.updateSiteConfig({gantt:bs.ganttConfig});for(var a,o=new V.initIdGeneratior(r.deterministicIds,r.deterministicIDSeed),s=function(r){var s=t[r];
/*! Check if previously processed */if(s.getAttribute("data-processed"))return"continue";s.setAttribute("data-processed",!0);var u="mermaid-".concat(o.next());a=i(a=s.innerHTML).trim().replace(/<br\s*\/?>/gi,"<br/>");var l=V.detectInit(a);l&&c.debug("Detected early reinit: ",l);try{vs.render(u,a,(function(t,n){s.innerHTML=t,void 0!==e&&e(u),n&&n(s)}),s)}catch(t){c.warn("Syntax Error rendering"),c.warn(t),n.parseError&&n.parseError(t)}},u=0;u<t.length;u++)s(u)},initialize:function(t){void 0!==t.mermaid&&(void 0!==t.mermaid.startOnLoad&&(bs.startOnLoad=t.mermaid.startOnLoad),void 0!==t.mermaid.htmlLabels&&(bs.htmlLabels="false"!==t.mermaid.htmlLabels&&!1!==t.mermaid.htmlLabels)),vs.initialize(t)},contentLoaded:ms};e.default=bs}]).default}));
void 0!==arguments[0]&&(qo.sequenceConfig=arguments[0]),t=arguments[1]):t=arguments[0],"function"==typeof arguments[arguments.length-1]?(e=arguments[arguments.length-1],c.debug("Callback function found")):void 0!==r.mermaid&&("function"==typeof r.mermaid.callback?(e=r.mermaid.callback,c.debug("Callback function found")):c.debug("No Callback function found")),t=void 0===t?document.querySelectorAll(".mermaid"):"string"==typeof t?document.querySelectorAll(t):t instanceof window.Node?[t]:t,c.debug("Start On Load before: "+qo.startOnLoad),void 0!==qo.startOnLoad&&(c.debug("Start On Load inner: "+qo.startOnLoad),Ho.updateSiteConfig({startOnLoad:qo.startOnLoad})),void 0!==qo.ganttConfig&&Ho.updateSiteConfig({gantt:qo.ganttConfig});for(var a,o=V.initIdGeneratior(r.deterministicIds,r.deterministicIDSeed).next,s=function(r){var s=t[r];
/*! Check if previously processed */if(s.getAttribute("data-processed"))return"continue";s.setAttribute("data-processed",!0);var u="mermaid-".concat(o());a=i(a=s.innerHTML).trim().replace(/<br\s*\/?>/gi,"<br/>");var l=V.detectInit(a);l&&c.debug("Detected early reinit: ",l);try{Ho.render(u,a,(function(t,n){s.innerHTML=t,void 0!==e&&e(u),n&&n(s)}),s)}catch(t){c.warn("Syntax Error rendering"),c.warn(t),n.parseError&&n.parseError(t)}},u=0;u<t.length;u++)s(u)},initialize:function(t){void 0!==t.mermaid&&(void 0!==t.mermaid.startOnLoad&&(qo.startOnLoad=t.mermaid.startOnLoad),void 0!==t.mermaid.htmlLabels&&(qo.htmlLabels=t.mermaid.htmlLabels)),Ho.initialize(t)},contentLoaded:Go};e.default=qo}]).default}));
//# sourceMappingURL=mermaid.min.js.map
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/renderer",
"version": "2.1.0",
"version": "2.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -4757,9 +4757,9 @@
}
},
"khroma": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/khroma/-/khroma-1.4.1.tgz",
"integrity": "sha512-+GmxKvmiRuCcUYDgR7g5Ngo0JEDeOsGdNONdU2zsiBQaK4z19Y2NvXqfEDE0ZiIrg45GTZyAnPLVsLZZACYm3Q=="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/khroma/-/khroma-1.1.0.tgz",
"integrity": "sha512-aTO+YX22tYOLEQJYFiatAj1lc5QZ+H5sHWFRBWNCiKwc5NWNUJZyeSeiHEPeURJ2a1GEVYcmyMUwGjjLe5ec5A=="
},
"kind-of": {
"version": "6.0.3",
@@ -4998,9 +4998,9 @@
"dev": true
},
"mermaid": {
"version": "8.10.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.10.2.tgz",
"integrity": "sha512-Za5MrbAOMbEsyY4ONgGjfYz06sbwF1iNGRzp1sQqpOtvXxjxGu/J1jRJ8QyE9kD/D9zj1/KlRrYegWEvA7eZ5Q==",
"version": "8.8.4",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.8.4.tgz",
"integrity": "sha512-YPn35uEAIrOcsDPjCiKNXXBdO1Aoazsv2zTZjG4+oXa7+tTVUb5sI81NqaTYa47RnoH9Vl4waLlEEJfB8KM9VA==",
"requires": {
"@braintree/sanitize-url": "^3.1.0",
"d3": "^5.7.0",
@@ -6539,9 +6539,9 @@
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"uglify-js": {
"version": "3.13.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz",
"integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g=="
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz",
"integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ=="
},
"union-value": {
"version": "1.0.1",
+1 -1
View File
@@ -45,7 +45,7 @@
"markdown-it-sup": "^1.0.0",
"markdown-it-toc-done-right": "^4.1.0",
"md5": "^2.2.1",
"mermaid": "^8.10.2",
"mermaid": "^8.8.4",
"uslug": "git+https://github.com/laurent22/uslug.git#emoji-support"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.1.3",
"version": "2.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.1.3",
"version": "2.1.1",
"private": true,
"scripts": {
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",
Binary file not shown.
-2
View File
@@ -356,7 +356,6 @@ export interface Item extends WithDates, WithUuid {
jop_share_id?: Uuid;
jop_type?: number;
jop_encryption_applied?: number;
jop_updated_time?: number;
}
export interface UserItem extends WithDates {
@@ -504,7 +503,6 @@ export const databaseSchema: DatabaseTables = {
jop_share_id: { type: 'string' },
jop_type: { type: 'number' },
jop_encryption_applied: { type: 'number' },
jop_updated_time: { type: 'number' },
},
user_items: {
id: { type: 'number' },
@@ -1,29 +0,0 @@
import { Knex } from 'knex';
import { DbConnection } from '../db';
export async function up(db: DbConnection): Promise<any> {
await db.schema.alterTable('items', function(table: Knex.CreateTableBuilder) {
table.bigInteger('jop_updated_time').defaultTo(0).notNullable();
});
while (true) {
const items = await db('items')
.select('id', 'content')
.where('jop_type', '>', 0)
.andWhere('jop_updated_time', '=', 0)
.limit(1000);
if (!items.length) break;
await db.transaction(async trx => {
for (const item of items) {
const unserialized = JSON.parse(item.content);
await trx('items').update({ jop_updated_time: unserialized.updated_time }).where('id', '=', item.id);
}
});
}
}
export async function down(_db: DbConnection): Promise<any> {
}
+9 -18
View File
@@ -5,12 +5,14 @@ import { ErrorResyncRequired } from '../utils/errors';
import BaseModel, { SaveOptions } from './BaseModel';
import { PaginatedResults, Pagination, PaginationOrderDir } from './utils/pagination';
export interface DeltaChange extends Change {
jop_updated_time?: number;
export interface ChangeWithItem {
item: Item;
updated_time: number;
type: ChangeType;
}
export interface PaginatedChanges extends PaginatedResults {
items: DeltaChange[];
items: Change[];
}
export interface ChangePagination {
@@ -156,20 +158,9 @@ export default class ChangeModel extends BaseModel<Change> {
.orderBy('counter', 'asc')
.limit(pagination.limit) as any[];
const changes: Change[] = await query;
const changes = await query;
const items: Item[] = await this.db('items').select('id', 'jop_updated_time').whereIn('items.id', changes.map(c => c.item_id));
let finalChanges: DeltaChange[] = this.compressChanges(changes);
finalChanges = await this.removeDeletedItems(finalChanges, items);
finalChanges = finalChanges.map(c => {
const item = items.find(item => item.id === c.item_id);
if (!item) return c;
return {
...c,
jop_updated_time: item.jop_updated_time,
};
});
const finalChanges = await this.removeDeletedItems(this.compressChanges(changes));
return {
items: finalChanges,
@@ -180,14 +171,14 @@ export default class ChangeModel extends BaseModel<Change> {
};
}
private async removeDeletedItems(changes: Change[], items: Item[] = null): Promise<Change[]> {
private async removeDeletedItems(changes: Change[]): Promise<Change[]> {
const itemIds = changes.map(c => c.item_id);
// We skip permission check here because, when an item is shared, we need
// to fetch files that don't belong to the current user. This check
// would not be needed anyway because the change items are generated in
// a context where permissions have already been checked.
items = items === null ? await this.db('items').select('id').whereIn('items.id', itemIds) : items;
const items: Item[] = await this.db('items').select('id').whereIn('items.id', itemIds);
const output: Change[] = [];
-3
View File
@@ -285,7 +285,6 @@ export default class ItemModel extends BaseModel<Item> {
item.share_id = itemRow.jop_share_id;
item.type_ = itemRow.jop_type;
item.encryption_applied = itemRow.jop_encryption_applied;
item.updated_time = itemRow.jop_updated_time;
return item;
}
@@ -337,7 +336,6 @@ export default class ItemModel extends BaseModel<Item> {
item.jop_type = joplinItem.type_;
item.jop_encryption_applied = joplinItem.encryption_applied || 0;
item.jop_share_id = joplinItem.share_id || '';
item.jop_updated_time = joplinItem.updated_time;
const joplinItemToSave = { ...joplinItem };
@@ -346,7 +344,6 @@ export default class ItemModel extends BaseModel<Item> {
delete joplinItemToSave.share_id;
delete joplinItemToSave.type_;
delete joplinItemToSave.encryption_applied;
delete joplinItemToSave.updated_time;
item.content = Buffer.from(JSON.stringify(joplinItemToSave));
} else {
+1 -8
View File
@@ -5,17 +5,14 @@ import Router from '../../utils/Router';
import { RouteType } from '../../utils/types';
import { AppContext } from '../../utils/types';
import * as fs from 'fs-extra';
import { ErrorForbidden, ErrorMethodNotAllowed, ErrorNotFound, ErrorPayloadTooLarge } from '../../utils/errors';
import { ErrorForbidden, ErrorMethodNotAllowed, ErrorNotFound } from '../../utils/errors';
import ItemModel, { ItemSaveOption, SaveFromRawContentItem } from '../../models/ItemModel';
import { requestDeltaPagination, requestPagination } from '../../models/utils/pagination';
import { AclAction } from '../../models/BaseModel';
import { safeRemove } from '../../utils/fileUtils';
import { formatBytes, MB } from '../../utils/bytes';
const router = new Router(RouteType.Api);
const batchMaxSize = 1 * MB;
export async function putItemContents(path: SubPath, ctx: AppContext, isBatch: boolean) {
if (!ctx.owner.can_upload) throw new ErrorForbidden('Uploading content is disabled');
@@ -26,16 +23,12 @@ export async function putItemContents(path: SubPath, ctx: AppContext, isBatch: b
let items: SaveFromRawContentItem[] = [];
if (isBatch) {
let totalSize = 0;
items = bodyFields.items.map((item: any) => {
totalSize += item.name.length + (item.body ? item.body.length : 0);
return {
name: item.name,
body: item.body ? Buffer.from(item.body, 'utf8') : Buffer.alloc(0),
};
});
if (totalSize > batchMaxSize) throw new ErrorPayloadTooLarge(`Size of items (${formatBytes(totalSize)}) is over the limit (${formatBytes(batchMaxSize)})`);
} else {
const filePath = parsedBody?.files?.file ? parsedBody.files.file.path : null;
+5 -8
View File
@@ -1,10 +1,9 @@
import { execCommand2, rootDir } from './tool-utils';
function getVersionFromTag(tagName: string, isPreRelease: boolean): string {
function getVersionFromTag(tagName: string): string {
if (tagName.indexOf('server-') !== 0) throw new Error(`Invalid tag: ${tagName}`);
const s = tagName.split('-');
const suffix = isPreRelease ? '-beta' : '';
return s[1].substr(1) + suffix;
return s[1].substr(1);
}
function getIsPreRelease(tagName: string): boolean {
@@ -16,8 +15,8 @@ async function main() {
if (!argv.tagName) throw new Error('--tag-name not provided');
const tagName = argv.tagName;
const imageVersion = getVersionFromTag(tagName);
const isPreRelease = getIsPreRelease(tagName);
const imageVersion = getVersionFromTag(tagName, isPreRelease);
process.chdir(rootDir);
console.info(`Running from: ${process.cwd()}`);
@@ -27,12 +26,10 @@ async function main() {
console.info('isPreRelease:', isPreRelease);
await execCommand2(`docker build -t "joplin/server:${imageVersion}" -f Dockerfile.server .`);
await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:latest"`);
await execCommand2(`docker push joplin/server:${imageVersion}`);
if (!isPreRelease) {
await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:latest"`);
await execCommand2('docker push joplin/server:latest');
}
if (!isPreRelease) await execCommand2('docker push joplin/server:latest');
}
main().catch((error) => {
+63 -63
View File
@@ -14,17 +14,15 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Francisco Mora <francisco.m.collao@gmail.com>\n"
"Last-Translator: Mario Campo <mario.campo@gmail.com>\n"
"Language-Team: Spanish <lucas.vieites@gmail.com>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0\n"
"X-Generator: Poedit 2.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: packages/app-desktop/bridge.js:106 packages/app-desktop/bridge.js:110
#: packages/app-desktop/bridge.js:126 packages/app-desktop/bridge.js:134
@@ -133,11 +131,11 @@ msgstr "Descargar"
#: packages/app-desktop/checkForUpdates.js:189
msgid "Skip this version"
msgstr "Omitir esta versión"
msgstr ""
#: packages/app-desktop/checkForUpdates.js:189
msgid "Full changelog"
msgstr "Registro de cambios completo"
msgstr ""
#: packages/app-desktop/gui/NoteRevisionViewer.min.js:75
#: packages/lib/services/RevisionService.js:242
@@ -282,12 +280,14 @@ msgid "Retry"
msgstr "Reintentar"
#: packages/app-desktop/gui/StatusScreen/StatusScreen.js:137
#, fuzzy
msgid "Advanced tools"
msgstr "Herramientas avanzadas"
msgstr "Opciones avanzadas"
#: packages/app-desktop/gui/StatusScreen/StatusScreen.js:139
#, fuzzy
msgid "Export debug report"
msgstr "Exportar Informe de depuración"
msgstr "Exportar Informe de Depuración"
#: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/CodeMirror.js:183
msgid "strong text"
@@ -345,23 +345,23 @@ msgstr "Casillas"
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:17
msgid "Highlight"
msgstr "Destacar"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:22
msgid "Strikethrough"
msgstr "Tachado"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:27
msgid "Insert"
msgstr "Insertar"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:33
msgid "Superscript"
msgstr "Superindice"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:39
msgid "Subscript"
msgstr "Subíndice"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js:564
#: packages/app-mobile/components/screens/Note.js:1016
@@ -527,8 +527,9 @@ msgid "Delete line"
msgstr "Borrar línea"
#: packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js:92
#, fuzzy
msgid "Duplicate line"
msgstr "Duplicar linea"
msgstr "Duplicar"
#: packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js:96
msgid "Undo"
@@ -721,12 +722,10 @@ msgid ""
"Safe mode is currently active. Note rendering and all plugins are "
"temporarily disabled."
msgstr ""
"El modo seguro está activo actualmente. La representación de notas y todos "
"los complementos están temporalmente deshabilitados."
#: packages/app-desktop/gui/MainScreen/MainScreen.js:447
msgid "Disable safe mode and restart"
msgstr "Desactivar el modo seguro y reiniciar"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:451
msgid ""
@@ -772,15 +771,15 @@ msgstr "Más información"
#: packages/app-desktop/gui/MainScreen/MainScreen.js:477
#, javascript-format
msgid "%s (%s) would like to share a notebook with you."
msgstr "%s (%s) le gustaría compartir una libreta contigo."
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:479
msgid "Accept"
msgstr "Aceptar"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:481
msgid "Reject"
msgstr "Rechazar"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:485
msgid "Some items cannot be synchronised."
@@ -866,8 +865,9 @@ msgid "Toggle editors"
msgstr "Alternar editores"
#: packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js:16
#, fuzzy
msgid "Share notebook..."
msgstr "Compartir libreta..."
msgstr "Compartir nota..."
#: packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js:16
msgid "Change application layout"
@@ -947,7 +947,7 @@ msgstr "¡El token ha sido copiado al portapapeles!"
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:44
msgid "Are you sure you want to renew the authorisation token?"
msgstr "¿Está seguro de que desea renovar el token de autorización?"
msgstr ""
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:84
msgid "The web clipper service is enabled and set to auto-start."
@@ -1031,7 +1031,7 @@ msgstr ""
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:222
msgid "Renew token"
msgstr "Renovar token"
msgstr ""
#: packages/app-desktop/gui/MenuBar.js:167
#, javascript-format
@@ -1124,8 +1124,9 @@ msgid "&Go"
msgstr "&Ir"
#: packages/app-desktop/gui/MenuBar.js:631
#, fuzzy
msgid "Note&book"
msgstr "Libreta"
msgstr "Libretas"
#: packages/app-desktop/gui/MenuBar.js:637
msgid "&Note"
@@ -1523,12 +1524,13 @@ msgid "You do not have any installed plugin."
msgstr "No tiene ningún plugin instalado."
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:232
#, fuzzy
msgid "Could not connect to plugin repository"
msgstr "No se pudo conectar con el repositorio del plugin: %s"
msgstr "No se pudo instalar el plugin: %s"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:234
msgid "Try again"
msgstr "Intenta nuevamente"
msgstr ""
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:242
msgid "Plugin tools"
@@ -1716,8 +1718,9 @@ msgstr ""
"(límite: %s)."
#: packages/app-desktop/gui/ShareNoteDialog.js:141
#, fuzzy
msgid "Unshare note"
msgstr "Dejar de compartir nota"
msgstr "Compartir"
#: packages/app-desktop/gui/ShareNoteDialog.js:168
msgid "Synchronising..."
@@ -1752,20 +1755,19 @@ msgstr[0] "Copiar Enlace Compartible"
msgstr[1] "Copiar Enlaces Compartible"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:138
#, fuzzy
msgid "Unshare"
msgstr "Dejar de compartir"
msgstr "Compartir"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:180
msgid ""
"Delete this invitation? The recipient will no longer have access to this "
"shared notebook."
msgstr ""
"¿Eliminar esta invitación? El destinatario ya no tendrá acceso a esta "
"libreta compartida."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:194
msgid "Add recipient:"
msgstr "Agregar destinatario:"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:197
#: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:28
@@ -1775,43 +1777,45 @@ msgstr "Compartir"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:206
msgid "Recipient has not yet accepted the invitation"
msgstr "El destinatario aún no ha aceptado la invitación"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:207
msgid "Recipient has rejected the invitation"
msgstr "El destinatario ha rechazado la invitación"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:208
msgid "Recipient has accepted the invitation"
msgstr "El destinatario ha aceptado la invitación"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:218
msgid "Recipients:"
msgstr "Destinatarios:"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:230
#, fuzzy
msgid "Synchronizing..."
msgstr "Sincronizando..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:231
#, fuzzy
msgid "Sharing notebook..."
msgstr "Compartir libreta..."
msgstr "Compartir nota..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:241
msgid ""
"Unshare this notebook? The recipients will no longer have access to its "
"content."
msgstr ""
"¿Dejar de compartir esta libreta? Los destinatarios ya no tendrán acceso a "
"su contenido."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:251
#, fuzzy
msgid "Share Notebook"
msgstr "Compartir Libreta"
msgstr "Compartir Notas"
#: packages/app-desktop/commands/toggleSafeMode.js:18
#, fuzzy
msgid "Toggle safe mode"
msgstr "Alternar modo seguro"
msgstr "Alternar la barra lateral"
#: packages/app-desktop/commands/toggleExternalEditing.js:18
msgid "Toggle external editing"
@@ -1903,7 +1907,7 @@ msgstr "Configuración"
#: packages/app-mobile/components/side-menu-content.js:351
msgid "Mobile data - auto-sync disabled"
msgstr "Datos móviles: sincronización automática deshabilitada"
msgstr ""
#: packages/app-mobile/components/note-list.js:97
msgid "You currently have no notebooks."
@@ -2253,15 +2257,15 @@ msgstr ""
"cambiada! [Cambiarla ahora](%s)"
#: packages/server/dist/models/UserModel.js:134
#, fuzzy
msgid "attachment"
msgstr "adjunto"
msgstr "Adjuntos"
#: packages/server/dist/models/UserModel.js:134
#, javascript-format
msgid ""
"Cannot save %s \"%s\" because it is larger than than the allowed limit (%s)"
msgstr ""
"No se puede guardar %s \"%s\" porque es mayor que el límite permitido (%s)"
#: packages/lib/onedrive-api-node-utils.js:46
#, javascript-format
@@ -2452,20 +2456,23 @@ msgid "Joplin Server URL"
msgstr "URL del Servidor de Joplin"
#: packages/lib/models/Setting.js:327
#, fuzzy
msgid "Joplin Server email"
msgstr "Email del servidor Joplin"
msgstr "Servidor de Joplin"
#: packages/lib/models/Setting.js:338
msgid "Joplin Server password"
msgstr "Contraseña del Servidor de Joplin"
#: packages/lib/models/Setting.js:365
#, fuzzy
msgid "Joplin Cloud email"
msgstr "Email del servidor Joplin"
msgstr "Servidor de Joplin"
#: packages/lib/models/Setting.js:376
#, fuzzy
msgid "Joplin Cloud password"
msgstr "Contraseña del servidor Joplin"
msgstr "Contraseña del Servidor de Joplin"
#: packages/lib/models/Setting.js:388
msgid "Attachment download behaviour"
@@ -2707,12 +2714,11 @@ msgid ""
"Used for most text in the markdown editor. If not found, a generic "
"proportional (variable width) font is used."
msgstr ""
"Se utiliza para la mayor parte del texto en el editor markdown. Si no se "
"encuentra, se utiliza una fuente genérica proporcional (ancho variable)."
#: packages/lib/models/Setting.js:758
#, fuzzy
msgid "Editor monospace font family"
msgstr "Familia de fuente monoespaciada del editor"
msgstr "Familia de fuente del editor"
#: packages/lib/models/Setting.js:759
msgid ""
@@ -2720,10 +2726,6 @@ msgid ""
"tables, checkboxes, code). If not found, a generic monospace (fixed width) "
"font is used."
msgstr ""
"Se utiliza cuando se necesita una fuente de ancho fijo para presentar el "
"texto de manera legible (por ejemplo, tablas, casillas de verificación, "
"código). Si no se encuentra, se utiliza una fuente genérica monoespaciada "
"(ancho fijo)."
#: packages/lib/models/Setting.js:780
msgid "Custom stylesheet for rendered Markdown"
@@ -2735,13 +2737,11 @@ msgstr "Hoja de estilos para personalizar todo Joplin"
#: packages/lib/models/Setting.js:806
msgid "Re-upload local data to sync target"
msgstr "Vuelva a cargar datos locales para sincronizar el destino"
msgstr ""
#: packages/lib/models/Setting.js:816
msgid "Delete local data and re-download from sync target"
msgstr ""
"Elimine los datos locales y vuelva a descargarlos desde el destino de "
"sincronización"
#: packages/lib/models/Setting.js:821
msgid "Automatically update the application"
@@ -2778,7 +2778,7 @@ msgstr "%d horas"
#: packages/lib/models/Setting.js:849
msgid "Synchronise only over WiFi connection"
msgstr "Sincronizar solo a través de una conexión WiFi"
msgstr ""
#: packages/lib/models/Setting.js:856
msgid "Text editor command"
@@ -3064,8 +3064,9 @@ msgid "Encrypted items cannot be modified"
msgstr "Los elementos cifrados no pueden ser modificados"
#: packages/lib/SyncTargetJoplinCloud.js:28
#, fuzzy
msgid "Joplin Cloud"
msgstr "Nube de Joplin"
msgstr "Foro de Joplin"
#: packages/lib/BaseApplication.js:152 packages/lib/BaseApplication.js:164
#: packages/lib/BaseApplication.js:196
@@ -3223,15 +3224,16 @@ msgstr ""
"Joplin a la última versión"
#: packages/lib/JoplinServerApi.js:80
#, javascript-format
#, fuzzy, javascript-format
msgid ""
"Could not connect to Joplin Server. Please check the Synchronisation options "
"in the config screen. Full error was:\n"
"\n"
"%s"
msgstr ""
"No se pudo conectar al servidor Joplin. Compruebe las opciones de "
"sincronización en la pantalla de configuración. El error completo fue:\n"
"No se pudo conectar con la aplicación de Joplin de Nextcloud. Verifique la "
"configuración en la pantalla de configuración de Sincronización. El error "
"completo fue:\n"
"\n"
"%s"
@@ -3906,8 +3908,6 @@ msgid ""
"Runs the commands contained in the text file. There should be one command "
"per line."
msgstr ""
"Ejecuta los comandos contenidos en el archivo de texto. Debe haber un "
"comando por línea."
#: packages/app-cli/app/command-version.js:11
msgid "Displays version information"
+12 -8
View File
@@ -6,15 +6,13 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: 南宫小骏 <jackytsu@vip.qq.com>\n"
"Language-Team: zh_CN <jackytsu.vip.qq.com>\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0\n"
"X-Generator: Poedit 2.4.3\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: packages/app-desktop/bridge.js:106 packages/app-desktop/bridge.js:110
@@ -1089,8 +1087,9 @@ msgid "&Go"
msgstr "跳转 (&G)"
#: packages/app-desktop/gui/MenuBar.js:631
#, fuzzy
msgid "Note&book"
msgstr "笔记本 (&B)"
msgstr "笔记本"
#: packages/app-desktop/gui/MenuBar.js:637
msgid "&Note"
@@ -1659,8 +1658,9 @@ msgid "Warning: not all resources shown for performance reasons (limit: %s)."
msgstr "警告: 由于性能原因无法显示所有资源 (最多:%s)。"
#: packages/app-desktop/gui/ShareNoteDialog.js:141
#, fuzzy
msgid "Unshare note"
msgstr "取消分享笔记"
msgstr "取消分享"
#: packages/app-desktop/gui/ShareNoteDialog.js:168
msgid "Synchronising..."
@@ -2172,6 +2172,7 @@ msgid ""
msgstr "默认管理员密码不安全且尚未更改![现在更改](%s)"
#: packages/server/dist/models/UserModel.js:134
#, fuzzy
msgid "attachment"
msgstr "附件"
@@ -2179,7 +2180,7 @@ msgstr "附件"
#, javascript-format
msgid ""
"Cannot save %s \"%s\" because it is larger than than the allowed limit (%s)"
msgstr "无法保存 %s “%s”,因为超过了允许的限制大小(%s)。"
msgstr ""
#: packages/lib/onedrive-api-node-utils.js:46
#, javascript-format
@@ -2369,12 +2370,14 @@ msgid "Joplin Server password"
msgstr "Joplin 服务器密码"
#: packages/lib/models/Setting.js:365
#, fuzzy
msgid "Joplin Cloud email"
msgstr "Joplin 论坛邮箱"
msgstr "Joplin Server 邮箱"
#: packages/lib/models/Setting.js:376
#, fuzzy
msgid "Joplin Cloud password"
msgstr "Joplin 论坛密码"
msgstr "Joplin 服务器密码"
#: packages/lib/models/Setting.js:388
msgid "Attachment download behaviour"
@@ -2953,6 +2956,7 @@ msgid "Encrypted items cannot be modified"
msgstr "无法修改已加密的条目"
#: packages/lib/SyncTargetJoplinCloud.js:28
#, fuzzy
msgid "Joplin Cloud"
msgstr "Joplin 论坛"
-4
View File
@@ -1,9 +1,5 @@
# Joplin Server Changelog
## [server-v2.1.3-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.1.3-beta) (Pre-release) - 2021-06-19T14:15:06Z
- New: Add support for uploading multiple items in one request (3b9c02e)
## [server-v2.1.1](https://github.com/laurent22/joplin/releases/tag/server-v2.1.1) - 2021-06-17T17:27:29Z
- New: Added account info to dashboard and title to pages (7f0b3fd)