1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-11 00:21:45 +02:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Laurent Cozic
bdf49302f9 update 2021-09-10 18:05:13 +01:00
Laurent Cozic
c5fa20dadb update 2021-09-10 16:29:45 +01:00
Laurent Cozic
9dbab0413a update 2021-09-10 16:14:57 +01:00
Laurent Cozic
a03e9811b6 update 2021-09-10 15:40:39 +01:00
85 changed files with 1527 additions and 2128 deletions

View File

@@ -76,9 +76,6 @@ packages/app-cli/app/command-e2ee.js.map
packages/app-cli/app/command-settingschema.d.ts
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-settingschema.js.map
packages/app-cli/app/command-testing.d.ts
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-testing.js.map
packages/app-cli/app/services/plugins/PluginRunner.d.ts
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/services/plugins/PluginRunner.js.map
@@ -112,9 +109,6 @@ packages/app-cli/tests/services/plugins/sandboxProxy.js.map
packages/app-cli/tests/testUtils.d.ts
packages/app-cli/tests/testUtils.js
packages/app-cli/tests/testUtils.js.map
packages/app-cli/tools/populateDatabase.d.ts
packages/app-cli/tools/populateDatabase.js
packages/app-cli/tools/populateDatabase.js.map
packages/app-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map

View File

@@ -35,6 +35,14 @@ jobs:
sudo apt-get update || true
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
# the next line enables multi-architecture support for docker, it basically makes it use qemu for non native platforms
# See https://hub.docker.com/r/tonistiigi/binfmt for more info
docker run --privileged --rm tonistiigi/binfmt --install all
# this just prints the info about what platforms are supported in the builder (can help debugging if something isn't working right)
# and also proves the above worked properly
sudo docker buildx ls
- uses: actions/checkout@v2
- uses: olegtarasov/get-tag@v2.1
- uses: actions/setup-node@v2

6
.gitignore vendored
View File

@@ -61,9 +61,6 @@ packages/app-cli/app/command-e2ee.js.map
packages/app-cli/app/command-settingschema.d.ts
packages/app-cli/app/command-settingschema.js
packages/app-cli/app/command-settingschema.js.map
packages/app-cli/app/command-testing.d.ts
packages/app-cli/app/command-testing.js
packages/app-cli/app/command-testing.js.map
packages/app-cli/app/services/plugins/PluginRunner.d.ts
packages/app-cli/app/services/plugins/PluginRunner.js
packages/app-cli/app/services/plugins/PluginRunner.js.map
@@ -97,9 +94,6 @@ packages/app-cli/tests/services/plugins/sandboxProxy.js.map
packages/app-cli/tests/testUtils.d.ts
packages/app-cli/tests/testUtils.js
packages/app-cli/tests/testUtils.js.map
packages/app-cli/tools/populateDatabase.d.ts
packages/app-cli/tools/populateDatabase.js
packages/app-cli/tools/populateDatabase.js.map
packages/app-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

View File

@@ -23,6 +23,4 @@ tests/support/dropbox-auth.txt
tests/support/nextcloud-auth.json
tests/support/onedrive-auth.txt
build/
patches/
createUsers-*.txt
tools/temp/
patches/

View File

@@ -89,7 +89,7 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
flags = cliUtils.parseFlags(flags);
if (!flags.arg) {
if (flags.short) booleanFlags.push(flags.short);
booleanFlags.push(flags.short);
if (flags.long) booleanFlags.push(flags.long);
}

View File

@@ -1,95 +0,0 @@
const { BaseCommand } = require('./base-command.js');
import { reg } from '@joplin/lib/registry';
import Note from '@joplin/lib/models/Note';
import uuid from '@joplin/lib/uuid';
import populateDatabase from '@joplin/lib/services/debug/populateDatabase';
function randomElement(array: any[]): any {
if (!array.length) return null;
return array[Math.floor(Math.random() * array.length)];
}
function itemCount(args: any) {
const count = Number(args.arg0);
if (!count || isNaN(count)) throw new Error('Note count must be specified');
return count;
}
class Command extends BaseCommand {
usage() {
return 'testing <command> [arg0]';
}
description() {
return 'testing';
}
enabled() {
return false;
}
options(): any[] {
return [
['--folder-count <count>', 'Folders to create'],
['--note-count <count>', 'Notes to create'],
['--tag-count <count>', 'Tags to create'],
['--tags-per-note <count>', 'Tags per note'],
['--silent', 'Silent'],
];
}
async action(args: any) {
const { command, options } = args;
if (command === 'populate') {
await populateDatabase(reg.db(), {
folderCount: options['folder-count'],
noteCount: options['note-count'],
tagCount: options['tag-count'],
tagsPerNote: options['tags-per-note'],
silent: options['silent'],
});
}
const promises: any[] = [];
if (command === 'createRandomNotes') {
const noteCount = itemCount(args);
for (let i = 0; i < noteCount; i++) {
promises.push(Note.save({
title: `Note ${uuid.createNano()}`,
}));
}
}
if (command === 'updateRandomNotes') {
const noteCount = itemCount(args);
const noteIds = await Note.allIds();
for (let i = 0; i < noteCount; i++) {
const noteId = randomElement(noteIds);
promises.push(Note.save({
id: noteId,
title: `Note ${uuid.createNano()}`,
}));
}
}
if (command === 'deleteRandomNotes') {
const noteCount = itemCount(args);
const noteIds = await Note.allIds();
for (let i = 0; i < noteCount; i++) {
const noteId = randomElement(noteIds);
promises.push(Note.delete(noteId));
}
}
await Promise.all(promises);
}
}
module.exports = Command;

View File

@@ -1,52 +0,0 @@
#!/bin/bash
# Start the server with:
#
# JOPLIN_IS_TESTING=1 npm run start-dev
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# curl --data '{"action": "clearDatabase"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
# SMALL
# curl --data '{"action": "createTestUsers", "count": 400, "fromNum": 1}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
NUM=398
while [ "$NUM" -lt 400 ]; do
NUM=$(( NUM + 1 ))
echo "User $NUM"
CMD_FILE="$SCRIPT_DIR/createUsers-$NUM.txt"
PROFILE_DIR=~/.config/joplindev-testing-$NUM
USER_EMAIL="user$NUM@example.com"
rm -rf "$CMD_FILE" "$PROFILE_DIR"
touch "$CMD_FILE"
FLAG_FOLDER_COUNT=100
FLAG_NOTE_COUNT=1000
FLAG_TAG_COUNT=20
if [ "$NUM" -gt 300 ]; then
FLAG_FOLDER_COUNT=2000
FLAG_NOTE_COUNT=10000
FLAG_TAG_COUNT=200
fi
if [ "$NUM" -gt 399 ]; then
FLAG_FOLDER_COUNT=10000
FLAG_NOTE_COUNT=150000
FLAG_TAG_COUNT=2000
fi
echo "testing populate --silent --folder-count $FLAG_FOLDER_COUNT --note-count $FLAG_NOTE_COUNT --tag-count $FLAG_TAG_COUNT" >> "$CMD_FILE"
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 10" >> "$CMD_FILE"
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.10.password hunter1hunter2hunter3" >> "$CMD_FILE"
echo "sync" >> "$CMD_FILE"
npm start -- --profile "$PROFILE_DIR" batch "$CMD_FILE"
done

View File

@@ -10,7 +10,6 @@
"test-ci": "jest --config=jest.config.js --forceExit",
"build": "gulp build",
"start": "gulp build -L && node \"build/main.js\" --stack-trace-enabled --log-level debug --env dev",
"start-no-build": "node \"build/main.js\" --stack-trace-enabled --log-level debug --env dev",
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json"
},

View File

@@ -1,108 +0,0 @@
// This script can be used to simulate a running production environment, by
// having multiple users in parallel changing notes and synchronising.
//
// To get it working:
//
// - Run the Postgres database -- `sudo docker-compose --file docker-compose.db-dev.yml up`
// - Update the DB parameters in ~/joplin-credentials/server.env to use the dev
// database
// - Run the server - `JOPLIN_IS_TESTING=1 npm run start-dev`
// - Then run this script - `node populateDatabase.js`
//
// Currently it doesn't actually create the users, so that should be done using:
//
// curl --data '{"action": "createTestUsers", "count": 400, "fromNum": 1}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
//
// That will create n users with email `user<n>@example.com`
import * as fs from 'fs-extra';
import { homedir } from 'os';
import { execCommand2 } from '@joplin/tools/tool-utils';
import { chdir } from 'process';
const minUserNum = 1;
const maxUserNum = 400;
const cliDir = `${__dirname}/..`;
const tempDir = `${__dirname}/temp`;
function randomInt(min: number, max: number) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const processing_: Record<number, boolean> = {};
const processUser = async (userNum: number) => {
if (processing_[userNum]) {
console.info(`User already being processed: ${userNum} - skipping`);
return;
}
processing_[userNum] = true;
try {
const userEmail = `user${userNum}@example.com`;
const userPassword = 'hunter1hunter2hunter3';
const commandFile = `${tempDir}/populateDatabase-${userNum}.txt`;
const profileDir = `${homedir()}/.config/joplindev-populate/joplindev-testing-${userNum}`;
const commands: string[] = [];
const jackpot = Math.random() >= 0.95 ? 100 : 1;
commands.push(`testing createRandomNotes ${randomInt(1, 500 * jackpot)}`);
commands.push(`testing updateRandomNotes ${randomInt(1, 1500 * jackpot)}`);
commands.push(`testing deleteRandomNotes ${randomInt(1, 200 * jackpot)}`);
commands.push('config keychain.supported 0');
commands.push('config sync.target 10');
commands.push(`config sync.10.username ${userEmail}`);
commands.push(`config sync.10.password ${userPassword}`);
commands.push('sync');
await fs.writeFile(commandFile, commands.join('\n'), 'utf8');
await chdir(cliDir);
await execCommand2(['npm', 'run', 'start-no-build', '--', '--profile', profileDir, 'batch', commandFile]);
} catch (error) {
console.error(`Could not process user ${userNum}:`, error);
} finally {
delete processing_[userNum];
}
};
const waitForProcessing = (count: number) => {
return new Promise((resolve) => {
const iid = setInterval(() => {
if (Object.keys(processing_).length <= count) {
clearInterval(iid);
resolve(null);
}
}, 100);
});
};
const main = async () => {
await fs.mkdirp(tempDir);
// Build the app once before starting, because we'll use start-no-build to
// run the scripts (faster)
await execCommand2(['npm', 'run', 'build']);
const focusUserNum = 400;
while (true) {
let userNum = randomInt(minUserNum, maxUserNum);
if (Math.random() >= .7) userNum = focusUserNum;
void processUser(userNum);
await waitForProcessing(10);
}
};
main().catch((error) => {
console.error('Fatal error', error);
process.exit(1);
});

View File

@@ -1,5 +0,0 @@
.encryption-config-test {
& > .item {
font-weight: bold;
}
}

View File

@@ -1,6 +1,5 @@
const gulp = require('gulp');
const utils = require('@joplin/tools/gulp/utils');
const compileSass = require('@joplin/tools/compileSass');
const tasks = {
compileScripts: {
@@ -21,14 +20,6 @@ const tasks = {
tsc: require('@joplin/tools/gulp/tasks/tsc'),
updateIgnoredTypeScriptBuild: require('@joplin/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
buildCommandIndex: require('@joplin/tools/gulp/tasks/buildCommandIndex'),
compileSass: {
fn: async () => {
const guiDir = `${__dirname}/gui`;
await compileSass([
`${guiDir}/EncryptionConfigScreen/style.scss`,
], `${__dirname}/style.min.css`);
},
},
};
utils.registerGulpTasks(gulp, tasks);
@@ -40,7 +31,6 @@ const buildParallel = [
'copyTinyMceLangs',
'updateIgnoredTypeScriptBuild',
'buildCommandIndex',
'compileSass',
];
gulp.task('build', gulp.parallel(...buildParallel));

View File

@@ -1,12 +1,12 @@
{
"name": "@joplin/app-desktop",
"version": "2.4.6",
"version": "2.4.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@joplin/app-desktop",
"version": "2.4.6",
"version": "2.4.5",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.13.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.4.6",
"version": "2.4.5",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@@ -1,5 +0,0 @@
.encryption-config-test > .item {
font-weight: bold;
}
/*# sourceMappingURL=style.min.css.map */

View File

@@ -1 +1 @@
module.exports = `cHJlIGNvZGUuaGxqc3tkaXNwbGF5OmJsb2NrO292ZXJmbG93LXg6YXV0bztwYWRkaW5nOjFlbX1jb2RlLmhsanN7cGFkZGluZzozcHggNXB4fS5obGpze2NvbG9yOiNhYmIyYmY7YmFja2dyb3VuZDojMjgyYzM0fS5obGpzLWtleXdvcmQsLmhsanMtb3BlcmF0b3IsLmhsanMtcGF0dGVybi1tYXRjaHtjb2xvcjojZjkyNjcyfS5obGpzLWZ1bmN0aW9uLC5obGpzLXBhdHRlcm4tbWF0Y2ggLmhsanMtY29uc3RydWN0b3J7Y29sb3I6IzYxYWVlZX0uaGxqcy1mdW5jdGlvbiAuaGxqcy1wYXJhbXN7Y29sb3I6I2E2ZTIyZX0uaGxqcy1mdW5jdGlvbiAuaGxqcy1wYXJhbXMgLmhsanMtdHlwaW5ne2NvbG9yOiNmZDk3MWZ9LmhsanMtbW9kdWxlLWFjY2VzcyAuaGxqcy1tb2R1bGV7Y29sb3I6IzdlNTdjMn0uaGxqcy1jb25zdHJ1Y3Rvcntjb2xvcjojZTJiOTNkfS5obGpzLWNvbnN0cnVjdG9yIC5obGpzLXN0cmluZ3tjb2xvcjojOWNjYzY1fS5obGpzLWNvbW1lbnQsLmhsanMtcXVvdGV7Y29sb3I6I2IxOGViMTtmb250LXN0eWxlOml0YWxpY30uaGxqcy1kb2N0YWcsLmhsanMtZm9ybXVsYXtjb2xvcjojYzY3OGRkfS5obGpzLWRlbGV0aW9uLC5obGpzLW5hbWUsLmhsanMtc2VjdGlvbiwuaGxqcy1zZWxlY3Rvci10YWcsLmhsanMtc3Vic3R7Y29sb3I6I2UwNmM3NX0uaGxqcy1saXRlcmFse2NvbG9yOiM1NmI2YzJ9LmhsanMtYWRkaXRpb24sLmhsanMtYXR0cmlidXRlLC5obGpzLW1ldGEgLmhsanMtc3RyaW5nLC5obGpzLXJlZ2V4cCwuaGxqcy1zdHJpbmd7Y29sb3I6Izk4YzM3OX0uaGxqcy1idWlsdF9pbiwuaGxqcy1jbGFzcyAuaGxqcy10aXRsZSwuaGxqcy10aXRsZS5jbGFzc197Y29sb3I6I2U2YzA3Yn0uaGxqcy1hdHRyLC5obGpzLW51bWJlciwuaGxqcy1zZWxlY3Rvci1hdHRyLC5obGpzLXNlbGVjdG9yLWNsYXNzLC5obGpzLXNlbGVjdG9yLXBzZXVkbywuaGxqcy10ZW1wbGF0ZS12YXJpYWJsZSwuaGxqcy10eXBlLC5obGpzLXZhcmlhYmxle2NvbG9yOiNkMTlhNjZ9LmhsanMtYnVsbGV0LC5obGpzLWxpbmssLmhsanMtbWV0YSwuaGxqcy1zZWxlY3Rvci1pZCwuaGxqcy1zeW1ib2wsLmhsanMtdGl0bGV7Y29sb3I6IzYxYWVlZX0uaGxqcy1lbXBoYXNpc3tmb250LXN0eWxlOml0YWxpY30uaGxqcy1zdHJvbmd7Zm9udC13ZWlnaHQ6NzAwfS5obGpzLWxpbmt7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZX0=`;
module.exports = `LyoKCkF0b20gT25lIERhcmsgV2l0aCBzdXBwb3J0IGZvciBSZWFzb25NTCBieSBHaWRpIE1vcnJpcywgYmFzZWQgb2ZmIHdvcmsgYnkgRGFuaWVsIEdhbWFnZQoKT3JpZ2luYWwgT25lIERhcmsgU3ludGF4IHRoZW1lIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL2F0b20vb25lLWRhcmstc3ludGF4CgoqLwouaGxqcyB7CiAgZGlzcGxheTogYmxvY2s7CiAgb3ZlcmZsb3cteDogYXV0bzsKICBwYWRkaW5nOiAwLjVlbTsKICBjb2xvcjogI2FiYjJiZjsKICBiYWNrZ3JvdW5kOiAjMjgyYzM0Owp9Ci5obGpzLWtleXdvcmQsIC5obGpzLW9wZXJhdG9yIHsKICBjb2xvcjogI0Y5MjY3MjsKfQouaGxqcy1wYXR0ZXJuLW1hdGNoIHsKICBjb2xvcjogI0Y5MjY3MjsKfQouaGxqcy1wYXR0ZXJuLW1hdGNoIC5obGpzLWNvbnN0cnVjdG9yIHsKICBjb2xvcjogIzYxYWVlZTsKfQouaGxqcy1mdW5jdGlvbiB7CiAgY29sb3I6ICM2MWFlZWU7Cn0KLmhsanMtZnVuY3Rpb24gLmhsanMtcGFyYW1zIHsKICBjb2xvcjogI0E2RTIyRTsKfQouaGxqcy1mdW5jdGlvbiAuaGxqcy1wYXJhbXMgLmhsanMtdHlwaW5nIHsKICBjb2xvcjogI0ZEOTcxRjsKfQouaGxqcy1tb2R1bGUtYWNjZXNzIC5obGpzLW1vZHVsZSB7CiAgY29sb3I6ICM3ZTU3YzI7Cn0KLmhsanMtY29uc3RydWN0b3IgewogIGNvbG9yOiAjZTJiOTNkOwp9Ci5obGpzLWNvbnN0cnVjdG9yIC5obGpzLXN0cmluZyB7CiAgY29sb3I6ICM5Q0NDNjU7Cn0KLmhsanMtY29tbWVudCwgLmhsanMtcXVvdGUgewogIGNvbG9yOiAjYjE4ZWIxOwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKfQouaGxqcy1kb2N0YWcsIC5obGpzLWZvcm11bGEgewogIGNvbG9yOiAjYzY3OGRkOwp9Ci5obGpzLXNlY3Rpb24sIC5obGpzLW5hbWUsIC5obGpzLXNlbGVjdG9yLXRhZywgLmhsanMtZGVsZXRpb24sIC5obGpzLXN1YnN0IHsKICBjb2xvcjogI2UwNmM3NTsKfQouaGxqcy1saXRlcmFsIHsKICBjb2xvcjogIzU2YjZjMjsKfQouaGxqcy1zdHJpbmcsIC5obGpzLXJlZ2V4cCwgLmhsanMtYWRkaXRpb24sIC5obGpzLWF0dHJpYnV0ZSwgLmhsanMtbWV0YS1zdHJpbmcgewogIGNvbG9yOiAjOThjMzc5Owp9Ci5obGpzLWJ1aWx0X2luLCAuaGxqcy1jbGFzcyAuaGxqcy10aXRsZSB7CiAgY29sb3I6ICNlNmMwN2I7Cn0KLmhsanMtYXR0ciwgLmhsanMtdmFyaWFibGUsIC5obGpzLXRlbXBsYXRlLXZhcmlhYmxlLCAuaGxqcy10eXBlLCAuaGxqcy1zZWxlY3Rvci1jbGFzcywgLmhsanMtc2VsZWN0b3ItYXR0ciwgLmhsanMtc2VsZWN0b3ItcHNldWRvLCAuaGxqcy1udW1iZXIgewogIGNvbG9yOiAjZDE5YTY2Owp9Ci5obGpzLXN5bWJvbCwgLmhsanMtYnVsbGV0LCAuaGxqcy1saW5rLCAuaGxqcy1tZXRhLCAuaGxqcy1zZWxlY3Rvci1pZCwgLmhsanMtdGl0bGUgewogIGNvbG9yOiAjNjFhZWVlOwp9Ci5obGpzLWVtcGhhc2lzIHsKICBmb250LXN0eWxlOiBpdGFsaWM7Cn0KLmhsanMtc3Ryb25nIHsKICBmb250LXdlaWdodDogYm9sZDsKfQouaGxqcy1saW5rIHsKICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKfQo=`;

View File

@@ -1 +1 @@
module.exports = `cHJlIGNvZGUuaGxqc3tkaXNwbGF5OmJsb2NrO292ZXJmbG93LXg6YXV0bztwYWRkaW5nOjFlbX1jb2RlLmhsanN7cGFkZGluZzozcHggNXB4fS5obGpze2NvbG9yOiMzODNhNDI7YmFja2dyb3VuZDojZmFmYWZhfS5obGpzLWNvbW1lbnQsLmhsanMtcXVvdGV7Y29sb3I6I2EwYTFhNztmb250LXN0eWxlOml0YWxpY30uaGxqcy1kb2N0YWcsLmhsanMtZm9ybXVsYSwuaGxqcy1rZXl3b3Jke2NvbG9yOiNhNjI2YTR9LmhsanMtZGVsZXRpb24sLmhsanMtbmFtZSwuaGxqcy1zZWN0aW9uLC5obGpzLXNlbGVjdG9yLXRhZywuaGxqcy1zdWJzdHtjb2xvcjojZTQ1NjQ5fS5obGpzLWxpdGVyYWx7Y29sb3I6IzAxODRiYn0uaGxqcy1hZGRpdGlvbiwuaGxqcy1hdHRyaWJ1dGUsLmhsanMtbWV0YSAuaGxqcy1zdHJpbmcsLmhsanMtcmVnZXhwLC5obGpzLXN0cmluZ3tjb2xvcjojNTBhMTRmfS5obGpzLWF0dHIsLmhsanMtbnVtYmVyLC5obGpzLXNlbGVjdG9yLWF0dHIsLmhsanMtc2VsZWN0b3ItY2xhc3MsLmhsanMtc2VsZWN0b3ItcHNldWRvLC5obGpzLXRlbXBsYXRlLXZhcmlhYmxlLC5obGpzLXR5cGUsLmhsanMtdmFyaWFibGV7Y29sb3I6Izk4NjgwMX0uaGxqcy1idWxsZXQsLmhsanMtbGluaywuaGxqcy1tZXRhLC5obGpzLXNlbGVjdG9yLWlkLC5obGpzLXN5bWJvbCwuaGxqcy10aXRsZXtjb2xvcjojNDA3OGYyfS5obGpzLWJ1aWx0X2luLC5obGpzLWNsYXNzIC5obGpzLXRpdGxlLC5obGpzLXRpdGxlLmNsYXNzX3tjb2xvcjojYzE4NDAxfS5obGpzLWVtcGhhc2lze2ZvbnQtc3R5bGU6aXRhbGljfS5obGpzLXN0cm9uZ3tmb250LXdlaWdodDo3MDB9LmhsanMtbGlua3t0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lfQ==`;
module.exports = `LyoKCkF0b20gT25lIExpZ2h0IGJ5IERhbmllbCBHYW1hZ2UKT3JpZ2luYWwgT25lIExpZ2h0IFN5bnRheCB0aGVtZSBmcm9tIGh0dHBzOi8vZ2l0aHViLmNvbS9hdG9tL29uZS1saWdodC1zeW50YXgKCmJhc2U6ICAgICNmYWZhZmEKbW9uby0xOiAgIzM4M2E0Mgptb25vLTI6ICAjNjg2Yjc3Cm1vbm8tMzogICNhMGExYTcKaHVlLTE6ICAgIzAxODRiYgpodWUtMjogICAjNDA3OGYyCmh1ZS0zOiAgICNhNjI2YTQKaHVlLTQ6ICAgIzUwYTE0ZgpodWUtNTogICAjZTQ1NjQ5Cmh1ZS01LTI6ICNjOTEyNDMKaHVlLTY6ICAgIzk4NjgwMQpodWUtNi0yOiAjYzE4NDAxCgoqLwoKLmhsanMgewogIGRpc3BsYXk6IGJsb2NrOwogIG92ZXJmbG93LXg6IGF1dG87CiAgcGFkZGluZzogMC41ZW07CiAgY29sb3I6ICMzODNhNDI7CiAgYmFja2dyb3VuZDogI2ZhZmFmYTsKfQoKLmhsanMtY29tbWVudCwKLmhsanMtcXVvdGUgewogIGNvbG9yOiAjYTBhMWE3OwogIGZvbnQtc3R5bGU6IGl0YWxpYzsKfQoKLmhsanMtZG9jdGFnLAouaGxqcy1rZXl3b3JkLAouaGxqcy1mb3JtdWxhIHsKICBjb2xvcjogI2E2MjZhNDsKfQoKLmhsanMtc2VjdGlvbiwKLmhsanMtbmFtZSwKLmhsanMtc2VsZWN0b3ItdGFnLAouaGxqcy1kZWxldGlvbiwKLmhsanMtc3Vic3QgewogIGNvbG9yOiAjZTQ1NjQ5Owp9CgouaGxqcy1saXRlcmFsIHsKICBjb2xvcjogIzAxODRiYjsKfQoKLmhsanMtc3RyaW5nLAouaGxqcy1yZWdleHAsCi5obGpzLWFkZGl0aW9uLAouaGxqcy1hdHRyaWJ1dGUsCi5obGpzLW1ldGEtc3RyaW5nIHsKICBjb2xvcjogIzUwYTE0ZjsKfQoKLmhsanMtYnVpbHRfaW4sCi5obGpzLWNsYXNzIC5obGpzLXRpdGxlIHsKICBjb2xvcjogI2MxODQwMTsKfQoKLmhsanMtYXR0ciwKLmhsanMtdmFyaWFibGUsCi5obGpzLXRlbXBsYXRlLXZhcmlhYmxlLAouaGxqcy10eXBlLAouaGxqcy1zZWxlY3Rvci1jbGFzcywKLmhsanMtc2VsZWN0b3ItYXR0ciwKLmhsanMtc2VsZWN0b3ItcHNldWRvLAouaGxqcy1udW1iZXIgewogIGNvbG9yOiAjOTg2ODAxOwp9CgouaGxqcy1zeW1ib2wsCi5obGpzLWJ1bGxldCwKLmhsanMtbGluaywKLmhsanMtbWV0YSwKLmhsanMtc2VsZWN0b3ItaWQsCi5obGpzLXRpdGxlIHsKICBjb2xvcjogIzQwNzhmMjsKfQoKLmhsanMtZW1waGFzaXMgewogIGZvbnQtc3R5bGU6IGl0YWxpYzsKfQoKLmhsanMtc3Ryb25nIHsKICBmb250LXdlaWdodDogYm9sZDsKfQoKLmhsanMtbGluayB7CiAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7Cn0K`;

View File

@@ -1,5 +1,5 @@
module.exports = {
hash:"de3871f000c87478973d7cd0913bd3ff", files: {
hash:"6608023b8053b48e0eec248644475e33", 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' },

File diff suppressed because one or more lines are too long

View File

@@ -36,6 +36,26 @@ dialogs.confirm = (parentComponent, message) => {
if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!');
return dialogs.confirmRef(parentComponent.dialogBox, message);
// return new Promise((resolve) => {
// Keyboard.dismiss();
// parentComponent.dialogbox.confirm({
// content: message,
// ok: {
// callback: () => {
// resolve(true);
// },
// },
// cancel: {
// callback: () => {
// resolve(false);
// },
// },
// });
// });
};
dialogs.pop = (parentComponent, message, buttons, options = null) => {

View File

@@ -5669,7 +5669,6 @@
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"deprecated": "This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.",
"optional": true,
"dependencies": {
"block-stream": "*",
@@ -15703,6 +15702,7 @@
},
"uslug": {
"version": "git+ssh://git@github.com/laurent22/uslug.git#ba2834d79beb0435318709958b2f5e817d96674d",
"integrity": "sha512-6zzxOsQp+hbOW4zeplEUhKXnBzYIrqYAVlPepBFz/u5q2OulN7tCmBKyWEzDxaiZOLYnUCTViDLazNoq1J6ciA==",
"from": "uslug@git+https://github.com/laurent22/uslug.git#emoji-support",
"requires": {
"node-emoji": "^1.10.0",

View File

@@ -589,25 +589,4 @@ describe('reducer', function() {
expect(state.selectedFolderId).toEqual(null);
expect(state.selectedNoteIds[0]).toEqual(notes[1].id);
});
// tests for NOTE_UPDATE_ALL about issue #5447
it('should not change selectedNoteIds object when selections are not changed', async () => {
const folders = await createNTestFolders(1);
const notes = await createNTestNotes(5, folders[0]);
{
// Case 1. Selected notes are changed when one of selected notes is deleted.
let state = initTestState(folders, 0, notes, [0, 2, 4]);
state = reducer(state, { type: 'NOTE_UPDATE_ALL', notes: notes.slice(0, 4), notesSource: 'test' });
const expected = [notes[0].id, notes[2].id].sort();
expect([...state.selectedNoteIds].sort()).toEqual(expected);
}
{
// Case 2. Selected notes and object identity are unchanged when notes are not changed.
let state = initTestState(folders, 0, notes, [0, 2, 4]);
const expected = state.selectedNoteIds;
state = reducer(state, { type: 'NOTE_UPDATE_ALL', notes: notes, notesSource: 'test' });
// Object identity is checked. Don't use toEqual() or toStrictEqual() here.
expect(state.selectedNoteIds).toBe(expected);
}
});
});

View File

@@ -433,7 +433,7 @@ function updateSelectedNotesFromExistingNotes(draft: Draft<State>) {
}
}
}
if (JSON.stringify(draft.selectedNoteIds) === JSON.stringify(newSelectedNoteIds)) return;
draft.selectedNoteIds = newSelectedNoteIds;
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1,75 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-keyword,.hljs-operator,.hljs-pattern-match{color:#f92672}.hljs-function,.hljs-pattern-match .hljs-constructor{color:#61aeee}.hljs-function .hljs-params{color:#a6e22e}.hljs-function .hljs-params .hljs-typing{color:#fd971f}.hljs-module-access .hljs-module{color:#7e57c2}.hljs-constructor{color:#e2b93d}.hljs-constructor .hljs-string{color:#9ccc65}.hljs-comment,.hljs-quote{color:#b18eb1;font-style:italic}.hljs-doctag,.hljs-formula{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
/*
Atom One Dark With support for ReasonML by Gidi Morris, based off work by Daniel Gamage
Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #abb2bf;
background: #282c34;
}
.hljs-keyword, .hljs-operator {
color: #F92672;
}
.hljs-pattern-match {
color: #F92672;
}
.hljs-pattern-match .hljs-constructor {
color: #61aeee;
}
.hljs-function {
color: #61aeee;
}
.hljs-function .hljs-params {
color: #A6E22E;
}
.hljs-function .hljs-params .hljs-typing {
color: #FD971F;
}
.hljs-module-access .hljs-module {
color: #7e57c2;
}
.hljs-constructor {
color: #e2b93d;
}
.hljs-constructor .hljs-string {
color: #9CCC65;
}
.hljs-comment, .hljs-quote {
color: #b18eb1;
font-style: italic;
}
.hljs-doctag, .hljs-formula {
color: #c678dd;
}
.hljs-section, .hljs-name, .hljs-selector-tag, .hljs-deletion, .hljs-subst {
color: #e06c75;
}
.hljs-literal {
color: #56b6c2;
}
.hljs-string, .hljs-regexp, .hljs-addition, .hljs-attribute, .hljs-meta-string {
color: #98c379;
}
.hljs-built_in, .hljs-class .hljs-title {
color: #e6c07b;
}
.hljs-attr, .hljs-variable, .hljs-template-variable, .hljs-type, .hljs-selector-class, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-number {
color: #d19a66;
}
.hljs-symbol, .hljs-bullet, .hljs-link, .hljs-meta, .hljs-selector-id, .hljs-title {
color: #61aeee;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-link {
text-decoration: underline;
}

View File

@@ -1 +1,96 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
/*
Atom One Light by Daniel Gamage
Original One Light Syntax theme from https://github.com/atom/one-light-syntax
base: #fafafa
mono-1: #383a42
mono-2: #686b77
mono-3: #a0a1a7
hue-1: #0184bb
hue-2: #4078f2
hue-3: #a626a4
hue-4: #50a14f
hue-5: #e45649
hue-5-2: #c91243
hue-6: #986801
hue-6-2: #c18401
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #383a42;
background: #fafafa;
}
.hljs-comment,
.hljs-quote {
color: #a0a1a7;
font-style: italic;
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #a626a4;
}
.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
color: #e45649;
}
.hljs-literal {
color: #0184bb;
}
.hljs-string,
.hljs-regexp,
.hljs-addition,
.hljs-attribute,
.hljs-meta-string {
color: #50a14f;
}
.hljs-built_in,
.hljs-class .hljs-title {
color: #c18401;
}
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
color: #986801;
}
.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
color: #4078f2;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-link {
text-decoration: underline;
}

File diff suppressed because one or more lines are too long

View File

@@ -29,7 +29,7 @@
"markdown-it-sup": "^1.0.0",
"markdown-it-toc-done-right": "^4.1.0",
"md5": "^2.2.1",
"mermaid": "^8.12.1",
"mermaid": "^8.10.2",
"uslug": "git+https://github.com/laurent22/uslug.git#emoji-support"
},
"devDependencies": {
@@ -1998,8 +1998,7 @@
"node_modules/buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"node_modules/cache-base": {
"version": "1.0.1",
@@ -2030,6 +2029,15 @@
"node": ">=6"
}
},
"node_modules/camel-case": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
"integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
"dependencies": {
"no-case": "^2.2.0",
"upper-case": "^1.1.1"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -2127,6 +2135,25 @@
"node": ">=0.10.0"
}
},
"node_modules/clean-css": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
"integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
"dependencies": {
"source-map": "~0.6.0"
},
"engines": {
"node": ">= 4.0"
}
},
"node_modules/clean-css/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -2259,6 +2286,17 @@
"node": "*"
}
},
"node_modules/css-b64-images": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz",
"integrity": "sha1-QgBdgyBLK0pdk7axpWRBM7WSegI=",
"bin": {
"css-b64-images": "bin/css-b64-images"
},
"engines": {
"node": "*"
}
},
"node_modules/cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@@ -2609,7 +2647,6 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@@ -2760,11 +2797,6 @@
"node": ">=8"
}
},
"node_modules/dompurify": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.1.tgz",
"integrity": "sha512-xGWt+NHAQS+4tpgbOAI08yxW0Pr256Gu/FNE2frZVTbgrBUn8M7tz7/ktS/LZ2MHeGqz6topj0/xY+y8R5FBFw=="
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -2807,6 +2839,14 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
},
"node_modules/entity-decode": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/entity-decode/-/entity-decode-2.0.2.tgz",
"integrity": "sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==",
"dependencies": {
"he": "^1.1.1"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3502,6 +3542,14 @@
"node": ">=0.10.0"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"bin": {
"he": "bin/he"
}
},
"node_modules/highlight.js": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.2.0.tgz",
@@ -3542,6 +3590,26 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
"node_modules/html-minifier": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
"integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
"dependencies": {
"camel-case": "^3.0.0",
"clean-css": "^4.2.1",
"commander": "^2.19.0",
"he": "^1.2.0",
"param-case": "^2.1.1",
"relateurl": "^0.2.7",
"uglify-js": "^3.5.1"
},
"bin": {
"html-minifier": "cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -5977,6 +6045,11 @@
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
},
"node_modules/lower-case": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
"integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -6161,19 +6234,21 @@
"dev": true
},
"node_modules/mermaid": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.12.1.tgz",
"integrity": "sha512-0UCcSF0FLoNcPBsRF4f9OIV32t41fV18//z8o3S+FDz2PbDA1CRGKdQF9IX84VP4Tv9kcgJI/oqJdcBEtB/GPA==",
"version": "8.10.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.10.2.tgz",
"integrity": "sha512-Za5MrbAOMbEsyY4ONgGjfYz06sbwF1iNGRzp1sQqpOtvXxjxGu/J1jRJ8QyE9kD/D9zj1/KlRrYegWEvA7eZ5Q==",
"dependencies": {
"@braintree/sanitize-url": "^3.1.0",
"d3": "^5.16.0",
"dagre": "^0.8.5",
"d3": "^5.7.0",
"dagre": "^0.8.4",
"dagre-d3": "^0.6.4",
"dompurify": "2.3.1",
"graphlib": "^2.1.8",
"khroma": "^1.4.1",
"moment-mini": "^2.24.0",
"stylis": "^4.0.10"
"entity-decode": "^2.0.2",
"graphlib": "^2.1.7",
"he": "^1.2.0",
"khroma": "^1.1.0",
"minify": "^4.1.1",
"moment-mini": "^2.22.1",
"stylis": "^3.5.2"
}
},
"node_modules/micromatch": {
@@ -6219,6 +6294,26 @@
"node": ">=6"
}
},
"node_modules/minify": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/minify/-/minify-4.1.3.tgz",
"integrity": "sha512-ykuscavxivSmVpcCzsXmsVTukWYLUUtPhHj0w2ILvHDGqC+hsuTCihBn9+PJBd58JNvWTNg9132J9nrrI2anzA==",
"dependencies": {
"clean-css": "^4.1.6",
"css-b64-images": "~0.2.5",
"debug": "^4.1.0",
"html-minifier": "^4.0.0",
"terser": "^4.0.0",
"try-catch": "^2.0.0",
"try-to-catch": "^1.0.2"
},
"bin": {
"minify": "bin/minify.js"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -6270,8 +6365,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/nanomatch": {
"version": "1.2.13",
@@ -6307,6 +6401,14 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"node_modules/no-case": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
"integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
"dependencies": {
"lower-case": "^1.1.1"
}
},
"node_modules/node-emoji": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
@@ -6582,6 +6684,14 @@
"node": ">=6"
}
},
"node_modules/param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
"integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
"dependencies": {
"no-case": "^2.2.0"
}
},
"node_modules/parse-json": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz",
@@ -6863,6 +6973,14 @@
"node": ">=0.10.0"
}
},
"node_modules/relateurl": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -7543,7 +7661,6 @@
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -7553,7 +7670,6 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -7760,9 +7876,9 @@
}
},
"node_modules/stylis": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz",
"integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg=="
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
"integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q=="
},
"node_modules/supports-color": {
"version": "5.5.0",
@@ -7832,6 +7948,30 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terser": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"dependencies": {
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.12"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/terser/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -7944,6 +8084,19 @@
"node": ">=8"
}
},
"node_modules/try-catch": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/try-catch/-/try-catch-2.0.1.tgz",
"integrity": "sha512-LsOrmObN/2WdM+y2xG+t16vhYrQsnV8wftXIcIOWZhQcBJvKGYuamJGwnU98A7Jxs2oZNkJztXlphEOoA0DWqg==",
"engines": {
"node": ">=0.4"
}
},
"node_modules/try-to-catch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-1.1.1.tgz",
"integrity": "sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA=="
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -8019,6 +8172,17 @@
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"node_modules/uglify-js": {
"version": "3.13.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz",
"integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g==",
"bin": {
"uglifyjs": "bin/uglifyjs"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -8098,6 +8262,11 @@
"node": ">=0.10.0"
}
},
"node_modules/upper-case": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
},
"node_modules/uri-js": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
@@ -10066,8 +10235,7 @@
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"cache-base": {
"version": "1.0.1",
@@ -10092,6 +10260,15 @@
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"camel-case": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
"integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
"requires": {
"no-case": "^2.2.0",
"upper-case": "^1.1.1"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -10170,6 +10347,21 @@
}
}
},
"clean-css": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
"integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
"requires": {
"source-map": "~0.6.0"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -10283,6 +10475,11 @@
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"css-b64-images": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz",
"integrity": "sha1-QgBdgyBLK0pdk7axpWRBM7WSegI="
},
"cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@@ -10614,7 +10811,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
@@ -10725,11 +10921,6 @@
}
}
},
"dompurify": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.1.tgz",
"integrity": "sha512-xGWt+NHAQS+4tpgbOAI08yxW0Pr256Gu/FNE2frZVTbgrBUn8M7tz7/ktS/LZ2MHeGqz6topj0/xY+y8R5FBFw=="
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -10766,6 +10957,14 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
},
"entity-decode": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/entity-decode/-/entity-decode-2.0.2.tgz",
"integrity": "sha512-5CCY/3ci4MC1m2jlumNjWd7VBFt4VfFnmSqSNmVcXq4gxM3Vmarxtt+SvmBnzwLS669MWdVuXboNVj1qN2esVg==",
"requires": {
"he": "^1.1.1"
}
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -11313,6 +11512,11 @@
}
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"highlight.js": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.2.0.tgz",
@@ -11344,6 +11548,20 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
"html-minifier": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
"integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
"requires": {
"camel-case": "^3.0.0",
"clean-css": "^4.2.1",
"commander": "^2.19.0",
"he": "^1.2.0",
"param-case": "^2.1.1",
"relateurl": "^0.2.7",
"uglify-js": "^3.5.1"
}
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -13209,6 +13427,11 @@
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
"integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE="
},
"lower-case": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
"integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -13371,19 +13594,21 @@
"dev": true
},
"mermaid": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.12.1.tgz",
"integrity": "sha512-0UCcSF0FLoNcPBsRF4f9OIV32t41fV18//z8o3S+FDz2PbDA1CRGKdQF9IX84VP4Tv9kcgJI/oqJdcBEtB/GPA==",
"version": "8.10.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.10.2.tgz",
"integrity": "sha512-Za5MrbAOMbEsyY4ONgGjfYz06sbwF1iNGRzp1sQqpOtvXxjxGu/J1jRJ8QyE9kD/D9zj1/KlRrYegWEvA7eZ5Q==",
"requires": {
"@braintree/sanitize-url": "^3.1.0",
"d3": "^5.16.0",
"dagre": "^0.8.5",
"d3": "^5.7.0",
"dagre": "^0.8.4",
"dagre-d3": "^0.6.4",
"dompurify": "2.3.1",
"graphlib": "^2.1.8",
"khroma": "^1.4.1",
"moment-mini": "^2.24.0",
"stylis": "^4.0.10"
"entity-decode": "^2.0.2",
"graphlib": "^2.1.7",
"he": "^1.2.0",
"khroma": "^1.1.0",
"minify": "^4.1.1",
"moment-mini": "^2.22.1",
"stylis": "^3.5.2"
}
},
"micromatch": {
@@ -13417,6 +13642,20 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"minify": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/minify/-/minify-4.1.3.tgz",
"integrity": "sha512-ykuscavxivSmVpcCzsXmsVTukWYLUUtPhHj0w2ILvHDGqC+hsuTCihBn9+PJBd58JNvWTNg9132J9nrrI2anzA==",
"requires": {
"clean-css": "^4.1.6",
"css-b64-images": "~0.2.5",
"debug": "^4.1.0",
"html-minifier": "^4.0.0",
"terser": "^4.0.0",
"try-catch": "^2.0.0",
"try-to-catch": "^1.0.2"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -13461,8 +13700,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"nanomatch": {
"version": "1.2.13",
@@ -13495,6 +13733,14 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"no-case": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
"integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
"requires": {
"lower-case": "^1.1.1"
}
},
"node-emoji": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz",
@@ -13705,6 +13951,14 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
"integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
"requires": {
"no-case": "^2.2.0"
}
},
"parse-json": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz",
@@ -13918,6 +14172,11 @@
"safe-regex": "^1.1.0"
}
},
"relateurl": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
},
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -14472,7 +14731,6 @@
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -14481,8 +14739,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
@@ -14648,9 +14905,9 @@
"dev": true
},
"stylis": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.10.tgz",
"integrity": "sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg=="
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
"integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q=="
},
"supports-color": {
"version": "5.5.0",
@@ -14704,6 +14961,23 @@
"supports-hyperlinks": "^2.0.0"
}
},
"terser": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.12"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -14794,6 +15068,16 @@
"punycode": "^2.1.1"
}
},
"try-catch": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/try-catch/-/try-catch-2.0.1.tgz",
"integrity": "sha512-LsOrmObN/2WdM+y2xG+t16vhYrQsnV8wftXIcIOWZhQcBJvKGYuamJGwnU98A7Jxs2oZNkJztXlphEOoA0DWqg=="
},
"try-to-catch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/try-to-catch/-/try-to-catch-1.1.1.tgz",
"integrity": "sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA=="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -14850,6 +15134,11 @@
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"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=="
},
"union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -14912,6 +15201,11 @@
}
}
},
"upper-case": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
},
"uri-js": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
@@ -14935,6 +15229,7 @@
},
"uslug": {
"version": "git+ssh://git@github.com/laurent22/uslug.git#ba2834d79beb0435318709958b2f5e817d96674d",
"integrity": "sha512-6zzxOsQp+hbOW4zeplEUhKXnBzYIrqYAVlPepBFz/u5q2OulN7tCmBKyWEzDxaiZOLYnUCTViDLazNoq1J6ciA==",
"from": "uslug@git+https://github.com/laurent22/uslug.git#emoji-support",
"requires": {
"node-emoji": "^1.10.0",

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.12.1",
"mermaid": "^8.10.2",
"uslug": "git+https://github.com/laurent22/uslug.git#emoji-support"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"

View File

@@ -1,12 +1,12 @@
{
"name": "@joplin/server",
"version": "2.4.7",
"version": "2.4.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@joplin/server",
"version": "2.4.7",
"version": "2.4.3",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"@koa/cors": "^3.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.4.7",
"version": "2.4.3",
"private": true,
"scripts": {
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",

View File

@@ -48,9 +48,6 @@ export interface EnvVariables {
BUSINESS_EMAIL?: string;
COOKIES_SECURE?: string;
SLOW_QUERY_LOG_ENABLED?: string;
SLOW_QUERY_LOG_MIN_DURATION?: string; // ms
}
let runningInDocker_: boolean = false;
@@ -59,17 +56,6 @@ export function runningInDocker(): boolean {
return runningInDocker_;
}
function envParseBool(s: string): boolean {
return s === '1';
}
function envParseInt(s: string, defaultValue: number = null): number {
if (!s) return defaultValue === null ? 0 : defaultValue;
const output = Number(s);
if (isNaN(output)) throw new Error(`Invalid number: ${s}`);
return output;
}
function databaseHostFromEnv(runningInDocker: boolean, env: EnvVariables): string {
if (env.POSTGRES_HOST) {
// When running within Docker, the app localhost is different from the
@@ -86,16 +72,8 @@ function databaseHostFromEnv(runningInDocker: boolean, env: EnvVariables): strin
}
function databaseConfigFromEnv(runningInDocker: boolean, env: EnvVariables): DatabaseConfig {
const baseConfig: DatabaseConfig = {
client: DatabaseConfigClient.Null,
name: '',
slowQueryLogEnabled: envParseBool(env.SLOW_QUERY_LOG_ENABLED),
slowQueryLogMinDuration: envParseInt(env.SLOW_QUERY_LOG_MIN_DURATION, 10000),
};
if (env.DB_CLIENT === 'pg') {
return {
...baseConfig,
client: DatabaseConfigClient.PostgreSQL,
name: env.POSTGRES_DATABASE || 'joplin',
user: env.POSTGRES_USER || 'joplin',
@@ -106,7 +84,6 @@ function databaseConfigFromEnv(runningInDocker: boolean, env: EnvVariables): Dat
}
return {
...baseConfig,
client: DatabaseConfigClient.SQLite,
name: env.SQLITE_DATABASE,
asyncStackTraces: true,

View File

@@ -104,63 +104,25 @@ export async function waitForConnection(dbConfig: DatabaseConfig): Promise<Conne
}
}
function makeSlowQueryHandler(duration: number, connection: any, sql: string, bindings: any[]) {
return setTimeout(() => {
try {
logger.warn(`Slow query (${duration}ms+):`, connection.raw(sql, bindings).toString());
} catch (error) {
logger.error('Could not log slow query', { sql, bindings }, error);
}
}, duration);
}
export function setupSlowQueryLog(connection: DbConnection, slowQueryLogMinDuration: number) {
interface QueryInfo {
timeoutId: any;
startTime: number;
}
const queryInfos: Record<any, QueryInfo> = {};
// These queries do not return a response, so "query-response" is not
// called.
const ignoredQueries = /^BEGIN|SAVEPOINT|RELEASE SAVEPOINT|COMMIT|ROLLBACK/gi;
connection.on('query', (data) => {
const sql: string = data.sql;
if (!sql || sql.match(ignoredQueries)) return;
const timeoutId = makeSlowQueryHandler(slowQueryLogMinDuration, connection, sql, data.bindings);
queryInfos[data.__knexQueryUid] = {
timeoutId,
startTime: Date.now(),
};
});
connection.on('query-response', (_response, data) => {
const q = queryInfos[data.__knexQueryUid];
if (q) {
clearTimeout(q.timeoutId);
delete queryInfos[data.__knexQueryUid];
}
});
connection.on('query-error', (_response, data) => {
const q = queryInfos[data.__knexQueryUid];
if (q) {
clearTimeout(q.timeoutId);
delete queryInfos[data.__knexQueryUid];
}
});
}
export async function connectDb(dbConfig: DatabaseConfig): Promise<DbConnection> {
const connection = knex(makeKnexConfig(dbConfig));
if (dbConfig.slowQueryLogEnabled) {
setupSlowQueryLog(connection, dbConfig.slowQueryLogMinDuration);
const debugSlowQueries = false;
if (debugSlowQueries) {
const startTimes: Record<string, number> = {};
const slowQueryDuration = 10;
connection.on('query', (data) => {
startTimes[data.__knexQueryUid] = Date.now();
});
connection.on('query-response', (_response, data) => {
const duration = Date.now() - startTimes[data.__knexQueryUid];
if (duration < slowQueryDuration) return;
console.info(`SQL: ${data.sql} (${duration}ms)`);
});
}
return connection;

View File

@@ -42,13 +42,6 @@ async function handleSqliteInProdNotification(ctx: AppContext) {
}
}
function levelClassName(level: NotificationLevel): string {
if (level === NotificationLevel.Important) return 'is-warning';
if (level === NotificationLevel.Normal) return 'is-info';
if (level === NotificationLevel.Error) return 'is-danger';
throw new Error(`Unknown level: ${level}`);
}
async function makeNotificationViews(ctx: AppContext): Promise<NotificationView[]> {
const markdownIt = new MarkdownIt();
@@ -59,7 +52,7 @@ async function makeNotificationViews(ctx: AppContext): Promise<NotificationView[
views.push({
id: n.id,
messageHtml: markdownIt.render(n.message),
levelClassName: levelClassName(n.level),
level: n.level === NotificationLevel.Important ? 'warning' : 'info',
closeUrl: notificationModel.closeUrl(n.id),
});
}

View File

@@ -2,7 +2,6 @@ import { routeResponseFormat, Response, RouteResponseFormat, execRequest } from
import { AppContext, Env } from '../utils/types';
import { isView, View } from '../services/MustacheService';
import config from '../config';
import { userIp } from '../utils/requestUtils';
export default async function(ctx: AppContext) {
const requestStartTime = Date.now();
@@ -27,9 +26,9 @@ export default async function(ctx: AppContext) {
}
} catch (error) {
if (error.httpCode >= 400 && error.httpCode < 500) {
ctx.joplin.appLogger().error(`${error.httpCode}: ` + `${ctx.request.method} ${ctx.path}` + `: ${userIp(ctx)}: ${error.message}`);
ctx.joplin.appLogger().error(`${error.httpCode}: ` + `${ctx.request.method} ${ctx.path}` + ` : ${error.message}`);
} else {
ctx.joplin.appLogger().error(userIp(ctx), error);
ctx.joplin.appLogger().error(error);
}
// Uncomment this when getting HTML blobs as errors while running tests.

View File

@@ -7,9 +7,6 @@ import { Models } from './factory';
import * as EventEmitter from 'events';
import { Config } from '../utils/types';
import personalizedUserContentBaseUrl from '@joplin/lib/services/joplinServer/personalizedUserContentBaseUrl';
import Logger from '@joplin/lib/Logger';
const logger = Logger.create('BaseModel');
export interface SaveOptions {
isNew?: boolean;
@@ -166,38 +163,36 @@ export default abstract class BaseModel<T> {
//
// The `name` argument is only for debugging, so that any stuck transaction
// can be more easily identified.
protected async withTransaction<T>(fn: Function, name: string): Promise<T> {
const debugSteps = false;
const debugTimeout = true;
const timeoutMs = 10000;
protected async withTransaction<T>(fn: Function, name: string = null): Promise<T> {
const debugTransaction = false;
let txIndex = 0;
const debugTimerId = debugTransaction ? setTimeout(() => {
console.info('Transaction did not complete:', name, txIndex);
}, 5000) : null;
const debugTimerId = debugTimeout ? setTimeout(() => {
logger.error(`Transaction #${txIndex} did not complete:`, name);
logger.error('Transaction stack:');
logger.error(this.transactionHandler_.stackInfo);
}, timeoutMs) : null;
const txIndex = await this.transactionHandler_.start();
txIndex = await this.transactionHandler_.start(name);
if (debugSteps) console.info('START', name, txIndex);
if (debugTransaction) console.info('START', name, txIndex);
let output: T = null;
try {
output = await fn();
} catch (error) {
if (debugSteps) console.info('ROLLBACK', name, txIndex);
await this.transactionHandler_.rollback(txIndex);
if (debugTransaction) {
console.info('ROLLBACK', name, txIndex);
clearTimeout(debugTimerId);
}
throw error;
} finally {
if (debugTimerId) clearTimeout(debugTimerId);
}
if (debugSteps) console.info('COMMIT', name, txIndex);
if (debugTransaction) {
console.info('COMMIT', name, txIndex);
clearTimeout(debugTimerId);
}
await this.transactionHandler_.commit(txIndex);
return output;

View File

@@ -34,7 +34,7 @@ export default class ItemResourceModel extends BaseModel<ItemResource> {
resource_id: resourceId,
});
}
}, 'ItemResourceModel::addResourceIds');
});
}
public async byItemId(itemId: Uuid): Promise<string[]> {

View File

@@ -48,7 +48,7 @@ export default class NotificationModel extends BaseModel<KeyValue> {
value: this.serializeValue(value),
type,
});
}, 'KeyValueModel::setValue');
});
}
public async value<T>(key: string, defaultValue: Value = null): Promise<T> {

View File

@@ -1,10 +1,8 @@
import { Notification, NotificationLevel, Uuid } from '../services/database/types';
import { ErrorUnprocessableEntity } from '../utils/errors';
import uuidgen from '../utils/uuidgen';
import BaseModel, { ValidateOptions } from './BaseModel';
export enum NotificationKey {
Any = 'any',
ConfirmEmail = 'confirmEmail',
PasswordSet = 'passwordSet',
EmailConfirmed = 'emailConfirmed',
@@ -54,10 +52,6 @@ export default class NotificationModel extends BaseModel<Notification> {
level: NotificationLevel.Normal,
message: 'Thank you! Your account has been successfully upgraded to Pro.',
},
[NotificationKey.Any]: {
level: NotificationLevel.Normal,
message: '',
},
};
const type = notificationTypes[key];
@@ -78,9 +72,7 @@ export default class NotificationModel extends BaseModel<Notification> {
}
}
const actualKey = key === NotificationKey.Any ? `any_${uuidgen()}` : key;
return this.save({ key: actualKey, message, level, owner_id: userId });
return this.save({ key, message, level, owner_id: userId });
}
public async markAsRead(userId: Uuid, key: NotificationKey): Promise<void> {

View File

@@ -315,7 +315,7 @@ export default class ShareModel extends BaseModel<Share> {
for (const item of items) {
await this.models().userItem().add(userId, item.id);
}
}, 'ShareModel::createSharedFolderUserItems');
});
}
public async shareFolder(owner: User, folderId: string): Promise<Share> {

View File

@@ -126,7 +126,7 @@ export default class ShareUserModel extends BaseModel<ShareUser> {
}
return this.save({ ...shareUser, status });
}, 'ShareUserModel::setStatus');
});
}
public async deleteByShare(share: Share): Promise<void> {

View File

@@ -91,14 +91,14 @@ export default class SubscriptionModel extends BaseModel<Subscription> {
last_payment_time: now,
last_payment_failed_time: 0,
});
}, 'SubscriptionModel::handlePayment');
});
} else {
// We only update the payment failed time if it's not already set
// since the only thing that matter is the first time the payment
// failed.
//
// We don't update the user can_upload and enabled properties here
// because it's done after a few days from TaskService.
// because it's done after a few days from CronService.
if (!sub.last_payment_failed_time) {
const user = await this.models().user().load(sub.user_id, { fields: ['email', 'id', 'full_name'] });
@@ -145,7 +145,7 @@ export default class SubscriptionModel extends BaseModel<Subscription> {
});
return { user, subscription };
}, 'SubscriptionModel::saveUserAndSubscription');
});
}
public async toggleSoftDelete(id: number, isDeleted: boolean) {

View File

@@ -70,7 +70,7 @@ export default class UserFlagModels extends BaseModel<UserFlag> {
await this.add(userId, flagType, { updateUser: false });
}
await this.updateUserFromFlags(userId);
}, 'UserFlagModels::addMulti');
});
}
public async removeMulti(userId: Uuid, flagTypes: UserFlagType[]) {
@@ -79,7 +79,7 @@ export default class UserFlagModels extends BaseModel<UserFlag> {
await this.remove(userId, flagType, { updateUser: false });
}
await this.updateUserFromFlags(userId);
}, 'UserFlagModels::removeMulti');
});
}
// As a general rule the `enabled` and `can_upload` properties should not
@@ -95,34 +95,17 @@ export default class UserFlagModels extends BaseModel<UserFlag> {
enabled: 1,
};
const accountWithoutSubscriptionFlag = flags.find(f => f.type === UserFlagType.AccountWithoutSubscription);
const accountOverLimitFlag = flags.find(f => f.type === UserFlagType.AccountOverLimit);
const failedPaymentWarningFlag = flags.find(f => f.type === UserFlagType.FailedPaymentWarning);
const failedPaymentFinalFlag = flags.find(f => f.type === UserFlagType.FailedPaymentFinal);
const subscriptionCancelledFlag = flags.find(f => f.type === UserFlagType.SubscriptionCancelled);
const manuallyDisabledFlag = flags.find(f => f.type === UserFlagType.ManuallyDisabled);
if (accountWithoutSubscriptionFlag) {
if (flags.find(f => f.type === UserFlagType.AccountWithoutSubscription)) {
newProps.can_upload = 0;
}
if (accountOverLimitFlag) {
} else if (flags.find(f => f.type === UserFlagType.AccountOverLimit)) {
newProps.can_upload = 0;
}
if (failedPaymentWarningFlag) {
} else if (flags.find(f => f.type === UserFlagType.FailedPaymentWarning)) {
newProps.can_upload = 0;
}
if (failedPaymentFinalFlag) {
} else if (flags.find(f => f.type === UserFlagType.FailedPaymentFinal)) {
newProps.enabled = 0;
}
if (subscriptionCancelledFlag) {
} else if (flags.find(f => f.type === UserFlagType.SubscriptionCancelled)) {
newProps.enabled = 0;
}
if (manuallyDisabledFlag) {
} else if (flags.find(f => f.type === UserFlagType.ManuallyDisabled)) {
newProps.enabled = 0;
}

View File

@@ -141,7 +141,7 @@ export default class UserItemModel extends BaseModel<UserItem> {
}
return super.save(userItem, options);
}, 'UserItemModel::save');
});
}
public async delete(_id: string | string[], _options: DeleteOptions = {}): Promise<void> {

View File

@@ -369,7 +369,7 @@ export default class UserModel extends BaseModel<User> {
key: `payment_failed_upload_disabled_${sub.last_payment_failed_time}`,
});
}
}, 'UserModel::handleFailedPaymentSubscriptions');
});
}
public async handleOversizedAccounts() {
@@ -430,12 +430,12 @@ export default class UserModel extends BaseModel<User> {
});
}
}
}, 'UserModel::handleOversizedAccounts');
});
}
private formatValues(user: User): User {
const output: User = { ...user };
if ('email' in output) output.email = (`${user.email}`).trim().toLowerCase();
if ('email' in output) output.email = user.email.trim().toLowerCase();
return output;
}
@@ -466,15 +466,7 @@ export default class UserModel extends BaseModel<User> {
if (isNew) UserModel.eventEmitter.emit('created');
return savedUser;
}, 'UserModel::save');
}
public async saveMulti(users: User[], options: SaveOptions = {}): Promise<void> {
await this.withTransaction(async () => {
for (const user of users) {
await this.save(user, options);
}
}, 'UserModel::saveMulti');
});
}
}

View File

@@ -1,5 +1,5 @@
import config from '../../config';
import { clearDatabase, createTestUsers, CreateTestUsersOptions } from '../../tools/debugTools';
import { createTestUsers } from '../../tools/debugTools';
import { bodyFields } from '../../utils/requestUtils';
import Router from '../../utils/Router';
import { RouteType } from '../../utils/types';
@@ -12,8 +12,6 @@ router.public = true;
interface Query {
action: string;
count?: number;
fromNum?: number;
}
router.post('api/debug', async (_path: SubPath, ctx: AppContext) => {
@@ -22,16 +20,7 @@ router.post('api/debug', async (_path: SubPath, ctx: AppContext) => {
console.info(`Action: ${query.action}`);
if (query.action === 'createTestUsers') {
const options: CreateTestUsersOptions = {};
if ('count' in query) options.count = query.count;
if ('fromNum' in query) options.fromNum = query.fromNum;
await createTestUsers(ctx.joplin.db, config(), options);
}
if (query.action === 'clearDatabase') {
await clearDatabase(ctx.joplin.db);
await createTestUsers(ctx.joplin.db, config());
}
});

View File

@@ -3,7 +3,7 @@ import Router from '../../utils/Router';
import { RouteType } from '../../utils/types';
import { ErrorForbidden } from '../../utils/errors';
import { AppContext } from '../../utils/types';
import { bodyFields, userIp } from '../../utils/requestUtils';
import { bodyFields } from '../../utils/requestUtils';
import { User } from '../../services/database/types';
import limiterLoginBruteForce from '../../utils/request/limiterLoginBruteForce';
@@ -12,7 +12,7 @@ const router = new Router(RouteType.Api);
router.public = true;
router.post('api/sessions', async (_path: SubPath, ctx: AppContext) => {
await limiterLoginBruteForce(userIp(ctx));
await limiterLoginBruteForce(ctx.ip);
const fields: User = await bodyFields(ctx.req);
const user = await ctx.joplin.models.user().login(fields.email, fields.password);

View File

@@ -2,7 +2,7 @@ import { SubPath, redirect, makeUrl, UrlType } from '../../utils/routeUtils';
import Router from '../../utils/Router';
import { RouteType } from '../../utils/types';
import { AppContext } from '../../utils/types';
import { formParse, userIp } from '../../utils/requestUtils';
import { formParse } from '../../utils/requestUtils';
import config from '../../config';
import defaultView from '../../utils/defaultView';
import { View } from '../../services/MustacheService';
@@ -27,7 +27,7 @@ router.get('login', async (_path: SubPath, _ctx: AppContext) => {
});
router.post('login', async (_path: SubPath, ctx: AppContext) => {
await limiterLoginBruteForce(userIp(ctx));
await limiterLoginBruteForce(ctx.ip);
try {
const body = await formParse(ctx.req);

View File

@@ -137,14 +137,6 @@ export const postHandlers: PostHandlers = {
//
// - The public config is under packages/server/stripeConfig.json
// - The private config is in the server .env file
//
// # Failed Stripe cli login
//
// If the tool show this error, with code "api_key_expired":
//
// > FATAL Error while authenticating with Stripe: Authorization failed
//
// Need to logout and login again to refresh the CLI token - `stripe logout && stripe login`
webhook: async (stripe: Stripe, _path: SubPath, ctx: AppContext, event: Stripe.Event = null, logErrors: boolean = true) => {
event = event ? event : await stripeEvent(stripe, ctx.req);
@@ -434,7 +426,7 @@ const getHandlers: Record<string, StripeRouteHandler> = {
<body>
<button id="checkout">Subscribe</button>
<script>
var PRICE_ID = ${JSON.stringify(basicPrice.id)};
var PRICE_ID = ${basicPrice.id};
function handleResult() {
console.info('Redirected to checkout');

View File

@@ -1,135 +0,0 @@
import { makeUrl, redirect, SubPath, UrlType } from '../../utils/routeUtils';
import Router from '../../utils/Router';
import { RouteType } from '../../utils/types';
import { AppContext } from '../../utils/types';
import { bodyFields } from '../../utils/requestUtils';
import { ErrorBadRequest, ErrorForbidden } from '../../utils/errors';
import defaultView from '../../utils/defaultView';
import { makeTableView, Row, Table } from '../../utils/views/table';
import { yesOrNo } from '../../utils/strings';
import { formatDateTime } from '../../utils/time';
import { createCsrfTag } from '../../utils/csrf';
import { RunType } from '../../services/TaskService';
import { NotificationKey } from '../../models/NotificationModel';
import { NotificationLevel } from '../../services/database/types';
const router: Router = new Router(RouteType.Web);
router.post('tasks', async (_path: SubPath, ctx: AppContext) => {
const user = ctx.joplin.owner;
if (!user.is_admin) throw new ErrorForbidden();
const taskService = ctx.joplin.services.tasks;
const fields: any = await bodyFields(ctx.req);
if (fields.startTaskButton) {
const errors: Error[] = [];
for (const k of Object.keys(fields)) {
if (k.startsWith('checkbox_')) {
const taskId = k.substr(9);
try {
void taskService.runTask(taskId, RunType.Manual);
} catch (error) {
errors.push(error);
}
}
}
if (errors.length) {
await ctx.joplin.models.notification().add(
user.id,
NotificationKey.Any,
NotificationLevel.Error,
`Some tasks could not be started: ${errors.join('. ')}`
);
}
} else {
throw new ErrorBadRequest('Invalid action');
}
return redirect(ctx, makeUrl(UrlType.Tasks));
});
router.get('tasks', async (_path: SubPath, ctx: AppContext) => {
const user = ctx.joplin.owner;
if (!user.is_admin) throw new ErrorForbidden();
const taskService = ctx.joplin.services.tasks;
const taskRows: Row[] = [];
for (const [taskId, task] of Object.entries(taskService.tasks)) {
const state = taskService.taskState(taskId);
taskRows.push([
{
value: `checkbox_${taskId}`,
checkbox: true,
},
{
value: taskId,
},
{
value: task.description,
},
{
value: task.schedule,
},
{
value: yesOrNo(state.running),
},
{
value: state.lastRunTime ? formatDateTime(state.lastRunTime) : '-',
},
{
value: state.lastCompletionTime ? formatDateTime(state.lastCompletionTime) : '-',
},
]);
}
const table: Table = {
headers: [
{
name: 'select',
label: '',
},
{
name: 'id',
label: 'ID',
},
{
name: 'description',
label: 'Description',
},
{
name: 'schedule',
label: 'Schedule',
},
{
name: 'running',
label: 'Running',
},
{
name: 'lastRunTime',
label: 'Last Run',
},
{
name: 'lastCompletionTime',
label: 'Last Completion',
},
],
rows: taskRows,
};
return {
...defaultView('tasks', 'Tasks'),
content: {
itemTable: makeTableView(table),
postUrl: makeUrl(UrlType.Tasks),
csrfTag: await createCsrfTag(ctx),
},
cssFiles: ['index/tasks'],
};
});
export default router;

View File

@@ -15,7 +15,7 @@ import uuidgen from '../../utils/uuidgen';
import { formatMaxItemSize, formatMaxTotalSize, formatTotalSize, formatTotalSizePercent, yesOrNo } from '../../utils/strings';
import { getCanShareFolder, totalSizeClass } from '../../models/utils/user';
import { yesNoDefaultOptions, yesNoOptions } from '../../utils/views/select';
import { confirmUrl, stripePortalUrl } from '../../utils/urlUtils';
import { confirmUrl } from '../../utils/urlUtils';
import { cancelSubscriptionByUserId, updateSubscriptionType } from '../../utils/stripe';
import { createCsrfTag } from '../../utils/csrf';
import { formatDateTime } from '../../utils/time';
@@ -175,7 +175,6 @@ router.get('users/:id', async (path: SubPath, ctx: AppContext, user: User = null
view.content.canShareFolderOptions = yesNoDefaultOptions(user, 'can_share_folder');
view.content.canUploadOptions = yesNoOptions(user, 'can_upload');
view.content.userFlags = userFlags;
view.content.stripePortalUrl = stripePortalUrl();
view.jsFiles.push('zxcvbn');
view.cssFiles.push('index/user');

View File

@@ -1,32 +1,31 @@
import { Routers } from '../utils/routeUtils';
import apiBatch from './api/batch';
import apiBatchItems from './api/batch_items';
import apiDebug from './api/debug';
import apiEvents from './api/events';
import apiBatchItems from './api/batch_items';
import apiItems from './api/items';
import apiPing from './api/ping';
import apiSessions from './api/sessions';
import apiUsers from './api/users';
import apiShares from './api/shares';
import apiShareUsers from './api/share_users';
import apiUsers from './api/users';
import indexChanges from './index/changes';
import indexHelp from './index/help';
import indexHome from './index/home';
import indexItems from './index/items';
import indexLogin from './index/login';
import indexLogout from './index/logout';
import indexNotifications from './index/notifications';
import indexPassword from './index/password';
import indexPrivacy from './index/privacy';
import indexShares from './index/shares';
import indexSignup from './index/signup';
import indexStripe from './index/stripe';
import indexTasks from './index/tasks';
import indexTerms from './index/terms';
import indexUpgrade from './index/upgrade';
import indexShares from './index/shares';
import indexUsers from './index/users';
import indexStripe from './index/stripe';
import indexTerms from './index/terms';
import indexPrivacy from './index/privacy';
import indexUpgrade from './index/upgrade';
import indexHelp from './index/help';
import defaultRoute from './default';
@@ -57,7 +56,6 @@ const routes: Routers = {
'privacy': indexPrivacy,
'upgrade': indexUpgrade,
'help': indexHelp,
'tasks': indexTasks,
'': defaultRoute,
};

View File

@@ -0,0 +1,42 @@
import Logger from '@joplin/lib/Logger';
import BaseService from './BaseService';
const cron = require('node-cron');
const logger = Logger.create('cron');
async function runCronTask(name: string, callback: Function) {
const startTime = Date.now();
logger.info(`Running task "${name}"`);
try {
await callback();
} catch (error) {
logger.error(`On task "${name}"`, error);
}
logger.info(`Completed task "${name}" in ${Date.now() - startTime}ms`);
}
export default class CronService extends BaseService {
public async runInBackground() {
cron.schedule('0 */6 * * *', async () => {
await runCronTask('deleteExpiredTokens', async () => this.models.token().deleteExpiredTokens());
});
cron.schedule('0 * * * *', async () => {
await runCronTask('updateTotalSizes', async () => this.models.item().updateTotalSizes());
});
cron.schedule('0 12 * * *', async () => {
await runCronTask('handleBetaUserEmails', async () => this.models.user().handleBetaUserEmails());
});
cron.schedule('0 13 * * *', async () => {
await runCronTask('handleFailedPaymentSubscriptions', async () => this.models.user().handleFailedPaymentSubscriptions());
});
cron.schedule('0 14 * * *', async () => {
await runCronTask('handleOversizedAccounts', async () => this.models.user().handleOversizedAccounts());
});
}
}

View File

@@ -1,82 +0,0 @@
import config from '../config';
import { Models } from '../models/factory';
import { afterAllTests, beforeAllDb, beforeEachDb, expectThrow, models, msleep } from '../utils/testing/testUtils';
import { Env } from '../utils/types';
import TaskService, { RunType, Task } from './TaskService';
const newService = () => {
return new TaskService(Env.Dev, models(), config());
};
describe('TaskService', function() {
beforeAll(async () => {
await beforeAllDb('TaskService');
});
afterAll(async () => {
await afterAllTests();
});
beforeEach(async () => {
await beforeEachDb();
});
test('should register a task', async function() {
const service = newService();
const task: Task = {
id: 'test',
description: '',
run: (_models: Models) => {},
schedule: '',
};
service.registerTask(task);
expect(service.tasks['test']).toBeTruthy();
await expectThrow(async () => service.registerTask(task));
});
test('should run a task', async function() {
const service = newService();
let finishTask = false;
let taskHasRan = false;
const task: Task = {
id: 'test',
description: '',
run: async (_models: Models) => {
const iid = setInterval(() => {
if (finishTask) {
clearInterval(iid);
taskHasRan = true;
}
}, 1);
},
schedule: '',
};
service.registerTask(task);
expect(service.taskState('test').running).toBe(false);
const startTime = new Date();
void service.runTask('test', RunType.Manual);
expect(service.taskState('test').running).toBe(true);
expect(service.taskState('test').lastCompletionTime).toBeFalsy();
expect(service.taskState('test').lastRunTime.getTime()).toBeGreaterThanOrEqual(startTime.getTime());
await msleep(1);
finishTask = true;
await msleep(3);
expect(taskHasRan).toBe(true);
expect(service.taskState('test').running).toBe(false);
expect(service.taskState('test').lastCompletionTime.getTime()).toBeGreaterThan(startTime.getTime());
});
});

View File

@@ -1,108 +0,0 @@
import Logger from '@joplin/lib/Logger';
import { Models } from '../models/factory';
import BaseService from './BaseService';
const cron = require('node-cron');
const logger = Logger.create('TaskService');
type TaskId = string;
export enum RunType {
Scheduled = 1,
Manual = 2,
}
const runTypeToString = (runType: RunType) => {
if (runType === RunType.Scheduled) return 'scheduled';
if (runType === RunType.Manual) return 'manual';
throw new Error(`Unknown run type: ${runType}`);
};
export interface Task {
id: TaskId;
description: string;
schedule: string;
run(models: Models): void;
}
export type Tasks = Record<TaskId, Task>;
interface TaskState {
running: boolean;
lastRunTime: Date;
lastCompletionTime: Date;
}
const defaultTaskState: TaskState = {
running: false,
lastRunTime: null,
lastCompletionTime: null,
};
export default class TaskService extends BaseService {
private tasks_: Tasks = {};
private taskStates_: Record<TaskId, TaskState> = {};
public registerTask(task: Task) {
if (this.tasks_[task.id]) throw new Error(`Already a task with this ID: ${task.id}`);
this.tasks_[task.id] = task;
this.taskStates_[task.id] = { ...defaultTaskState };
}
public registerTasks(tasks: Task[]) {
for (const task of tasks) this.registerTask(task);
}
public get tasks(): Tasks {
return this.tasks_;
}
public taskState(id: TaskId): TaskState {
if (!this.taskStates_[id]) throw new Error(`No such task: ${id}`);
return this.taskStates_[id];
}
// TODO: add tests
public async runTask(id: TaskId, runType: RunType) {
const state = this.taskState(id);
if (state.running) throw new Error(`Task is already running: ${id}`);
const startTime = Date.now();
this.taskStates_[id] = {
...this.taskStates_[id],
running: true,
lastRunTime: new Date(),
};
try {
logger.info(`Running "${id}" (${runTypeToString(runType)})...`);
await this.tasks_[id].run(this.models);
} catch (error) {
logger.error(`On task "${id}"`, error);
}
this.taskStates_[id] = {
...this.taskStates_[id],
running: false,
lastCompletionTime: new Date(),
};
logger.info(`Completed "${id}" in ${Date.now() - startTime}ms`);
}
public async runInBackground() {
for (const [taskId, task] of Object.entries(this.tasks_)) {
if (!task.schedule) continue;
logger.info(`Scheduling task "${taskId}": ${task.schedule}`);
cron.schedule(task.schedule, async () => {
await this.runTask(taskId, RunType.Scheduled);
});
}
}
}

View File

@@ -6,7 +6,6 @@ export enum ItemAddressingType {
}
export enum NotificationLevel {
Error = 5,
Important = 10,
Normal = 20,
}

View File

@@ -1,11 +1,11 @@
import CronService from './CronService';
import EmailService from './EmailService';
import MustacheService from './MustacheService';
import ShareService from './ShareService';
import TaskService from './TaskService';
export interface Services {
share: ShareService;
email: EmailService;
cron: CronService;
mustache: MustacheService;
tasks: TaskService;
}

View File

@@ -1,14 +1,9 @@
import { DbConnection, dropTables, migrateLatest } from '../db';
import newModelFactory from '../models/factory';
import { AccountType } from '../models/UserModel';
import { User, UserFlagType } from '../services/database/types';
import { UserFlagType } from '../services/database/types';
import { Config } from '../utils/types';
export interface CreateTestUsersOptions {
count?: number;
fromNum?: number;
}
export async function handleDebugCommands(argv: any, db: DbConnection, config: Config): Promise<boolean> {
if (argv.debugCreateTestUsers) {
await createTestUsers(db, config);
@@ -19,79 +14,51 @@ export async function handleDebugCommands(argv: any, db: DbConnection, config: C
return true;
}
export async function clearDatabase(db: DbConnection) {
export async function createTestUsers(db: DbConnection, config: Config) {
await dropTables(db);
await migrateLatest(db);
}
export async function createTestUsers(db: DbConnection, config: Config, options: CreateTestUsersOptions = null) {
options = {
count: 0,
fromNum: 1,
...options,
};
const password = 'hunter1hunter2hunter3';
const models = newModelFactory(db, config);
if (options.count) {
const models = newModelFactory(db, config);
for (let userNum = 1; userNum <= 2; userNum++) {
await models.user().save({
email: `user${userNum}@example.com`,
password,
full_name: `User ${userNum}`,
});
}
const users: User[] = [];
{
const { user } = await models.subscription().saveUserAndSubscription(
'usersub@example.com',
'With Sub',
AccountType.Basic,
'usr_111',
'sub_111'
);
await models.user().save({ id: user.id, password });
}
for (let i = 0; i < options.count; i++) {
const userNum = i + options.fromNum;
users.push({
email: `user${userNum}@example.com`,
password,
full_name: `User ${userNum}`,
});
}
{
const { user, subscription } = await models.subscription().saveUserAndSubscription(
'userfailedpayment@example.com',
'Failed Payment',
AccountType.Basic,
'usr_222',
'sub_222'
);
await models.user().save({ id: user.id, password });
await models.subscription().handlePayment(subscription.stripe_subscription_id, false);
}
await models.user().saveMulti(users);
} else {
await dropTables(db);
await migrateLatest(db);
const models = newModelFactory(db, config);
{
const user = await models.user().save({
email: 'userwithflags@example.com',
password,
full_name: 'User Withflags',
});
for (let userNum = 1; userNum <= 2; userNum++) {
await models.user().save({
email: `user${userNum}@example.com`,
password,
full_name: `User ${userNum}`,
});
}
{
const { user } = await models.subscription().saveUserAndSubscription(
'usersub@example.com',
'With Sub',
AccountType.Basic,
'usr_111',
'sub_111'
);
await models.user().save({ id: user.id, password });
}
{
const { user, subscription } = await models.subscription().saveUserAndSubscription(
'userfailedpayment@example.com',
'Failed Payment',
AccountType.Basic,
'usr_222',
'sub_222'
);
await models.user().save({ id: user.id, password });
await models.subscription().handlePayment(subscription.stripe_subscription_id, false);
}
{
const user = await models.user().save({
email: 'userwithflags@example.com',
password,
full_name: 'User Withflags',
});
await models.userFlag().add(user.id, UserFlagType.AccountOverLimit);
}
await models.userFlag().add(user.id, UserFlagType.AccountOverLimit);
}
}

View File

@@ -1,12 +1,6 @@
import { Knex } from 'knex';
import { DbConnection } from '../db';
interface TransactionInfo {
name: string;
index: number;
timestamp: Date;
}
// This transaction handler allows abstracting away the complexity of managing nested transactions
// within models.
// Any method in a model can start a transaction and, if one is already started, it
@@ -15,7 +9,7 @@ interface TransactionInfo {
// Set logEnabled_ to `true` to see what happens with nested transactions.
export default class TransactionHandler {
private transactionStack_: TransactionInfo[] = [];
private transactionStack_: number[] = [];
private activeTransaction_: Knex.Transaction = null;
private transactionIndex_: number = 0;
private logEnabled_: boolean = false;
@@ -42,15 +36,7 @@ export default class TransactionHandler {
return this.activeTransaction_;
}
public get stackInfo(): string {
const output: string[] = [];
for (const t of this.transactionStack_) {
output.push(`#${t.index}: ${t.name}: ${t.timestamp.toUTCString()}`);
}
return output.join('\n');
}
public async start(name: string): Promise<number> {
public async start(): Promise<number> {
const txIndex = ++this.transactionIndex_;
this.log(`Starting transaction: ${txIndex}`);
@@ -61,19 +47,14 @@ export default class TransactionHandler {
this.log(`Got transaction: ${txIndex}`);
}
this.transactionStack_.push({
name,
index: txIndex,
timestamp: new Date(),
});
this.transactionStack_.push(txIndex);
return txIndex;
}
private finishTransaction(txIndex: number): boolean {
if (!this.transactionStack_.length) throw new Error('Committing but no transaction was started');
const lastTx = this.transactionStack_.pop();
if (lastTx.index !== txIndex) throw new Error(`Committing a transaction but was not last to start one: ${txIndex}. Expected: ${lastTx.index}`);
const lastTxIndex = this.transactionStack_.pop();
if (lastTxIndex !== txIndex) throw new Error(`Committing a transaction but was not last to start one: ${txIndex}. Expected: ${lastTxIndex}`);
return !this.transactionStack_.length;
}

View File

@@ -70,8 +70,3 @@ export function contextSessionId(ctx: AppContext, throwIfNotFound = true): strin
export function isApiRequest(ctx: AppContext): boolean {
return ctx.path.indexOf('/api/') === 0;
}
export function userIp(ctx: AppContext): string {
if (ctx.headers['x-real-ip']) return ctx.headers['x-real-ip'];
return ctx.ip;
}

View File

@@ -271,7 +271,6 @@ export enum UrlType {
Login = 'login',
Terms = 'terms',
Privacy = 'privacy',
Tasks = 'tasks',
}
export function makeUrl(urlType: UrlType): string {

View File

@@ -7,15 +7,15 @@ import routes from '../routes/routes';
import ShareService from '../services/ShareService';
import { Services } from '../services/types';
import EmailService from '../services/EmailService';
import CronService from '../services/CronService';
import MustacheService from '../services/MustacheService';
import setupTaskService from './setupTaskService';
async function setupServices(env: Env, models: Models, config: Config): Promise<Services> {
const output: Services = {
share: new ShareService(env, models, config),
email: new EmailService(env, models, config),
cron: new CronService(env, models, config),
mustache: new MustacheService(config.viewDir, config.baseUrl),
tasks: setupTaskService(env, models, config),
};
await output.mustache.loadPartials();

View File

@@ -1,49 +0,0 @@
import { Models } from '../models/factory';
import TaskService, { Task } from '../services/TaskService';
import { Config, Env } from './types';
export default function(env: Env, models: Models, config: Config): TaskService {
const taskService = new TaskService(env, models, config);
let tasks: Task[] = [
{
id: 'deleteExpiredTokens',
description: 'Delete expired tokens',
schedule: '0 */6 * * *',
run: (models: Models) => models.token().deleteExpiredTokens(),
},
{
id: 'updateTotalSizes',
description: 'Update total sizes',
schedule: '0 * * * *',
run: (models: Models) => models.item().updateTotalSizes(),
},
{
id: 'handleOversizedAccounts',
description: 'Process oversized accounts',
schedule: '0 14 * * *',
run: (models: Models) => models.user().handleOversizedAccounts(),
},
];
if (config.isJoplinCloud) {
tasks = tasks.concat([
{
id: 'handleBetaUserEmails',
description: 'Process beta user emails',
schedule: '0 12 * * *',
run: (models: Models) => models.user().handleBetaUserEmails(),
},
{
id: 'handleFailedPaymentSubscriptions',
description: 'Process failed payment subscriptions',
schedule: '0 13 * * *',
run: (models: Models) => models.user().handleFailedPaymentSubscriptions(),
},
]);
}
taskService.registerTasks(tasks);
return taskService;
}

View File

@@ -3,5 +3,5 @@ import { Services } from '../services/types';
export default async function startServices(services: Services) {
void services.share.runInBackground();
void services.email.runInBackground();
void services.tasks.runInBackground();
void services.cron.runInBackground();
}

View File

@@ -29,8 +29,7 @@ export function msleep(ms: number) {
});
}
export function formatDateTime(ms: number | Date): string {
ms = ms instanceof Date ? ms.getTime() : ms;
export function formatDateTime(ms: number): string {
return `${dayjs(ms).format('D MMM YY HH:mm:ss')} (${defaultTimezone()})`;
}

View File

@@ -17,7 +17,7 @@ export enum Env {
export interface NotificationView {
id: Uuid;
messageHtml: string;
levelClassName: string;
level: string;
closeUrl: string;
}
@@ -50,7 +50,6 @@ export interface AppContext extends Koa.Context {
}
export enum DatabaseConfigClient {
Null = 'null',
PostgreSQL = 'pg',
SQLite = 'sqlite3',
}
@@ -65,8 +64,6 @@ export interface DatabaseConfig {
user?: string;
password?: string;
asyncStackTraces?: boolean;
slowQueryLogEnabled?: boolean;
slowQueryLogMinDuration?: number;
}
export interface MailerConfig {

View File

@@ -4,13 +4,11 @@ import { setQueryParameters } from '../urlUtils';
const defaultSortOrder = PaginationOrderDir.ASC;
function headerIsSelectedClass(name: string, pagination: Pagination): string {
if (!pagination) return '';
const orderBy = pagination.order[0].by;
return name === orderBy ? 'is-selected' : '';
}
function headerSortIconDir(name: string, pagination: Pagination): string {
if (!pagination) return '';
const orderBy = pagination.order[0].by;
const orderDir = orderBy === name ? pagination.order[0].dir : defaultSortOrder;
return orderDir === PaginationOrderDir.ASC ? 'up' : 'down';
@@ -37,7 +35,6 @@ interface HeaderView {
interface RowItem {
value: string;
checkbox?: boolean;
url?: string;
stretch?: boolean;
}
@@ -48,7 +45,6 @@ interface RowItemView {
value: string;
classNames: string[];
url: string;
checkbox: boolean;
}
type RowView = RowItemView[];
@@ -56,10 +52,10 @@ type RowView = RowItemView[];
export interface Table {
headers: Header[];
rows: Row[];
baseUrl?: string;
requestQuery?: any;
pageCount?: number;
pagination?: Pagination;
baseUrl: string;
requestQuery: any;
pageCount: number;
pagination: Pagination;
}
export interface TableView {
@@ -81,7 +77,7 @@ export function makeTablePagination(query: any, defaultOrderField: string, defau
function makeHeaderView(header: Header, parentBaseUrl: string, baseUrlQuery: PaginationQueryParams, pagination: Pagination): HeaderView {
return {
label: header.label,
sortLink: !pagination ? null : setQueryParameters(parentBaseUrl, { ...baseUrlQuery, 'order_by': header.name, 'order_dir': headerNextOrder(header.name, pagination) }),
sortLink: setQueryParameters(parentBaseUrl, { ...baseUrlQuery, 'order_by': header.name, 'order_dir': headerNextOrder(header.name, pagination) }),
classNames: [header.stretch ? 'stretch' : 'nowrap', headerIsSelectedClass(header.name, pagination)],
iconDir: headerSortIconDir(header.name, pagination),
};
@@ -93,21 +89,14 @@ function makeRowView(row: Row): RowView {
value: rowItem.value,
classNames: [rowItem.stretch ? 'stretch' : 'nowrap'],
url: rowItem.url,
checkbox: rowItem.checkbox,
};
});
}
export function makeTableView(table: Table): TableView {
let paginationLinks: PageLink[] = [];
let baseUrlQuery: PaginationQueryParams = null;
let pagination: Pagination = null;
if (table.pageCount) {
baseUrlQuery = filterPaginationQueryParams(table.requestQuery);
pagination = table.pagination;
paginationLinks = createPaginationLinks(pagination.page, table.pageCount, setQueryParameters(table.baseUrl, { ...baseUrlQuery, 'page': 'PAGE_NUMBER' }));
}
const baseUrlQuery = filterPaginationQueryParams(table.requestQuery);
const pagination = table.pagination;
const paginationLinks = createPaginationLinks(pagination.page, table.pageCount, setQueryParameters(table.baseUrl, { ...baseUrlQuery, 'page': 'PAGE_NUMBER' }));
return {
headers: table.headers.map(h => makeHeaderView(h, table.baseUrl, baseUrlQuery, pagination)),

View File

@@ -4,20 +4,16 @@
Most of your details can be found in your Profile page. To open it, click on the Profile button - this is the button in the top right corner, with your name or email on it.
## How can I cancel my account?
Click on the [Profile button](#how-can-i-change-my-details), then scroll down and click on "Cancel subscription".
## How can I get more space?
If you are on a Basic account, you may upgrade to a Pro account to get more space. Click on the [Profile button](#how-can-i-change-my-details), then scroll down and select "Upgrade account".
If you are already on a Pro account, and you need more space for specific reasons, please contact us as we may increase the cap in some cases.
## How can I manage my payment details?
To update your card or other payment details, click on the [Profile button](#how-can-i-change-my-details), then scroll down and click on "Manage payment details".
## How can I cancel my account?
Click on the [Profile button](#how-can-i-change-my-details), then scroll down and click on "Cancel subscription".
## Further information
- [Joplin Offical Website](https://joplinapp.org)

View File

@@ -1,11 +0,0 @@
<form method='POST' action="{{postUrl}}">
{{{csrfTag}}}
{{#itemTable}}
{{>table}}
{{/itemTable}}
<div class="block">
<input class="button is-link" type="submit" value="Start selected tasks" name="startTaskButton"/>
</div>
</form>

View File

@@ -131,9 +131,6 @@
{{#showUpdateSubscriptionPro}}
<a href="{{{global.baseUrl}}}/upgrade" class="button is-warning block">Upgrade to Pro</a>
{{/showUpdateSubscriptionPro}}
{{#showCancelSubscription}}
<p class="block"><a href="{{stripePortalUrl}}">Manage payment details</a></p>
{{/showCancelSubscription}}
{{#showCancelSubscription}}
<p id="user_cancel_subscription_link" class="block"><a href="#">Cancel subscription</a></p>
<input type="submit" id="user_cancel_subscription_button" name="user_cancel_subscription_button" class="button is-danger" value="Cancel subscription" />

View File

@@ -16,9 +16,6 @@
{{/global.owner.is_admin}}
<a class="navbar-item" href="{{{global.baseUrl}}}/items">Items</a>
<a class="navbar-item" href="{{{global.baseUrl}}}/changes">Log</a>
{{#global.owner.is_admin}}
<a class="navbar-item" href="{{{global.baseUrl}}}/tasks">Tasks</a>
{{/global.owner.is_admin}}
</div>
<div class="navbar-end">
{{#global.isJoplinCloud}}

View File

@@ -1,6 +1,6 @@
{{#global.hasNotifications}}
{{#global.notifications}}
<div class="notification {{levelClassName}}" id="notification-{{id}}">
<div class="notification is-{{level}}" id="notification-{{id}}">
<button data-close-url="{{closeUrl}}" data-id="{{id}}" class="delete close-notification-button"></button>
{{{messageHtml}}}
</div>

View File

@@ -1,22 +1,20 @@
<div class="table-container">
<table class="table is-fullwidth is-hoverable ">
<thead>
<table class="table is-fullwidth is-hoverable">
<thead>
<tr>
{{#headers}}
{{>tableHeader}}
{{/headers}}
</tr>
</thead>
<tbody>
{{#rows}}
<tr>
{{#headers}}
{{>tableHeader}}
{{/headers}}
{{#.}}
{{>tableRowItem}}
{{/.}}
</tr>
</thead>
<tbody>
{{#rows}}
<tr>
{{#.}}
{{>tableRowItem}}
{{/.}}
</tr>
{{/rows}}
</tbody>
</table>
</div>
{{/rows}}
</tbody>
</table>
{{>pagination}}

View File

@@ -1,8 +1,3 @@
<th class="{{#classNames}}{{.}} {{/classNames}}">
{{#sortLink}}
<a href="{{sortLink}}" class="sort-button">{{label}} <i class="fas fa-caret-{{iconDir}}"></i></a>
{{/sortLink}}
{{^sortLink}}
{{label}}
{{/sortLink}}
<a href="{{sortLink}}" class="sort-button">{{label}} <i class="fas fa-caret-{{iconDir}}"></i></a>
</th>

View File

@@ -1,8 +1,3 @@
<td class="{{#classNames}}{{.}} {{/classNames}}">
{{#checkbox}}
<input type="checkbox" name="{{value}}"/>
{{/checkbox}}
{{^checkbox}}
{{#url}}<a href="{{.}}"></span>{{/url}}{{value}}</a>
{{/checkbox}}
{{#url}}<a href="{{.}}"></span>{{/url}}{{value}}</a>
</td>

View File

@@ -1,5 +0,0 @@
.encryption-config-test > .item {
font-weight: bold;
}
/*# sourceMappingURL=style.min.css.map */

View File

@@ -1,6 +1,8 @@
import { execCommand2, rootDir } from './tool-utils';
import * as moment from 'moment';
const DockerImageName = 'joplin/server';
function getVersionFromTag(tagName: string, isPreRelease: boolean): string {
if (tagName.indexOf('server-') !== 0) throw new Error(`Invalid tag: ${tagName}`);
const s = tagName.split('-');
@@ -12,6 +14,10 @@ function getIsPreRelease(tagName: string): boolean {
return tagName.indexOf('-beta') > 0;
}
function normalizePlatform(platform: string) {
return platform.replace(/\//g, '-');
}
async function main() {
const argv = require('yargs').argv;
if (!argv.tagName) throw new Error('--tag-name not provided');
@@ -27,7 +33,11 @@ async function main() {
} catch (error) {
console.info('Could not get git commit: metadata revision field will be empty');
}
const buildArgs = `--build-arg BUILD_DATE="${buildDate}" --build-arg REVISION="${revision}" --build-arg VERSION="${imageVersion}"`;
const buildArgs = [
`--build-arg BUILD_DATE="${buildDate}"`,
`--build-arg REVISION="${revision}"`,
`--build-arg VERSION="${imageVersion}"`,
];
const dockerTags: string[] = [];
const versionPart = imageVersion.split('.');
dockerTags.push(isPreRelease ? 'beta' : 'latest');
@@ -44,10 +54,48 @@ async function main() {
console.info('isPreRelease:', isPreRelease);
console.info('Docker tags:', dockerTags.join(', '));
await execCommand2(`docker build -t "joplin/server:${imageVersion}" ${buildArgs} -f Dockerfile.server .`);
for (const tag of dockerTags) {
await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:${tag}"`);
if (pushImages) await execCommand2(`docker push joplin/server:${tag}`);
const platforms = [
'linux/amd64',
'linux/arm64',
'linux/arm/v7',
];
// this will build a bunch of local image tags named: ${imageVersion}-${platform} with the slashes replaced with dashes
for (const platform of platforms) {
const normalizedPlatform = normalizePlatform(platform);
await execCommand2([
'docker', 'build',
'--platform', platform,
'-t', `${DockerImageName}:${imageVersion}-${normalizedPlatform}`,
...buildArgs,
'-f', 'Dockerfile.server',
'.',
]);
if (pushImages) {
await execCommand2([
'docker', 'push', `${DockerImageName}:${imageVersion}-${normalizedPlatform}`,
]);
}
}
// now we have to create the right manifests and push them
if (pushImages) {
for (const tag of dockerTags) {
// manifest create requires the tags being amended in to exist on the remote, so this all can only happen if pushImages is true
const platformArgs: string[] = [];
for (const platform in platforms) {
platformArgs.concat('--amend', `${DockerImageName}:${imageVersion}-${normalizePlatform(platform)}`);
}
await execCommand2([
'docker', 'manifest', 'create',
`${DockerImageName}:${tag}`,
...platformArgs,
]);
await execCommand2([
'docker', 'manifest', 'push',
`${DockerImageName}:${tag}`,
]);
}
}
}

View File

@@ -1,45 +0,0 @@
const sass = require('sass');
const fs = require('fs-extra');
// The SASS doc claims that renderSync is twice as fast as render, so if speed
// turns out to be an issue we could use that instead. The advantage of async is
// that we can run complation of each file in parallel (and running other async
// gulp tasks in parallel too).
// sasss.render is old school async, so convert it to a promise here.
async function sassRender(options) {
return new Promise((resolve, reject) => {
sass.render(options, ((error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
}));
});
}
module.exports = async function compileSass(inputPaths, outputPath) {
const promises = [];
for (const inputPath of inputPaths) {
console.info(`Compiling ${inputPath}...`);
promises.push(sassRender({
file: inputPath,
sourceMap: true,
outFile: outputPath,
}));
}
const results = await Promise.all(promises);
const cssString = results.map(r => r.css.toString()).join('\n');
const mapString = results.map(r => r.map.toString()).join('\n');
await Promise.all([
fs.writeFile(outputPath, cssString, 'utf8'),
fs.writeFile(`${outputPath}.map`, mapString, 'utf8'),
]);
console.info(`Generated ${outputPath}`);
};

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,6 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: 12.09.2021\n"
"Last-Translator: mrkaato\n"
"Language-Team: \n"
"Language: fi_FI\n"
@@ -16,7 +14,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0\n"
"X-Generator: Poedit 2.4.1\n"
#: packages/app-cli/app/app-gui.js:452
msgid "To delete a tag, untag the associated notes."
@@ -135,7 +133,6 @@ msgid ""
"Runs the commands contained in the text file. There should be one command "
"per line."
msgstr ""
"Suorittaa tekstitiedoston komennot. Riviä kohden pitäisi olla yksi komento."
#: packages/app-cli/app/command-cat.js:14
msgid "Displays the given note."
@@ -602,8 +599,8 @@ msgid ""
"\n"
"%s"
msgstr ""
"Asettaa ominaisuuden <name> annetusta <note> annettuun [arvoon]. Mahdolliset "
"ominaisuudet ovat:\n"
"Sets the property <name> of the given <note> to the given [value]. Possible "
"properties are:\n"
"\n"
"%s"
@@ -650,7 +647,7 @@ msgstr ""
#: packages/app-desktop/gui/DropboxLoginScreen.js:30
#: packages/app-mobile/components/screens/dropbox-login.js:59
msgid "Step 1: Open this URL in your browser to authorise the application:"
msgstr "Vaihe 1: Avaa tämä URL osoite selaimessa valtuuttaaksesi sovelluksen:"
msgstr "Vaihe 1: Avaa tämä URL-osoite selaimessa valtuuttaaksesi sovelluksen:"
#: packages/app-cli/app/command-sync.js:94
#: packages/app-desktop/gui/DropboxLoginScreen.js:32
@@ -911,11 +908,11 @@ msgstr "Lataa"
#: packages/app-desktop/checkForUpdates.js:189
msgid "Skip this version"
msgstr "Ohita tämä versio"
msgstr ""
#: packages/app-desktop/checkForUpdates.js:189
msgid "Full changelog"
msgstr "Täysi muutosloki"
msgstr ""
#: packages/app-desktop/commands/copyDevCommand.js:18
msgid "Copy dev mode command to clipboard"
@@ -942,8 +939,9 @@ msgid "Stop"
msgstr "Seis"
#: packages/app-desktop/commands/toggleSafeMode.js:18
#, fuzzy
msgid "Toggle safe mode"
msgstr "Vaihda turvalliseen tilaan"
msgstr "Näytä sivupalkki"
#: packages/app-desktop/gui/ClipperConfigScreen.js:34
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:40
@@ -953,7 +951,7 @@ msgstr "Tunnus on kopioitu leikepöydälle!"
#: packages/app-desktop/gui/ClipperConfigScreen.js:37
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:44
msgid "Are you sure you want to renew the authorisation token?"
msgstr "Haluatko varmasti uusia valtuutustunnuksen?"
msgstr ""
#: packages/app-desktop/gui/ClipperConfigScreen.js:67
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:84
@@ -1055,7 +1053,7 @@ msgstr ""
#: packages/app-desktop/gui/ClipperConfigScreen.js:111
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:222
msgid "Renew token"
msgstr "Uusi tunnus"
msgstr ""
#: packages/app-desktop/gui/ConfigScreen/ButtonBar.js:27
msgid "Apply"
@@ -1181,12 +1179,13 @@ msgid "You do not have any installed plugin."
msgstr "Sinulla ei ole asennettua laajennusta."
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:232
#, fuzzy
msgid "Could not connect to plugin repository"
msgstr "Yhteyden muodostaminen laajennusten arkistoon epäonnistui"
msgstr "Laajennuksen asentaminen epäonnistui: %s"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:234
msgid "Try again"
msgstr "Yritä uudestaan"
msgstr ""
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:242
msgid "Plugin tools"
@@ -1220,16 +1219,19 @@ msgid "Submit"
msgstr "Lähetä"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:73
#, fuzzy
msgid "Source: "
msgstr "Lähde: "
msgstr "Lähde"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:76
#, fuzzy
msgid "Created: "
msgstr "Luotu: "
msgstr "Luotu: %s"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:79
#, fuzzy
msgid "Updated: "
msgstr "Päivitetty: "
msgstr "Päivitetty: %s"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:84
#: packages/app-desktop/gui/EncryptionConfigScreen.min.js:96
@@ -1239,8 +1241,9 @@ msgid "Save"
msgstr "Tallenna"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:87
#, fuzzy
msgid "Disable"
msgstr "Poista käytöstä"
msgstr "Poistettu käytöstä"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:87
#: packages/app-mobile/components/screens/encryption-config.js:157
@@ -1352,11 +1355,11 @@ msgstr "Pääavaimet"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:142
msgid "Hide disabled master keys"
msgstr "Piilota käytöstä poistetut pääavaimet"
msgstr ""
#: packages/app-desktop/gui/EncryptionConfigScreen.js:142
msgid "Show disabled master keys"
msgstr "Näytä käytöstä poistetut pääavaimet"
msgstr ""
#: packages/app-desktop/gui/EncryptionConfigScreen.js:143
#: packages/app-desktop/gui/EncryptionConfigScreen.min.js:343
@@ -1377,7 +1380,7 @@ msgstr "Aktiivinen"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:149
msgid "Date"
msgstr "Päivämäärä"
msgstr ""
#: packages/app-desktop/gui/EncryptionConfigScreen.js:150
#: packages/app-desktop/gui/EncryptionConfigScreen.min.js:329
@@ -1385,12 +1388,14 @@ msgid "Password"
msgstr "Salasana"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:151
#, fuzzy
msgid "Valid"
msgstr "Kelvollinen"
msgstr "Virheellinen"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:152
#, fuzzy
msgid "Actions"
msgstr "Toiminnot"
msgstr "Toiminta"
#: packages/app-desktop/gui/EncryptionConfigScreen.js:182
#: packages/app-desktop/gui/EncryptionConfigScreen.min.js:244
@@ -1570,12 +1575,12 @@ msgstr "Sulje ikkuna"
#: packages/app-desktop/gui/KeymapConfig/utils/getLabel.js:24
msgid "Preferences"
msgstr "Oletusasetukset"
msgstr "Asetukset"
#: packages/app-desktop/gui/KeymapConfig/utils/getLabel.js:24
#: packages/app-desktop/gui/MenuBar.js:310 packages/app-desktop/gui/Root.js:163
msgid "Options"
msgstr "Asetukset"
msgstr "Vaihtoehdot"
#: packages/app-desktop/gui/KeymapConfig/utils/getLabel.js:31
msgid "Invalid"
@@ -1586,12 +1591,10 @@ msgid ""
"Safe mode is currently active. Note rendering and all plugins are "
"temporarily disabled."
msgstr ""
"Turvallinen tila on tällä hetkellä aktiivinen. Huomautusten renderöinti ja "
"kaikki laajennukset on tilapäisesti poistettu käytöstä."
#: packages/app-desktop/gui/MainScreen/MainScreen.js:470
msgid "Disable safe mode and restart"
msgstr "Lopeta turvallinen tila ja käynnistä uudelleen"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:473
msgid ""
@@ -1635,16 +1638,16 @@ msgstr "Lisätietoja"
#: packages/app-desktop/gui/MainScreen/MainScreen.js:487
#, javascript-format
msgid "%s (%s) would like to share a notebook with you."
msgstr "%s (%s) haluaa jakaa muistikirjan kanssasi."
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:487
msgid "Accept"
msgstr "Hyväksy"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:487
#: packages/app-desktop/gui/Root.js:121
msgid "Reject"
msgstr "Hylkää"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:490
msgid "Some items cannot be synchronised."
@@ -1663,8 +1666,9 @@ msgid "Use the arrows to move the layout items. Press \"Escape\" to exit."
msgstr "Siirrä asettelukohteita nuolilla. Paina \"Esc\" poistuaksesi."
#: packages/app-desktop/gui/MainScreen/commands/commandPalette.js:18
#, fuzzy
msgid "Command palette..."
msgstr "Komentovalikoima..."
msgstr "Komentovalikoima"
#: packages/app-desktop/gui/MainScreen/commands/editAlarm.js:20
#: packages/app-mobile/components/SelectDateTimeDialog.js:84
@@ -1679,7 +1683,7 @@ msgstr "Aseta hälytys:"
#: packages/app-desktop/gui/MainScreen/commands/exportPdf.js:20
#: packages/app-desktop/gui/MainScreen/commands/exportPdf.js:32
msgid "PDF File"
msgstr "PDF tiedosto"
msgstr "PDF-tiedosto"
#: packages/app-desktop/gui/MainScreen/commands/gotoAnything.js:23
#: packages/app-desktop/plugins/GotoAnything.js:497
@@ -1762,12 +1766,14 @@ msgid "Note properties"
msgstr "Muistiinpanon ominaisuudet"
#: packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js:16
#, fuzzy
msgid "Share notebook..."
msgstr "Muistikirjan jakaminen..."
msgstr "Muistiinpanon jakaminen..."
#: packages/app-desktop/gui/MainScreen/commands/showShareNoteDialog.js:16
#, fuzzy
msgid "Publish note..."
msgstr "Muistiinpanon julkaiseminen..."
msgstr "Muistiinpanon jakaminen..."
#: packages/app-desktop/gui/MainScreen/commands/showSpellCheckerMenu.js:19
#: packages/lib/services/spellChecker/SpellCheckerService.js:180
@@ -1827,7 +1833,7 @@ msgstr "Joplinista"
#: packages/app-desktop/gui/MenuBar.js:367
msgid "Preferences..."
msgstr "Oletusasetukset..."
msgstr "Asetukset..."
#: packages/app-desktop/gui/MenuBar.js:377
#: packages/app-desktop/gui/MenuBar.js:618
@@ -1855,7 +1861,7 @@ msgstr "&Näytä"
#: packages/app-desktop/gui/MenuBar.js:506
msgid "Layout button sequence"
msgstr "Aseta painike järjestys"
msgstr "Asettele painike järjestys"
#: packages/app-desktop/gui/MenuBar.js:551
#: packages/app-desktop/gui/MenuBar.js:557
@@ -1876,11 +1882,12 @@ msgstr "&Mene"
#: packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js:18
#: packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.js:18
msgid "Focus"
msgstr "Osoita"
msgstr "Keskittää"
#: packages/app-desktop/gui/MenuBar.js:585
#, fuzzy
msgid "Note&book"
msgstr "&Muistikirjat"
msgstr "Muistikirjat"
#: packages/app-desktop/gui/MenuBar.js:591
msgid "&Note"
@@ -2042,23 +2049,23 @@ msgstr "Muokkaa"
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:17
msgid "Highlight"
msgstr "Korosta"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:22
msgid "Strikethrough"
msgstr "Yliviivaus"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:27
msgid "Insert"
msgstr "Aseta"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:33
msgid "Superscript"
msgstr "Yläindeksi"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:39
msgid "Subscript"
msgstr "Alaindeksi"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteEditor.js:285
msgid "Click to add tags..."
@@ -2155,8 +2162,9 @@ msgid "Delete line"
msgstr "Poista rivi"
#: packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js:92
#, fuzzy
msgid "Duplicate line"
msgstr "Rivin kaksoiskappale"
msgstr "Duplikaatti, toinen samanlainen"
#: packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js:96
msgid "Undo"
@@ -2364,16 +2372,18 @@ msgstr ""
"Varoitus: kaikkia resursseja ei näytetä suorituskyvyn vuoksi (raja: %s)."
#: packages/app-desktop/gui/Root.js:106
#, fuzzy
msgid "Confirmation"
msgstr "Vahvistus"
msgstr "Konfigurointi"
#: packages/app-desktop/gui/Root.js:119
msgid "The Web Clipper needs your authorisation to access your data."
msgstr "Web Clipper tarvitsee valtuutuksen tietojen käyttöön."
msgstr ""
#: packages/app-desktop/gui/Root.js:120
#, fuzzy
msgid "Grant authorisation"
msgstr "Myönnä lupa"
msgstr "Valtuutuksen tunnus:"
#: packages/app-desktop/gui/Root.js:160
msgid "OneDrive Login"
@@ -2388,20 +2398,19 @@ msgid "Note attachments"
msgstr "Muistiinpanon liitteet"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:141
#, fuzzy
msgid "Unshare"
msgstr "Poista jakaminen"
msgstr "Jaa"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:183
msgid ""
"Delete this invitation? The recipient will no longer have access to this "
"shared notebook."
msgstr ""
"Poistetaanko tämä kutsu? Vastaanottaja ei voi enää käyttää tätä jaettua "
"muistikirjaa."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:197
msgid "Add recipient:"
msgstr "Lisää vastaanottaja:"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:200
#: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:28
@@ -2411,43 +2420,45 @@ msgstr "Jaa"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:209
msgid "Recipient has not yet accepted the invitation"
msgstr "Vastaanottaja ei ole vielä hyväksynyt kutsua"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:210
msgid "Recipient has rejected the invitation"
msgstr "Vastaanottaja on hylännyt kutsun"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:211
msgid "Recipient has accepted the invitation"
msgstr "Vastaanottaja on hyväksynyt kutsun"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:221
msgid "Recipients:"
msgstr "Vastaanottajat:"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:233
#, fuzzy
msgid "Synchronizing..."
msgstr "Synkronoidaan..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:234
#, fuzzy
msgid "Sharing notebook..."
msgstr "Jaetaan muistikirjaa..."
msgstr "Muistiinpanon jakaminen..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:244
msgid ""
"Unshare this notebook? The recipients will no longer have access to its "
"content."
msgstr ""
"Poistetaanko tämän muistikirjan jakaminen? Vastaanottajilla ei ole enää "
"pääsyä sen sisältöön."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:254
#, fuzzy
msgid "Share Notebook"
msgstr "Jaa muistikirja"
msgstr "Jaa muistiinpanoja"
#: packages/app-desktop/gui/ShareNoteDialog.js:144
#, fuzzy
msgid "Unpublish note"
msgstr "Peruuta muistiinpanon julkaisu"
msgstr "Jaa"
#: packages/app-desktop/gui/ShareNoteDialog.js:171
msgid "Synchronising..."
@@ -2473,7 +2484,7 @@ msgstr ""
#: packages/app-desktop/gui/ShareNoteDialog.js:187
msgid "Publish Notes"
msgstr "Julkaise muistiinpanot"
msgstr ""
#: packages/app-desktop/gui/ShareNoteDialog.js:189
msgid "Copy Shareable Link"
@@ -2554,28 +2565,32 @@ msgid "Retry"
msgstr "Yritä uudelleen"
#: packages/app-desktop/gui/StatusScreen/StatusScreen.js:137
#, fuzzy
msgid "Advanced tools"
msgstr "Lisätyökalut"
msgstr "Lisäasetukset"
#: packages/app-desktop/gui/StatusScreen/StatusScreen.js:139
#, fuzzy
msgid "Export debug report"
msgstr "Vie virheenkorjausraportti"
#: packages/app-desktop/gui/SyncWizard/Dialog.js:157
#, fuzzy
msgid "Sync your notes"
msgstr "Synkronoi muistiinpanosi"
msgstr "Lajittele muistiinpanot"
#: packages/app-desktop/gui/SyncWizard/Dialog.js:158
msgid "Publish notes to the internet"
msgstr "Julkaise muistiinpanot Internetissä"
msgstr ""
#: packages/app-desktop/gui/SyncWizard/Dialog.js:159
#, fuzzy
msgid "Collaborate on notebooks with others"
msgstr "Muistikirjojen yhteiskäyttö muiden kanssa"
msgstr "Luo ensin muistikirja"
#: packages/app-desktop/gui/SyncWizard/Dialog.js:182
msgid "Thank you! Your Joplin Cloud account is now setup and ready to use."
msgstr "Kiitos! Your Joplin Cloud tilisi on nyt määritetty ja käyttövalmis."
msgstr ""
#: packages/app-desktop/gui/SyncWizard/Dialog.js:190
#, javascript-format
@@ -2585,34 +2600,30 @@ msgid ""
"\n"
"%s"
msgstr ""
"Joplin Cloud tilisi määrittämisessä tapahtui virhe. Vahvista "
"sähköpostiosoitteesi ja salasanasi sekä yritä uudelleen. Virhe oli:\n"
"\n"
"%s"
#: packages/app-desktop/gui/SyncWizard/Dialog.js:203
msgid "Login below."
msgstr "Kirjaudu sisään alla."
msgstr ""
#: packages/app-desktop/gui/SyncWizard/Dialog.js:205
#, fuzzy
msgid "Or create an account."
msgstr "Tai luo tili."
msgstr "Luo uuden muistiinpanon."
#: packages/app-desktop/gui/SyncWizard/Dialog.js:210
msgid "Login"
msgstr "Kirjaudu"
msgstr ""
#: packages/app-desktop/gui/SyncWizard/Dialog.js:231
#, fuzzy
msgid "Select"
msgstr "Valitse"
msgstr "Valitse kaikki"
#: packages/app-desktop/gui/SyncWizard/Dialog.js:278
msgid ""
"Joplin can synchronise your notes using various providers. Select one from "
"the list below."
msgstr ""
"Joplin voi synkronoida muistiinpanosi eri palveluntarjoajien avulla. Valitse "
"yksi alla olevasta luettelosta."
#: packages/app-desktop/gui/utils/NoteListUtils.js:43
msgid "Duplicate"
@@ -2633,7 +2644,7 @@ msgstr "Vaihda muistiinpanotyyppiin"
#: packages/app-desktop/gui/utils/NoteListUtils.js:93
msgid "Switch to to-do type"
msgstr "Vaihda tehtävätyyppiin"
msgstr "Siirry tehtävätyyppiin"
#: packages/app-desktop/gui/utils/NoteListUtils.js:100
#: packages/app-mobile/components/screens/Note.js:847
@@ -2786,7 +2797,7 @@ msgstr "Vain virheenkorjausta varten: vie profiilisi ulkoiselle SD-kortille."
#: packages/app-mobile/components/screens/ConfigScreen.js:436
msgid "Feature flags"
msgstr "Ominaisuus liput"
msgstr ""
#: packages/app-mobile/components/screens/ConfigScreen.js:439
msgid "More information"
@@ -2798,8 +2809,8 @@ msgid ""
"them in your phone settings, in Apps > Joplin > Permissions"
msgstr ""
"Toimiakseen oikein sovellus tarvitsee seuraavat käyttöoikeudet. Ota ne "
"käyttöön puhelimesi asetuksissa, valitsemalla Sovellukset> Joplin> Luvat "
"(käyttöoikeudet)"
"käyttöön puhelimesi asetuksissa, valitsemalla Sovellukset> Joplin> "
"Käyttöoikeudet"
#: packages/app-mobile/components/screens/ConfigScreen.js:446
msgid ""
@@ -2811,11 +2822,11 @@ msgstr ""
#: packages/app-mobile/components/screens/ConfigScreen.js:447
msgid "- Camera: to allow taking a picture and attaching it to a note."
msgstr "- Kamera: sallia kuvan ottamisen ja liittämisen muistiinpanoon."
msgstr "- Kamera: sallii kuvan ottamisen ja liittämisen muistiinpanoon."
#: packages/app-mobile/components/screens/ConfigScreen.js:448
msgid "- Location: to allow attaching geo-location information to a note."
msgstr "- Sijainti: sallia paikkatietojen liittämisen muistiinpanoon."
msgstr "- Sijainti: sallii paikkatietojen liittämisen muistiinpanoon."
#: packages/app-mobile/components/screens/ConfigScreen.js:459
msgid "Joplin website"
@@ -2920,7 +2931,7 @@ msgstr "Näytä kartalla"
#: packages/app-mobile/components/screens/Note.js:761
msgid "Go to source URL"
msgstr "Siirry lähteen URL osoitteeseen"
msgstr "Siirry lähteen URL-osoitteeseen"
#: packages/app-mobile/components/screens/Note.js:790
msgid "Attach..."
@@ -3037,7 +3048,7 @@ msgstr "Uusi muistikirja"
#: packages/app-mobile/components/side-menu-content.js:351
msgid "Mobile data - auto-sync disabled"
msgstr "Mobiilidata - automaattinen synkronointi poistettu käytöstä"
msgstr ""
#: packages/lib/BaseApplication.js:154 packages/lib/BaseApplication.js:166
#: packages/lib/BaseApplication.js:198
@@ -3059,7 +3070,7 @@ msgid ""
"%s"
msgstr ""
"Yhteyden muodostaminen Joplinin palvelimelle epäonnistui. Tarkista "
"synkronoinnin asetukset. Koko virhe oli:\n"
"vaihtoehdot Synkronointiasetukset-näytössä. Koko virhe oli:\n"
"\n"
"%s"
@@ -3076,17 +3087,15 @@ msgid "File system"
msgstr "Tiedostojärjestelmä"
#: packages/lib/SyncTargetJoplinCloud.js:28
#, fuzzy
msgid "Joplin Cloud"
msgstr "Joplin Cloud"
msgstr "Joplin Foorumi"
#: packages/lib/SyncTargetJoplinCloud.js:31
msgid ""
"Joplin's own sync service. Also gives access to Joplin-specific features "
"such as publishing notes or collaborating on notebooks with others."
msgstr ""
"Joplinin oma synkronointipalvelu. Voit myös käyttää Joplin "
"erityisominaisuuksia, kuten muistiinpanojen julkaisemista tai muistikirjojen "
"yhteiskäyttöä muiden kanssa."
#: packages/lib/SyncTargetJoplinServer.js:60
msgid "Joplin Server"
@@ -3098,7 +3107,7 @@ msgstr "Nextcloud"
#: packages/lib/SyncTargetNone.js:22
msgid "(None)"
msgstr "(Ei mitään)"
msgstr ""
#: packages/lib/SyncTargetOneDrive.js:32
msgid "OneDrive"
@@ -3148,9 +3157,9 @@ msgid "Cancelling..."
msgstr "Peruutetaan..."
#: packages/lib/Synchronizer.js:159
#, javascript-format
#, fuzzy, javascript-format
msgid "Completed: %s (%s)"
msgstr "Valmis: %s (%s)"
msgstr "Valmis: %s"
#: packages/lib/Synchronizer.js:161
#, javascript-format
@@ -3187,8 +3196,8 @@ msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
"Virhe. Tarkista URL osoite, käyttäjänimi, salasana jne. ovat oikeita ja "
"synkronointi kohde on käytettävissä. Raportoitu virhe oli:"
"Virhe. Tarkista URL-osoite, käyttäjänimi, salasana jne. ovat oikeita ja "
"synkronointikohde on käytettävissä. Raportoitu virhe oli:"
#: packages/lib/components/shared/dropbox-login-shared.js:39
msgid "The application has been authorised!"
@@ -3243,7 +3252,7 @@ msgstr "Purettuja kohteita: %s / %s"
#: packages/lib/components/shared/encryption-config-shared.js:151
#, javascript-format
msgid "Encryption will be enabled using the master key created on %s"
msgstr "Salaus otetaan käyttöön käyttämällä luotua pääavainta %s"
msgstr ""
#: packages/lib/models/BaseItem.js:721
msgid "Encrypted"
@@ -3313,8 +3322,9 @@ msgid "Error"
msgstr "Virhe"
#: packages/lib/models/Resource.js:408
#, fuzzy
msgid "Conflicts (attachments)"
msgstr "Ristiriidat (liitteet)"
msgstr "Muistiinpanon liitteet"
#: packages/lib/models/Resource.js:422
#, javascript-format
@@ -3390,7 +3400,7 @@ msgstr "OLED Dark"
#: packages/lib/models/Setting.js:152
msgid "Open Sync Wizard..."
msgstr "Avaa ohjattu synkronointitoiminto."
msgstr ""
#: packages/lib/models/Setting.js:162
msgid "Synchronisation target"
@@ -3454,20 +3464,23 @@ msgid "Joplin Server URL"
msgstr "Joplin palvelimen URL-osoite"
#: packages/lib/models/Setting.js:348
#, fuzzy
msgid "Joplin Server email"
msgstr "Joplin palvelimen sähköposti"
msgstr "Joplin Palvelin"
#: packages/lib/models/Setting.js:359
msgid "Joplin Server password"
msgstr "Joplin palvelimen salasana"
#: packages/lib/models/Setting.js:386
#, fuzzy
msgid "Joplin Cloud email"
msgstr "Joplin Cloud sähköposti"
msgstr "Joplin Palvelin"
#: packages/lib/models/Setting.js:397
#, fuzzy
msgid "Joplin Cloud password"
msgstr "Joplin Cloud salasana"
msgstr "Joplin palvelimen salasana"
#: packages/lib/models/Setting.js:409
msgid "Attachment download behaviour"
@@ -3479,8 +3492,8 @@ msgid ""
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
"the attachments are downloaded whether you open the note or not."
msgstr ""
"Manuaalisessa tilassa liitteet ladataan vain, kun napsautat niitä. \"Auto\" "
"valinnassa ne ladataan, kun avaat muistiinpanon. \"Aina\" valinnassa kaikki "
"Manuaalisessa tilassa liitteet ladataan vain, kun napsautat niitä. \"Auto\" -"
"kohdassa ne ladataan, kun avaat muistiinpanon. \"Aina\" -kohdassa kaikki "
"liitteet ladataan riippumatta siitä, avaatko muistiinpanon vai et."
#: packages/lib/models/Setting.js:413
@@ -3707,12 +3720,11 @@ msgid ""
"Used for most text in the markdown editor. If not found, a generic "
"proportional (variable width) font is used."
msgstr ""
"Käytetään useimmissa teksteissä markdown editorissa. Jos sitä ei löydy, "
"käytetään yleistä suhteellista fonttia (vaihtelevaa leveyttä)."
#: packages/lib/models/Setting.js:779
#, fuzzy
msgid "Editor monospace font family"
msgstr "Editorin monospace fonttiperhe"
msgstr "Editorin fonttiperhe"
#: packages/lib/models/Setting.js:780
msgid ""
@@ -3720,17 +3732,14 @@ msgid ""
"tables, checkboxes, code). If not found, a generic monospace (fixed width) "
"font is used."
msgstr ""
"Käytetään, kun tekstiin tarvitaan kiinteäleveyksinen fontti (esim. taulukot, "
"valintaruudut, koodi). Jos sitä ei löydy, käytetään yleistä monospace "
"fonttia (kiinteäleveyksinen)."
#: packages/lib/models/Setting.js:783
msgid "Editor maximum width"
msgstr "Editorin enimmäisleveys"
msgstr ""
#: packages/lib/models/Setting.js:783
msgid "Set it to 0 to make it take the complete available space."
msgstr "Aseta arvoksi 0, jotta se vie koko käytettävissä olevan tilan."
msgstr ""
#: packages/lib/models/Setting.js:802
msgid "Custom stylesheet for rendered Markdown"
@@ -3742,11 +3751,11 @@ msgstr "Mukautettu tyylitaulukko Joplinin sovellustyyleille"
#: packages/lib/models/Setting.js:828
msgid "Re-upload local data to sync target"
msgstr "Lataa paikalliset tiedot uudelleen kohteen synkronointia varten"
msgstr ""
#: packages/lib/models/Setting.js:838
msgid "Delete local data and re-download from sync target"
msgstr "Poista paikalliset tiedot ja lataa uudelleen synkronointikohteesta"
msgstr ""
#: packages/lib/models/Setting.js:843
msgid "Automatically update the application"
@@ -3783,7 +3792,7 @@ msgstr "%d tuntia"
#: packages/lib/models/Setting.js:871
msgid "Synchronise only over WiFi connection"
msgstr "Synkronoi vain WiFi yhteyden kautta"
msgstr ""
#: packages/lib/models/Setting.js:878
msgid "Text editor command"
@@ -3800,7 +3809,7 @@ msgstr ""
#: packages/lib/models/Setting.js:879
msgid "Page size for PDF export"
msgstr "PDF viennin sivukoko"
msgstr "PDF-viennin sivukoko"
#: packages/lib/models/Setting.js:881
msgid "A4"
@@ -3828,15 +3837,15 @@ msgstr "Legal"
#: packages/lib/models/Setting.js:889
msgid "Page orientation for PDF export"
msgstr "Sivun suunta PDF vientiä varten"
msgstr "Sivun suunta PDF-vientiä varten"
#: packages/lib/models/Setting.js:891
msgid "Portrait"
msgstr "Pystysuunta"
msgstr "Pysty"
#: packages/lib/models/Setting.js:892
msgid "Landscape"
msgstr "Vaakasuunta"
msgstr "Vaaka"
#: packages/lib/models/Setting.js:902
msgid "Keyboard Mode"
@@ -3852,11 +3861,11 @@ msgstr "Vim"
#: packages/lib/models/Setting.js:920
msgid "Do not resize images"
msgstr "Älä muuta kuvien kokoa"
msgstr ""
#: packages/lib/models/Setting.js:935
msgid "Custom TLS certificates"
msgstr "Mukautetut TLS varmenteet"
msgstr "Mukautetut TLS-varmenteet"
#: packages/lib/models/Setting.js:936
msgid ""
@@ -3867,13 +3876,13 @@ msgid ""
msgstr ""
"Pilkuilla erotettu luettelo hakemistoiden poluista, joilta varmenteet "
"ladataan, tai polku yksittäisiin varmennetiedostoihin. Esimerkiksi: /my/"
"cert_dir, /other/custom.pem. Huomaa, että jos teet muutoksia TLS asetuksiin, "
"cert_dir, /other/custom.pem. Huomaa, että jos teet muutoksia TLS-asetuksiin, "
"sinun on tallennettava muutokset ennen kuin napsautat \"Tarkista "
"synkronointiasetukset\"."
#: packages/lib/models/Setting.js:958
msgid "Ignore TLS certificate errors"
msgstr "Ohita TLS varmenteen virheet"
msgstr "Ohita TLS-varmenteen virheet"
#: packages/lib/models/Setting.js:967
msgid "Fail-safe"
@@ -3884,15 +3893,15 @@ msgid ""
"Fail-safe: Do not wipe out local data when sync target is empty (often the "
"result of a misconfiguration or bug)"
msgstr ""
"Vikaturvallinen: Älä pyyhi paikallisia tietoja, kun synkronointikohde on "
"tyhjä (usein virheellisen määritysvirheen tai virheen seurauksena)"
"Vikasietoinen: Älä pyyhi paikallisia tietoja, kun synkronointikohde on tyhjä "
"(usein virheellisen määritysvirheen tai virheen seurauksena)"
#: packages/lib/models/Setting.js:972
msgid ""
"Specify the port that should be used by the API server. If not set, a "
"default will be used."
msgstr ""
"Määritä portti, jota API palvelimen on käytettävä. Jos sitä ei ole "
"Määritä portti, jota API-palvelimen on käytettävä. Jos sitä ei ole "
"määritetty, käytetään oletusarvoa."
#: packages/lib/models/Setting.js:977
@@ -3924,10 +3933,10 @@ msgid ""
"item with a factor of 2 will take twice as much space as an item with a "
"factor of 1.Restart app to see changes."
msgstr ""
"Kerroin ominaisuus määrittää, miten kohde kasvaa tai kutistuu niin, että se "
"Kerroin-ominaisuus määrittää, miten kohde kasvaa tai kutistuu niin, että se "
"sopii säiliössä olevaan tilaan suhteessa muihin kohteisiin. Näin ollen "
"kohde, jonka kerroin on 2, vie kaksi kertaa enemmän tilaa kuin kohde, jonka "
"kerroin on 1.Restart sovellus muutosten näkemistä varten."
"kerroin on 1.Restart-sovellus muutosten näkemistä varten."
#: packages/lib/models/Setting.js:1029
msgid "Note list growth factor"
@@ -4001,7 +4010,7 @@ msgstr ""
#: packages/lib/models/Setting.js:1709
#, javascript-format
msgid "Notes and settings are stored in: %s"
msgstr "Muistiinpanot ja oletusasetukset tallennetaan: %s"
msgstr "Muistiinpanot ja asetukset tallennetaan: %s"
#: packages/lib/models/Tag.js:223
#, javascript-format
@@ -4204,28 +4213,28 @@ msgstr "Muistiinpano \"%s\" on palautettu muistikirjaan \"%s\"."
#: packages/lib/services/interop/InteropService.js:48
#: packages/lib/services/interop/InteropService.js:57
msgid "Joplin Export File"
msgstr "Joplin vie tiedosto"
msgstr "Joplin Vie tiedosto"
#: packages/lib/services/interop/InteropService.js:50
#: packages/lib/services/interop/InteropService.js:58
msgid "Joplin Export Directory"
msgstr "Joplin vie hakemisto"
msgstr "Joplin Vie hakemisto"
#: packages/lib/services/interop/InteropService.js:51
msgid "Evernote Export File (as Markdown)"
msgstr "Evernote vie tiedosto (kuten Markdown)"
msgstr "Evernote Vie tiedosto (kuten Markdown)"
#: packages/lib/services/interop/InteropService.js:52
msgid "Evernote Export File (as HTML)"
msgstr "Evernote vie tiedosto (kuten HTML)"
msgstr "Evernote Vie tiedosto (kuten HTML)"
#: packages/lib/services/interop/InteropService.js:60
msgid "HTML File"
msgstr "HTML tiedosto"
msgstr "HTML Tiedosto"
#: packages/lib/services/interop/InteropService.js:61
msgid "HTML Directory"
msgstr "HTML hakemisto"
msgstr "HTML Hakemisto"
#: packages/lib/services/interop/InteropService.js:127
#, javascript-format
@@ -4315,19 +4324,19 @@ msgid ""
"The default admin password is insecure and has not been changed! [Change it "
"now](%s)"
msgstr ""
"Järjestelmänvalvojan oletussalasana on turvaton, eikä sitä ole muutettu "
"[Vaihda se nyt](%s)"
"Järjestelmänvalvojan oletussalasana on epävarma, eikä sitä ole muutettu "
"[Vaihda se nyt] (%s)"
#: packages/server/dist/models/UserModel.js:199
#: packages/server/dist/models/UserModel.js:204
#, fuzzy
msgid "attachment"
msgstr "liite"
msgstr "Liitteet"
#: packages/server/dist/models/UserModel.js:199
#, javascript-format
msgid "Cannot save %s \"%s\" because it is larger than the allowed limit (%s)"
msgstr ""
"Tallennus ei onnistu %s \"%s\" koska se on suurempi kuin sallittu raja (%s)"
#: packages/server/dist/models/UserModel.js:204
#, javascript-format
@@ -4335,8 +4344,6 @@ msgid ""
"Cannot save %s \"%s\" because it would go over the total allowed size (%s) "
"for this account"
msgstr ""
"Kohdetta %s \"%s\" ei voida tallentaa, koska se ylittäisi tämän tilin "
"sallitun kokonaiskoon (%s)"
#, javascript-format
#~ msgid "%s %s (%s)"
@@ -4363,8 +4370,8 @@ msgstr ""
#~ msgid "Templates"
#~ msgstr "Mallit"
#~ msgid "Full Release Notes"
#~ msgstr "Täydelliset julkaisutiedot"
#~ msgid "Share Notes"
#~ msgstr "Jaa muistiinpanoja"
#~ msgid "Joplin Server Directory"
#~ msgstr "Joplin palvelimen hakemisto"
@@ -4372,11 +4379,25 @@ msgstr ""
#~ msgid "Joplin Server username"
#~ msgstr "Joplin palvelimen käyttäjänimi"
#, fuzzy
#~ msgid "marked text"
#~ msgstr "korostettu teksti"
#, fuzzy
#~ msgid "Mark"
#~ msgstr "Merkintä"
#~ msgid "Full Release Notes"
#~ msgstr "Täydelliset julkaisutiedot"
#, fuzzy
#~ msgid ""
#~ "If the font is incorrect or empty, it will default to a generic monospace "
#~ "font."
#~ msgstr ""
#~ "Jos fontti on väärä tai tyhjä, se on oletuksena yleinen monospace fontti."
#~ "Tämän on oltava *monospace* fontti, muuten se ei toimi oikein. Jos "
#~ "kirjasin on väärä tai tyhjä, se on oletusarvoisesti yleinen monospace "
#~ "fontti."
#~ msgid ""
#~ "This should be a *monospace* font or some elements will render "

View File

@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "@joplin/tools",
"version": "2.4.1",
"version": "2.3.0",
"license": "MIT",
"dependencies": {
"execa": "^4.1.0",
@@ -31,7 +31,6 @@
"@types/mustache": "^0.8.32",
"@types/node": "^14.14.6",
"gulp": "^4.0.2",
"sass": "^1.39.2",
"sqlite3": "^5.0.0",
"typescript": "^4.1.3"
}
@@ -4198,18 +4197,6 @@
"integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -4667,159 +4654,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass": {
"version": "1.39.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.39.2.tgz",
"integrity": "sha512-4/6Vn2RPc+qNwSclUSKvssh7dqK1Ih3FfHBW16I/GfH47b3scbYeOw65UIrYG7PkweFiKbpJjgkf5CV8EMmvzw==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/sass/node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/sass/node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/sass/node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/sass/node_modules/chokidar": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
"dev": true,
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/sass/node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/sass/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/sass/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/sass/node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/sass/node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/sass/node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/sass/node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -9437,12 +9271,6 @@
"integrity": "sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==",
"dev": true
},
"picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -9814,116 +9642,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sass": {
"version": "1.39.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.39.2.tgz",
"integrity": "sha512-4/6Vn2RPc+qNwSclUSKvssh7dqK1Ih3FfHBW16I/GfH47b3scbYeOw65UIrYG7PkweFiKbpJjgkf5CV8EMmvzw==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0"
},
"dependencies": {
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"chokidar": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
}
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",

View File

@@ -42,7 +42,6 @@
"@types/mustache": "^0.8.32",
"@types/node": "^14.14.6",
"gulp": "^4.0.2",
"sass": "^1.39.2",
"sqlite3": "^5.0.0",
"typescript": "^4.1.3"
},

View File

@@ -79,14 +79,6 @@
{
"name": "cuongtransc",
"id": "808091"
},
{
"name": "iamwillbar",
"id": "3266447"
},
{
"name": "marcdw1289",
"id": "42319182"
}
],
"orgs": [

View File

@@ -147,6 +147,7 @@ export function execCommandVerbose(commandName: string, args: string[] = []) {
interface ExecCommandOptions {
showInput?: boolean;
showOutput?: boolean;
showError?: boolean;
quiet?: boolean;
}
@@ -160,6 +161,7 @@ export async function execCommand2(command: string | string[], options: ExecComm
options = {
showInput: true,
showOutput: true,
showError: true,
quiet: false,
...options,
};
@@ -167,6 +169,7 @@ export async function execCommand2(command: string | string[], options: ExecComm
if (options.quiet) {
options.showInput = false;
options.showOutput = false;
options.showError = false;
}
if (options.showInput) {
@@ -182,6 +185,7 @@ export async function execCommand2(command: string | string[], options: ExecComm
args.splice(0, 1);
const promise = execa(executableName, args);
if (options.showOutput) promise.stdout.pipe(process.stdout);
if (options.showError) promise.stderr.pipe(process.stderr);
const result = await promise;
return result.stdout.trim();
}

View File

@@ -1,15 +1,5 @@
# Joplin Server Changelog
## [server-v2.4.7-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.4.7-beta) (Pre-release) - 2021-09-15T15:58:46Z
- Improved: Improve flag logic (c229821)
- Fixed: Fixed handling of brute force limiter by getting correct user IP (3ce947e)
## [server-v2.4.6-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.4.6-beta) (Pre-release) - 2021-09-14T15:02:21Z
- New: Add link to Stripe subscription page to manage payment details (4e7fe66)
- New: Add transaction info to debug deadlock issues (01b653f)
## [server-v2.4.3-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.4.3-beta) (Pre-release) - 2021-09-02T17:49:11Z
- New: Added Help page for Joplin Cloud (6520a48)