Compare commits

..
Author SHA1 Message Date
Laurent Cozic 84a793270d move back to test 2021-05-21 15:15:48 +02:00
Laurent Cozic 5f8b73350d other tdsts 2021-05-21 15:09:08 +02:00
Laurent Cozic a733cb0394 services 2021-05-21 13:44:57 +02:00
Laurent Cozic 7da4cb0d80 sync tests 2021-05-21 13:22:50 +02:00
Laurent Cozic 3233082b4c move more 2021-05-21 13:14:45 +02:00
Laurent Cozic c782f5e981 move 2021-05-21 13:01:43 +02:00
Laurent Cozic db51e2dd4b cli setup 2021-05-21 12:42:19 +02:00
Laurent Cozic f00f3d7f04 test move 2021-05-21 12:33:33 +02:00
260 changed files with 18506 additions and 21974 deletions

No files matched your search

+3 -21
View File
@@ -83,9 +83,6 @@ packages/app-cli/tests/MdToHtml.js.map
packages/app-cli/tests/MdToMd.d.ts
packages/app-cli/tests/MdToMd.js
packages/app-cli/tests/MdToMd.js.map
packages/app-cli/tests/services/keychain/KeychainService.d.ts
packages/app-cli/tests/services/keychain/KeychainService.js
packages/app-cli/tests/services/keychain/KeychainService.js.map
packages/app-cli/tests/services/plugins/PluginService.d.ts
packages/app-cli/tests/services/plugins/PluginService.js
packages/app-cli/tests/services/plugins/PluginService.js.map
@@ -104,9 +101,6 @@ packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
packages/app-cli/tests/services/plugins/sandboxProxy.js
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-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map
@@ -140,9 +134,6 @@ packages/app-desktop/commands/openProfileDirectory.js.map
packages/app-desktop/commands/replaceMisspelling.d.ts
packages/app-desktop/commands/replaceMisspelling.js
packages/app-desktop/commands/replaceMisspelling.js.map
packages/app-desktop/commands/restoreNoteRevision.d.ts
packages/app-desktop/commands/restoreNoteRevision.js
packages/app-desktop/commands/restoreNoteRevision.js.map
packages/app-desktop/commands/startExternalEditing.d.ts
packages/app-desktop/commands/startExternalEditing.js
packages/app-desktop/commands/startExternalEditing.js.map
@@ -341,9 +332,6 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearch.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map
@@ -827,9 +815,6 @@ packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinCloud.d.ts
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinCloud.js.map
packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map
@@ -1181,6 +1166,9 @@ packages/lib/services/keychain/KeychainServiceDriver.node.js.map
packages/lib/services/keychain/KeychainServiceDriverBase.d.ts
packages/lib/services/keychain/KeychainServiceDriverBase.js
packages/lib/services/keychain/KeychainServiceDriverBase.js.map
packages/lib/services/keychain/keychainService.test.d.ts
packages/lib/services/keychain/keychainService.test.js
packages/lib/services/keychain/keychainService.test.js.map
packages/lib/services/plugins/BasePluginRunner.d.ts
packages/lib/services/plugins/BasePluginRunner.js
packages/lib/services/plugins/BasePluginRunner.js.map
@@ -1484,9 +1472,6 @@ packages/lib/uuid.js.map
packages/lib/versionInfo.d.ts
packages/lib/versionInfo.js
packages/lib/versionInfo.js.map
packages/plugin-repo-cli/commands/updateRelease.d.ts
packages/plugin-repo-cli/commands/updateRelease.js
packages/plugin-repo-cli/commands/updateRelease.js.map
packages/plugin-repo-cli/index.d.ts
packages/plugin-repo-cli/index.js
packages/plugin-repo-cli/index.js.map
@@ -1625,7 +1610,4 @@ packages/tools/release-server.js.map
packages/tools/tool-utils.d.ts
packages/tools/tool-utils.js
packages/tools/tool-utils.js.map
packages/tools/update-readme-sponsors.d.ts
packages/tools/update-readme-sponsors.js
packages/tools/update-readme-sponsors.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
-127
View File
@@ -1,127 +0,0 @@
#!/bin/bash
# =============================================================================
# Setup environment variables
# =============================================================================
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
IS_PULL_REQUEST=0
IS_DEV_BRANCH=0
IS_LINUX=0
IS_MACOS=0
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
IS_PULL_REQUEST=1
fi
if [ "$GITHUB_REF" == "refs/heads/dev" ]; then
IS_DEV_BRANCH=1
fi
if [ "$RUNNER_OS" == "Linux" ]; then
IS_LINUX=1
IS_MACOS=0
else
IS_LINUX=0
IS_MACOS=1
fi
# =============================================================================
# Print environment
# =============================================================================
echo "GITHUB_WORKFLOW=$GITHUB_WORKFLOW"
echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME"
echo "GITHUB_REF=$GITHUB_REF"
echo "RUNNER_OS=$RUNNER_OS"
echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
echo "IS_DEV_BRANCH=$IS_DEV_BRANCH"
echo "IS_LINUX=$IS_LINUX"
echo "IS_MACOS=$IS_MACOS"
echo "Node $( node -v )"
echo "Npm $( npm -v )"
# =============================================================================
# Install packages
# =============================================================================
cd "$ROOT_DIR"
npm install
# =============================================================================
# Run test units. Only do it for pull requests and dev branch because we don't
# want it to randomly fail when trying to create a desktop release.
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
npm run test-ci
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Run linter for pull requests only. We also don't want this to make the desktop
# release randomly fail.
# =============================================================================
if [ "$IS_PULL_REQUEST" != "1" ]; then
npm run linter-ci ./
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Validate translations - this is needed as some users manually edit .po files
# (and often make mistakes) instead of using a proper tool like poedit. Doing it
# for Linux only is sufficient.
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ]; then
if [ "$IS_LINUX" == "1" ]; then
node packages/tools/validate-translation.js
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
fi
# =============================================================================
# Find out if we should run the build or not. Electron-builder gets stuck when
# building PRs so we disable it in this case. The Linux build should provide
# enough info if the app builds or not.
# https://github.com/electron-userland/electron-builder/issues/4263
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ]; then
if [ "$IS_MACOS" == "1" ]; then
exit 0
fi
fi
# =============================================================================
# Prepare the Electron app and build it
#
# If the current tag is a desktop release tag (starts with "v", such as
# "v1.4.7"), we build and publish to github
#
# Otherwise we only build but don't publish to GitHub. It helps finding
# out any issue in pull requests and dev branch.
# =============================================================================
cd "$ROOT_DIR/packages/app-desktop"
if [[ $GIT_TAG_NAME = v* ]]; then
USE_HARD_LINKS=false npm run dist
else
USE_HARD_LINKS=false npm run dist -- --publish=never
fi
-37
View File
@@ -1,37 +0,0 @@
name: Joplin Continuous Integration
on: [push]
jobs:
Main:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
steps:
# Silence apt-get update errors (for example when a module doesn't
# exist) since otherwise it will make the whole build fails, even though
# it might work without update. libsecret-1-dev is required for keytar -
# https://github.com/atom/node-keytar
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update || true
sudo apt-get install -y gettext
sudo apt-get install -y libsecret-1-dev
- uses: actions/checkout@v2
- uses: olegtarasov/get-tag@v2.1
- uses: actions/setup-node@v2
with:
node-version: '12'
- name: Run script...
env:
APPLE_ASC_PROVIDER: ${{ secrets.APPLE_ASC_PROVIDER }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
"${GITHUB_WORKSPACE}/.github/scripts/run_ci.sh"
+3 -21
View File
@@ -69,9 +69,6 @@ packages/app-cli/tests/MdToHtml.js.map
packages/app-cli/tests/MdToMd.d.ts
packages/app-cli/tests/MdToMd.js
packages/app-cli/tests/MdToMd.js.map
packages/app-cli/tests/services/keychain/KeychainService.d.ts
packages/app-cli/tests/services/keychain/KeychainService.js
packages/app-cli/tests/services/keychain/KeychainService.js.map
packages/app-cli/tests/services/plugins/PluginService.d.ts
packages/app-cli/tests/services/plugins/PluginService.js
packages/app-cli/tests/services/plugins/PluginService.js.map
@@ -90,9 +87,6 @@ packages/app-cli/tests/services/plugins/api/JoplinWorkspace.js.map
packages/app-cli/tests/services/plugins/sandboxProxy.d.ts
packages/app-cli/tests/services/plugins/sandboxProxy.js
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-desktop/ElectronAppWrapper.d.ts
packages/app-desktop/ElectronAppWrapper.js
packages/app-desktop/ElectronAppWrapper.js.map
@@ -126,9 +120,6 @@ packages/app-desktop/commands/openProfileDirectory.js.map
packages/app-desktop/commands/replaceMisspelling.d.ts
packages/app-desktop/commands/replaceMisspelling.js
packages/app-desktop/commands/replaceMisspelling.js.map
packages/app-desktop/commands/restoreNoteRevision.d.ts
packages/app-desktop/commands/restoreNoteRevision.js
packages/app-desktop/commands/restoreNoteRevision.js.map
packages/app-desktop/commands/startExternalEditing.d.ts
packages/app-desktop/commands/startExternalEditing.js
packages/app-desktop/commands/startExternalEditing.js.map
@@ -327,9 +318,6 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearch.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useExternalPlugins.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinCommands.js.map
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.d.ts
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js.map
@@ -813,9 +801,6 @@ packages/lib/Logger.js.map
packages/lib/PoorManIntervals.d.ts
packages/lib/PoorManIntervals.js
packages/lib/PoorManIntervals.js.map
packages/lib/SyncTargetJoplinCloud.d.ts
packages/lib/SyncTargetJoplinCloud.js
packages/lib/SyncTargetJoplinCloud.js.map
packages/lib/SyncTargetJoplinServer.d.ts
packages/lib/SyncTargetJoplinServer.js
packages/lib/SyncTargetJoplinServer.js.map
@@ -1167,6 +1152,9 @@ packages/lib/services/keychain/KeychainServiceDriver.node.js.map
packages/lib/services/keychain/KeychainServiceDriverBase.d.ts
packages/lib/services/keychain/KeychainServiceDriverBase.js
packages/lib/services/keychain/KeychainServiceDriverBase.js.map
packages/lib/services/keychain/keychainService.test.d.ts
packages/lib/services/keychain/keychainService.test.js
packages/lib/services/keychain/keychainService.test.js.map
packages/lib/services/plugins/BasePluginRunner.d.ts
packages/lib/services/plugins/BasePluginRunner.js
packages/lib/services/plugins/BasePluginRunner.js.map
@@ -1470,9 +1458,6 @@ packages/lib/uuid.js.map
packages/lib/versionInfo.d.ts
packages/lib/versionInfo.js
packages/lib/versionInfo.js.map
packages/plugin-repo-cli/commands/updateRelease.d.ts
packages/plugin-repo-cli/commands/updateRelease.js
packages/plugin-repo-cli/commands/updateRelease.js.map
packages/plugin-repo-cli/index.d.ts
packages/plugin-repo-cli/index.js
packages/plugin-repo-cli/index.js.map
@@ -1611,7 +1596,4 @@ packages/tools/release-server.js.map
packages/tools/tool-utils.d.ts
packages/tools/tool-utils.js
packages/tools/tool-utils.js.map
packages/tools/update-readme-sponsors.d.ts
packages/tools/update-readme-sponsors.js
packages/tools/update-readme-sponsors.js.map
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
+138
View File
@@ -0,0 +1,138 @@
# Only build tags (Doesn't work - doesn't build anything)
if: tag IS present OR type = pull_request OR branch = dev
rvm: 2.3.3
# It's important to only build production branches otherwise Electron Builder
# might take assets from dev branches and overwrite those of production.
# https://docs.travis-ci.com/user/customizing-the-build/#Building-Specific-Branches
branches:
only:
- master
- dev
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
matrix:
include:
- os: osx
osx_image: xcode12
language: node_js
node_js: "12"
cache:
npm: false
# Cache was disabled because when changing from node_js 10 to node_js 12
# it was still using build files from Node 10 when building SQLite which
# was making it fail. Might be ok to re-enable later on, although it doesn't
# make build that much faster.
#
# env:
# - ELECTRON_CACHE=$HOME/.cache/electron
# - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- os: linux
sudo: required
dist: trusty
language: node_js
node_js: "12"
cache:
npm: false
# env:
# - ELECTRON_CACHE=$HOME/.cache/electron
# - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
# cache:
# directories:
# - node_modules
# - $HOME/.cache/electron
# - $HOME/.cache/electron-builder
before_install:
# HOMEBREW_NO_AUTO_UPDATE needed so that Homebrew doesn't upgrade to the next
# version, which requires Ruby 2.3, which is not available on the Travis VM.
# Silence apt-get update errors (for example when a module doesn't exist) since
# otherwise it will make the whole build fails, even though all we need is yarn.
# libsecret-1-dev is required for keytar - https://github.com/atom/node-keytar
- |
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
HOMEBREW_NO_AUTO_UPDATE=1 brew install yarn
else
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update || true
sudo apt-get install -y yarn
sudo apt-get install -y gettext
sudo apt-get install -y libsecret-1-dev
fi
script:
- |
# Prints some env variables
echo "TRAVIS_OS_NAME=$TRAVIS_OS_NAME"
echo "TRAVIS_BRANCH=$TRAVIS_BRANCH"
echo "TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST"
echo "TRAVIS_TAG=$TRAVIS_TAG"
# Install tools
npm install
# Run test units.
# Only do it for pull requests because Travis randomly fails to run them
# and that would break the desktop release.
if [ "$TRAVIS_PULL_REQUEST" != "false" ] || [ "$TRAVIS_BRANCH" = "dev" ]; then
npm run test-ci
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# Run linter for pull requests only - this is so that
# bypassing eslint is allowed for urgent fixes.
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
npm run linter-ci ./
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# Validate translations - this is needed as some users manually
# edit .po files (and often make mistakes) instead of using a proper
# tool like poedit. Doing it for Linux only is sufficient.
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
if [ "$TRAVIS_OS_NAME" != "osx" ]; then
node packages/tools/validate-translation.js
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
fi
# Find out if we should run the build or not. Electron-builder gets stuck when
# building PRs so we disable it in this case. The Linux build should provide
# enough info if the app builds or not.
# https://github.com/electron-userland/electron-builder/issues/4263
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
exit 0
fi
fi
# Prepare the Electron app and build it
#
# If the current tag is a desktop release tag (starts with "v", such as
# "v1.4.7"), we build and publish to github
#
# Otherwise we only build but don't publish to GitHub. It helps finding
# out any issue in pull requests and dev branch.
cd packages/app-desktop
if [[ $TRAVIS_TAG = v* ]]; then
USE_HARD_LINKS=false npm run dist
else
USE_HARD_LINKS=false npm run dist -- --publish=never
fi
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

+2 -2
View File
@@ -1,4 +1,4 @@
[![Travis Build Status](https://travis-ci.com/laurent22/joplin.svg?branch=master)](https://travis-ci.com/laurent22/joplin) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/laurent22/joplin?branch=master&passingText=master%20-%20OK&svg=true)](https://ci.appveyor.com/project/laurent22/joplin)
[![Travis Build Status](https://travis-ci.org/laurent22/joplin.svg?branch=master)](https://travis-ci.org/laurent22/joplin) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/laurent22/joplin?branch=master&passingText=master%20-%20OK&svg=true)](https://ci.appveyor.com/project/laurent22/joplin)
# Building the applications
@@ -64,7 +64,7 @@ Normally the **bundler** should start automatically with the application. If it
npm install
npm run watch # To watch for changes
To test the extension please refer to the relevant pages for each browser: [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#Trying_it_out) / [Chrome](https://developer.chrome.com/docs/extensions/mv3/getstarted/). Please note that the extension in dev mode will only connect to a dev instance of the desktop app (and vice-versa).
To test the extension please refer to the relevant pages for each browser: [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#Trying_it_out) / [Chrome](https://developer.chrome.com/extensions/faq#faq-dev-01). Please note that the extension in dev mode will only connect to a dev instance of the desktop app (and vice-versa).
## Watching files
+96 -132
View File
@@ -64,19 +64,17 @@ The Web Clipper is a browser extension that allows you to save web pages and scr
# Sponsors
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a> <a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a> <a href=" https://tranio.com/italy/"><img title="Tranio" width="256" src="https://joplinapp.org/images/sponsors/Tranio.png"/></a>
<a href="https://seirei.ne.jp"><img title="Serei Network" width="256" src="https://joplinapp.org/images/sponsors/SeireiNetwork.png"/></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="https://usrigging.com/"><img title="U.S. Ringing Supply" width="256" src="https://joplinapp.org/images/sponsors/RingingSupply.svg"/></a>
* * *
<!-- SPONSORS -->
| | | | |
| :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/3061769?s=96&v=4"/></br>[c-nagy](https://github.com/c-nagy) | <img width="50" src="https://avatars2.githubusercontent.com/u/70780798?s=96&v=4"/></br>[cabottech](https://github.com/cabottech) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/4862947?s=96&v=4"/></br>[chrootlogin](https://github.com/chrootlogin) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) | <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[fbloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/38898566?s=96&v=4"/></br>[h4sh5](https://github.com/h4sh5) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) | <img width="50" src="https://avatars2.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars2.githubusercontent.com/u/1168659?s=96&v=4"/></br>[nicholashead](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars2.githubusercontent.com/u/47742?s=96&v=4"/></br>[ravenscroftj](https://github.com/ravenscroftj) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/73081837?s=96&v=4"/></br>[thismarty](https://github.com/thismarty) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[thomasbroussard](https://github.com/thomasbroussard) | <img width="50" src="https://avatars2.githubusercontent.com/u/53228972?s=96&v=4"/></br>[wasteisobscene](https://github.com/wasteisobscene) | |
<!-- SPONSORS -->
| | | |
| :---: | :---: | :---: |
| <img width="50" src="https://avatars0.githubusercontent.com/u/6979755?s=96&v=4"/></br>[Devon Zuegel](https://github.com/devonzuegel) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[小西 孝宗](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[Alexander van der Berg](https://github.com/avanderberg)
| <img width="50" src="https://avatars0.githubusercontent.com/u/1168659?s=96&v=4"/></br>[Nicholas Head](https://github.com/nicholashead) | <img width="50" src="https://avatars2.githubusercontent.com/u/1439535?s=96&v=4"/></br>[Frank Bloise](https://github.com/fbloise) | <img width="50" src="https://avatars2.githubusercontent.com/u/15859362?s=96&v=4"/></br>[Thomas Broussard](https://github.com/thomasbroussard)
| <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[Brandon Johnson](https://github.com/dbrandonjohnson) | <img width="50" src="https://avatars1.githubusercontent.com/u/3061769?s=96&v=4"/></br>[@cnagy](https://github.com/c-nagy) | <img width="50" src="https://avatars3.githubusercontent.com/u/53228972?s=96&v=4"/></br>[clmntsl](https://github.com/clmntsl)
| <img width="50" src="https://avatars1.githubusercontent.com/u/29300939?s=96&v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars.githubusercontent.com/u/1248504?s=96&v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m)
| <img width="50" src="https://avatars.githubusercontent.com/u/5782817?s=96&v=4"/></br>[piccobit](https://github.com/piccobit) | <img width="50" src="https://avatars3.githubusercontent.com/u/37297218?s=96&v=4"/></br>[Jess Sullivan](https://github.com/jesssullivan)
<!-- TOC -->
# Table of contents
@@ -409,12 +407,6 @@ For more information see [Plugins](https://github.com/laurent22/joplin/blob/dev/
Joplin implements the SQLite Full Text Search (FTS4) extension. It means the content of all the notes is indexed in real time and search queries return results very fast. Both [Simple FTS Queries](https://www.sqlite.org/fts3.html#simple_fts_queries) and [Full-Text Index Queries](https://www.sqlite.org/fts3.html#full_text_index_queries) are supported. See below for the list of supported queries:
One caveat of SQLite FTS is that it does not support languages which do not use Latin word boundaries (spaces, tabs, punctuation). To solve this issue, Joplin has a custom search mode, that does not use FTS, but still has all of its features (multi term search, filters, etc.). One of its drawbacks is that it can get slow on larger note collections. Also, the sorting of the results will be less accurate, as the ranking algorithm (BM25) is, for now, only implemented for FTS. Finally, in this mode there are no restrictions on using the `*` wildcard (`swim*`, `*swim` and `ast*rix` all work). This search mode is currently enabled if one of the following languages are detected:
- Chinese
- Japanese
- Korean
- Thai
## Supported queries
Search type | Description | Example
@@ -519,47 +511,47 @@ Current translations:
<!-- LOCALE-TABLE-AUTO-GENERATED -->
&nbsp; | Language | Po File | Last translator | Percent done
---|---|---|---|---
<img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" width="16px"/> | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 95%
<img src="https://joplinapp.org/images/flags/es/basque_country.png" width="16px"/> | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 30%
<img src="https://joplinapp.org/images/flags/country-4x3/ba.png" width="16px"/> | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 74%
<img src="https://joplinapp.org/images/flags/country-4x3/bg.png" width="16px"/> | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 57%
<img src="https://joplinapp.org/images/flags/es/catalonia.png" width="16px"/> | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 82%
<img src="https://joplinapp.org/images/flags/country-4x3/hr.png" width="16px"/> | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/cz.png" width="16px"/> | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 85%
<img src="https://joplinapp.org/images/flags/country-4x3/dk.png" width="16px"/> | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 95%
<img src="https://joplinapp.org/images/flags/country-4x3/de.png" width="16px"/> | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/ee.png" width="16px"/> | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 56%
<img src="https://joplinapp.org/images/flags/country-4x3/gb.png" width="16px"/> | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/us.png" width="16px"/> | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
<img src="https://joplinapp.org/images/flags/country-4x3/es.png" width="16px"/> | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 94%
<img src="https://joplinapp.org/images/flags/esperanto.png" width="16px"/> | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 32%
<img src="https://joplinapp.org/images/flags/country-4x3/fi.png" width="16px"/> | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/fr.png" width="16px"/> | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 98%
<img src="https://joplinapp.org/images/flags/es/galicia.png" width="16px"/> | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 38%
<img src="https://joplinapp.org/images/flags/country-4x3/id.png" width="16px"/> | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 92%
<img src="https://joplinapp.org/images/flags/country-4x3/it.png" width="16px"/> | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Manuel Tassi](mailto:mannivuwiki@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/hu.png" width="16px"/> | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 88%
<img src="https://joplinapp.org/images/flags/country-4x3/be.png" width="16px"/> | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 91%
<img src="https://joplinapp.org/images/flags/country-4x3/nl.png" width="16px"/> | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/no.png" width="16px"/> | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 75%
<img src="https://joplinapp.org/images/flags/country-4x3/ir.png" width="16px"/> | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 71%
<img src="https://joplinapp.org/images/flags/country-4x3/pl.png" width="16px"/> | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/br.png" width="16px"/> | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/pt.png" width="16px"/> | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/ro.png" width="16px"/> | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 66%
<img src="https://joplinapp.org/images/flags/country-4x3/si.png" width="16px"/> | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 95%
<img src="https://joplinapp.org/images/flags/country-4x3/se.png" width="16px"/> | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 61%
<img src="https://joplinapp.org/images/flags/country-4x3/th.png" width="16px"/> | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 45%
<img src="https://joplinapp.org/images/flags/country-4x3/vi.png" width="16px"/> | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 73%
<img src="https://joplinapp.org/images/flags/country-4x3/tr.png" width="16px"/> | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/ua.png" width="16px"/> | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/gr.png" width="16px"/> | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/ru.png" width="16px"/> | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 94%
<img src="https://joplinapp.org/images/flags/country-4x3/rs.png" width="16px"/> | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 71%
<img src="https://joplinapp.org/images/flags/country-4x3/cn.png" width="16px"/> | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [南宫小骏](mailto:jackytsu@vip.qq.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/tw.png" width="16px"/> | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Po-Chiang Chao](mailto:BobChao%29%20%28bobchao@gmail.com) | 99%
<img src="https://joplinapp.org/images/flags/country-4x3/jp.png" width="16px"/> | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 96%
<img src="https://joplinapp.org/images/flags/country-4x3/kr.png" width="16px"/> | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 99%
![](https://joplinapp.org/images/flags/country-4x3/arableague.png) | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 96%
![](https://joplinapp.org/images/flags/es/basque_country.png) | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 30%
![](https://joplinapp.org/images/flags/country-4x3/ba.png) | Bosnian (Bosna i Hercegovina) | [bs_BA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bs_BA.po) | [Derviš T.](mailto:dervis.t@pm.me) | 75%
![](https://joplinapp.org/images/flags/country-4x3/bg.png) | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 58%
![](https://joplinapp.org/images/flags/es/catalonia.png) | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 83%
![](https://joplinapp.org/images/flags/country-4x3/hr.png) | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 96%
![](https://joplinapp.org/images/flags/country-4x3/cz.png) | Czech (Česká republika) | [cs_CZ](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/cs_CZ.po) | [Lukas Helebrandt](mailto:lukas@aiya.cz) | 86%
![](https://joplinapp.org/images/flags/country-4x3/dk.png) | Dansk (Danmark) | [da_DK](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/da_DK.po) | Mustafa Al-Dailemi (dailemi@hotmail.com)Language-Team: | 96%
![](https://joplinapp.org/images/flags/country-4x3/de.png) | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/ee.png) | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 57%
![](https://joplinapp.org/images/flags/country-4x3/gb.png) | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/us.png) | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
![](https://joplinapp.org/images/flags/country-4x3/es.png) | Español (España) | [es_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/es_ES.po) | [Mario Campo](mailto:mario.campo@gmail.com) | 94%
![](https://joplinapp.org/images/flags/esperanto.png) | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 33%
![](https://joplinapp.org/images/flags/country-4x3/fi.png) | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 94%
![](https://joplinapp.org/images/flags/country-4x3/fr.png) | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
![](https://joplinapp.org/images/flags/es/galicia.png) | Galician (España) | [gl_ES](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/gl_ES.po) | [Marcos Lans](mailto:marcoslansgarza@gmail.com) | 38%
![](https://joplinapp.org/images/flags/country-4x3/id.png) | Indonesian (Indonesia) | [id_ID](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/id_ID.po) | [eresytter](mailto:42007357+eresytter@users.noreply.github.com) | 93%
![](https://joplinapp.org/images/flags/country-4x3/it.png) | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/hu.png) | Magyar (Magyarország) | [hu_HU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hu_HU.po) | [Szőke Sándor](mailto:mail@szokesandor.hu) | 88%
![](https://joplinapp.org/images/flags/country-4x3/be.png) | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 92%
![](https://joplinapp.org/images/flags/country-4x3/nl.png) | Nederlands (Nederland) | [nl_NL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_NL.po) | [MetBril](mailto:metbril@users.noreply.github.com) | 95%
![](https://joplinapp.org/images/flags/country-4x3/no.png) | Norwegian (Norge, Noreg) | [nb_NO](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nb_NO.po) | [Mats Estensen](mailto:code@mxe.no) | 76%
![](https://joplinapp.org/images/flags/country-4x3/ir.png) | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 71%
![](https://joplinapp.org/images/flags/country-4x3/pl.png) | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_BR.po) | [Nicolas Suzuki](mailto:nicolas.suzuki@pm.me) | 94%
![](https://joplinapp.org/images/flags/country-4x3/pt.png) | Português (Portugal) | [pt_PT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pt_PT.po) | [Diogo Caveiro](mailto:dcaveiro@yahoo.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/ro.png) | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 66%
![](https://joplinapp.org/images/flags/country-4x3/si.png) | Slovenian (Slovenija) | [sl_SI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sl_SI.po) | [Martin Korelič](mailto:martin.korelic@protonmail.com) | 96%
![](https://joplinapp.org/images/flags/country-4x3/se.png) | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 61%
![](https://joplinapp.org/images/flags/country-4x3/th.png) | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 45%
![](https://joplinapp.org/images/flags/country-4x3/vi.png) | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 73%
![](https://joplinapp.org/images/flags/country-4x3/tr.png) | Türkçe (Türkiye) | [tr_TR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/tr_TR.po) | [Arda Kılıçdağı](mailto:arda@kilicdagi.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/ua.png) | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/gr.png) | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 97%
![](https://joplinapp.org/images/flags/country-4x3/ru.png) | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/rs.png) | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 71%
![](https://joplinapp.org/images/flags/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 94%
![](https://joplinapp.org/images/flags/country-4x3/tw.png) | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 92%
![](https://joplinapp.org/images/flags/country-4x3/jp.png) | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
![](https://joplinapp.org/images/flags/country-4x3/kr.png) | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 96%
<!-- LOCALE-TABLE-AUTO-GENERATED -->
# Contributors
@@ -569,80 +561,52 @@ Thank you to everyone who've contributed to Joplin's source code!
<!-- CONTRIBUTORS-TABLE-AUTO-GENERATED -->
| | | | | |
| :---: | :---: | :---: | :---: | :---: |
| <img width="50" src="https://avatars.githubusercontent.com/u/1285584?v=4"/></br>[laurent22](https://github.com/laurent22) | <img width="50" src="https://avatars.githubusercontent.com/u/223439?v=4"/></br>[tessus](https://github.com/tessus) | <img width="50" src="https://avatars.githubusercontent.com/u/2179547?v=4"/></br>[CalebJohn](https://github.com/CalebJohn) | <img width="50" src="https://avatars.githubusercontent.com/u/1732810?v=4"/></br>[mic704b](https://github.com/mic704b) | <img width="50" src="https://avatars.githubusercontent.com/u/995612?v=4"/></br>[roman-r-m](https://github.com/roman-r-m) |
| <img width="50" src="https://avatars.githubusercontent.com/u/29672555?v=4"/></br>[genneko](https://github.com/genneko) | <img width="50" src="https://avatars.githubusercontent.com/u/63491353?v=4"/></br>[j-krl](https://github.com/j-krl) | <img width="50" src="https://avatars.githubusercontent.com/u/4553672?v=4"/></br>[tanrax](https://github.com/tanrax) | <img width="50" src="https://avatars.githubusercontent.com/u/30305957?v=4"/></br>[naviji](https://github.com/naviji) | <img width="50" src="https://avatars.githubusercontent.com/u/3542031?v=4"/></br>[PackElend](https://github.com/PackElend) |
| <img width="50" src="https://avatars.githubusercontent.com/u/8701534?v=4"/></br>[rtmkrlv](https://github.com/rtmkrlv) | <img width="50" src="https://avatars.githubusercontent.com/u/10997189?v=4"/></br>[fmrtn](https://github.com/fmrtn) | <img width="50" src="https://avatars.githubusercontent.com/u/4374338?v=4"/></br>[potatogim](https://github.com/potatogim) | <img width="50" src="https://avatars.githubusercontent.com/u/6979755?v=4"/></br>[devonzuegel](https://github.com/devonzuegel) | <img width="50" src="https://avatars.githubusercontent.com/u/26695184?v=4"/></br>[anjulalk](https://github.com/anjulalk) |
| <img width="50" src="https://avatars.githubusercontent.com/u/16101778?v=4"/></br>[gabcoh](https://github.com/gabcoh) | <img width="50" src="https://avatars.githubusercontent.com/u/10927304?v=4"/></br>[matsest](https://github.com/matsest) | <img width="50" src="https://avatars.githubusercontent.com/u/6319051?v=4"/></br>[abonte](https://github.com/abonte) | <img width="50" src="https://avatars.githubusercontent.com/u/1685517?v=4"/></br>[Abijeet](https://github.com/Abijeet) | <img width="50" src="https://avatars.githubusercontent.com/u/27751740?v=4"/></br>[ishantgupta777](https://github.com/ishantgupta777) |
| <img width="50" src="https://avatars.githubusercontent.com/u/24863925?v=4"/></br>[JackGruber](https://github.com/JackGruber) | <img width="50" src="https://avatars.githubusercontent.com/u/2063957?v=4"/></br>[Ardakilic](https://github.com/Ardakilic) | <img width="50" src="https://avatars.githubusercontent.com/u/44024553?v=4"/></br>[rabeehrz](https://github.com/rabeehrz) | <img width="50" src="https://avatars.githubusercontent.com/u/35633575?v=4"/></br>[coderrsid](https://github.com/coderrsid) | <img width="50" src="https://avatars.githubusercontent.com/u/208212?v=4"/></br>[foxmask](https://github.com/foxmask) |
| <img width="50" src="https://avatars.githubusercontent.com/u/6557454?v=4"/></br>[innocuo](https://github.com/innocuo) | <img width="50" src="https://avatars.githubusercontent.com/u/54268438?v=4"/></br>[Rahulm2310](https://github.com/Rahulm2310) | <img width="50" src="https://avatars.githubusercontent.com/u/1904967?v=4"/></br>[readingsnail](https://github.com/readingsnail) | <img width="50" src="https://avatars.githubusercontent.com/u/7415668?v=4"/></br>[mablin7](https://github.com/mablin7) | <img width="50" src="https://avatars.githubusercontent.com/u/3985557?v=4"/></br>[XarisA](https://github.com/XarisA) |
| <img width="50" src="https://avatars.githubusercontent.com/u/49979415?v=4"/></br>[jonath92](https://github.com/jonath92) | <img width="50" src="https://avatars.githubusercontent.com/u/4237724?v=4"/></br>[alexdevero](https://github.com/alexdevero) | <img width="50" src="https://avatars.githubusercontent.com/u/35904727?v=4"/></br>[Runo-saduwa](https://github.com/Runo-saduwa) | <img width="50" src="https://avatars.githubusercontent.com/u/5365582?v=4"/></br>[marcosvega91](https://github.com/marcosvega91) | <img width="50" src="https://avatars.githubusercontent.com/u/37639389?v=4"/></br>[petrz12](https://github.com/petrz12) |
| <img width="50" src="https://avatars.githubusercontent.com/u/51550769?v=4"/></br>[rnbastos](https://github.com/rnbastos) | <img width="50" src="https://avatars.githubusercontent.com/u/32396?v=4"/></br>[ProgramFan](https://github.com/ProgramFan) | <img width="50" src="https://avatars.githubusercontent.com/u/4245227?v=4"/></br>[zblesk](https://github.com/zblesk) | <img width="50" src="https://avatars.githubusercontent.com/u/5730052?v=4"/></br>[vsimkus](https://github.com/vsimkus) | <img width="50" src="https://avatars.githubusercontent.com/u/3194829?v=4"/></br>[moltenform](https://github.com/moltenform) |
| <img width="50" src="https://avatars.githubusercontent.com/u/36989112?v=4"/></br>[nishantwrp](https://github.com/nishantwrp) | <img width="50" src="https://avatars.githubusercontent.com/u/5199995?v=4"/></br>[zuphilip](https://github.com/zuphilip) | <img width="50" src="https://avatars.githubusercontent.com/u/54576074?v=4"/></br>[Rishabh-malhotraa](https://github.com/Rishabh-malhotraa) | <img width="50" src="https://avatars.githubusercontent.com/u/559346?v=4"/></br>[metbril](https://github.com/metbril) | <img width="50" src="https://avatars.githubusercontent.com/u/47623588?v=4"/></br>[WhiredPlanck](https://github.com/WhiredPlanck) |
| <img width="50" src="https://avatars.githubusercontent.com/u/43657314?v=4"/></br>[milotype](https://github.com/milotype) | <img width="50" src="https://avatars.githubusercontent.com/u/32196447?v=4"/></br>[yaozeye](https://github.com/yaozeye) | <img width="50" src="https://avatars.githubusercontent.com/u/12264626?v=4"/></br>[ylc395](https://github.com/ylc395) | <img width="50" src="https://avatars.githubusercontent.com/u/17768566?v=4"/></br>[RenatoXSR](https://github.com/RenatoXSR) | <img width="50" src="https://avatars.githubusercontent.com/u/54888685?v=4"/></br>[RedDocMD](https://github.com/RedDocMD) |
| <img width="50" src="https://avatars.githubusercontent.com/u/31567272?v=4"/></br>[q1011](https://github.com/q1011) | <img width="50" src="https://avatars.githubusercontent.com/u/12906090?v=4"/></br>[amitsin6h](https://github.com/amitsin6h) | <img width="50" src="https://avatars.githubusercontent.com/u/628474?v=4"/></br>[Atalanttore](https://github.com/Atalanttore) | <img width="50" src="https://avatars.githubusercontent.com/u/42747216?v=4"/></br>[Mannivu](https://github.com/Mannivu) | <img width="50" src="https://avatars.githubusercontent.com/u/23281486?v=4"/></br>[martonpaulo](https://github.com/martonpaulo) |
| <img width="50" src="https://avatars.githubusercontent.com/u/390889?v=4"/></br>[mmahmoudian](https://github.com/mmahmoudian) | <img width="50" src="https://avatars.githubusercontent.com/u/4497566?v=4"/></br>[rccavalcanti](https://github.com/rccavalcanti) | <img width="50" src="https://avatars.githubusercontent.com/u/1540054?v=4"/></br>[ShaneKilkelly](https://github.com/ShaneKilkelly) | <img width="50" src="https://avatars.githubusercontent.com/u/7091080?v=4"/></br>[sinkuu](https://github.com/sinkuu) | <img width="50" src="https://avatars.githubusercontent.com/u/6734573?v=4"/></br>[stweil](https://github.com/stweil) |
| <img width="50" src="https://avatars.githubusercontent.com/u/692072?v=4"/></br>[conyx](https://github.com/conyx) | <img width="50" src="https://avatars.githubusercontent.com/u/49116134?v=4"/></br>[anihm136](https://github.com/anihm136) | <img width="50" src="https://avatars.githubusercontent.com/u/937861?v=4"/></br>[archont00](https://github.com/archont00) | <img width="50" src="https://avatars.githubusercontent.com/u/32770029?v=4"/></br>[bradmcl](https://github.com/bradmcl) | <img width="50" src="https://avatars.githubusercontent.com/u/22592201?v=4"/></br>[tfinnberg](https://github.com/tfinnberg) |
| <img width="50" src="https://avatars.githubusercontent.com/u/8716226?v=4"/></br>[amandamcg](https://github.com/amandamcg) | <img width="50" src="https://avatars.githubusercontent.com/u/3870964?v=4"/></br>[marcushill](https://github.com/marcushill) | <img width="50" src="https://avatars.githubusercontent.com/u/102242?v=4"/></br>[nathanleiby](https://github.com/nathanleiby) | <img width="50" src="https://avatars.githubusercontent.com/u/226708?v=4"/></br>[RaphaelKimmig](https://github.com/RaphaelKimmig) | <img width="50" src="https://avatars.githubusercontent.com/u/20461071?v=4"/></br>[Vaso3](https://github.com/Vaso3) |
| <img width="50" src="https://avatars.githubusercontent.com/u/36303913?v=4"/></br>[sensor-freak](https://github.com/sensor-freak) | <img width="50" src="https://avatars.githubusercontent.com/u/63918341?v=4"/></br>[lkiThakur](https://github.com/lkiThakur) | <img width="50" src="https://avatars.githubusercontent.com/u/28987176?v=4"/></br>[infinity052](https://github.com/infinity052) | <img width="50" src="https://avatars.githubusercontent.com/u/21161146?v=4"/></br>[BartBucknill](https://github.com/BartBucknill) | <img width="50" src="https://avatars.githubusercontent.com/u/2494769?v=4"/></br>[mrwulf](https://github.com/mrwulf) |
| <img width="50" src="https://avatars.githubusercontent.com/u/560571?v=4"/></br>[chrisb86](https://github.com/chrisb86) | <img width="50" src="https://avatars.githubusercontent.com/u/1686759?v=4"/></br>[chrmoritz](https://github.com/chrmoritz) | <img width="50" src="https://avatars.githubusercontent.com/u/58074586?v=4"/></br>[Daeraxa](https://github.com/Daeraxa) | <img width="50" src="https://avatars.githubusercontent.com/u/71190696?v=4"/></br>[Elaborendum](https://github.com/Elaborendum) | <img width="50" src="https://avatars.githubusercontent.com/u/5001259?v=4"/></br>[ethan42411](https://github.com/ethan42411) |
| <img width="50" src="https://avatars.githubusercontent.com/u/2733783?v=4"/></br>[JOJ0](https://github.com/JOJ0) | <img width="50" src="https://avatars.githubusercontent.com/u/17108695?v=4"/></br>[jalajcodes](https://github.com/jalajcodes) | <img width="50" src="https://avatars.githubusercontent.com/u/238088?v=4"/></br>[jblunck](https://github.com/jblunck) | <img width="50" src="https://avatars.githubusercontent.com/u/3140223?v=4"/></br>[jdrobertso](https://github.com/jdrobertso) | <img width="50" src="https://avatars.githubusercontent.com/u/37297218?v=4"/></br>[Jesssullivan](https://github.com/Jesssullivan) |
| <img width="50" src="https://avatars.githubusercontent.com/u/339645?v=4"/></br>[jmontane](https://github.com/jmontane) | <img width="50" src="https://avatars.githubusercontent.com/u/69011?v=4"/></br>[johanhammar](https://github.com/johanhammar) | <img width="50" src="https://avatars.githubusercontent.com/u/4168339?v=4"/></br>[solariz](https://github.com/solariz) | <img width="50" src="https://avatars.githubusercontent.com/u/25288?v=4"/></br>[maicki](https://github.com/maicki) | <img width="50" src="https://avatars.githubusercontent.com/u/2136373?v=4"/></br>[mjjzf](https://github.com/mjjzf) |
| <img width="50" src="https://avatars.githubusercontent.com/u/27608187?v=4"/></br>[rt-oliveira](https://github.com/rt-oliveira) | <img width="50" src="https://avatars.githubusercontent.com/u/2486806?v=4"/></br>[sebastienjust](https://github.com/sebastienjust) | <img width="50" src="https://avatars.githubusercontent.com/u/28362310?v=4"/></br>[sealch](https://github.com/sealch) | <img width="50" src="https://avatars.githubusercontent.com/u/34258070?v=4"/></br>[StarFang208](https://github.com/StarFang208) | <img width="50" src="https://avatars.githubusercontent.com/u/59690052?v=4"/></br>[Subhra264](https://github.com/Subhra264) |
| <img width="50" src="https://avatars.githubusercontent.com/u/1782292?v=4"/></br>[SubodhDahal](https://github.com/SubodhDahal) | <img width="50" src="https://avatars.githubusercontent.com/u/5912371?v=4"/></br>[TobiasDev](https://github.com/TobiasDev) | <img width="50" src="https://avatars.githubusercontent.com/u/13502069?v=4"/></br>[Whaell](https://github.com/Whaell) | <img width="50" src="https://avatars.githubusercontent.com/u/29891001?v=4"/></br>[jyuvaraj03](https://github.com/jyuvaraj03) | <img width="50" src="https://avatars.githubusercontent.com/u/15380913?v=4"/></br>[kowalskidev](https://github.com/kowalskidev) |
| <img width="50" src="https://avatars.githubusercontent.com/u/337455?v=4"/></br>[alexchee](https://github.com/alexchee) | <img width="50" src="https://avatars.githubusercontent.com/u/5077221?v=4"/></br>[axq](https://github.com/axq) | <img width="50" src="https://avatars.githubusercontent.com/u/8808502?v=4"/></br>[barbowza](https://github.com/barbowza) | <img width="50" src="https://avatars.githubusercontent.com/u/42007357?v=4"/></br>[eresytter](https://github.com/eresytter) | <img width="50" src="https://avatars.githubusercontent.com/u/4316805?v=4"/></br>[lightray22](https://github.com/lightray22) |
| <img width="50" src="https://avatars.githubusercontent.com/u/11711053?v=4"/></br>[lscolombo](https://github.com/lscolombo) | <img width="50" src="https://avatars.githubusercontent.com/u/36228623?v=4"/></br>[mrkaato](https://github.com/mrkaato) | <img width="50" src="https://avatars.githubusercontent.com/u/17399340?v=4"/></br>[pf-siedler](https://github.com/pf-siedler) | <img width="50" src="https://avatars.githubusercontent.com/u/17232523?v=4"/></br>[ruuti](https://github.com/ruuti) | <img width="50" src="https://avatars.githubusercontent.com/u/23638148?v=4"/></br>[s1nceri7y](https://github.com/s1nceri7y) |
| <img width="50" src="https://avatars.githubusercontent.com/u/10117386?v=4"/></br>[kornava](https://github.com/kornava) | <img width="50" src="https://avatars.githubusercontent.com/u/7471938?v=4"/></br>[ShuiHuo](https://github.com/ShuiHuo) | <img width="50" src="https://avatars.githubusercontent.com/u/11596277?v=4"/></br>[ikunya](https://github.com/ikunya) | <img width="50" src="https://avatars.githubusercontent.com/u/8184424?v=4"/></br>[Ahmad45123](https://github.com/Ahmad45123) | <img width="50" src="https://avatars.githubusercontent.com/u/59133880?v=4"/></br>[bedwardly-down](https://github.com/bedwardly-down) |
| <img width="50" src="https://avatars.githubusercontent.com/u/50335724?v=4"/></br>[dcaveiro](https://github.com/dcaveiro) | <img width="50" src="https://avatars.githubusercontent.com/u/47456195?v=4"/></br>[hexclover](https://github.com/hexclover) | <img width="50" src="https://avatars.githubusercontent.com/u/45535789?v=4"/></br>[2jaeyeol](https://github.com/2jaeyeol) | <img width="50" src="https://avatars.githubusercontent.com/u/25622825?v=4"/></br>[thackeraaron](https://github.com/thackeraaron) | <img width="50" src="https://avatars.githubusercontent.com/u/15862474?v=4"/></br>[aaronxn](https://github.com/aaronxn) |
| <img width="50" src="https://avatars.githubusercontent.com/u/40672207?v=4"/></br>[xUser5000](https://github.com/xUser5000) | <img width="50" src="https://avatars.githubusercontent.com/u/56785486?v=4"/></br>[iamabhi222](https://github.com/iamabhi222) | <img width="50" src="https://avatars.githubusercontent.com/u/63443657?v=4"/></br>[Aksh-Konda](https://github.com/Aksh-Konda) | <img width="50" src="https://avatars.githubusercontent.com/u/3660978?v=4"/></br>[alanfortlink](https://github.com/alanfortlink) | <img width="50" src="https://avatars.githubusercontent.com/u/53372753?v=4"/></br>[AverageUser2](https://github.com/AverageUser2) |
| <img width="50" src="https://avatars.githubusercontent.com/u/4056990?v=4"/></br>[afischer211](https://github.com/afischer211) | <img width="50" src="https://avatars.githubusercontent.com/u/26230870?v=4"/></br>[a13xk](https://github.com/a13xk) | <img width="50" src="https://avatars.githubusercontent.com/u/14836659?v=4"/></br>[apankratov](https://github.com/apankratov) | <img width="50" src="https://avatars.githubusercontent.com/u/7045739?v=4"/></br>[teterkin](https://github.com/teterkin) | <img width="50" src="https://avatars.githubusercontent.com/u/215668?v=4"/></br>[avanderberg](https://github.com/avanderberg) |
| <img width="50" src="https://avatars.githubusercontent.com/u/41290751?v=4"/></br>[serenitatis](https://github.com/serenitatis) | <img width="50" src="https://avatars.githubusercontent.com/u/4408379?v=4"/></br>[lex111](https://github.com/lex111) | <img width="50" src="https://avatars.githubusercontent.com/u/60134194?v=4"/></br>[Alkindi42](https://github.com/Alkindi42) | <img width="50" src="https://avatars.githubusercontent.com/u/7129815?v=4"/></br>[Jumanjii](https://github.com/Jumanjii) | <img width="50" src="https://avatars.githubusercontent.com/u/19962243?v=4"/></br>[AlphaJack](https://github.com/AlphaJack) |
| <img width="50" src="https://avatars.githubusercontent.com/u/65647302?v=4"/></br>[Lord-Aman](https://github.com/Lord-Aman) | <img width="50" src="https://avatars.githubusercontent.com/u/14096959?v=4"/></br>[richtwin567](https://github.com/richtwin567) | <img width="50" src="https://avatars.githubusercontent.com/u/487182?v=4"/></br>[ajilderda](https://github.com/ajilderda) | <img width="50" src="https://avatars.githubusercontent.com/u/922429?v=4"/></br>[adrynov](https://github.com/adrynov) | <img width="50" src="https://avatars.githubusercontent.com/u/94937?v=4"/></br>[andrewperry](https://github.com/andrewperry) |
| <img width="50" src="https://avatars.githubusercontent.com/u/5417051?v=4"/></br>[tekdel](https://github.com/tekdel) | <img width="50" src="https://avatars.githubusercontent.com/u/54475686?v=4"/></br>[anshuman9999](https://github.com/anshuman9999) | <img width="50" src="https://avatars.githubusercontent.com/u/25694659?v=4"/></br>[rasklaad](https://github.com/rasklaad) | <img width="50" src="https://avatars.githubusercontent.com/u/17809291?v=4"/></br>[Technik-J](https://github.com/Technik-J) | <img width="50" src="https://avatars.githubusercontent.com/u/498326?v=4"/></br>[Shaxine](https://github.com/Shaxine) |
| <img width="50" src="https://avatars.githubusercontent.com/u/9095073?v=4"/></br>[antonio-ramadas](https://github.com/antonio-ramadas) | <img width="50" src="https://avatars.githubusercontent.com/u/28067395?v=4"/></br>[heyapoorva](https://github.com/heyapoorva) | <img width="50" src="https://avatars.githubusercontent.com/u/201215?v=4"/></br>[assimd](https://github.com/assimd) | <img width="50" src="https://avatars.githubusercontent.com/u/26827848?v=4"/></br>[Atrate](https://github.com/Atrate) | <img width="50" src="https://avatars.githubusercontent.com/u/60288895?v=4"/></br>[Beowulf2](https://github.com/Beowulf2) |
| <img width="50" src="https://avatars.githubusercontent.com/u/7034200?v=4"/></br>[bimlas](https://github.com/bimlas) | <img width="50" src="https://avatars.githubusercontent.com/u/47641641?v=4"/></br>[brenobaptista](https://github.com/brenobaptista) | <img width="50" src="https://avatars.githubusercontent.com/u/60824?v=4"/></br>[brttbndr](https://github.com/brttbndr) | <img width="50" src="https://avatars.githubusercontent.com/u/16287077?v=4"/></br>[carlbordum](https://github.com/carlbordum) | <img width="50" src="https://avatars.githubusercontent.com/u/20382?v=4"/></br>[carlosedp](https://github.com/carlosedp) |
| <img width="50" src="https://avatars.githubusercontent.com/u/105843?v=4"/></br>[chaifeng](https://github.com/chaifeng) | <img width="50" src="https://avatars.githubusercontent.com/u/549349?v=4"/></br>[charles-e](https://github.com/charles-e) | <img width="50" src="https://avatars.githubusercontent.com/u/19870089?v=4"/></br>[cyy5358](https://github.com/cyy5358) | <img width="50" src="https://avatars.githubusercontent.com/u/32337926?v=4"/></br>[Chillu1](https://github.com/Chillu1) | <img width="50" src="https://avatars.githubusercontent.com/u/2348463?v=4"/></br>[Techwolf12](https://github.com/Techwolf12) |
| <img width="50" src="https://avatars.githubusercontent.com/u/2282880?v=4"/></br>[cloudtrends](https://github.com/cloudtrends) | <img width="50" src="https://avatars.githubusercontent.com/u/17257053?v=4"/></br>[idcristi](https://github.com/idcristi) | <img width="50" src="https://avatars.githubusercontent.com/u/15956322?v=4"/></br>[damienmascre](https://github.com/damienmascre) | <img width="50" src="https://avatars.githubusercontent.com/u/1044056?v=4"/></br>[daniellandau](https://github.com/daniellandau) | <img width="50" src="https://avatars.githubusercontent.com/u/12847693?v=4"/></br>[danil-tolkachev](https://github.com/danil-tolkachev) |
| <img width="50" src="https://avatars.githubusercontent.com/u/7279100?v=4"/></br>[darshani28](https://github.com/darshani28) | <img width="50" src="https://avatars.githubusercontent.com/u/26189247?v=4"/></br>[daukadolt](https://github.com/daukadolt) | <img width="50" src="https://avatars.githubusercontent.com/u/28535750?v=4"/></br>[NeverMendel](https://github.com/NeverMendel) | <img width="50" src="https://avatars.githubusercontent.com/u/26790323?v=4"/></br>[dervist](https://github.com/dervist) | <img width="50" src="https://avatars.githubusercontent.com/u/11378282?v=4"/></br>[diego-betto](https://github.com/diego-betto) |
| <img width="50" src="https://avatars.githubusercontent.com/u/215270?v=4"/></br>[erdody](https://github.com/erdody) | <img width="50" src="https://avatars.githubusercontent.com/u/10371667?v=4"/></br>[domgoodwin](https://github.com/domgoodwin) | <img width="50" src="https://avatars.githubusercontent.com/u/72066?v=4"/></br>[b4mboo](https://github.com/b4mboo) | <img width="50" src="https://avatars.githubusercontent.com/u/5131923?v=4"/></br>[donbowman](https://github.com/donbowman) | <img width="50" src="https://avatars.githubusercontent.com/u/579727?v=4"/></br>[sirnacnud](https://github.com/sirnacnud) |
| <img width="50" src="https://avatars.githubusercontent.com/u/47756?v=4"/></br>[dflock](https://github.com/dflock) | <img width="50" src="https://avatars.githubusercontent.com/u/7990534?v=4"/></br>[drobilica](https://github.com/drobilica) | <img width="50" src="https://avatars.githubusercontent.com/u/21699905?v=4"/></br>[educbraga](https://github.com/educbraga) | <img width="50" src="https://avatars.githubusercontent.com/u/67867099?v=4"/></br>[eduardokimmel](https://github.com/eduardokimmel) | <img width="50" src="https://avatars.githubusercontent.com/u/30393516?v=4"/></br>[VodeniZeko](https://github.com/VodeniZeko) |
| <img width="50" src="https://avatars.githubusercontent.com/u/17415256?v=4"/></br>[ei-ke](https://github.com/ei-ke) | <img width="50" src="https://avatars.githubusercontent.com/u/1962738?v=4"/></br>[einverne](https://github.com/einverne) | <img width="50" src="https://avatars.githubusercontent.com/u/16492558?v=4"/></br>[eodeluga](https://github.com/eodeluga) | <img width="50" src="https://avatars.githubusercontent.com/u/16875937?v=4"/></br>[fathyar](https://github.com/fathyar) | <img width="50" src="https://avatars.githubusercontent.com/u/3057302?v=4"/></br>[fer22f](https://github.com/fer22f) |
| <img width="50" src="https://avatars.githubusercontent.com/u/43272148?v=4"/></br>[fpindado](https://github.com/fpindado) | <img width="50" src="https://avatars.githubusercontent.com/u/1714374?v=4"/></br>[FleischKarussel](https://github.com/FleischKarussel) | <img width="50" src="https://avatars.githubusercontent.com/u/18525376?v=4"/></br>[talkdirty](https://github.com/talkdirty) | <img width="50" src="https://avatars.githubusercontent.com/u/19814827?v=4"/></br>[gmaubach](https://github.com/gmaubach) | <img width="50" src="https://avatars.githubusercontent.com/u/6190183?v=4"/></br>[gmag11](https://github.com/gmag11) |
| <img width="50" src="https://avatars.githubusercontent.com/u/6209647?v=4"/></br>[Jackymancs4](https://github.com/Jackymancs4) | <img width="50" src="https://avatars.githubusercontent.com/u/297578?v=4"/></br>[Glandos](https://github.com/Glandos) | <img width="50" src="https://avatars.githubusercontent.com/u/24235344?v=4"/></br>[vibraniumdev](https://github.com/vibraniumdev) | <img width="50" src="https://avatars.githubusercontent.com/u/2257024?v=4"/></br>[gusbemacbe](https://github.com/gusbemacbe) | <img width="50" src="https://avatars.githubusercontent.com/u/64917442?v=4"/></br>[HOLLYwyh](https://github.com/HOLLYwyh) |
| <img width="50" src="https://avatars.githubusercontent.com/u/18524580?v=4"/></br>[Fvbor](https://github.com/Fvbor) | <img width="50" src="https://avatars.githubusercontent.com/u/22606250?v=4"/></br>[bennetthanna](https://github.com/bennetthanna) | <img width="50" src="https://avatars.githubusercontent.com/u/67231570?v=4"/></br>[harshitkathuria](https://github.com/harshitkathuria) | <img width="50" src="https://avatars.githubusercontent.com/u/1716229?v=4"/></br>[Vistaus](https://github.com/Vistaus) | <img width="50" src="https://avatars.githubusercontent.com/u/6509881?v=4"/></br>[ianjs](https://github.com/ianjs) |
| <img width="50" src="https://avatars.githubusercontent.com/u/19862172?v=4"/></br>[iahmedbacha](https://github.com/iahmedbacha) | <img width="50" src="https://avatars.githubusercontent.com/u/1533624?v=4"/></br>[IrvinDominin](https://github.com/IrvinDominin) | <img width="50" src="https://avatars.githubusercontent.com/u/33200024?v=4"/></br>[ishammahajan](https://github.com/ishammahajan) | <img width="50" src="https://avatars.githubusercontent.com/u/6916297?v=4"/></br>[ffadilaputra](https://github.com/ffadilaputra) | <img width="50" src="https://avatars.githubusercontent.com/u/19985741?v=4"/></br>[JRaiden16](https://github.com/JRaiden16) |
| <img width="50" src="https://avatars.githubusercontent.com/u/11466782?v=4"/></br>[jacobherrington](https://github.com/jacobherrington) | <img width="50" src="https://avatars.githubusercontent.com/u/9365179?v=4"/></br>[jamesadjinwa](https://github.com/jamesadjinwa) | <img width="50" src="https://avatars.githubusercontent.com/u/20801821?v=4"/></br>[jrwrigh](https://github.com/jrwrigh) | <img width="50" src="https://avatars.githubusercontent.com/u/4995433?v=4"/></br>[jaredcrowe](https://github.com/jaredcrowe) | <img width="50" src="https://avatars.githubusercontent.com/u/4087105?v=4"/></br>[volatilevar](https://github.com/volatilevar) |
| <img width="50" src="https://avatars.githubusercontent.com/u/47724360?v=4"/></br>[innkuika](https://github.com/innkuika) | <img width="50" src="https://avatars.githubusercontent.com/u/163555?v=4"/></br>[JoelRSimpson](https://github.com/JoelRSimpson) | <img width="50" src="https://avatars.githubusercontent.com/u/6965062?v=4"/></br>[joeltaylor](https://github.com/joeltaylor) | <img width="50" src="https://avatars.githubusercontent.com/u/242107?v=4"/></br>[exic](https://github.com/exic) | <img width="50" src="https://avatars.githubusercontent.com/u/13716151?v=4"/></br>[JonathanPlasse](https://github.com/JonathanPlasse) |
| <img width="50" src="https://avatars.githubusercontent.com/u/1248504?v=4"/></br>[joesfer](https://github.com/joesfer) | <img width="50" src="https://avatars.githubusercontent.com/u/6048003?v=4"/></br>[joybinchen](https://github.com/joybinchen) | <img width="50" src="https://avatars.githubusercontent.com/u/37601331?v=4"/></br>[kaustubhsh](https://github.com/kaustubhsh) | <img width="50" src="https://avatars.githubusercontent.com/u/1560189?v=4"/></br>[y-usuzumi](https://github.com/y-usuzumi) | <img width="50" src="https://avatars.githubusercontent.com/u/1660460?v=4"/></br>[xuhcc](https://github.com/xuhcc) |
| <img width="50" src="https://avatars.githubusercontent.com/u/16933735?v=4"/></br>[kirtanprht](https://github.com/kirtanprht) | <img width="50" src="https://avatars.githubusercontent.com/u/37491732?v=4"/></br>[k0ur0x](https://github.com/k0ur0x) | <img width="50" src="https://avatars.githubusercontent.com/u/7824233?v=4"/></br>[kklas](https://github.com/kklas) | <img width="50" src="https://avatars.githubusercontent.com/u/8622992?v=4"/></br>[xmlangel](https://github.com/xmlangel) | <img width="50" src="https://avatars.githubusercontent.com/u/1055100?v=4"/></br>[troilus](https://github.com/troilus) |
| <img width="50" src="https://avatars.githubusercontent.com/u/2599210?v=4"/></br>[lboullo0](https://github.com/lboullo0) | <img width="50" src="https://avatars.githubusercontent.com/u/1562062?v=4"/></br>[dbinary](https://github.com/dbinary) | <img width="50" src="https://avatars.githubusercontent.com/u/15436007?v=4"/></br>[marc-bouvier](https://github.com/marc-bouvier) | <img width="50" src="https://avatars.githubusercontent.com/u/5699725?v=4"/></br>[mvonmaltitz](https://github.com/mvonmaltitz) | <img width="50" src="https://avatars.githubusercontent.com/u/11036464?v=4"/></br>[mlkood](https://github.com/mlkood) |
| <img width="50" src="https://avatars.githubusercontent.com/u/2480960?v=4"/></br>[plextoriano](https://github.com/plextoriano) | <img width="50" src="https://avatars.githubusercontent.com/u/5788516?v=4"/></br>[Marmo](https://github.com/Marmo) | <img width="50" src="https://avatars.githubusercontent.com/u/29300939?v=4"/></br>[mcejp](https://github.com/mcejp) | <img width="50" src="https://avatars.githubusercontent.com/u/640949?v=4"/></br>[freaktechnik](https://github.com/freaktechnik) | <img width="50" src="https://avatars.githubusercontent.com/u/79802125?v=4"/></br>[martinkorelic](https://github.com/martinkorelic) |
| <img width="50" src="https://avatars.githubusercontent.com/u/287105?v=4"/></br>[Petemir](https://github.com/Petemir) | <img width="50" src="https://avatars.githubusercontent.com/u/5218859?v=4"/></br>[matsair](https://github.com/matsair) | <img width="50" src="https://avatars.githubusercontent.com/u/12831489?v=4"/></br>[mgroth0](https://github.com/mgroth0) | <img width="50" src="https://avatars.githubusercontent.com/u/21796?v=4"/></br>[silentmatt](https://github.com/silentmatt) | <img width="50" src="https://avatars.githubusercontent.com/u/76700192?v=4"/></br>[maxs-test](https://github.com/maxs-test) |
| <img width="50" src="https://avatars.githubusercontent.com/u/59669349?v=4"/></br>[MichBoi](https://github.com/MichBoi) | <img width="50" src="https://avatars.githubusercontent.com/u/51273874?v=4"/></br>[MichipX](https://github.com/MichipX) | <img width="50" src="https://avatars.githubusercontent.com/u/53177864?v=4"/></br>[MrTraduttore](https://github.com/MrTraduttore) | <img width="50" src="https://avatars.githubusercontent.com/u/48156230?v=4"/></br>[sanjarcode](https://github.com/sanjarcode) | <img width="50" src="https://avatars.githubusercontent.com/u/43955099?v=4"/></br>[Mustafa-ALD](https://github.com/Mustafa-ALD) |
| <img width="50" src="https://avatars.githubusercontent.com/u/9076687?v=4"/></br>[NJannasch](https://github.com/NJannasch) | <img width="50" src="https://avatars.githubusercontent.com/u/8016073?v=4"/></br>[zomglings](https://github.com/zomglings) | <img width="50" src="https://avatars.githubusercontent.com/u/10386884?v=4"/></br>[Frichetten](https://github.com/Frichetten) | <img width="50" src="https://avatars.githubusercontent.com/u/5541611?v=4"/></br>[nicolas-suzuki](https://github.com/nicolas-suzuki) | <img width="50" src="https://avatars.githubusercontent.com/u/12369770?v=4"/></br>[Ouvill](https://github.com/Ouvill) |
| <img width="50" src="https://avatars.githubusercontent.com/u/43815417?v=4"/></br>[shorty2380](https://github.com/shorty2380) | <img width="50" src="https://avatars.githubusercontent.com/u/15014287?v=4"/></br>[dist3r](https://github.com/dist3r) | <img width="50" src="https://avatars.githubusercontent.com/u/19418601?v=4"/></br>[rakleed](https://github.com/rakleed) | <img width="50" src="https://avatars.githubusercontent.com/u/7881932?v=4"/></br>[idle-code](https://github.com/idle-code) | <img width="50" src="https://avatars.githubusercontent.com/u/168931?v=4"/></br>[bobchao](https://github.com/bobchao) |
| <img width="50" src="https://avatars.githubusercontent.com/u/6306608?v=4"/></br>[Diadlo](https://github.com/Diadlo) | <img width="50" src="https://avatars.githubusercontent.com/u/42793024?v=4"/></br>[pranavmodx](https://github.com/pranavmodx) | <img width="50" src="https://avatars.githubusercontent.com/u/50834839?v=4"/></br>[R3dError](https://github.com/R3dError) | <img width="50" src="https://avatars.githubusercontent.com/u/42652941?v=4"/></br>[rajprakash00](https://github.com/rajprakash00) | <img width="50" src="https://avatars.githubusercontent.com/u/32304956?v=4"/></br>[rahil1304](https://github.com/rahil1304) |
| <img width="50" src="https://avatars.githubusercontent.com/u/8257474?v=4"/></br>[rasulkireev](https://github.com/rasulkireev) | <img width="50" src="https://avatars.githubusercontent.com/u/17312341?v=4"/></br>[reinhart1010](https://github.com/reinhart1010) | <img width="50" src="https://avatars.githubusercontent.com/u/60484714?v=4"/></br>[Retew](https://github.com/Retew) | <img width="50" src="https://avatars.githubusercontent.com/u/10456131?v=4"/></br>[ambrt](https://github.com/ambrt) | <img width="50" src="https://avatars.githubusercontent.com/u/15892014?v=4"/></br>[Derkades](https://github.com/Derkades) |
| <img width="50" src="https://avatars.githubusercontent.com/u/49439044?v=4"/></br>[fourstepper](https://github.com/fourstepper) | <img width="50" src="https://avatars.githubusercontent.com/u/54365?v=4"/></br>[rodgco](https://github.com/rodgco) | <img width="50" src="https://avatars.githubusercontent.com/u/96014?v=4"/></br>[Ronnie76er](https://github.com/Ronnie76er) | <img width="50" src="https://avatars.githubusercontent.com/u/79168?v=4"/></br>[roryokane](https://github.com/roryokane) | <img width="50" src="https://avatars.githubusercontent.com/u/744655?v=4"/></br>[ruzaq](https://github.com/ruzaq) |
| <img width="50" src="https://avatars.githubusercontent.com/u/20490839?v=4"/></br>[szokesandor](https://github.com/szokesandor) | <img width="50" src="https://avatars.githubusercontent.com/u/19328605?v=4"/></br>[SamuelBlickle](https://github.com/SamuelBlickle) | <img width="50" src="https://avatars.githubusercontent.com/u/80849457?v=4"/></br>[livingc0l0ur](https://github.com/livingc0l0ur) | <img width="50" src="https://avatars.githubusercontent.com/u/1776?v=4"/></br>[bronson](https://github.com/bronson) | <img width="50" src="https://avatars.githubusercontent.com/u/24606935?v=4"/></br>[semperor](https://github.com/semperor) |
| <img width="50" src="https://avatars.githubusercontent.com/u/607938?v=4"/></br>[shawnaxsom](https://github.com/shawnaxsom) | <img width="50" src="https://avatars.githubusercontent.com/u/9937486?v=4"/></br>[SFoskitt](https://github.com/SFoskitt) | <img width="50" src="https://avatars.githubusercontent.com/u/505011?v=4"/></br>[kcrt](https://github.com/kcrt) | <img width="50" src="https://avatars.githubusercontent.com/u/538584?v=4"/></br>[xissy](https://github.com/xissy) | <img width="50" src="https://avatars.githubusercontent.com/u/164962?v=4"/></br>[tams](https://github.com/tams) |
| <img width="50" src="https://avatars.githubusercontent.com/u/466122?v=4"/></br>[Tekki](https://github.com/Tekki) | <img width="50" src="https://avatars.githubusercontent.com/u/2112477?v=4"/></br>[ThatcherC](https://github.com/ThatcherC) | <img width="50" src="https://avatars.githubusercontent.com/u/21969426?v=4"/></br>[TheoDutch](https://github.com/TheoDutch) | <img width="50" src="https://avatars.githubusercontent.com/u/8731922?v=4"/></br>[tbroadley](https://github.com/tbroadley) | <img width="50" src="https://avatars.githubusercontent.com/u/114300?v=4"/></br>[Kriechi](https://github.com/Kriechi) |
| <img width="50" src="https://avatars.githubusercontent.com/u/3457339?v=4"/></br>[tkilaker](https://github.com/tkilaker) | <img width="50" src="https://avatars.githubusercontent.com/u/802148?v=4"/></br>[Tim-Erwin](https://github.com/Tim-Erwin) | <img width="50" src="https://avatars.githubusercontent.com/u/4201229?v=4"/></br>[tcyrus](https://github.com/tcyrus) | <img width="50" src="https://avatars.githubusercontent.com/u/834914?v=4"/></br>[tobias-grasse](https://github.com/tobias-grasse) | <img width="50" src="https://avatars.githubusercontent.com/u/6691273?v=4"/></br>[strobeltobias](https://github.com/strobeltobias) |
| <img width="50" src="https://avatars.githubusercontent.com/u/1677578?v=4"/></br>[kostegit](https://github.com/kostegit) | <img width="50" src="https://avatars.githubusercontent.com/u/70296?v=4"/></br>[tbergeron](https://github.com/tbergeron) | <img width="50" src="https://avatars.githubusercontent.com/u/10265443?v=4"/></br>[Ullas-Aithal](https://github.com/Ullas-Aithal) | <img width="50" src="https://avatars.githubusercontent.com/u/6104498?v=4"/></br>[MyTheValentinus](https://github.com/MyTheValentinus) | <img width="50" src="https://avatars.githubusercontent.com/u/2830093?v=4"/></br>[vassudanagunta](https://github.com/vassudanagunta) |
| <img width="50" src="https://avatars.githubusercontent.com/u/54314949?v=4"/></br>[vijayjoshi16](https://github.com/vijayjoshi16) | <img width="50" src="https://avatars.githubusercontent.com/u/59287619?v=4"/></br>[max-keviv](https://github.com/max-keviv) | <img width="50" src="https://avatars.githubusercontent.com/u/598576?v=4"/></br>[vandreykiv](https://github.com/vandreykiv) | <img width="50" src="https://avatars.githubusercontent.com/u/26511487?v=4"/></br>[WisdomCode](https://github.com/WisdomCode) | <img width="50" src="https://avatars.githubusercontent.com/u/1921957?v=4"/></br>[xsak](https://github.com/xsak) |
| <img width="50" src="https://avatars.githubusercontent.com/u/11031696?v=4"/></br>[ymitsos](https://github.com/ymitsos) | <img width="50" src="https://avatars.githubusercontent.com/u/63324960?v=4"/></br>[abolishallprivateproperty](https://github.com/abolishallprivateproperty) | <img width="50" src="https://avatars.githubusercontent.com/u/11336076?v=4"/></br>[aerotog](https://github.com/aerotog) | <img width="50" src="https://avatars.githubusercontent.com/u/39854348?v=4"/></br>[albertopasqualetto](https://github.com/albertopasqualetto) | <img width="50" src="https://avatars.githubusercontent.com/u/44570278?v=4"/></br>[asrient](https://github.com/asrient) |
| <img width="50" src="https://avatars.githubusercontent.com/u/621360?v=4"/></br>[bestlibre](https://github.com/bestlibre) | <img width="50" src="https://avatars.githubusercontent.com/u/35600612?v=4"/></br>[boring10](https://github.com/boring10) | <img width="50" src="https://avatars.githubusercontent.com/u/13894820?v=4"/></br>[cadolphs](https://github.com/cadolphs) | <img width="50" src="https://avatars.githubusercontent.com/u/12461043?v=4"/></br>[colorchestra](https://github.com/colorchestra) | <img width="50" src="https://avatars.githubusercontent.com/u/30935096?v=4"/></br>[cybertramp](https://github.com/cybertramp) |
| <img width="50" src="https://avatars.githubusercontent.com/u/15824892?v=4"/></br>[dartero](https://github.com/dartero) | <img width="50" src="https://avatars.githubusercontent.com/u/9694906?v=4"/></br>[delta-emil](https://github.com/delta-emil) | <img width="50" src="https://avatars.githubusercontent.com/u/926263?v=4"/></br>[doc75](https://github.com/doc75) | <img width="50" src="https://avatars.githubusercontent.com/u/5589253?v=4"/></br>[dsp77](https://github.com/dsp77) | <img width="50" src="https://avatars.githubusercontent.com/u/2903013?v=4"/></br>[ebayer](https://github.com/ebayer) |
| <img width="50" src="https://avatars.githubusercontent.com/u/9206310?v=4"/></br>[elsiehupp](https://github.com/elsiehupp) | <img width="50" src="https://avatars.githubusercontent.com/u/701050?v=4"/></br>[espinosa](https://github.com/espinosa) | <img width="50" src="https://avatars.githubusercontent.com/u/18619090?v=4"/></br>[exponentactivity](https://github.com/exponentactivity) | <img width="50" src="https://avatars.githubusercontent.com/u/16708935?v=4"/></br>[exprez135](https://github.com/exprez135) | <img width="50" src="https://avatars.githubusercontent.com/u/9768112?v=4"/></br>[fab4x](https://github.com/fab4x) |
| <img width="50" src="https://avatars.githubusercontent.com/u/47755037?v=4"/></br>[fabianski7](https://github.com/fabianski7) | <img width="50" src="https://avatars.githubusercontent.com/u/14201321?v=4"/></br>[rasperepodvipodvert](https://github.com/rasperepodvipodvert) | <img width="50" src="https://avatars.githubusercontent.com/u/748808?v=4"/></br>[gasolin](https://github.com/gasolin) | <img width="50" src="https://avatars.githubusercontent.com/u/47191051?v=4"/></br>[githubaccount073](https://github.com/githubaccount073) | <img width="50" src="https://avatars.githubusercontent.com/u/43672033?v=4"/></br>[hms5232](https://github.com/hms5232) |
| <img width="50" src="https://avatars.githubusercontent.com/u/11388094?v=4"/></br>[hydrandt](https://github.com/hydrandt) | <img width="50" src="https://avatars.githubusercontent.com/u/61012185?v=4"/></br>[iamtalwinder](https://github.com/iamtalwinder) | <img width="50" src="https://avatars.githubusercontent.com/u/557540?v=4"/></br>[jabdoa2](https://github.com/jabdoa2) | <img width="50" src="https://avatars.githubusercontent.com/u/29166402?v=4"/></br>[jduar](https://github.com/jduar) | <img width="50" src="https://avatars.githubusercontent.com/u/2678545?v=4"/></br>[jibedoubleve](https://github.com/jibedoubleve) |
| <img width="50" src="https://avatars.githubusercontent.com/u/53862536?v=4"/></br>[johanvanheusden](https://github.com/johanvanheusden) | <img width="50" src="https://avatars.githubusercontent.com/u/38327267?v=4"/></br>[jtagcat](https://github.com/jtagcat) | <img width="50" src="https://avatars.githubusercontent.com/u/61631665?v=4"/></br>[konhi](https://github.com/konhi) | <img width="50" src="https://avatars.githubusercontent.com/u/54991735?v=4"/></br>[krzysiekwie](https://github.com/krzysiekwie) | <img width="50" src="https://avatars.githubusercontent.com/u/12849008?v=4"/></br>[lighthousebulb](https://github.com/lighthousebulb) |
| <img width="50" src="https://avatars.githubusercontent.com/u/4140247?v=4"/></br>[luzpaz](https://github.com/luzpaz) | <img width="50" src="https://avatars.githubusercontent.com/u/29355048?v=4"/></br>[majsterkovic](https://github.com/majsterkovic) | <img width="50" src="https://avatars.githubusercontent.com/u/77744862?v=4"/></br>[mak2002](https://github.com/mak2002) | <img width="50" src="https://avatars.githubusercontent.com/u/30428258?v=4"/></br>[nmiquan](https://github.com/nmiquan) | <img width="50" src="https://avatars.githubusercontent.com/u/31123054?v=4"/></br>[nullpointer666](https://github.com/nullpointer666) |
| <img width="50" src="https://avatars.githubusercontent.com/u/2979926?v=4"/></br>[oscaretu](https://github.com/oscaretu) | <img width="50" src="https://avatars.githubusercontent.com/u/36965591?v=4"/></br>[oskarsh](https://github.com/oskarsh) | <img width="50" src="https://avatars.githubusercontent.com/u/52031346?v=4"/></br>[osso73](https://github.com/osso73) | <img width="50" src="https://avatars.githubusercontent.com/u/29743024?v=4"/></br>[over-soul](https://github.com/over-soul) | <img width="50" src="https://avatars.githubusercontent.com/u/42961947?v=4"/></br>[pensierocrea](https://github.com/pensierocrea) |
| <img width="50" src="https://avatars.githubusercontent.com/u/45542782?v=4"/></br>[pomeloy](https://github.com/pomeloy) | <img width="50" src="https://avatars.githubusercontent.com/u/10206967?v=4"/></br>[rhtenhove](https://github.com/rhtenhove) | <img width="50" src="https://avatars.githubusercontent.com/u/16728217?v=4"/></br>[rikanotank1](https://github.com/rikanotank1) | <img width="50" src="https://avatars.githubusercontent.com/u/24560368?v=4"/></br>[rxliuli](https://github.com/rxliuli) | <img width="50" src="https://avatars.githubusercontent.com/u/14062932?v=4"/></br>[simonsan](https://github.com/simonsan) |
| <img width="50" src="https://avatars.githubusercontent.com/u/5004545?v=4"/></br>[stellarpower](https://github.com/stellarpower) | <img width="50" src="https://avatars.githubusercontent.com/u/20983267?v=4"/></br>[suixinio](https://github.com/suixinio) | <img width="50" src="https://avatars.githubusercontent.com/u/12995773?v=4"/></br>[sumomo-99](https://github.com/sumomo-99) | <img width="50" src="https://avatars.githubusercontent.com/u/367170?v=4"/></br>[xtatsux](https://github.com/xtatsux) | <img width="50" src="https://avatars.githubusercontent.com/u/6908872?v=4"/></br>[taw00](https://github.com/taw00) |
| <img width="50" src="https://avatars.githubusercontent.com/u/10956653?v=4"/></br>[tcassaert](https://github.com/tcassaert) | <img width="50" src="https://avatars.githubusercontent.com/u/46327531?v=4"/></br>[victante](https://github.com/victante) | <img width="50" src="https://avatars.githubusercontent.com/u/7252567?v=4"/></br>[Voltinus](https://github.com/Voltinus) | <img width="50" src="https://avatars.githubusercontent.com/u/2216902?v=4"/></br>[xcffl](https://github.com/xcffl) | <img width="50" src="https://avatars.githubusercontent.com/u/46404814?v=4"/></br>[yourcontact](https://github.com/yourcontact) |
| <img width="50" src="https://avatars.githubusercontent.com/u/37692927?v=4"/></br>[zaoyifan](https://github.com/zaoyifan) | <img width="50" src="https://avatars.githubusercontent.com/u/10813608?v=4"/></br>[zawnk](https://github.com/zawnk) | <img width="50" src="https://avatars.githubusercontent.com/u/55245068?v=4"/></br>[zen-quo](https://github.com/zen-quo) | <img width="50" src="https://avatars.githubusercontent.com/u/23507174?v=4"/></br>[zozolina123](https://github.com/zozolina123) | <img width="50" src="https://avatars.githubusercontent.com/u/25315?v=4"/></br>[xcession](https://github.com/xcession) |
| <img width="50" src="https://avatars.githubusercontent.com/u/34542665?v=4"/></br>[paventyang](https://github.com/paventyang) | <img width="50" src="https://avatars.githubusercontent.com/u/608014?v=4"/></br>[jackytsu](https://github.com/jackytsu) | <img width="50" src="https://avatars.githubusercontent.com/u/1308646?v=4"/></br>[zhangmx](https://github.com/zhangmx) | | |
| <img width="50" src="https://avatars0.githubusercontent.com/u/1285584?v=4"/></br>[laurent22](https://api.github.com/users/laurent22) | <img width="50" src="https://avatars3.githubusercontent.com/u/223439?v=4"/></br>[tessus](https://api.github.com/users/tessus) | <img width="50" src="https://avatars0.githubusercontent.com/u/1732810?v=4"/></br>[mic704b](https://api.github.com/users/mic704b) | <img width="50" src="https://avatars3.githubusercontent.com/u/2179547?v=4"/></br>[CalebJohn](https://api.github.com/users/CalebJohn) | <img width="50" src="https://avatars1.githubusercontent.com/u/3542031?v=4"/></br>[PackElend](https://api.github.com/users/PackElend) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/4553672?v=4"/></br>[tanrax](https://api.github.com/users/tanrax) | <img width="50" src="https://avatars0.githubusercontent.com/u/8701534?v=4"/></br>[rtmkrlv](https://api.github.com/users/rtmkrlv) | <img width="50" src="https://avatars3.githubusercontent.com/u/10997189?v=4"/></br>[fmrtn](https://api.github.com/users/fmrtn) | <img width="50" src="https://avatars1.githubusercontent.com/u/29672555?v=4"/></br>[genneko](https://api.github.com/users/genneko) | <img width="50" src="https://avatars1.githubusercontent.com/u/6979755?v=4"/></br>[devonzuegel](https://api.github.com/users/devonzuegel) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/16101778?v=4"/></br>[gabcoh](https://api.github.com/users/gabcoh) | <img width="50" src="https://avatars3.githubusercontent.com/u/10927304?v=4"/></br>[matsest](https://api.github.com/users/matsest) | <img width="50" src="https://avatars0.githubusercontent.com/u/6319051?v=4"/></br>[abonte](https://api.github.com/users/abonte) | <img width="50" src="https://avatars2.githubusercontent.com/u/1685517?v=4"/></br>[Abijeet](https://api.github.com/users/Abijeet) | <img width="50" src="https://avatars0.githubusercontent.com/u/27751740?v=4"/></br>[ishantgupta777](https://api.github.com/users/ishantgupta777) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/208212?v=4"/></br>[foxmask](https://api.github.com/users/foxmask) | <img width="50" src="https://avatars2.githubusercontent.com/u/6557454?v=4"/></br>[innocuo](https://api.github.com/users/innocuo) | <img width="50" src="https://avatars1.githubusercontent.com/u/26695184?v=4"/></br>[anjulalk](https://api.github.com/users/anjulalk) | <img width="50" src="https://avatars1.githubusercontent.com/u/44024553?v=4"/></br>[rabeehrz](https://api.github.com/users/rabeehrz) | <img width="50" src="https://avatars0.githubusercontent.com/u/35633575?v=4"/></br>[coderrsid](https://api.github.com/users/coderrsid) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/4237724?v=4"/></br>[alexdevero](https://api.github.com/users/alexdevero) | <img width="50" src="https://avatars3.githubusercontent.com/u/35904727?v=4"/></br>[Runo-saduwa](https://api.github.com/users/Runo-saduwa) | <img width="50" src="https://avatars2.githubusercontent.com/u/5365582?v=4"/></br>[marcosvega91](https://api.github.com/users/marcosvega91) | <img width="50" src="https://avatars3.githubusercontent.com/u/37639389?v=4"/></br>[petrz12](https://api.github.com/users/petrz12) | <img width="50" src="https://avatars0.githubusercontent.com/u/3194829?v=4"/></br>[moltenform](https://api.github.com/users/moltenform) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/5199995?v=4"/></br>[zuphilip](https://api.github.com/users/zuphilip) | <img width="50" src="https://avatars1.githubusercontent.com/u/1904967?v=4"/></br>[readingsnail](https://api.github.com/users/readingsnail) | <img width="50" src="https://avatars0.githubusercontent.com/u/3985557?v=4"/></br>[XarisA](https://api.github.com/users/XarisA) | <img width="50" src="https://avatars2.githubusercontent.com/u/4245227?v=4"/></br>[zblesk](https://api.github.com/users/zblesk) | <img width="50" src="https://avatars2.githubusercontent.com/u/31567272?v=4"/></br>[0ndrey](https://api.github.com/users/0ndrey) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/12906090?v=4"/></br>[amitsin6h](https://api.github.com/users/amitsin6h) | <img width="50" src="https://avatars3.githubusercontent.com/u/23281486?v=4"/></br>[martonpaulo](https://api.github.com/users/martonpaulo) | <img width="50" src="https://avatars3.githubusercontent.com/u/4497566?v=4"/></br>[rccavalcanti](https://api.github.com/users/rccavalcanti) | <img width="50" src="https://avatars0.githubusercontent.com/u/54268438?v=4"/></br>[Rahulm2310](https://api.github.com/users/Rahulm2310) | <img width="50" src="https://avatars0.githubusercontent.com/u/559346?v=4"/></br>[metbril](https://api.github.com/users/metbril) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/1540054?v=4"/></br>[ShaneKilkelly](https://api.github.com/users/ShaneKilkelly) | <img width="50" src="https://avatars1.githubusercontent.com/u/6734573?v=4"/></br>[stweil](https://api.github.com/users/stweil) | <img width="50" src="https://avatars3.githubusercontent.com/u/937861?v=4"/></br>[archont00](https://api.github.com/users/archont00) | <img width="50" src="https://avatars3.githubusercontent.com/u/32770029?v=4"/></br>[bradmcl](https://api.github.com/users/bradmcl) | <img width="50" src="https://avatars1.githubusercontent.com/u/22592201?v=4"/></br>[tfinnberg](https://api.github.com/users/tfinnberg) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/3870964?v=4"/></br>[marcushill](https://api.github.com/users/marcushill) | <img width="50" src="https://avatars3.githubusercontent.com/u/102242?v=4"/></br>[nathanleiby](https://api.github.com/users/nathanleiby) | <img width="50" src="https://avatars0.githubusercontent.com/u/226708?v=4"/></br>[RaphaelKimmig](https://api.github.com/users/RaphaelKimmig) | <img width="50" src="https://avatars0.githubusercontent.com/u/17768566?v=4"/></br>[RenatoXSR](https://api.github.com/users/RenatoXSR) | <img width="50" src="https://avatars1.githubusercontent.com/u/36303913?v=4"/></br>[sensor-freak](https://api.github.com/users/sensor-freak) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/2063957?v=4"/></br>[Ardakilic](https://api.github.com/users/Ardakilic) | <img width="50" src="https://avatars3.githubusercontent.com/u/21161146?v=4"/></br>[BartBucknill](https://api.github.com/users/BartBucknill) | <img width="50" src="https://avatars3.githubusercontent.com/u/2494769?v=4"/></br>[mrwulf](https://api.github.com/users/mrwulf) | <img width="50" src="https://avatars2.githubusercontent.com/u/560571?v=4"/></br>[chrisb86](https://api.github.com/users/chrisb86) | <img width="50" src="https://avatars3.githubusercontent.com/u/1686759?v=4"/></br>[chrmoritz](https://api.github.com/users/chrmoritz) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/5001259?v=4"/></br>[ethan42411](https://api.github.com/users/ethan42411) | <img width="50" src="https://avatars2.githubusercontent.com/u/2733783?v=4"/></br>[JOJ0](https://api.github.com/users/JOJ0) | <img width="50" src="https://avatars2.githubusercontent.com/u/3140223?v=4"/></br>[jdrobertso](https://api.github.com/users/jdrobertso) | <img width="50" src="https://avatars2.githubusercontent.com/u/339645?v=4"/></br>[jmontane](https://api.github.com/users/jmontane) | <img width="50" src="https://avatars2.githubusercontent.com/u/4168339?v=4"/></br>[solariz](https://api.github.com/users/solariz) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/390889?v=4"/></br>[mmahmoudian](https://api.github.com/users/mmahmoudian) | <img width="50" src="https://avatars1.githubusercontent.com/u/25288?v=4"/></br>[maicki](https://api.github.com/users/maicki) | <img width="50" src="https://avatars3.githubusercontent.com/u/2136373?v=4"/></br>[mjjzf](https://api.github.com/users/mjjzf) | <img width="50" src="https://avatars3.githubusercontent.com/u/30305957?v=4"/></br>[naviji](https://api.github.com/users/naviji) | <img width="50" src="https://avatars3.githubusercontent.com/u/27608187?v=4"/></br>[rt-oliveira](https://api.github.com/users/rt-oliveira) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/54576074?v=4"/></br>[Rishgod](https://api.github.com/users/Rishgod) | <img width="50" src="https://avatars0.githubusercontent.com/u/2486806?v=4"/></br>[sebastienjust](https://api.github.com/users/sebastienjust) | <img width="50" src="https://avatars2.githubusercontent.com/u/28362310?v=4"/></br>[sealch](https://api.github.com/users/sealch) | <img width="50" src="https://avatars1.githubusercontent.com/u/34258070?v=4"/></br>[StarFang208](https://api.github.com/users/StarFang208) | <img width="50" src="https://avatars2.githubusercontent.com/u/1782292?v=4"/></br>[SubodhDahal](https://api.github.com/users/SubodhDahal) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/5912371?v=4"/></br>[TobiasDev](https://api.github.com/users/TobiasDev) | <img width="50" src="https://avatars2.githubusercontent.com/u/692072?v=4"/></br>[conyx](https://api.github.com/users/conyx) | <img width="50" src="https://avatars2.githubusercontent.com/u/5730052?v=4"/></br>[vsimkus](https://api.github.com/users/vsimkus) | <img width="50" src="https://avatars1.githubusercontent.com/u/4079047?v=4"/></br>[Zorbeyd](https://api.github.com/users/Zorbeyd) | <img width="50" src="https://avatars3.githubusercontent.com/u/5077221?v=4"/></br>[axq](https://api.github.com/users/axq) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/8808502?v=4"/></br>[barbowza](https://api.github.com/users/barbowza) | <img width="50" src="https://avatars1.githubusercontent.com/u/4316805?v=4"/></br>[lightray22](https://api.github.com/users/lightray22) | <img width="50" src="https://avatars0.githubusercontent.com/u/17399340?v=4"/></br>[pf-siedler](https://api.github.com/users/pf-siedler) | <img width="50" src="https://avatars1.githubusercontent.com/u/17232523?v=4"/></br>[ruuti](https://api.github.com/users/ruuti) | <img width="50" src="https://avatars2.githubusercontent.com/u/23638148?v=4"/></br>[s1nceri7y](https://api.github.com/users/s1nceri7y) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/10117386?v=4"/></br>[kornava](https://api.github.com/users/kornava) | <img width="50" src="https://avatars1.githubusercontent.com/u/7471938?v=4"/></br>[ShuiHuo](https://api.github.com/users/ShuiHuo) | <img width="50" src="https://avatars2.githubusercontent.com/u/11596277?v=4"/></br>[ikunya](https://api.github.com/users/ikunya) | <img width="50" src="https://avatars3.githubusercontent.com/u/59133880?v=4"/></br>[bedwardly-down](https://api.github.com/users/bedwardly-down) | <img width="50" src="https://avatars2.githubusercontent.com/u/47456195?v=4"/></br>[hexclover](https://api.github.com/users/hexclover) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/45535789?v=4"/></br>[2jaeyeol](https://api.github.com/users/2jaeyeol) | <img width="50" src="https://avatars1.githubusercontent.com/u/15862474?v=4"/></br>[aaronxn](https://api.github.com/users/aaronxn) | <img width="50" src="https://avatars1.githubusercontent.com/u/3660978?v=4"/></br>[alanfortlink](https://api.github.com/users/alanfortlink) | <img width="50" src="https://avatars3.githubusercontent.com/u/14836659?v=4"/></br>[apankratov](https://api.github.com/users/apankratov) | <img width="50" src="https://avatars1.githubusercontent.com/u/7045739?v=4"/></br>[teterkin](https://api.github.com/users/teterkin) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/41290751?v=4"/></br>[serenitatis](https://api.github.com/users/serenitatis) | <img width="50" src="https://avatars2.githubusercontent.com/u/4408379?v=4"/></br>[lex111](https://api.github.com/users/lex111) | <img width="50" src="https://avatars2.githubusercontent.com/u/5417051?v=4"/></br>[tekdel](https://api.github.com/users/tekdel) | <img width="50" src="https://avatars1.githubusercontent.com/u/498326?v=4"/></br>[Shaxine](https://api.github.com/users/Shaxine) | <img width="50" src="https://avatars0.githubusercontent.com/u/201215?v=4"/></br>[assimd](https://api.github.com/users/assimd) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/42698687?v=4"/></br>[baymoe](https://api.github.com/users/baymoe) | <img width="50" src="https://avatars2.githubusercontent.com/u/7034200?v=4"/></br>[bimlas](https://api.github.com/users/bimlas) | <img width="50" src="https://avatars0.githubusercontent.com/u/16287077?v=4"/></br>[carlbordum](https://api.github.com/users/carlbordum) | <img width="50" src="https://avatars0.githubusercontent.com/u/105843?v=4"/></br>[chaifeng](https://api.github.com/users/chaifeng) | <img width="50" src="https://avatars2.githubusercontent.com/u/549349?v=4"/></br>[charles-e](https://api.github.com/users/charles-e) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/2348463?v=4"/></br>[Techwolf12](https://api.github.com/users/Techwolf12) | <img width="50" src="https://avatars0.githubusercontent.com/u/2282880?v=4"/></br>[cloudtrends](https://api.github.com/users/cloudtrends) | <img width="50" src="https://avatars2.githubusercontent.com/u/1044056?v=4"/></br>[daniellandau](https://api.github.com/users/daniellandau) | <img width="50" src="https://avatars2.githubusercontent.com/u/26189247?v=4"/></br>[daukadolt](https://api.github.com/users/daukadolt) | <img width="50" src="https://avatars2.githubusercontent.com/u/28535750?v=4"/></br>[NeverMendel](https://api.github.com/users/NeverMendel) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/11378282?v=4"/></br>[diego-betto](https://api.github.com/users/diego-betto) | <img width="50" src="https://avatars0.githubusercontent.com/u/215270?v=4"/></br>[erdody](https://api.github.com/users/erdody) | <img width="50" src="https://avatars0.githubusercontent.com/u/10371667?v=4"/></br>[domgoodwin](https://api.github.com/users/domgoodwin) | <img width="50" src="https://avatars3.githubusercontent.com/u/72066?v=4"/></br>[b4mboo](https://api.github.com/users/b4mboo) | <img width="50" src="https://avatars0.githubusercontent.com/u/5131923?v=4"/></br>[donbowman](https://api.github.com/users/donbowman) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/47756?v=4"/></br>[dflock](https://api.github.com/users/dflock) | <img width="50" src="https://avatars0.githubusercontent.com/u/7990534?v=4"/></br>[drobilica](https://api.github.com/users/drobilica) | <img width="50" src="https://avatars3.githubusercontent.com/u/1962738?v=4"/></br>[einverne](https://api.github.com/users/einverne) | <img width="50" src="https://avatars0.githubusercontent.com/u/628474?v=4"/></br>[Atalanttore](https://api.github.com/users/Atalanttore) | <img width="50" src="https://avatars1.githubusercontent.com/u/16492558?v=4"/></br>[eodeluga](https://api.github.com/users/eodeluga) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/3057302?v=4"/></br>[fer22f](https://api.github.com/users/fer22f) | <img width="50" src="https://avatars0.githubusercontent.com/u/43272148?v=4"/></br>[fpindado](https://api.github.com/users/fpindado) | <img width="50" src="https://avatars2.githubusercontent.com/u/1714374?v=4"/></br>[FleischKarussel](https://api.github.com/users/FleischKarussel) | <img width="50" src="https://avatars1.githubusercontent.com/u/18525376?v=4"/></br>[talkdirty](https://api.github.com/users/talkdirty) | <img width="50" src="https://avatars0.githubusercontent.com/u/6190183?v=4"/></br>[gmag11](https://api.github.com/users/gmag11) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/24235344?v=4"/></br>[guiemi](https://api.github.com/users/guiemi) | <img width="50" src="https://avatars2.githubusercontent.com/u/2257024?v=4"/></br>[gusbemacbe](https://api.github.com/users/gusbemacbe) | <img width="50" src="https://avatars0.githubusercontent.com/u/18524580?v=4"/></br>[Fvbor](https://api.github.com/users/Fvbor) | <img width="50" src="https://avatars0.githubusercontent.com/u/22606250?v=4"/></br>[bennetthanna](https://api.github.com/users/bennetthanna) | <img width="50" src="https://avatars3.githubusercontent.com/u/3379379?v=4"/></br>[sczhg](https://api.github.com/users/sczhg) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/1716229?v=4"/></br>[Vistaus](https://api.github.com/users/Vistaus) | <img width="50" src="https://avatars1.githubusercontent.com/u/19862172?v=4"/></br>[iahmedbacha](https://api.github.com/users/iahmedbacha) | <img width="50" src="https://avatars0.githubusercontent.com/u/1533624?v=4"/></br>[IrvinDominin](https://api.github.com/users/IrvinDominin) | <img width="50" src="https://avatars3.githubusercontent.com/u/33200024?v=4"/></br>[ishammahajan](https://api.github.com/users/ishammahajan) | <img width="50" src="https://avatars0.githubusercontent.com/u/19985741?v=4"/></br>[JRaiden16](https://api.github.com/users/JRaiden16) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/11466782?v=4"/></br>[jacobherrington](https://api.github.com/users/jacobherrington) | <img width="50" src="https://avatars2.githubusercontent.com/u/9365179?v=4"/></br>[jamesadjinwa](https://api.github.com/users/jamesadjinwa) | <img width="50" src="https://avatars1.githubusercontent.com/u/4995433?v=4"/></br>[jaredcrowe](https://api.github.com/users/jaredcrowe) | <img width="50" src="https://avatars3.githubusercontent.com/u/4374338?v=4"/></br>[potatogim](https://api.github.com/users/potatogim) | <img width="50" src="https://avatars0.githubusercontent.com/u/163555?v=4"/></br>[JoelRSimpson](https://api.github.com/users/JoelRSimpson) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/6965062?v=4"/></br>[joeltaylor](https://api.github.com/users/joeltaylor) | <img width="50" src="https://avatars3.githubusercontent.com/u/242107?v=4"/></br>[exic](https://api.github.com/users/exic) | <img width="50" src="https://avatars1.githubusercontent.com/u/23194385?v=4"/></br>[jony0008](https://api.github.com/users/jony0008) | <img width="50" src="https://avatars1.githubusercontent.com/u/6048003?v=4"/></br>[joybinchen](https://api.github.com/users/joybinchen) | <img width="50" src="https://avatars1.githubusercontent.com/u/1560189?v=4"/></br>[y-usuzumi](https://api.github.com/users/y-usuzumi) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/1660460?v=4"/></br>[xuhcc](https://api.github.com/users/xuhcc) | <img width="50" src="https://avatars0.githubusercontent.com/u/16933735?v=4"/></br>[kirtanprht](https://api.github.com/users/kirtanprht) | <img width="50" src="https://avatars3.githubusercontent.com/u/7824233?v=4"/></br>[kklas](https://api.github.com/users/kklas) | <img width="50" src="https://avatars1.githubusercontent.com/u/8622992?v=4"/></br>[xmlangel](https://api.github.com/users/xmlangel) | <img width="50" src="https://avatars0.githubusercontent.com/u/1055100?v=4"/></br>[troilus](https://api.github.com/users/troilus) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/50335724?v=4"/></br>[Lorinson](https://api.github.com/users/Lorinson) | <img width="50" src="https://avatars2.githubusercontent.com/u/2599210?v=4"/></br>[lboullo0](https://api.github.com/users/lboullo0) | <img width="50" src="https://avatars1.githubusercontent.com/u/1562062?v=4"/></br>[dbinary](https://api.github.com/users/dbinary) | <img width="50" src="https://avatars3.githubusercontent.com/u/5699725?v=4"/></br>[mvonmaltitz](https://api.github.com/users/mvonmaltitz) | <img width="50" src="https://avatars3.githubusercontent.com/u/11036464?v=4"/></br>[mlkood](https://api.github.com/users/mlkood) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/5788516?v=4"/></br>[Marmo](https://api.github.com/users/Marmo) | <img width="50" src="https://avatars0.githubusercontent.com/u/640949?v=4"/></br>[freaktechnik](https://api.github.com/users/freaktechnik) | <img width="50" src="https://avatars2.githubusercontent.com/u/12831489?v=4"/></br>[mgroth0](https://api.github.com/users/mgroth0) | <img width="50" src="https://avatars0.githubusercontent.com/u/21796?v=4"/></br>[silentmatt](https://api.github.com/users/silentmatt) | <img width="50" src="https://avatars0.githubusercontent.com/u/51273874?v=4"/></br>[MichipX](https://api.github.com/users/MichipX) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/53177864?v=4"/></br>[MrTraduttore](https://api.github.com/users/MrTraduttore) | <img width="50" src="https://avatars3.githubusercontent.com/u/9076687?v=4"/></br>[NJannasch](https://api.github.com/users/NJannasch) | <img width="50" src="https://avatars2.githubusercontent.com/u/12369770?v=4"/></br>[Ouvill](https://api.github.com/users/Ouvill) | <img width="50" src="https://avatars3.githubusercontent.com/u/43815417?v=4"/></br>[shorty2380](https://api.github.com/users/shorty2380) | <img width="50" src="https://avatars0.githubusercontent.com/u/19418601?v=4"/></br>[Rakleed](https://api.github.com/users/Rakleed) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/6306608?v=4"/></br>[Diadlo](https://api.github.com/users/Diadlo) | <img width="50" src="https://avatars1.githubusercontent.com/u/13197246?v=4"/></br>[R-L-T-Y](https://api.github.com/users/R-L-T-Y) | <img width="50" src="https://avatars2.githubusercontent.com/u/42652941?v=4"/></br>[rajprakash00](https://api.github.com/users/rajprakash00) | <img width="50" src="https://avatars0.githubusercontent.com/u/54888685?v=4"/></br>[RedDocMD](https://api.github.com/users/RedDocMD) | <img width="50" src="https://avatars2.githubusercontent.com/u/17312341?v=4"/></br>[reinhart1010](https://api.github.com/users/reinhart1010) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/744655?v=4"/></br>[ruzaq](https://api.github.com/users/ruzaq) | <img width="50" src="https://avatars0.githubusercontent.com/u/19328605?v=4"/></br>[SamuelBlickle](https://api.github.com/users/SamuelBlickle) | <img width="50" src="https://avatars1.githubusercontent.com/u/1776?v=4"/></br>[bronson](https://api.github.com/users/bronson) | <img width="50" src="https://avatars0.githubusercontent.com/u/24606935?v=4"/></br>[semperor](https://api.github.com/users/semperor) | <img width="50" src="https://avatars0.githubusercontent.com/u/7091080?v=4"/></br>[sinkuu](https://api.github.com/users/sinkuu) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/9937486?v=4"/></br>[SFoskitt](https://api.github.com/users/SFoskitt) | <img width="50" src="https://avatars2.githubusercontent.com/u/505011?v=4"/></br>[kcrt](https://api.github.com/users/kcrt) | <img width="50" src="https://avatars1.githubusercontent.com/u/538584?v=4"/></br>[xissy](https://api.github.com/users/xissy) | <img width="50" src="https://avatars3.githubusercontent.com/u/466122?v=4"/></br>[Tekki](https://api.github.com/users/Tekki) | <img width="50" src="https://avatars0.githubusercontent.com/u/21969426?v=4"/></br>[TheoDutch](https://api.github.com/users/TheoDutch) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/8731922?v=4"/></br>[tbroadley](https://api.github.com/users/tbroadley) | <img width="50" src="https://avatars1.githubusercontent.com/u/114300?v=4"/></br>[Kriechi](https://api.github.com/users/Kriechi) | <img width="50" src="https://avatars0.githubusercontent.com/u/3457339?v=4"/></br>[tkilaker](https://api.github.com/users/tkilaker) | <img width="50" src="https://avatars1.githubusercontent.com/u/4201229?v=4"/></br>[tcyrus](https://api.github.com/users/tcyrus) | <img width="50" src="https://avatars2.githubusercontent.com/u/834914?v=4"/></br>[tobias-grasse](https://api.github.com/users/tobias-grasse) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/6691273?v=4"/></br>[strobeltobias](https://api.github.com/users/strobeltobias) | <img width="50" src="https://avatars2.githubusercontent.com/u/70296?v=4"/></br>[tbergeron](https://api.github.com/users/tbergeron) | <img width="50" src="https://avatars1.githubusercontent.com/u/10265443?v=4"/></br>[Ullas-Aithal](https://api.github.com/users/Ullas-Aithal) | <img width="50" src="https://avatars2.githubusercontent.com/u/6104498?v=4"/></br>[MyTheValentinus](https://api.github.com/users/MyTheValentinus) | <img width="50" src="https://avatars3.githubusercontent.com/u/26511487?v=4"/></br>[WisdomCode](https://api.github.com/users/WisdomCode) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/1921957?v=4"/></br>[xsak](https://api.github.com/users/xsak) | <img width="50" src="https://avatars2.githubusercontent.com/u/11031696?v=4"/></br>[ymitsos](https://api.github.com/users/ymitsos) | <img width="50" src="https://avatars3.githubusercontent.com/u/29891001?v=4"/></br>[jyuvaraj03](https://api.github.com/users/jyuvaraj03) | <img width="50" src="https://avatars0.githubusercontent.com/u/15380913?v=4"/></br>[kowalskidev](https://api.github.com/users/kowalskidev) | <img width="50" src="https://avatars0.githubusercontent.com/u/63324960?v=4"/></br>[abolishallprivateproperty](https://api.github.com/users/abolishallprivateproperty) |
| <img width="50" src="https://avatars2.githubusercontent.com/u/11336076?v=4"/></br>[aerotog](https://api.github.com/users/aerotog) | <img width="50" src="https://avatars2.githubusercontent.com/u/49116134?v=4"/></br>[anihm136](https://api.github.com/users/anihm136) | <img width="50" src="https://avatars2.githubusercontent.com/u/35600612?v=4"/></br>[boring10](https://api.github.com/users/boring10) | <img width="50" src="https://avatars0.githubusercontent.com/u/35413451?v=4"/></br>[chenlhlinux](https://api.github.com/users/chenlhlinux) | <img width="50" src="https://avatars3.githubusercontent.com/u/30935096?v=4"/></br>[cybertramp](https://api.github.com/users/cybertramp) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/9694906?v=4"/></br>[delta-emil](https://api.github.com/users/delta-emil) | <img width="50" src="https://avatars0.githubusercontent.com/u/926263?v=4"/></br>[doc75](https://api.github.com/users/doc75) | <img width="50" src="https://avatars2.githubusercontent.com/u/2903013?v=4"/></br>[ebayer](https://api.github.com/users/ebayer) | <img width="50" src="https://avatars3.githubusercontent.com/u/701050?v=4"/></br>[espinosa](https://api.github.com/users/espinosa) | <img width="50" src="https://avatars1.githubusercontent.com/u/18619090?v=4"/></br>[exponentactivity](https://api.github.com/users/exponentactivity) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/16708935?v=4"/></br>[exprez135](https://api.github.com/users/exprez135) | <img width="50" src="https://avatars1.githubusercontent.com/u/9768112?v=4"/></br>[fab4x](https://api.github.com/users/fab4x) | <img width="50" src="https://avatars0.githubusercontent.com/u/47755037?v=4"/></br>[fabianski7](https://api.github.com/users/fabianski7) | <img width="50" src="https://avatars0.githubusercontent.com/u/14201321?v=4"/></br>[rasperepodvipodvert](https://api.github.com/users/rasperepodvipodvert) | <img width="50" src="https://avatars1.githubusercontent.com/u/748808?v=4"/></br>[gasolin](https://api.github.com/users/gasolin) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/47191051?v=4"/></br>[githubaccount073](https://api.github.com/users/githubaccount073) | <img width="50" src="https://avatars1.githubusercontent.com/u/11388094?v=4"/></br>[hydrandt](https://api.github.com/users/hydrandt) | <img width="50" src="https://avatars0.githubusercontent.com/u/557540?v=4"/></br>[jabdoa2](https://api.github.com/users/jabdoa2) | <img width="50" src="https://avatars3.githubusercontent.com/u/53862536?v=4"/></br>[johanvanheusden](https://api.github.com/users/johanvanheusden) | <img width="50" src="https://avatars1.githubusercontent.com/u/54991735?v=4"/></br>[krzysiekwie](https://api.github.com/users/krzysiekwie) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/12849008?v=4"/></br>[lighthousebulb](https://api.github.com/users/lighthousebulb) | <img width="50" src="https://avatars0.githubusercontent.com/u/4140247?v=4"/></br>[luzpaz](https://api.github.com/users/luzpaz) | <img width="50" src="https://avatars2.githubusercontent.com/u/30428258?v=4"/></br>[nmiquan](https://api.github.com/users/nmiquan) | <img width="50" src="https://avatars0.githubusercontent.com/u/31123054?v=4"/></br>[nullpointer666](https://api.github.com/users/nullpointer666) | <img width="50" src="https://avatars2.githubusercontent.com/u/2979926?v=4"/></br>[oscaretu](https://api.github.com/users/oscaretu) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/36965591?v=4"/></br>[daehruoydeef](https://api.github.com/users/daehruoydeef) | <img width="50" src="https://avatars1.githubusercontent.com/u/42961947?v=4"/></br>[pensierocrea](https://api.github.com/users/pensierocrea) | <img width="50" src="https://avatars3.githubusercontent.com/u/10206967?v=4"/></br>[rhtenhove](https://api.github.com/users/rhtenhove) | <img width="50" src="https://avatars2.githubusercontent.com/u/16728217?v=4"/></br>[rikanotank1](https://api.github.com/users/rikanotank1) | <img width="50" src="https://avatars1.githubusercontent.com/u/51550769?v=4"/></br>[rnbastos](https://api.github.com/users/rnbastos) |
| <img width="50" src="https://avatars3.githubusercontent.com/u/14062932?v=4"/></br>[simonsan](https://api.github.com/users/simonsan) | <img width="50" src="https://avatars2.githubusercontent.com/u/5004545?v=4"/></br>[stellarpower](https://api.github.com/users/stellarpower) | <img width="50" src="https://avatars1.githubusercontent.com/u/12995773?v=4"/></br>[sumomo-99](https://api.github.com/users/sumomo-99) | <img width="50" src="https://avatars0.githubusercontent.com/u/6908872?v=4"/></br>[taw00](https://api.github.com/users/taw00) | <img width="50" src="https://avatars0.githubusercontent.com/u/10956653?v=4"/></br>[tcassaert](https://api.github.com/users/tcassaert) |
| <img width="50" src="https://avatars1.githubusercontent.com/u/46327531?v=4"/></br>[vicoutorama](https://api.github.com/users/vicoutorama) | <img width="50" src="https://avatars0.githubusercontent.com/u/2216902?v=4"/></br>[xcffl](https://api.github.com/users/xcffl) | <img width="50" src="https://avatars2.githubusercontent.com/u/37692927?v=4"/></br>[zaoyifan](https://api.github.com/users/zaoyifan) | <img width="50" src="https://avatars3.githubusercontent.com/u/55245068?v=4"/></br>[zen-quo](https://api.github.com/users/zen-quo) | <img width="50" src="https://avatars0.githubusercontent.com/u/25315?v=4"/></br>[xcession](https://api.github.com/users/xcession) |
| <img width="50" src="https://avatars0.githubusercontent.com/u/34542665?v=4"/></br>[paventyang](https://api.github.com/users/paventyang) | <img width="50" src="https://avatars1.githubusercontent.com/u/1308646?v=4"/></br>[zhangmx](https://api.github.com/users/zhangmx) | | | |
<!-- CONTRIBUTORS-TABLE-AUTO-GENERATED -->
# Known bugs
@@ -654,7 +618,7 @@ Thank you to everyone who've contributed to Joplin's source code!
MIT License
Copyright (c) 2016-2021 Laurent Cozic
Copyright (c) 2016-2020 Laurent Cozic
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-2
View File
@@ -9,8 +9,6 @@ version: '3'
services:
db:
image: postgres:13.1
volumes:
- ./data/postgres:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
@@ -86,28 +86,6 @@
</ul>
<p>To view what arguments are supported, you can open any of these files
and look at the <code>execute()</code> command.</p>
<a href="#executing-editor-commands" id="executing-editor-commands" style="color: inherit; text-decoration: none;">
<h2>Executing editor commands</h2>
</a>
<p>There might be a situation where you want to invoke editor commands
without using a <a href="joplincontentscripts.html">contentScript</a>. For this
reason Joplin provides the built in <code>editor.execCommand</code> command.</p>
<p><code>editor.execCommand</code> should work with any core command in both the
<a href="https://codemirror.net/doc/manual.html#execCommand">CodeMirror</a> and
<a href="https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand">TinyMCE</a> editors,
as well as most functions calls directly on a CodeMirror editor object (extensions).</p>
<ul>
<li><a href="https://codemirror.net/doc/manual.html#commands">CodeMirror commands</a></li>
<li><a href="https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands">TinyMCE core editor commands</a></li>
</ul>
<p><code>editor.execCommand</code> supports adding arguments for the commands.</p>
<pre><code class="language-typescript"><span class="hljs-keyword">await</span> joplin.commands.execute(<span class="hljs-string">&#x27;editor.execCommand&#x27;</span>, {
<span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;madeUpCommand&#x27;</span>, <span class="hljs-comment">// CodeMirror and TinyMCE</span>
<span class="hljs-attr">args</span>: [], <span class="hljs-comment">// CodeMirror and TinyMCE</span>
<span class="hljs-attr">ui</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// TinyMCE only</span>
<span class="hljs-attr">value</span>: <span class="hljs-string">&#x27;&#x27;</span>, <span class="hljs-comment">// TinyMCE only</span>
});</code></pre>
<p><a href="https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts">View the example using the CodeMirror editor</a></p>
</div>
</section>
<!--
@@ -108,12 +108,11 @@
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Defines whether the command should be enabled or disabled, which in turns
affects the enabled state of any associated button or menu item.</p>
<p>Defines whether the command should be enabled or disabled, which in turns affects
the enabled state of any associated button or menu item.</p>
</div>
<p>The condition should be expressed as a &quot;when-clause&quot; (as in Visual Studio
Code). It&#39;s a simple boolean expression that evaluates to <code>true</code> or
<code>false</code>. It supports the following operators:</p>
<p>The condition should be expressed as a &quot;when-clause&quot; (as in Visual Studio Code). It&#39;s a simple boolean expression that evaluates to
<code>true</code> or <code>false</code>. It supports the following operators:</p>
<table>
<thead>
<tr>
@@ -143,15 +142,7 @@
<td>&quot;oneNoteSelected &amp;&amp; !inConflictFolder&quot;</td>
</tr>
</tbody></table>
<p>Joplin, unlike VSCode, also supports parenthesis, which allows creating
more complex expressions such as <code>cond1 || (cond2 &amp;&amp; cond3)</code>. Only one
level of parenthesis is possible (nested ones aren&#39;t supported).</p>
<p>Currently the supported context variables aren&#39;t documented, but you can
find the list below:</p>
<ul>
<li><a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">Global When Clauses</a></li>
<li><a href="https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts">Desktop app When Clauses</a></li>
</ul>
<p>Currently the supported context variables aren&#39;t documented, but you can <a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">find the list here</a>.</p>
<p>Note: Commands are enabled by default unless you use this property.</p>
</div>
</section>
-15
View File
@@ -721,11 +721,6 @@ async function fetchAllNotes() {
<td></td>
</tr>
<tr>
<td>share_id</td>
<td>text</td>
<td></td>
</tr>
<tr>
<td>body_html</td>
<td>text</td>
<td>Note body, in HTML format</td>
@@ -846,11 +841,6 @@ async function fetchAllNotes() {
<td>int</td>
<td></td>
</tr>
<tr>
<td>share_id</td>
<td>text</td>
<td></td>
</tr>
</tbody>
</table>
<h2>GET /folders<a name="get-folders" href="#get-folders" class="heading-anchor">🔗</a></h2>
@@ -947,11 +937,6 @@ async function fetchAllNotes() {
<td>int</td>
<td></td>
</tr>
<tr>
<td>share_id</td>
<td>text</td>
<td></td>
</tr>
</tbody>
</table>
<h2>GET /resources<a name="get-resources" href="#get-resources" class="heading-anchor">🔗</a></h2>
-46
View File
@@ -405,52 +405,6 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog.md
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=E8JMYD2LQ8MMA&amp;lc=GB&amp;item_name=Joplin+Development&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplinapp.org/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal"></a> <a href="https://github.com/sponsors/laurent22/"><img src="https://joplinapp.org/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub"></a> <a href="https://www.patreon.com/joplin"><img src="https://joplinapp.org/images/badges/Patreon-Badge.svg" alt="Become a patron"></a> <a href="https://joplinapp.org/donate/#donations"><img src="https://joplinapp.org/images/badges/Donate-IBAN.svg" alt="Donate using IBAN"></a></p>
<hr>
<h1>Joplin changelog<a name="joplin-changelog" href="#joplin-changelog" class="heading-anchor">🔗</a></h1>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.4">v2.0.4</a> (Pre-release) - 2021-06-02T12:54:17Z<a name="v2-0-4-https-github-com-laurent22-joplin-releases-tag-v2-0-4-pre-release-2021-06-02t12-54-17z" href="#v2-0-4-https-github-com-laurent22-joplin-releases-tag-v2-0-4-pre-release-2021-06-02t12-54-17z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Improved: Download plugins from GitHub release (8f6a475)</li>
<li>Fixed: Count tags based on showCompletedTodos setting (<a href="https://github.com/laurent22/joplin/issues/4957">#4957</a>) (<a href="https://github.com/laurent22/joplin/issues/4411">#4411</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
<li>Fixed: Fixes panels overflowing window (<a href="https://github.com/laurent22/joplin/issues/4991">#4991</a>) (<a href="https://github.com/laurent22/joplin/issues/4864">#4864</a> by <a href="https://github.com/mablin7">@mablin7</a>)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.2">v2.0.2</a> (Pre-release) - 2021-05-21T18:07:48Z<a name="v2-0-2-https-github-com-laurent22-joplin-releases-tag-v2-0-2-pre-release-2021-05-21t18-07-48z" href="#v2-0-2-https-github-com-laurent22-joplin-releases-tag-v2-0-2-pre-release-2021-05-21t18-07-48z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add Share Notebook menu item (6f2f241)</li>
<li>New: Add classnames to DOM elements for theming purposes (<a href="https://github.com/laurent22/joplin/issues/4933">#4933</a> by <a href="https://github.com/ajilderda">@ajilderda</a>)</li>
<li>Improved: Allow unsharing a note (f7d164b)</li>
<li>Improved: Displays error info when Joplin Server fails (3f0586e)</li>
<li>Improved: Handle too large items for Joplin Server (d29624c)</li>
<li>Improved: Import SVG as images when importing ENEX files (<a href="https://github.com/laurent22/joplin/issues/4968">#4968</a>)</li>
<li>Improved: Import linked local files when importing Markdown files (<a href="https://github.com/laurent22/joplin/issues/4966">#4966</a>) (<a href="https://github.com/laurent22/joplin/issues/4433">#4433</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
<li>Improved: Improved usability when plugin repository cannot be connected to (<a href="https://github.com/laurent22/joplin/issues/4462">#4462</a>)</li>
<li>Improved: Made sync more reliable by making it skip items that time out, and improved sync status screen (15fe119)</li>
<li>Improved: Pass custom CSS property to all export handlers and renderers (bd08041)</li>
<li>Improved: Regression: It was no longer possible to add list items in an empty note (6577f4f)</li>
<li>Improved: Regression: Pasting plain text in Rich Text editor was broken (9e9bf63)</li>
<li>Fixed: Fixed issue with empty panels being created by plugins (<a href="https://github.com/laurent22/joplin/issues/4926">#4926</a>)</li>
<li>Fixed: Fixed pasting HTML in Rich Text editor, and improved pasting plain text (2226b79)</li>
<li>Fixed: Improved importing Evernote notes that contain codeblocks (<a href="https://github.com/laurent22/joplin/issues/4965">#4965</a>)</li>
<li>Fixed: Prevent cursor from jumping to top of page when pasting image (<a href="https://github.com/laurent22/joplin/issues/4591">#4591</a>)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.1">v2.0.1</a> (Pre-release) - 2021-05-15T13:22:58Z<a name="v2-0-1-https-github-com-laurent22-joplin-releases-tag-v2-0-1-pre-release-2021-05-15t13-22-58z" href="#v2-0-1-https-github-com-laurent22-joplin-releases-tag-v2-0-1-pre-release-2021-05-15t13-22-58z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add support for sharing notebooks with Joplin Server (<a href="https://github.com/laurent22/joplin/issues/4772">#4772</a>)</li>
<li>New: Add new date format YYMMDD (<a href="https://github.com/laurent22/joplin/issues/4954">#4954</a> by Helmut K. C. Tessarek)</li>
<li>New: Added button to skip an application update (a31b402)</li>
<li>Fixed: Display proper error message when JEX file is corrupted (<a href="https://github.com/laurent22/joplin/issues/4958">#4958</a>)</li>
<li>Fixed: Show or hide completed todos in search results based on user settings (<a href="https://github.com/laurent22/joplin/issues/4951">#4951</a>) (<a href="https://github.com/laurent22/joplin/issues/4581">#4581</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
<li>Fixed: Solve &quot;Resource Id not provided&quot; error (<a href="https://github.com/laurent22/joplin/issues/4943">#4943</a>) (<a href="https://github.com/laurent22/joplin/issues/4891">#4891</a> by <a href="https://github.com/Subhra264">@Subhra264</a>)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.5">v1.8.5</a> - 2021-05-10T11:58:14Z<a name="v1-8-5-https-github-com-laurent22-joplin-releases-tag-v1-8-5-2021-05-10t11-58-14z" href="#v1-8-5-https-github-com-laurent22-joplin-releases-tag-v1-8-5-2021-05-10t11-58-14z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Fixed: Fixed pasting of text and images from Word on Windows (<a href="https://github.com/laurent22/joplin/issues/4916">#4916</a>)</li>
<li>Security: Filter out NOSCRIPT tags that could be used to cause an XSS (found by <a href="https://twitter.com/jubairfolder">Jubair Rehman Yousafzai</a>) (9c20d59)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.4">v1.8.4</a> (Pre-release) - 2021-05-09T18:05:05Z<a name="v1-8-4-https-github-com-laurent22-joplin-releases-tag-v1-8-4-pre-release-2021-05-09t18-05-05z" href="#v1-8-4-https-github-com-laurent22-joplin-releases-tag-v1-8-4-pre-release-2021-05-09t18-05-05z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Improved: Improve display of release notes for new versions (f76f99b)</li>
<li>Fixed: Ensure that image paths that contain spaces are pasted correctly in the Rich Text editor (<a href="https://github.com/laurent22/joplin/issues/4916">#4916</a>)</li>
<li>Fixed: Make sure sync startup operations are cleared after startup (<a href="https://github.com/laurent22/joplin/issues/4919">#4919</a>)</li>
<li>Security: Apply npm audit security fixes (0b67446)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.3">v1.8.3</a> (Pre-release) - 2021-05-04T10:38:16Z<a name="v1-8-3-https-github-com-laurent22-joplin-releases-tag-v1-8-3-pre-release-2021-05-04t10-38-16z" href="#v1-8-3-https-github-com-laurent22-joplin-releases-tag-v1-8-3-pre-release-2021-05-04t10-38-16z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add &quot;id&quot; and &quot;due&quot; search filters (<a href="https://github.com/laurent22/joplin/issues/4898">#4898</a> by <a href="https://github.com/JackGruber">@JackGruber</a>)</li>
-443
View File
@@ -1,443 +0,0 @@
<!doctype html>
<html>
<!--
!!! WARNING !!!
This file was auto-generated from readme/changelog_android.md and any manual change
made to it will be overwritten. To make a change to this file please modify
the source Markdown file:
https://github.com/laurent22/joplin/blob/dev/readme/changelog_android.md
-->
<head>
<title>Joplin Android app changelog | Joplin</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://joplinapp.org/css/bootstrap.min.css">
<link rel="shortcut icon" type="image/x-icon" href="https://joplinapp.org/favicon.ico">
<!-- <link rel="stylesheet" href="https://joplinapp.org/css/fontawesome-all.min.css"> -->
<link rel="stylesheet" href="https://joplinapp.org/css/fork-awesome.min.css">
<script src="https://joplinapp.org/js/jquery-3.2.1.slim.min.js"></script>
<style>
body {
background-color: #F1F1F1;
color: #333333;
}
.root {
overflow: hidden;
}
a[href^="mailto:"] {
word-break: break-all;
}
table {
margin-bottom: 1em;
}
td, th {
padding: .8em;
border: 1px solid #ccc;
}
.page-markdown table pre,
.page-markdown table blockquote {
margin-bottom: 0;
}
.page-markdown table pre,
.page-markdown table blockquote {
margin-bottom: 0;
}
.page-markdown table pre {
background-color: rgba(0,0,0,0);
border: none;
margin: 0;
padding: 0;
}
h1, h2 {
border-bottom: 1px solid #eaecef;
padding-bottom: 0.3em;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-weight: 600;
font-size: 2em;
margin-bottom: 16px;
}
h2 {
font-size: 1.6em;
}
h3 {
font-size: 1.3em;
}
code {
color: black;
background-color: #eee;
border: 1px solid #ccc;
font-size: .85em;
/* word-break: break-all; */
}
pre code {
border: none;
}
pre {
font-size: .85em;
}
blockquote {
font-size: 1em;
color: #555;
};
#toc ul {
margin-bottom: 10px;
}
#toc > ul > li {
margin-bottom: 10px;
}
#toc {
padding-bottom: 1em;
}
.title {
display: flex;
align-items: center;
}
.title-icon {
display: flex;
height: 1em;
}
.title-text {
display: flex;
font-weight: normal;
margin-bottom: .2em;
margin-left: .5em;
}
.sub-title {
font-weight: normal;
}
.container {
background-color: white;
padding: 0;
box-shadow: 0 10px 20px #888888;
}
table.screenshots {
margin-top: 2em;
margin-bottom: 2em;
}
table.screenshots th {
height: 3em;
text-align: center;
}
table.screenshots th,
table.screenshots td {
border: 1px solid #C2C2C2;
}
img[align="left"] {
margin-right: 10px;
margin-bottom: 10px;
}
.mobile-screenshot {
height: 40em;
padding: 1em;
}
.cli-screenshot-wrapper {
background-color: black;
vertical-align: top;
padding: 1em 2em 1em 1em;
}
.cli-screenshot {
font-family: "Monaco", "Inconsolata", "CONSOLAS", "Deja Vu Sans Mono", "Droid Sans Mono", "Andale Mono", monospace;
background-color: black;
color: white;
border: none;
}
.cli-screenshot .prompt {
color: #48C2F0;
}
.top-screenshot {
margin-top: 2em;
text-align: center;
}
.header {
position: relative;
padding-left: 2em;
padding-right: 2em;
padding-top: 1em;
padding-bottom: 1em;
color: white;
background-color: #2B2B3D;
}
.header a h1 {
color: white;
}
.header a:hover {
text-decoration: none;
}
.content {
padding-left: 2em;
padding-right: 2em;
padding-bottom: 2em;
padding-top: 2em;
}
.forkme {
position: absolute;
right: 0;
top:0;
}
.nav-wrapper {
position: relative;
width: inherit;
}
.nav {
background-color: black;
display: flex;
flex-direction: row;
align-items: center;
}
.nav.sticky {
position:fixed;
top: 0;
width: inherit;
box-shadow: 0 0 10px #000000;
}
.nav a {
color: white;
display: inline-block;
padding: .6em .9em .6em .9em;
}
.nav ul {
padding-left: 2em;
margin-bottom: 0;
display: table-cell;
display: flex;
width: 100%;
/* For GSoC: */
min-width: 470px;
}
.nav ul li {
display: inline-block;
padding: 0;
}
.nav li.selected {
background-color: #222;
font-weight: bold;
}
.nav-right {
display: flex;
text-align: right;
vertical-align: middle;
line-height: 0;
margin-right: 10px;
}
.nav-right .share-btn {
display: none;
}
.nav-right .small-share-btn {
display: none;
}
.footer {
padding: 2em;
border-top: 1px solid #d4d4d4;
margin-top: 2em;
color: gray;
font-size: .9em;
}
a.heading-anchor {
display: inline-block;
opacity: 0;
width: 1.3em;
font-size: 0.7em;
margin-left: 0.4em;
line-height: 1em;
text-decoration: none;
transition: opacity 0.3s;
}
a.heading-anchor:hover,
h1:hover a.heading-anchor,
h2:hover a.heading-anchor,
h3:hover a.heading-anchor,
h4:hover a.heading-anchor,
h5:hover a.heading-anchor,
h6:hover a.heading-anchor {
opacity: 1;
}
@media (min-width: 992px) {
.content{
display: flex;
}
#toc{
display: block!important;
align-self: flex-start;
width: 300px;
position: sticky; top: 20px; left: 0;
}
.main{
width: calc(100% - 300px);
}
}
.bottom-links {
display: flex;
justify-content: center;
border-top: 1px solid #d4d4d4;
margin-top: 30px;
padding-top: 25px;
}
@media all and (min-width: 400px) {
.nav-right .share-btn {
display: inline-block;
}
.nav-right .small-share-btn {
display: none;
}
}
</style>
</head>
<body>
<div class="container root page-changelog_android">
<div class="header">
<a class="forkme" href="https://github.com/laurent22/joplin"><img src="https://joplinapp.org/images/ForkMe.png"/></a>
<a href="https://joplinapp.org"><h1 class="title"><img class="title-icon" src="https://joplinapp.org/images/Icon512.png"><span class="title-text">Joplin</span></h1></a>
<p class="sub-title">An open source note taking and to-do application with synchronisation capabilities</p>
</div>
<div class="nav-wrapper">
<div class="nav">
<ul>
<li class=""><a href="https:&#x2F;&#x2F;joplinapp.org/" title="Home"><i class="fa fa-home"></i></a></li>
<li><a href="https://discourse.joplinapp.org" title="Forum">Forum</a></li>
<li><a class="gsoc" href="https://joplinapp.org/gsoc2021/index/" title="Google Summer of Code 2021">GSoC 2021</a></li>
</ul>
<div class="nav-right">
<iframe class="share-btn share-btn-github" src="https://ghbtns.com/github-btn.html?user=laurent22&repo=joplin&type=star&count=true" frameborder="0" scrolling="0" width="115px" height="20px"></iframe>
</div>
</div>
</div>
<div class="content">
<div id="toc"><ul>
<li>
<p>Applications</p>
<ul>
<li><a href="https://joplinapp.org/desktop/">Desktop application</a></li>
<li><a href="https://joplinapp.org/mobile/">Mobile applications</a></li>
<li><a href="https://joplinapp.org/terminal/">Terminal application</a></li>
<li><a href="https://joplinapp.org/clipper/">Web Clipper</a></li>
</ul>
</li>
<li>
<p>Support</p>
<ul>
<li><a href="https://discourse.joplinapp.org">Joplin Forum</a></li>
<li><a href="https://joplinapp.org/markdown/">Markdown Guide</a></li>
<li><a href="https://joplinapp.org/e2ee/">How to enable end-to-end encryption</a></li>
<li><a href="https://joplinapp.org/conflict/">What is a conflict?</a></li>
<li><a href="https://joplinapp.org/debugging/">How to enable debug mode</a></li>
<li><a href="https://joplinapp.org/rich_text_editor/">About the Rich Text editor limitations</a></li>
<li><a href="https://joplinapp.org/faq/">FAQ</a></li>
</ul>
</li>
<li>
<p>Joplin API - Get Started</p>
<ul>
<li><a href="https://joplinapp.org/api/overview/">Joplin API Overview</a></li>
<li><a href="https://joplinapp.org/api/get_started/plugins/">Plugin development</a></li>
<li><a href="https://joplinapp.org/api/tutorials/toc_plugin/">Plugin tutorial</a></li>
</ul>
</li>
<li>
<p>Joplin API - References</p>
<ul>
<li><a href="https://joplinapp.org/api/references/plugin_api/classes/joplin.html">Plugin API</a></li>
<li><a href="https://joplinapp.org/api/references/rest_api/">Data API</a></li>
<li><a href="https://joplinapp.org/api/references/plugin_manifest/">Plugin manifest</a></li>
<li><a href="https://joplinapp.org/api/references/plugin_loading_rules/">Plugin loading rules</a></li>
<li><a href="https://joplinapp.org/api/references/plugin_theming/">Plugin theming</a></li>
</ul>
</li>
<li>
<p>Development</p>
<ul>
<li><a href="https://github.com/laurent22/joplin/blob/dev/BUILD.md">How to build the apps</a></li>
<li><a href="https://joplinapp.org/spec/e2ee/">End-to-end encryption spec</a></li>
<li><a href="https://joplinapp.org/spec/history/">Note History spec</a></li>
<li><a href="https://joplinapp.org/spec/sync_lock/">Sync Lock spec</a></li>
<li><a href="https://joplinapp.org/spec/plugins/">Plugin Architecture spec</a></li>
<li><a href="https://joplinapp.org/spec/search_sorting/">Search Sorting spec</a></li>
<li><a href="https://joplinapp.org/spec/server_file_url_format/">Server: File URL Format</a></li>
<li><a href="https://joplinapp.org/spec/server_delta_sync/">Server: Delta Sync</a></li>
<li><a href="https://joplinapp.org/spec/server_sharing/">Server: Sharing</a></li>
</ul>
</li>
<li>
<p>Google Summer of Code 2021</p>
<ul>
<li><a href="https://joplinapp.org/gsoc2021/index/">Google Summer of Code 2021</a></li>
<li><a href="https://joplinapp.org/gsoc2021/pull_request_guidelines/">How to submit a GSoC pull request</a></li>
<li><a href="https://joplinapp.org/gsoc2021/ideas/">Project Ideas</a></li>
</ul>
</li>
<li>
<p>About</p>
<ul>
<li><a href="https://joplinapp.org/changelog/">Changelog (Desktop App)</a></li>
<li><a href="https://joplinapp.org/changelog_cli/">Changelog (CLI App)</a></li>
<li><a href="https://joplinapp.org/changelog_server/">Changelog (Server)</a></li>
<li><a href="https://joplinapp.org/stats/">Stats</a></li>
<li><a href="https://joplinapp.org/donate/">Donate</a></li>
</ul>
</li>
</ul>
</div>
<div class="main">
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=E8JMYD2LQ8MMA&amp;lc=GB&amp;item_name=Joplin+Development&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplinapp.org/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal"></a> <a href="https://github.com/sponsors/laurent22/"><img src="https://joplinapp.org/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub"></a> <a href="https://www.patreon.com/joplin"><img src="https://joplinapp.org/images/badges/Patreon-Badge.svg" alt="Become a patron"></a> <a href="https://joplinapp.org/donate/#donations"><img src="https://joplinapp.org/images/badges/Donate-IBAN.svg" alt="Donate using IBAN"></a></p>
<hr>
<h1>Joplin Android app changelog<a name="joplin-android-app-changelog" href="#joplin-android-app-changelog" class="heading-anchor">🔗</a></h1>
<div class="bottom-links">
<a href="https://github.com/laurent22/joplin/blob/dev/readme/changelog_android.md">
<i class="fa fa-github"></i> Improve this doc
</a>
</div>
<script>
function stickyHeader() {
return; // Disabled
if ($(window).scrollTop() > 179) {
$('.nav').addClass('sticky');
} else {
$('.nav').removeClass('sticky');
}
}
$(window).scroll(function() {
stickyHeader();
});
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-103586105-1', 'auto');
ga('send', 'pageview');
</script>
</div></div>
<div class="footer">
Copyright (C) 2016-2021 Laurent Cozic
</div>
</body>
</html>
-44
View File
@@ -405,50 +405,6 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog_server.md
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=E8JMYD2LQ8MMA&amp;lc=GB&amp;item_name=Joplin+Development&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"><img src="https://joplinapp.org/images/badges/Donate-PayPal-green.svg" alt="Donate using PayPal"></a> <a href="https://github.com/sponsors/laurent22/"><img src="https://joplinapp.org/images/badges/GitHub-Badge.svg" alt="Sponsor on GitHub"></a> <a href="https://www.patreon.com/joplin"><img src="https://joplinapp.org/images/badges/Patreon-Badge.svg" alt="Become a patron"></a> <a href="https://joplinapp.org/donate/#donations"><img src="https://joplinapp.org/images/badges/Donate-IBAN.svg" alt="Donate using IBAN"></a></p>
<hr>
<h1>Joplin Server Changelog<a name="joplin-server-changelog" href="#joplin-server-changelog" class="heading-anchor">🔗</a></h1>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.5">server-v2.0.5</a> (Pre-release) - 2021-06-02T08:14:47Z<a name="server-v2-0-5-https-github-com-laurent22-joplin-releases-tag-server-v2-0-5-pre-release-2021-06-02t08-14-47z" href="#server-v2-0-5-https-github-com-laurent22-joplin-releases-tag-server-v2-0-5-pre-release-2021-06-02t08-14-47z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add version number on website (0ef7e98)</li>
<li>New: Added signup pages (41ed66d)</li>
<li>Improved: Allow disabling item upload for a user (f8a26cf)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.4">server-v2.0.4</a> (Pre-release) - 2021-05-25T18:33:11Z<a name="server-v2-0-4-https-github-com-laurent22-joplin-releases-tag-server-v2-0-4-pre-release-2021-05-25t18-33-11z" href="#server-v2-0-4-https-github-com-laurent22-joplin-releases-tag-server-v2-0-4-pre-release-2021-05-25t18-33-11z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Fixed: Fixed Item and Log page when using Postgres (ee0f237)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.3">server-v2.0.3</a> (Pre-release) - 2021-05-25T18:08:46Z<a name="server-v2-0-3-https-github-com-laurent22-joplin-releases-tag-server-v2-0-3-pre-release-2021-05-25t18-08-46z" href="#server-v2-0-3-https-github-com-laurent22-joplin-releases-tag-server-v2-0-3-pre-release-2021-05-25t18-08-46z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Fixed: Fixed handling of request origin (12a6634)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.2">server-v2.0.2</a> (Pre-release) - 2021-05-25T19:15:50Z<a name="server-v2-0-2-https-github-com-laurent22-joplin-releases-tag-server-v2-0-2-pre-release-2021-05-25t19-15-50z" href="#server-v2-0-2-https-github-com-laurent22-joplin-releases-tag-server-v2-0-2-pre-release-2021-05-25t19-15-50z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add mailer service (ed8ee67)</li>
<li>New: Add support for item size limit (6afde54)</li>
<li>New: Added API end points to manage users (77b284f)</li>
<li>Improved: Allow enabling or disabling the sharing feature per user (daaaa13)</li>
<li>Improved: Allow setting the path to the SQLite database using SQLITE_DATABASE env variable (68e79f1)</li>
<li>Improved: Allow using a different domain for API, main website and user content (83cef7a)</li>
<li>Improved: Generate only one share link per note (e156ee1)</li>
<li>Improved: Go back to home page when there is an error and user is logged in (a24b009)</li>
<li>Improved: Improved Items table and added item size to it (7f05420)</li>
<li>Improved: Improved log table too and made it sortable (ec7f0f4)</li>
<li>Improved: Make it more difficult to delete all data (b01aa7e)</li>
<li>Improved: Redirect to correct page when trying to access the root (51051e0)</li>
<li>Improved: Use external directory to store Postgres data in Docker-compose config (71a7fc0)</li>
<li>Fixed: Fixed /items page when using Postgres (2d0580f)</li>
<li>Fixed: Fixed bug when unsharing a notebook that has no recipients (6ddb69e)</li>
<li>Fixed: Fixed deleting a note that has been shared (489995d)</li>
<li>Fixed: Make sure temp files are deleted after upload is done (#4540)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v2.0.1">server-v2.0.1</a> (Pre-release) - 2021-05-14T13:55:45Z<a name="server-v2-0-1-https-github-com-laurent22-joplin-releases-tag-server-v2-0-1-pre-release-2021-05-14t13-55-45z" href="#server-v2-0-1-https-github-com-laurent22-joplin-releases-tag-server-v2-0-1-pre-release-2021-05-14t13-55-45z" class="heading-anchor">🔗</a></h2>
<ul>
<li>New: Add support for sharing notes via a link (ccbc329)</li>
<li>New: Add support for sharing a folder (#4772)</li>
<li>New: Added log page to view latest changes to files (874f301)</li>
<li>Fixed: Prevent new user password from being hashed twice (76c143e)</li>
<li>Fixed: Fixed crash when rendering note with links to non-existing resources or notes (07484de)</li>
<li>Fixed: Fixed error handling when no session is provided (63a5bfa)</li>
<li>Fixed: Fixed uploading empty file to the API (#4402)</li>
</ul>
<h2><a href="https://github.com/laurent22/joplin/releases/tag/server-v1.7.2">server-v1.7.2</a> - 2021-01-24T19:11:10Z<a name="server-v1-7-2-https-github-com-laurent22-joplin-releases-tag-server-v1-7-2-2021-01-24t19-11-10z" href="#server-v1-7-2-https-github-com-laurent22-joplin-releases-tag-server-v1-7-2-2021-01-24t19-11-10z" class="heading-anchor">🔗</a></h2>
<ul>
<li>Fixed: Fixed password hashing when changing password</li>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

+428 -632
View File
File diff suppressed because it is too large. Load diff
+15 -9
View File
@@ -99,17 +99,22 @@
"sync.9.path": {
"type": "string",
"default": "",
"description": "Joplin Cloud URL. Attention: If you change this location, make sure you copy all your content to it before syncing, otherwise all files will be removed! See the FAQ for more details: https://joplinapp.org/faq/"
"description": "Joplin Server URL. Attention: If you change this location, make sure you copy all your content to it before syncing, otherwise all files will be removed! See the FAQ for more details: https://joplinapp.org/faq/"
},
"sync.9.directory": {
"type": "string",
"default": "Apps/Joplin",
"description": "Joplin Server Directory"
},
"sync.9.username": {
"type": "string",
"default": "",
"description": "Joplin Cloud email"
"description": "Joplin Server username"
},
"sync.9.password": {
"type": "string",
"default": "",
"description": "Joplin Cloud password",
"description": "Joplin Server password",
"$comment": "private"
},
"sync.5.syncTargets": {
@@ -597,11 +602,6 @@
"default": -1,
"$comment": "private"
},
"sync.userId": {
"type": "string",
"default": "",
"$comment": "private"
},
"style.zoom": {
"type": "integer",
"default": 100,
@@ -633,7 +633,7 @@
},
"autoUpdateEnabled": {
"type": "boolean",
"default": true,
"default": false,
"description": "Automatically update the application"
},
"autoUpdate.includePreReleases": {
@@ -726,6 +726,12 @@
"description": "Enable spell checking in Markdown editor? (WARNING BETA feature). Spell checker in the Markdown editor was previously unstable (cursor location was not stable, sometimes edits would not be saved or reflected in the viewer, etc.) however it appears to be more reliable now. If you notice any issue, please report it on GitHub or the Joplin Forum (Help -> Joplin Forum)",
"$comment": "private"
},
"image.noresizing": {
"type": "boolean",
"default": false,
"description": "Do not resize images",
"$comment": "private"
},
"net.customCertificates": {
"type": "string",
"default": "",
+273 -313
View File
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -33,7 +33,8 @@ module.exports = {
'<rootDir>/node_modules/',
'<rootDir>/tests/support/',
'<rootDir>/build/',
'<rootDir>/tests/testUtils.js',
'<rootDir>/tests/test-utils.js',
'<rootDir>/tests/test-utils-synchronizer.js',
'<rootDir>/tests/tmp/',
'<rootDir>/tests/test data/',
],
+102 -3
View File
@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "2.0.0",
"version": "1.8.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1806,6 +1806,80 @@
}
}
},
"clean-html": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/clean-html/-/clean-html-1.5.0.tgz",
"integrity": "sha512-eDu0vN44ZBvoEU0oRIKwWPIccGWXtdnUNmKJuTukZ1de00Uoqavb5pfIMKiC7/r+knQ5RbvAjGuVZiN3JwJL4Q==",
"requires": {
"htmlparser2": "^3.8.2",
"minimist": "^1.1.1"
},
"dependencies": {
"domelementtype": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
},
"domhandler": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
"requires": {
"domelementtype": "1"
}
},
"domutils": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
}
},
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
"requires": {
"domelementtype": "^1.3.1",
"domhandler": "^2.3.0",
"domutils": "^1.5.1",
"entities": "^1.1.1",
"inherits": "^2.0.1",
"readable-stream": "^3.1.1"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
}
}
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
@@ -2329,6 +2403,27 @@
"integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
"dev": true
},
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
"requires": {
"domelementtype": "^2.0.1",
"entities": "^2.0.0"
},
"dependencies": {
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
}
}
},
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"domexception": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
@@ -2450,6 +2545,11 @@
"once": "^1.4.0"
}
},
"entities": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -4566,8 +4666,7 @@
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"resolved": "",
"dev": true
},
"yargs": {
@@ -1 +1 @@
<P>Hello World:<span class="joplin-editable"><span class="joplin-source" data-joplin-source-open="$" data-joplin-source-close="$">katexcode</span><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msubsup><mo>∫</mo><mrow><mo>−</mo><mi mathvariant="normal">∞</mi></mrow><mi mathvariant="normal">∞</mi></msubsup><mover accent="true"><mi>f</mi><mo>^</mo></mover><mo stretchy="false">(</mo><mi>ξ</mi><mo stretchy="false">)</mo><mtext> </mtext><msup><mi>e</mi><mrow><mn>2</mn><mi>π</mi><mi>i</mi><mi>ξ</mi><mi>x</mi></mrow></msup><mtext> </mtext><mi>d</mi><mi>ξ</mi></mrow><annotation encoding="application/x-tex">f(x) = \int_{-\infty}^\infty \hat f(\xi)\,e^{2 \pi i \xi x} \,d\xi </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 1em; vertical-align: -0.25em;" data-mce-style="height: 1em; vertical-align: -0.25em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span><span class="mopen">(</span><span class="mord mathdefault">x</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height: 2.384573em; vertical-align: -0.970281em;" data-mce-style="height: 2.384573em; vertical-align: -0.970281em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;" data-mce-style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 1.414292em;" data-mce-style="height: 1.414292em;"><span style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;" data-mce-style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top: -3.8129000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.8129000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.970281em;" data-mce-style="height: 0.970281em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord accent"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.9578799999999998em;" data-mce-style="height: 0.9578799999999998em;"><span style="top: -3em;" data-mce-style="top: -3em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span></span><span style="top: -3.26344em;" data-mce-style="top: -3.26344em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="accent-body" style="left: -0.08332999999999999em;" data-mce-style="left: -0.08332999999999999em;"><span class="mord">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.19444em;" data-mce-style="height: 0.19444em;"><span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right: 0.04601em;" data-mce-style="margin-right: 0.04601em;">ξ</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord"><span class="mord mathdefault">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8991079999999999em;" data-mce-style="height: 0.8991079999999999em;"><span style="top: -3.1130000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.1130000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</spLine truncated
<span class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="$" data-joplin-source-close="$">katexcode</pre><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msubsup><mo>∫</mo><mrow><mo>−</mo><mi mathvariant="normal">∞</mi></mrow><mi mathvariant="normal">∞</mi></msubsup><mover accent="true"><mi>f</mi><mo>^</mo></mover><mo stretchy="false">(</mo><mi>ξ</mi><mo stretchy="false">)</mo><mtext> </mtext><msup><mi>e</mi><mrow><mn>2</mn><mi>π</mi><mi>i</mi><mi>ξ</mi><mi>x</mi></mrow></msup><mtext> </mtext><mi>d</mi><mi>ξ</mi></mrow><annotation encoding="application/x-tex">f(x) = \int_{-\infty}^\infty \hat f(\xi)\,e^{2 \pi i \xi x} \,d\xi </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 1em; vertical-align: -0.25em;" data-mce-style="height: 1em; vertical-align: -0.25em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span><span class="mopen">(</span><span class="mord mathdefault">x</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height: 2.384573em; vertical-align: -0.970281em;" data-mce-style="height: 2.384573em; vertical-align: -0.970281em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;" data-mce-style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 1.414292em;" data-mce-style="height: 1.414292em;"><span style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;" data-mce-style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top: -3.8129000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.8129000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.970281em;" data-mce-style="height: 0.970281em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord accent"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.9578799999999998em;" data-mce-style="height: 0.9578799999999998em;"><span style="top: -3em;" data-mce-style="top: -3em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span></span><span style="top: -3.26344em;" data-mce-style="top: -3.26344em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="accent-body" style="left: -0.08332999999999999em;" data-mce-style="left: -0.08332999999999999em;"><span class="mord">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.19444em;" data-mce-style="height: 0.19444em;"><span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right: 0.04601em;" data-mce-style="margin-right: 0.04601em;">ξ</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord"><span class="mord mathdefault">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8991079999999999em;" data-mce-style="height: 0.8991079999999999em;"><span style="top: -3.1130000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.1130000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mLine truncated
@@ -1 +1 @@
Hello World:$katexcode$
$katexcode$
@@ -1,8 +1 @@
<p>Hello World :</p>
<div class="joplin-editable">
<pre class="joplin-source"
data-joplin-language="katex"
data-joplin-source-open="$$&#10;"
data-joplin-source-close="&#10;$$&#10;">\sqrt{3x}</pre>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msubsup><mo>∫</mo><mrow><mo>−</mo><mi mathvariant="normal">∞</mi></mrow><mi mathvariant="normal">∞</mi></msubsup><mover accent="true"><mi>f</mi><mo>^</mo></mover><mo stretchy="false">(</mo><mi>ξ</mi><mo stretchy="false">)</mo><mtext> </mtext><msup><mi>e</mi><mrow><mn>2</mn><mi>π</mi><mi>i</mi><mi>ξ</mi><mi>x</mi></mrow></msup><mtext> </mtext><mi>d</mi><mi>ξ</mi></mrow><annotation encoding="application/x-tex">f(x) = \int_{-\infty}^\infty \hat f(\xi)\,e^{2 \pi i \xi x} \,d\xi </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 1em; vertical-align: -0.25em;" data-mce-style="height: 1em; vertical-align: -0.25em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span><span class="mopen">(</span><span class="mord mathdefault">x</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height: 2.384573em; vertical-align: -0.970281em;" data-mce-style="height: 2.384573em; vertical-align: -0.970281em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;" data-mce-style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 1.414292em;" data-mce-style="height: 1.414292em;"><span style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;" data-mce-style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top: -3.8129000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.8129000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.970281em;" data-mce-style="height: 0.970281em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord accent"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.9578799999999998em;" data-mce-style="height: 0.9578799999999998em;"><span style="top: -3em;" data-mce-style="top: -3em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span></span><span style="top: -3.26344em;" data-mce-style="top: -3.26344em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="accent-body" style="left: -0.08332999999999999em;" data-mce-style="left: -0.08332999999999999em;"><span class="mord">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.19444em;" data-mce-style="height: 0.19444em;"><span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right: 0.04601em;" data-mce-style="margin-right: 0.04601em;">ξ</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord"><span class="mord mathdefault">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8991079999999999em;" data-mce-style="height: 0.8991079999999999em;"><span style="top: -3.1130000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.1130000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathdefault mtight" style="margin-right: 0.03588em;" data-mce-style="margin-right: 0.03588em;">π</span><span class="mordLine truncated
</div>
<div class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="$$&#10;" data-joplin-source-close="&#10;$$">katexcode</pre><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><msubsup><mo>∫</mo><mrow><mo>−</mo><mi mathvariant="normal">∞</mi></mrow><mi mathvariant="normal">∞</mi></msubsup><mover accent="true"><mi>f</mi><mo>^</mo></mover><mo stretchy="false">(</mo><mi>ξ</mi><mo stretchy="false">)</mo><mtext> </mtext><msup><mi>e</mi><mrow><mn>2</mn><mi>π</mi><mi>i</mi><mi>ξ</mi><mi>x</mi></mrow></msup><mtext> </mtext><mi>d</mi><mi>ξ</mi></mrow><annotation encoding="application/x-tex">f(x) = \int_{-\infty}^\infty \hat f(\xi)\,e^{2 \pi i \xi x} \,d\xi </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height: 1em; vertical-align: -0.25em;" data-mce-style="height: 1em; vertical-align: -0.25em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span><span class="mopen">(</span><span class="mord mathdefault">x</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right: 0.2777777777777778em;" data-mce-style="margin-right: 0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height: 2.384573em; vertical-align: -0.970281em;" data-mce-style="height: 2.384573em; vertical-align: -0.970281em;"></span><span class="mop"><span class="mop op-symbol large-op" style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;" data-mce-style="margin-right: 0.44445em; position: relative; top: -0.0011249999999999316em;">∫</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 1.414292em;" data-mce-style="height: 1.414292em;"><span style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;" data-mce-style="top: -1.7880500000000001em; margin-left: -0.44445em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">∞</span></span></span></span><span style="top: -3.8129000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.8129000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.970281em;" data-mce-style="height: 0.970281em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord accent"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height: 0.9578799999999998em;" data-mce-style="height: 0.9578799999999998em;"><span style="top: -3em;" data-mce-style="top: -3em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="mord mathdefault" style="margin-right: 0.10764em;" data-mce-style="margin-right: 0.10764em;">f</span></span><span style="top: -3.26344em;" data-mce-style="top: -3.26344em;"><span class="pstrut" style="height: 3em;" data-mce-style="height: 3em;"></span><span class="accent-body" style="left: -0.08332999999999999em;" data-mce-style="left: -0.08332999999999999em;"><span class="mord">^</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height: 0.19444em;" data-mce-style="height: 0.19444em;"><span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right: 0.04601em;" data-mce-style="margin-right: 0.04601em;">ξ</span><span class="mclose">)</span><span class="mspace" style="margin-right: 0.16666666666666666em;" data-mce-style="margin-right: 0.16666666666666666em;"></span><span class="mord"><span class="mord mathdefault">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height: 0.8991079999999999em;" data-mce-style="height: 0.8991079999999999em;"><span style="top: -3.1130000000000004em; margin-right: 0.05em;" data-mce-style="top: -3.1130000000000004em; margin-right: 0.05em;"><span class="pstrut" style="height: 2.7em;" data-mce-style="height: 2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><spLine truncated
@@ -1,5 +1,3 @@
Hello World :
$$
\sqrt{3x}
katexcode
$$
@@ -7,8 +7,8 @@ import Setting from '@joplin/lib/models/Setting';
import * as fs from 'fs-extra';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import { newPluginScript } from '@joplin/lib/testing/test-utils';
import { expectNotThrow, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir, supportDir } from '@joplin/lib/testing/test-utils';
import { newPluginScript } from '../../testUtils';
const testPluginDir = `${supportDir}/plugins`;
@@ -4,7 +4,7 @@ import { setupDatabaseAndSynchronizer, switchClient, supportDir, createTempDir }
async function newRepoApi(): Promise<RepositoryApi> {
const repo = new RepositoryApi(`${supportDir}/pluginRepo`, await createTempDir());
await repo.initialize();
await repo.loadManifests();
return repo;
}
@@ -1,7 +1,7 @@
import Setting from '@joplin/lib/models/Setting';
import { waitForFolderCount, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '@joplin/lib/testing/test-utils';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const { waitForFolderCount, newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('@joplin/lib/testing/test-utils');
import Folder from '@joplin/lib/models/Folder';
import { newPluginScript, newPluginService } from '../../../testUtils';
describe('JoplinSettings', () => {
@@ -16,7 +16,7 @@ describe('JoplinSettings', () => {
});
test('should listen to setting change event', async () => {
const service = newPluginService();
const service = new newPluginService() as PluginService;
const pluginScript = newPluginScript(`
joplin.plugins.register({
@@ -68,7 +68,7 @@ describe('JoplinSettings', () => {
});
test('should allow registering multiple settings', async () => {
const service = newPluginService();
const service = new newPluginService() as PluginService;
const pluginScript = newPluginScript(`
joplin.plugins.register({
@@ -1,6 +1,6 @@
import KeymapService from '@joplin/lib/services/KeymapService';
import { setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '@joplin/lib/testing/test-utils';
import { newPluginScript, newPluginService } from '../../../testUtils';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } = require('@joplin/lib/testing/test-utils');
describe('JoplinViewMenuItem', () => {
@@ -15,7 +15,7 @@ describe('JoplinViewMenuItem', () => {
});
test('should register commands with the keymap service', async () => {
const service = newPluginService();
const service = new newPluginService() as PluginService;
KeymapService.instance().initialize();
@@ -1,9 +1,8 @@
import Setting from '@joplin/lib/models/Setting';
import { setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '@joplin/lib/testing/test-utils';
import { newPluginService, newPluginScript, setupDatabaseAndSynchronizer, switchClient, afterEachCleanUp } from '@joplin/lib/testing/test-utils';
import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import ItemChange from '@joplin/lib/models/ItemChange';
import { newPluginScript, newPluginService } from '../../../testUtils';
describe('JoplinWorkspace', () => {
@@ -1,5 +1,6 @@
import sandboxProxy, { Target } from '@joplin/lib/services/plugins/sandboxProxy';
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
const { setupDatabaseAndSynchronizer, switchClient } = require('@joplin/lib/testing/test-utils.js');
describe('services_plugins_sandboxProxy', function() {
-41
View File
@@ -1,41 +0,0 @@
import PluginService from '@joplin/lib/services/plugins/PluginService';
import PluginRunner from '../app/services/plugins/PluginRunner';
export interface PluginServiceOptions {
getState?(): Record<string, any>;
}
export function newPluginService(appVersion = '1.4', options: PluginServiceOptions = null): PluginService {
options = options || {};
const runner = new PluginRunner();
const service = new PluginService();
service.initialize(
appVersion,
{
joplin: {},
},
runner,
{
dispatch: () => {},
getState: options.getState ? options.getState : () => {},
}
);
return service;
}
export function newPluginScript(script: string) {
return `
/* joplin-manifest:
{
"id": "org.joplinapp.plugins.PluginTest",
"manifest_version": 1,
"app_min_version": "1.4",
"name": "JS Bundle test",
"version": "1.0.0"
}
*/
${script}
`;
}
-1
View File
@@ -96,7 +96,6 @@ const globalCommands = [
require('./commands/stopExternalEditing'),
require('./commands/toggleExternalEditing'),
require('./commands/toggleSafeMode'),
require('./commands/restoreNoteRevision'),
require('@joplin/lib/commands/historyBackward'),
require('@joplin/lib/commands/historyForward'),
require('@joplin/lib/commands/synchronize'),
@@ -1,20 +0,0 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import RevisionService from '@joplin/lib/services/RevisionService';
export const declaration: CommandDeclaration = {
name: 'restoreNoteRevision',
label: 'Restore a note from history',
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext, noteId: string, reverseRevIndex: number = 0) => {
try {
const note = await RevisionService.instance().restoreNoteById(noteId, reverseRevIndex);
alert(RevisionService.instance().restoreSuccessMessage(note));
} catch (error) {
alert(error.message);
}
},
};
};
@@ -113,7 +113,7 @@ export default function(props: Props) {
let loadError: Error = null;
try {
await repoApi().initialize();
await repoApi().loadManifests();
} catch (error) {
logger.error(error);
loadError = error;
-8
View File
@@ -526,14 +526,6 @@ function useMenu(props: Props) {
click: () => { bridge().electronApp().hide(); },
} : noItem,
shim.isMac() ? {
role: 'hideothers',
} : noItem,
shim.isMac() ? {
role: 'unhide',
} : noItem,
{
type: 'separator',
},
@@ -224,12 +224,10 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
textHeading: () => addListItem('## ', ''),
textHorizontalRule: () => addListItem('* * *'),
'editor.execCommand': (value: CommandValue) => {
if (!('args' in value)) value.args = [];
if (editorRef.current[value.name]) {
if (!('args' in value)) value.args = [];
editorRef.current[value.name](...value.args);
} else if (editorRef.current.commandExists(value.name)) {
editorRef.current.execCommand(value.name);
} else {
reg.logger().warn('CodeMirror execCommand: unsupported command: ', value.name);
}
@@ -20,7 +20,6 @@ import useEditorSearch from './utils/useEditorSearch';
import useJoplinMode from './utils/useJoplinMode';
import useKeymap from './utils/useKeymap';
import useExternalPlugins from './utils/useExternalPlugins';
import useJoplinCommands from './utils/useJoplinCommands';
import 'codemirror/keymap/emacs';
import 'codemirror/keymap/vim';
@@ -108,7 +107,6 @@ function Editor(props: EditorProps, ref: any) {
useJoplinMode(CodeMirror);
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
useKeymap(CodeMirror);
useJoplinCommands(CodeMirror);
useImperativeHandle(ref, () => {
return editor;
@@ -2,76 +2,19 @@ import { modifyListLines } from './useCursorUtils';
describe('useCursorUtils', () => {
const listWithDashes = [
'- item1',
'- item2',
'- item3',
];
const listWithDashes = `- item1
- item2
- item3`;
const listWithNoPrefixes = [
'item1',
'item2',
'item3',
];
const listNoDashes = `item1
item2
item3`;
const listWithNumbers = [
'1. item1',
'2. item2',
'3. item3',
];
const listWithOnes = [
'1. item1',
'1. item2',
'1. item3',
];
const listWithSomeNumbers = [
'1. item1',
'item2',
'2. item3',
];
const numberedListWithEmptyLines = [
'1. item1',
'2. item2',
'3. ' ,
'4. item3',
];
const noPrefixListWithEmptyLines = [
'item1',
'item2',
'' ,
'item3',
];
test('should remove "- " from beginning of each line of input string', () => {
expect(modifyListLines([...listWithDashes], NaN, '- ')).toStrictEqual(listWithNoPrefixes);
test('should remove "- " from beggining of each line of input string', () => {
expect(JSON.stringify(modifyListLines(listWithDashes.split('\n'), 0, '- '))).toBe(JSON.stringify(listNoDashes.split('\n')));
});
test('should add "- " at the beginning of each line of the input string', () => {
expect(modifyListLines([...listWithNoPrefixes], NaN, '- ')).toStrictEqual(listWithDashes);
});
test('should remove "n. " at the beginning of each line of the input string', () => {
expect(modifyListLines([...listWithNumbers], 4, '1. ')).toStrictEqual(listWithNoPrefixes);
});
test('should add "n. " at the beginning of each line of the input string', () => {
expect(modifyListLines([...listWithNoPrefixes], 1, '1. ')).toStrictEqual(listWithNumbers);
});
test('should remove "1. " at the beginning of each line of the input string', () => {
expect(modifyListLines([...listWithOnes], 2, '1. ')).toStrictEqual(listWithNoPrefixes);
});
test('should remove "n. " from each line that has it, and ignore' +
' lines which do not', () => {
expect(modifyListLines([...listWithSomeNumbers], 2, '2. ')).toStrictEqual(listWithNoPrefixes);
});
test('should add numbers to each line including empty one', () => {
expect(modifyListLines(noPrefixListWithEmptyLines, 1, '1. ')).toStrictEqual(numberedListWithEmptyLines);
test('should add "- " at the beggining of each line of the input string', () => {
expect(JSON.stringify(modifyListLines(listNoDashes.split('\n'), 0, '- '))).toBe(JSON.stringify(listWithDashes.split('\n')));
});
});
@@ -1,27 +1,20 @@
import markdownUtils from '@joplin/lib/markdownUtils';
import Setting from '@joplin/lib/models/Setting';
export function modifyListLines(lines: string[], num: number, listSymbol: string) {
const isNotNumbered = num === 1;
export function modifyListLines(lines: string[],num: number,listSymbol: string) {
for (let j = 0; j < lines.length; j++) {
const line = lines[j];
if (!line && j === lines.length - 1) continue;
// Only add the list token if it's not already there
// if it is, remove it
if (num) {
const lineNum = markdownUtils.olLineNumber(line);
if (!lineNum && isNotNumbered) {
if (!line.startsWith(listSymbol)) {
if (num) {
lines[j] = `${num.toString()}. ${line}`;
num++;
} else {
const listToken = markdownUtils.extractListToken(line);
lines[j] = line.substr(listToken.length, line.length - listToken.length);
lines[j] = listSymbol + line;
}
} else {
if (!line.startsWith(listSymbol)) {
lines[j] = listSymbol + line;
} else {
lines[j] = line.substr(listSymbol.length, line.length - listSymbol.length);
}
lines[j] = line.substr(listSymbol.length, line.length - listSymbol.length);
}
}
return lines;
@@ -1,7 +0,0 @@
// Helper commands added to the the CodeMirror instance
export default function useJoplinCommands(CodeMirror: any) {
CodeMirror.defineExtension('commandExists', function(name: string) {
return !!CodeMirror.commands[name];
});
}
@@ -9,7 +9,6 @@ const { urlDecode } = require('@joplin/lib/string-utils');
const urlUtils = require('@joplin/lib/urlUtils');
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import { reg } from '@joplin/lib/registry';
const uri2path = require('file-uri-to-path');
export default function useMessageHandler(scrollWhenReady: any, setScrollWhenReady: Function, editorRef: any, setLocalSearchResultCount: Function, dispatch: Function, formNote: FormNote) {
return useCallback(async (event: any) => {
@@ -52,14 +51,8 @@ export default function useMessageHandler(scrollWhenReady: any, setScrollWhenRea
} else if (urlUtils.urlProtocol(msg)) {
if (msg.indexOf('file://') === 0) {
// When using the file:// protocol, openPath doesn't work (does
// nothing) with URL-encoded paths.
//
// shell.openPath seems to work with file:// urls on Windows,
// but doesn't on macOS, so we need to convert it to a path
// before passing it to openPath.
const decodedPath = uri2path(urlDecode(msg));
require('electron').shell.openPath(decodedPath);
// When using the file:// protocol, openPath doesn't work (does nothing) with URL-encoded paths
require('electron').shell.openPath(urlDecode(msg));
} else {
require('electron').shell.openExternal(msg);
}
@@ -13,7 +13,7 @@ const shared = require('@joplin/lib/components/shared/note-screen-shared.js');
const { MarkupToHtml } = require('@joplin/renderer');
const time = require('@joplin/lib/time').default;
const ReactTooltip = require('react-tooltip');
const { urlDecode } = require('@joplin/lib/string-utils');
const { urlDecode, substrWithEllipsis } = require('@joplin/lib/string-utils');
const bridge = require('electron').remote.require('./bridge').default;
const markupLanguageUtils = require('../utils/markupLanguageUtils').default;
@@ -75,7 +75,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
this.setState({ restoring: true });
await RevisionService.instance().importRevisionNote(this.state.note);
this.setState({ restoring: false });
alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
alert(_('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(this.state.note.title, 0, 32), RevisionService.instance().restoreFolderTitle()));
}
backButton_click() {
@@ -2,7 +2,7 @@ import * as React from 'react';
import { useRef, useState, useEffect } from 'react';
import useWindowResizeEvent from './utils/useWindowResizeEvent';
import setLayoutItemProps from './utils/setLayoutItemProps';
import useLayoutItemSizes, { LayoutItemSizes, itemSize, calculateMaxSizeAvailableForItem, itemMinWidth, itemMinHeight } from './utils/useLayoutItemSizes';
import useLayoutItemSizes, { LayoutItemSizes, itemSize } from './utils/useLayoutItemSizes';
import validateLayout from './utils/validateLayout';
import { Size, LayoutItem } from './utils/types';
import { canMove, MoveDirection } from './utils/movements';
@@ -11,6 +11,9 @@ import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootWrapper, MoveModeRoot
import { Resizable } from 're-resizable';
const EventEmitter = require('events');
const itemMinWidth = 20;
const itemMinHeight = 20;
interface onResizeEvent {
layout: LayoutItem;
}
@@ -32,7 +35,7 @@ function itemVisible(item: LayoutItem, moveMode: boolean) {
return item.visible !== false;
}
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, resizedItemMaxSize: Size | null, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
const style: any = {
display: itemVisible(item, moveMode) ? 'flex' : 'none',
flexDirection: item.direction,
@@ -65,8 +68,6 @@ function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: Lay
enable={enable}
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
maxWidth={resizedItemMaxSize?.width}
maxHeight={resizedItemMaxSize?.height}
>
{children}
</Resizable>
@@ -113,7 +114,6 @@ function ResizableLayout(props: Props) {
key: item.key,
initialWidth: sizes[item.key].width,
initialHeight: sizes[item.key].height,
maxSize: calculateMaxSizeAvailableForItem(item, parent, sizes),
});
}
@@ -143,7 +143,6 @@ function ResizableLayout(props: Props) {
setResizedItem(null);
}
const resizedItemMaxSize = item.key === resizedItem?.key ? resizedItem.maxSize : null;
if (!item.children) {
const size = itemSize(item, parent, sizes, false);
@@ -156,7 +155,7 @@ function ResizableLayout(props: Props) {
const wrapper = renderItemWrapper(comp, item, parent, size, props.moveMode);
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
} else {
const childrenComponents = [];
for (let i = 0; i < item.children.length; i++) {
@@ -164,7 +163,7 @@ function ResizableLayout(props: Props) {
childrenComponents.push(renderLayoutItem(child, item, sizes, isVisible && itemVisible(child, props.moveMode), i === item.children.length - 1));
}
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
}
}
@@ -1,4 +1,4 @@
import useLayoutItemSizes, { itemSize, calculateMaxSizeAvailableForItem } from './useLayoutItemSizes';
import useLayoutItemSizes, { itemSize } from './useLayoutItemSizes';
import { LayoutItem, LayoutItemDirection } from './types';
import { renderHook } from '@testing-library/react-hooks';
import validateLayout from './validateLayout';
@@ -138,219 +138,4 @@ describe('useLayoutItemSizes', () => {
expect(itemSize(parent.children[1], parent, sizes, false)).toEqual({ width: 95, height: 50 });
});
test('should decrease size of the largest item if the total size would be larger than the container', () => {
const layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
width: 110,
},
{
key: 'col2',
width: 100,
},
{
key: 'col3',
minWidth: 50,
},
],
});
const { result } = renderHook(() => useLayoutItemSizes(layout));
const sizes = result.current;
expect(sizes.col1.width).toBe(50);
expect(sizes.col2.width).toBe(100);
expect(sizes.col3.width).toBe(50);
});
test('should not allow a minWidth of 0, should still make space for the item', () => {
const layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
width: 210,
},
{
key: 'col2',
minWidth: 0,
},
],
});
const { result } = renderHook(() => useLayoutItemSizes(layout));
const sizes = result.current;
expect(sizes.col1.width).toBe(160);
expect(sizes.col2.width).toBe(40); // default minWidth is 40
});
test('should ignore invisible items when counting remaining size', () => {
const layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
width: 110,
visible: false,
},
{
key: 'col2',
width: 100,
},
{
key: 'col3',
minWidth: 50,
},
],
});
const { result } = renderHook(() => useLayoutItemSizes(layout));
const sizes = result.current;
expect(sizes.col1.width).toBe(0);
expect(sizes.col2.width).toBe(100);
expect(sizes.col3.width).toBe(100);
});
test('should ignore invisible items when selecting largest child', () => {
const layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
width: 110,
visible: false,
},
{
key: 'col2',
width: 100,
},
{
key: 'col3',
width: 110,
},
{
key: 'col4',
minWidth: 50,
},
],
});
const { result } = renderHook(() => useLayoutItemSizes(layout));
const sizes = result.current;
expect(sizes.col1.width).toBe(0);
expect(sizes.col2.width).toBe(100);
expect(sizes.col3.width).toBe(50);
expect(sizes.col4.width).toBe(50);
});
});
describe('calculateMaxSizeAvailableForItem', () => {
test('should give maximum available space this item can take up during resizing', () => {
const layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
width: 50,
},
{
key: 'col2',
width: 70,
},
{
key: 'col3',
},
],
});
const { result } = renderHook(() => useLayoutItemSizes(layout));
const sizes = result.current;
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
const maxSize2 = calculateMaxSizeAvailableForItem(layout.children[1], layout, sizes);
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
expect(maxSize1.width).toBe(90); // 90 = layout.width - (col2.width + col3.minWidth(=40) )
expect(maxSize2.width).toBe(110); // 110 = layout.width - (col1.width + col3.minWidth(=40) )
});
test('should respect minimum sizes', () => {
const layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
width: 50,
},
{
key: 'col2',
width: 70,
},
{
key: 'col3',
minWidth: 60,
},
],
});
const { result } = renderHook(() => useLayoutItemSizes(layout));
const sizes = result.current;
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
const maxSize2 = calculateMaxSizeAvailableForItem(layout.children[1], layout, sizes);
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
expect(maxSize1.width).toBe(70); // 70 = layout.width - (col2.width + col3.minWidth)
expect(maxSize2.width).toBe(90); // 90 = layout.width - (col1.width + col3.minWidth)
});
test('should not allow a minWidth of 0, should still leave space for the item', () => {
const layout: LayoutItem = validateLayout({
key: 'root',
width: 200,
height: 100,
direction: LayoutItemDirection.Row,
children: [
{
key: 'col1',
width: 50,
},
{
key: 'col2',
minWidth: 0,
},
],
});
const { result } = renderHook(() => useLayoutItemSizes(layout));
const sizes = result.current;
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
expect(maxSize1.width).toBe(160); // 160 = layout.width - col2.minWidth(=40)
});
});
@@ -3,9 +3,6 @@ import { LayoutItem, Size } from './types';
const dragBarThickness = 5;
export const itemMinWidth = 40;
export const itemMinHeight = 40;
export interface LayoutItemSizes {
[key: string]: Size;
}
@@ -20,8 +17,8 @@ export function itemSize(item: LayoutItem, parent: LayoutItem | null, sizes: Lay
const bottomGap = !isContainer && (item.resizableBottom || parentResizableBottom) ? dragBarThickness : 0;
return {
width: sizes[item.key].width - rightGap,
height: sizes[item.key].height - bottomGap,
width: ('width' in item ? item.width : sizes[item.key].width) - rightGap,
height: ('height' in item ? item.height : sizes[item.key].height) - bottomGap,
};
}
@@ -41,10 +38,6 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
const noWidthChildren: any[] = [];
const noHeightChildren: any[] = [];
// The minimum space required for items with no defined size
let noWidthChildrenMinWidth = 0;
let noHeightChildrenMinHeight = 0;
for (const child of item.children) {
let w = 'width' in child ? child.width : null;
let h = 'height' in child ? child.height : null;
@@ -54,43 +47,10 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
}
sizes[child.key] = { width: w, height: h };
if (w !== null) remainingSize.width -= w;
if (h !== null) remainingSize.height -= h;
if (w === null) {
noWidthChildren.push({ item: child, parent: item });
noWidthChildrenMinWidth += child.minWidth || itemMinWidth;
}
if (h === null) {
noHeightChildren.push({ item: child, parent: item });
noHeightChildrenMinHeight += child.minHeight || itemMinHeight;
}
}
while (remainingSize.width < noWidthChildrenMinWidth) {
// There is not enough space, the widest item will be made smaller
let widestChild = item.children[0].key;
for (const child of item.children) {
if (!child.visible) continue;
if (sizes[child.key].width > sizes[widestChild].width) widestChild = child.key;
}
const dw = Math.abs(remainingSize.width - noWidthChildrenMinWidth);
sizes[widestChild].width -= dw;
remainingSize.width += dw;
}
while (remainingSize.height < noHeightChildrenMinHeight) {
// There is not enough space, the tallest item will be made smaller
let tallestChild = item.children[0].key;
for (const child of item.children) {
if (!child.visible) continue;
if (sizes[child.key].height > sizes[tallestChild].height) tallestChild = child.key;
}
const dh = Math.abs(remainingSize.height - noHeightChildrenMinHeight);
sizes[tallestChild].height -= dh;
remainingSize.height += dh;
if (w === null) noWidthChildren.push({ item: child, parent: item });
if (h === null) noHeightChildren.push({ item: child, parent: item });
}
if (noWidthChildren.length) {
@@ -117,24 +77,6 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
return sizes;
}
// Gives the maximum available space for this item that it can take up during resizing
// availableSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
export function calculateMaxSizeAvailableForItem(item: LayoutItem, parent: LayoutItem, sizes: LayoutItemSizes): Size {
const availableSize: Size = { ...sizes[parent.key] };
for (const sibling of parent.children) {
if (!sibling.visible) continue;
availableSize.width -= 'width' in sibling ? sizes[sibling.key].width : (sibling.minWidth || itemMinWidth);
availableSize.height -= 'height' in sibling ? sizes[sibling.key].height : (sibling.minHeight || itemMinHeight);
}
availableSize.width += sizes[item.key].width;
availableSize.height += sizes[item.key].height;
return availableSize;
}
export default function useLayoutItemSizes(layout: LayoutItem, makeAllVisible: boolean = false) {
return useMemo(() => {
let sizes: LayoutItemSizes = {};
+1 -5
View File
@@ -449,11 +449,7 @@ class SidebarComponent extends React.Component<Props, State> {
renderTag(tag: any, selected: boolean) {
const anchorRef = this.anchorItemRef('tag', tag.id);
let noteCount = null;
if (Setting.value('showNoteCounts')) {
if (Setting.value('showCompletedTodos')) noteCount = this.renderNoteCount(tag.note_count);
else noteCount = this.renderNoteCount(tag.note_count - tag.todo_completed_count);
}
const noteCount = Setting.value('showNoteCounts') ? this.renderNoteCount(tag.note_count) : '';
return (
<StyledListItem selected={selected}
@@ -147,10 +147,7 @@ function StatusScreen(props: Props) {
}
if (section.canRetryAll) {
itemsHtml.push(renderSectionRetryAllHtml(section.title, async () => {
await section.retryAllHandler();
void resfreshScreen();
}));
itemsHtml.push(renderSectionRetryAllHtml(section.title, section.retryAllHandler));
}
return <div key={key}>{itemsHtml}</div>;
@@ -1,5 +1,6 @@
import { utils as pluginUtils, PluginStates } from '@joplin/lib/services/plugins/reducer';
import CommandService from '@joplin/lib/services/CommandService';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
import eventManager from '@joplin/lib/eventManager';
import InteropService from '@joplin/lib/services/interop/InteropService';
import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
@@ -133,7 +134,7 @@ export default class NoteListUtils {
})
);
if ([9, 10].includes(Setting.value('sync.target'))) {
if (Setting.value('sync.target') === SyncTargetJoplinServer.id()) {
menu.append(
new MenuItem(
menuUtils.commandToStatefulMenuItem('showShareNoteDialog', noteIds.slice())
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.0.5",
"version": "2.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.0.5",
"version": "2.0.1",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
+5 -26
View File
@@ -47,12 +47,6 @@ interface State {
listType: number;
showHelp: boolean;
resultsInBody: boolean;
commandArgs: string[];
}
interface CommandQuery {
name: string;
args: string[];
}
class GotoAnything {
@@ -93,7 +87,6 @@ class Dialog extends React.PureComponent<Props, State> {
listType: BaseModel.TYPE_NOTE,
showHelp: false,
resultsInBody: false,
commandArgs: [],
};
this.styles_ = {};
@@ -257,15 +250,6 @@ class Dialog extends React.PureComponent<Props, State> {
return this.markupToHtml_;
}
private parseCommandQuery(query: string): CommandQuery {
const fullQuery = query;
const splitted = fullQuery.split(/\s+/);
return {
name: splitted.length ? splitted[0] : '',
args: splitted.slice(1),
};
}
async updateList() {
let resultsInBody = false;
@@ -276,16 +260,13 @@ class Dialog extends React.PureComponent<Props, State> {
let listType = null;
let searchQuery = '';
let keywords = null;
let commandArgs: string[] = [];
if (this.state.query.indexOf(':') === 0) { // COMMANDS
const commandQuery = this.parseCommandQuery(this.state.query.substr(1));
const query = this.state.query.substr(1);
listType = BaseModel.TYPE_COMMAND;
keywords = [commandQuery.name];
commandArgs = commandQuery.args;
keywords = [query];
const commandResults = CommandService.instance().searchCommands(commandQuery.name, true);
const commandResults = CommandService.instance().searchCommands(query, true);
results = commandResults.map((result: CommandSearchResult) => {
return {
@@ -386,7 +367,6 @@ class Dialog extends React.PureComponent<Props, State> {
keywords: keywords ? keywords : await this.keywords(searchQuery),
selectedItemId: results.length === 0 ? null : results[0].id,
resultsInBody: resultsInBody,
commandArgs: commandArgs,
});
}
}
@@ -399,7 +379,7 @@ class Dialog extends React.PureComponent<Props, State> {
});
if (item.type === BaseModel.TYPE_COMMAND) {
void CommandService.instance().execute(item.id, ...item.commandArgs);
void CommandService.instance().execute(item.id);
void focusEditorIfEditorCommand(item.id, CommandService.instance());
return;
}
@@ -446,7 +426,6 @@ class Dialog extends React.PureComponent<Props, State> {
id: itemId,
parent_id: parentId,
type: itemType,
commandArgs: this.state.commandArgs,
});
}
@@ -487,7 +466,7 @@ class Dialog extends React.PureComponent<Props, State> {
selectedItem() {
const index = this.selectedItemIndex();
if (index < 0) return null;
return { ...this.state.results[index], commandArgs: this.state.commandArgs };
return this.state.results[index];
}
input_onKeyDown(event: any) {
+3 -3
View File
@@ -26,12 +26,12 @@ if [ "$RESET_ALL" == "1" ]; then
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 9" >> "$CMD_FILE"
echo "config sync.9.path http://api-joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.9.path http://localhost:22300" >> "$CMD_FILE"
echo "config sync.9.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.9.password 123456" >> "$CMD_FILE"
if [ "$USER_NUM" == "1" ]; then
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api-joplincloud.local:22300/api/debug
if [ "$1" == "1" ]; then
curl --data '{"action": "createTestUsers"}' http://localhost:22300/api/debug
echo 'mkbook "shared"' >> "$CMD_FILE"
echo 'mkbook "other"' >> "$CMD_FILE"
@@ -16,7 +16,6 @@ import com.facebook.soloader.SoLoader;
import net.cozic.joplin.share.SharePackage;
import net.cozic.joplin.ssl.SslPackage;
import net.cozic.joplin.textinput.TextInputPackage;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -45,7 +44,6 @@ public class MainApplication extends Application implements ReactApplication {
// Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new SharePackage());
packages.add(new SslPackage());
packages.add(new TextInputPackage());
return packages;
}
@@ -1,63 +0,0 @@
package net.cozic.joplin.textinput;
import android.text.Selection;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.views.textinput.ReactEditText;
import com.facebook.react.views.textinput.ReactTextInputManager;
import java.util.Collections;
import java.util.List;
/**
* This class provides a workaround for <a href="https://github.com/facebook/react-native/issues/29911">
* https://github.com/facebook/react-native/issues/29911</a>
*
* The reason the editor is scrolled seems to be due to this block in
* <pre>android.widget.Editor#onFocusChanged:</pre>
*
* <pre>
* // The DecorView does not have focus when the 'Done' ExtractEditText button is
* // pressed. Since it is the ViewAncestor's mView, it requests focus before
* // ExtractEditText clears focus, which gives focus to the ExtractEditText.
* // This special case ensure that we keep current selection in that case.
* // It would be better to know why the DecorView does not have focus at that time.
* if (((mTextView.isInExtractedMode()) || mSelectionMoved)
* && selStart >= 0 && selEnd >= 0) {
* Selection.setSelection((Spannable)mTextView.getText(),selStart,selEnd);
* }
* </pre>
* When using native Android TextView mSelectionMoved is false so this block is skipped,
* with RN however it's true and this is where the scrolling comes from.
*
* The below workaround resets the selection before a focus event is passed on to the native component.
* This way when the above condition is reached <pre>selStart == selEnd == -1</pre> and no scrolling
* happens.
*/
public class TextInputPackage implements com.facebook.react.ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.singletonList(new ReactTextInputManager() {
@Override
public void receiveCommand(ReactEditText reactEditText, String commandId, @Nullable ReadableArray args) {
if ("focus".equals(commandId) || "focusTextInput".equals(commandId)) {
Selection.removeSelection(reactEditText.getText());
}
super.receiveCommand(reactEditText, commandId, args);
}
});
}
}
@@ -105,7 +105,7 @@ class SearchScreenComponent extends BaseScreenComponent {
if (query) {
if (this.props.settings['db.ftsEnabled']) {
notes = await SearchEngineUtils.notesForQuery(query, true);
notes = await SearchEngineUtils.notesForQuery(query);
} else {
const p = query.split(' ');
const temp = [];
+5 -5
View File
@@ -4905,11 +4905,6 @@
"resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.6.tgz",
"integrity": "sha512-JNAkmPeB/GS2tCRqUzRPsTOHpGDah7xP18vGJfIjZC+W2sxEHbxgJxetIjIqhjQ3yYbYNEELkM/spKLtwoOSUQ=="
},
"joplin-rn-alarm-notification": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/joplin-rn-alarm-notification/-/joplin-rn-alarm-notification-1.0.3.tgz",
"integrity": "sha512-HZGDrLmYf6aMVgzk02w4DS9CjaTogE1hnOLdMDsrWkZzRskO6g3bZw+Bwlc63cCX4ZLZeeWIaABzHoWKAbLzpQ=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7102,6 +7097,11 @@
"prop-types": "^15.5.10"
}
},
"react-native-alarm-notification": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/react-native-alarm-notification/-/react-native-alarm-notification-1.7.1.tgz",
"integrity": "sha512-cvfSqCCfw48NyeFTEL5WOF/tkeWLNI7X1mVoEQ/9aY+2fuBtkCfZUoJ7vvOOHeryPbDJrlDNpRWTi3erLphZ+w=="
},
"react-native-camera": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-3.40.0.tgz",
-2
View File
@@ -25,7 +25,6 @@ import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtil
import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile';
import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale';
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform } = require('react-native');
@@ -91,7 +90,6 @@ SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
import FsDriverRN from './utils/fs-driver-rn';
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/fork-htmlparser2",
"version": "4.1.26",
"version": "4.1.24",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@joplin/fork-htmlparser2",
"description": "Fast & forgiving HTML/XML/RSS parser",
"version": "4.1.26",
"version": "4.1.24",
"author": "Felix Boehm <me@feedic.com>",
"publishConfig": {
"access": "public"
+2 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/fork-sax",
"version": "1.2.30",
"version": "1.2.28",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -143,12 +143,6 @@
}
}
},
"balanced-match": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz",
"integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=",
"dev": true
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
@@ -181,11 +175,9 @@
},
"brace-expansion": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
"integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=",
"resolved": "",
"dev": true,
"requires": {
"balanced-match": "^0.3.0",
"concat-map": "0.0.1"
}
},
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@joplin/fork-sax",
"description": "An evented streaming XML parser in JavaScript",
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
"version": "1.2.30",
"version": "1.2.28",
"main": "lib/sax.js",
"publishConfig": {
"access": "public"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "generator-joplin",
"version": "2.0.0",
"version": "1.8.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+2 -11
View File
@@ -1,4 +1,4 @@
import Setting, { Env } from './models/Setting';
import Setting from './models/Setting';
import Logger, { TargetType, LoggerWrapper } from './Logger';
import shim from './shim';
import BaseService from './services/BaseService';
@@ -46,7 +46,6 @@ const { loadKeychainServiceAndSettings } = require('./services/SettingUtils');
import MigrationService from './services/MigrationService';
import ShareService from './services/share/ShareService';
import handleSyncStartupOperation from './services/synchronizer/utils/handleSyncStartupOperation';
import SyncTargetJoplinCloud from './SyncTargetJoplinCloud';
const { toSystemSlashes } = require('./path-utils');
const { setAutoFreeze } = require('immer');
@@ -313,7 +312,7 @@ export default class BaseApplication {
notes = await Tag.notes(parentId, options);
} else if (parentType === BaseModel.TYPE_SEARCH) {
const search = BaseModel.byId(state.searches, parentId);
notes = await SearchEngineUtils.notesForQuery(search.query_pattern, true);
notes = await SearchEngineUtils.notesForQuery(search.query_pattern);
const parsedQuery = await SearchEngine.instance().parseQuery(search.query_pattern);
highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
} else if (parentType === BaseModel.TYPE_SMART_FILTER) {
@@ -692,7 +691,6 @@ export default class BaseApplication {
SyncTargetRegistry.addClass(SyncTargetDropbox);
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
try {
await shim.fsDriver().remove(tempDir);
@@ -765,13 +763,6 @@ export default class BaseApplication {
setLocale(Setting.value('locale'));
}
if (Setting.value('env') === Env.Dev) {
Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');
// Setting.setValue('sync.10.path', 'http://api-joplincloud.local:22300');
// Setting.setValue('sync.10.userContentPath', 'http://joplinusercontent.local:22300');
}
// For now always disable fuzzy search due to performance issues:
// https://discourse.joplinapp.org/t/1-1-4-keyboard-locks-up-while-typing/11231/11
// https://discourse.joplinapp.org/t/serious-lagging-when-there-are-tens-of-thousands-of-notes/11215/23
+1 -13
View File
@@ -343,7 +343,7 @@ export default class JoplinDatabase extends Database {
// must be set in the synchronizer too.
// Note: v16 and v17 don't do anything. They were used to debug an issue.
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38];
const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37];
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
@@ -876,18 +876,6 @@ export default class JoplinDatabase extends Database {
queries.push('ALTER TABLE resources ADD COLUMN share_id TEXT NOT NULL DEFAULT ""');
}
if (targetVersion == 38) {
queries.push('DROP VIEW tags_with_note_count');
queries.push(`CREATE VIEW tags_with_note_count AS
SELECT tags.id as id, tags.title as title, tags.created_time as created_time, tags.updated_time as updated_time, COUNT(notes.id) as note_count,
SUM(CASE WHEN notes.todo_completed > 0 THEN 1 ELSE 0 END) AS todo_completed_count
FROM tags
LEFT JOIN note_tags nt on nt.tag_id = tags.id
LEFT JOIN notes on notes.id = nt.note_id
WHERE notes.id IS NOT NULL
GROUP BY tags.id`);
}
const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] };
queries.push(updateVersionQuery);
+13
View File
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class JoplinError extends Error {
constructor(message, code = null, details = null) {
super(message);
this.code = null;
this.details = '';
this.code = code;
this.details = details;
}
}
exports.default = JoplinError;
//# sourceMappingURL=JoplinError.js.map
+70 -109
View File
@@ -3,14 +3,10 @@ import { _ } from './locale';
const { rtrimSlashes } = require('./path-utils.js');
import JoplinError from './JoplinError';
import { Env } from './models/Setting';
import Logger from './Logger';
const { stringify } = require('query-string');
const logger = Logger.create('JoplinServerApi');
interface Options {
baseUrl(): string;
userContentBaseUrl(): string;
username(): string;
password(): string;
env?: Env;
@@ -48,7 +44,7 @@ export default class JoplinServerApi {
this.options_ = options;
if (options.env === Env.Dev) {
// this.debugRequests_ = true;
this.debugRequests_ = true;
}
}
@@ -56,24 +52,15 @@ export default class JoplinServerApi {
return rtrimSlashes(this.options_.baseUrl());
}
public userContentBaseUrl() {
return this.options_.userContentBaseUrl() || this.baseUrl();
}
private async session() {
if (this.session_) return this.session_;
try {
this.session_ = await this.exec('POST', 'api/sessions', null, {
email: this.options_.username(),
password: this.options_.password(),
});
this.session_ = await this.exec('POST', 'api/sessions', null, {
email: this.options_.username(),
password: this.options_.password(),
});
return this.session_;
} catch (error) {
logger.error('Could not acquire session:', error.details, '\n', error);
throw error;
}
return this.session_;
}
private async sessionId() {
@@ -146,97 +133,71 @@ export default class JoplinServerApi {
url += stringify(query);
}
const startTime = Date.now();
let response: any = null;
try {
if (this.debugRequests_) {
logger.debug(this.requestToCurl_(url, fetchOptions));
}
let response: any = null;
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
if (fetchOptions.path) {
const fileStat = await shim.fsDriver().stat(fetchOptions.path);
if (fileStat) fetchOptions.headers['Content-Length'] = `${fileStat.size}`;
}
response = await shim.uploadBlob(url, fetchOptions);
} else if (options.target == 'string') {
if (typeof body === 'string') fetchOptions.headers['Content-Length'] = `${shim.stringByteLength(body)}`;
response = await shim.fetch(url, fetchOptions);
} else {
// file
response = await shim.fetchBlob(url, fetchOptions);
}
const responseText = await response.text();
if (this.debugRequests_) {
logger.debug('Response', Date.now() - startTime, options.responseFormat, responseText);
}
const shortResponseText = () => {
return (`${responseText}`).substr(0, 1024);
};
// Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier
const newError = (message: string, code: number = 0) => {
// Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of
// JSON. That way the error message will still show there's a problem but without filling up the log or screen.
// return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code);
return new JoplinError(message, code, `${method} ${path}: ${message} (${code}): ${shortResponseText()}`);
};
let responseJson_: any = null;
const loadResponseJson = async () => {
if (!responseText) return null;
if (responseJson_) return responseJson_;
responseJson_ = JSON.parse(responseText);
if (!responseJson_) throw newError('Cannot parse JSON response', response.status);
return responseJson_;
};
if (!response.ok) {
if (options.target === 'file') throw newError('fetchBlob error', response.status);
let json = null;
try {
json = await loadResponseJson();
} catch (error) {
// Just send back the plain text in newErro()
}
if (json && json.error) {
throw newError(`${json.error}`, json.code ? json.code : response.status);
}
// "Unknown error" means it probably wasn't generated by the
// application but for example by the Nginx or Apache reverse
// proxy. So in that case we attach the response content to the
// error message so that it shows up in logs. It might be for
// example an error returned by the Nginx or Apache reverse
// proxy. For example:
//
// <html>
// <head><title>413 Request Entity Too Large</title></head>
// <body>
// <center><h1>413 Request Entity Too Large</h1></center>
// <hr><center>nginx/1.18.0 (Ubuntu)</center>
// </body>
// </html>
throw newError(`Unknown error: ${shortResponseText()}`, response.status);
}
if (options.responseFormat === 'text') return responseText;
const output = await loadResponseJson();
return output;
} catch (error) {
if (error.code !== 404) {
logger.warn(this.requestToCurl_(url, fetchOptions));
logger.warn(error);
}
throw error;
if (this.debugRequests_) {
console.info('Joplin API Call', `${method} ${url}`, headers, options);
console.info(this.requestToCurl_(url, fetchOptions));
}
if (options.source == 'file' && (method == 'POST' || method == 'PUT')) {
if (fetchOptions.path) {
const fileStat = await shim.fsDriver().stat(fetchOptions.path);
if (fileStat) fetchOptions.headers['Content-Length'] = `${fileStat.size}`;
}
response = await shim.uploadBlob(url, fetchOptions);
} else if (options.target == 'string') {
if (typeof body === 'string') fetchOptions.headers['Content-Length'] = `${shim.stringByteLength(body)}`;
response = await shim.fetch(url, fetchOptions);
} else {
// file
response = await shim.fetchBlob(url, fetchOptions);
}
const responseText = await response.text();
if (this.debugRequests_) {
console.info('Joplin API Response', responseText);
}
// Creates an error object with as much data as possible as it will appear in the log, which will make debugging easier
const newError = (message: string, code: number = 0) => {
// Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of
// JSON. That way the error message will still show there's a problem but without filling up the log or screen.
const shortResponseText = (`${responseText}`).substr(0, 1024);
// return new JoplinError(`${method} ${path}: ${message} (${code}): ${shortResponseText}`, code);
return new JoplinError(message, code, `${method} ${path}: ${message} (${code}): ${shortResponseText}`);
};
let responseJson_: any = null;
const loadResponseJson = async () => {
if (!responseText) return null;
if (responseJson_) return responseJson_;
responseJson_ = JSON.parse(responseText);
if (!responseJson_) throw newError('Cannot parse JSON response', response.status);
return responseJson_;
};
if (!response.ok) {
if (options.target === 'file') throw newError('fetchBlob error', response.status);
let json = null;
try {
json = await loadResponseJson();
} catch (error) {
// Just send back the plain text in newErro()
}
if (json && json.error) {
throw newError(`${json.error}`, json.code ? json.code : response.status);
}
throw newError('Unknown error', response.status);
}
if (options.responseFormat === 'text') return responseText;
const output = await loadResponseJson();
return output;
}
}
-59
View File
@@ -1,59 +0,0 @@
import Setting from './models/Setting';
import Synchronizer from './Synchronizer';
import { _ } from './locale.js';
import BaseSyncTarget from './BaseSyncTarget';
import { FileApi } from './file-api';
import SyncTargetJoplinServer, { initFileApi } from './SyncTargetJoplinServer';
interface FileApiOptions {
path(): string;
userContentPath(): string;
username(): string;
password(): string;
}
export default class SyncTargetJoplinCloud extends BaseSyncTarget {
public static id() {
return 10;
}
public static supportsConfigCheck() {
return SyncTargetJoplinServer.supportsConfigCheck();
}
public static targetName() {
return 'joplinCloud';
}
public static label() {
return _('Joplin Cloud');
}
public async isAuthenticated() {
return true;
}
public async fileApi(): Promise<FileApi> {
return super.fileApi();
}
public static async checkConfig(options: FileApiOptions) {
return SyncTargetJoplinServer.checkConfig({
...options,
}, SyncTargetJoplinCloud.id());
}
protected async initFileApi() {
return initFileApi(SyncTargetJoplinCloud.id(), this.logger(), {
path: () => Setting.value('sync.10.path'),
userContentPath: () => Setting.value('sync.10.userContentPath'),
username: () => Setting.value('sync.10.username'),
password: () => Setting.value('sync.10.password'),
});
}
protected async initSynchronizer() {
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));
}
}
+23 -31
View File
@@ -5,38 +5,13 @@ import { _ } from './locale.js';
import JoplinServerApi from './JoplinServerApi';
import BaseSyncTarget from './BaseSyncTarget';
import { FileApi } from './file-api';
import Logger from './Logger';
interface FileApiOptions {
path(): string;
userContentPath(): string;
username(): string;
password(): string;
}
export async function newFileApi(id: number, options: FileApiOptions) {
const apiOptions = {
baseUrl: () => options.path(),
userContentBaseUrl: () => options.userContentPath(),
username: () => options.username(),
password: () => options.password(),
env: Setting.value('env'),
};
const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(id);
await fileApi.initialize();
return fileApi;
}
export async function initFileApi(syncTargetId: number, logger: Logger, options: FileApiOptions) {
const fileApi = await newFileApi(syncTargetId, options);
fileApi.setLogger(logger);
return fileApi;
}
export default class SyncTargetJoplinServer extends BaseSyncTarget {
public static id() {
@@ -63,16 +38,30 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
return super.fileApi();
}
public static async checkConfig(options: FileApiOptions, syncTargetId: number = null) {
private static async newFileApi_(options: FileApiOptions) {
const apiOptions = {
baseUrl: () => options.path(),
username: () => options.username(),
password: () => options.password(),
env: Setting.value('env'),
};
const api = new JoplinServerApi(apiOptions);
const driver = new FileApiDriverJoplinServer(api);
const fileApi = new FileApi('', driver);
fileApi.setSyncTargetId(this.id());
await fileApi.initialize();
return fileApi;
}
public static async checkConfig(options: FileApiOptions) {
const output = {
ok: false,
errorMessage: '',
};
syncTargetId = syncTargetId === null ? SyncTargetJoplinServer.id() : syncTargetId;
try {
const fileApi = await newFileApi(syncTargetId, options);
const fileApi = await SyncTargetJoplinServer.newFileApi_(options);
fileApi.requestRepeatCount_ = 0;
await fileApi.put('testing.txt', 'testing');
@@ -89,12 +78,15 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
}
protected async initFileApi() {
return initFileApi(SyncTargetJoplinServer.id(), this.logger(), {
const fileApi = await SyncTargetJoplinServer.newFileApi_({
path: () => Setting.value('sync.9.path'),
userContentPath: () => Setting.value('sync.9.userContentPath'),
username: () => Setting.value('sync.9.username'),
password: () => Setting.value('sync.9.password'),
});
fileApi.setLogger(this.logger());
return fileApi;
}
protected async initSynchronizer() {
+1 -1
View File
@@ -24,7 +24,7 @@ class SyncTargetRegistry {
if (!this.reg_.hasOwnProperty(n)) continue;
if (this.reg_[n].name === name) return this.reg_[n].id;
}
throw new Error(`Name not found: ${name}. Was the sync target registered?`);
throw new Error(`Name not found: ${name}`);
}
static idToMetadata(id) {
-1
View File
@@ -926,7 +926,6 @@ export default class Synchronizer {
this.logger().error(error);
} else {
this.logger().error(error);
if (error.details) this.logger().error('Details:', error.details);
// Don't save to the report errors that are due to things like temporary network errors or timeout.
if (!shim.fetchRequestCanBeRetried(error)) {
+2 -20
View File
@@ -80,19 +80,7 @@ export default class FileApiDriverJoplinServer {
const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/delta`, query);
const stats = response.items
.filter((item: any) => {
// We don't need to know about lock changes, since this
// is handled by the LockHandler.
if (item.item_name.indexOf('locks/') === 0) return false;
// We don't need to sync what's in the temp folder
if (item.item_name.indexOf('temp/') === 0) return false;
// Although we sync the content of .resource, whether we
// fetch or upload data to it is driven by the
// associated resource item (.md) file. So at this point
// we don't want to automatically fetch from it.
if (item.item_name.indexOf('.resource/') === 0) return false;
return true;
return item.item_name.indexOf('locks/') !== 0 && item.item_name.indexOf('temp/') !== 0;
})
.map((item: any) => {
return this.metadataToStat_(item, item.item_name, item.type === 3, '');
@@ -183,12 +171,6 @@ export default class FileApiDriverJoplinServer {
}
public async clearRoot(path: string) {
const response = await this.list(path);
for (const item of response.items) {
await this.delete(item.path);
}
if (response.has_more) throw new Error('has_more support not implemented');
await this.delete(path);
}
}
+10 -1
View File
@@ -1,8 +1,17 @@
const { afterEachCleanUp } = require('./testing/test-utils.js');
const { shimInit } = require('./shim-init-node.js');
const shim = require('./shim').default;
const sharp = require('sharp');
shimInit(sharp, null);
let keytar;
try {
keytar = shim.platformSupportsKeyChain() ? require('keytar') : null;
} catch (error) {
console.error('Cannot load keytar - keychain support will be disabled', error);
keytar = null;
}
shimInit(sharp, keytar);
global.afterEach(async () => {
await afterEachCleanUp();
+21 -21
View File
@@ -41,45 +41,45 @@ locales['uk_UA'] = require('./uk_UA.json');
locales['vi'] = require('./vi.json');
locales['zh_CN'] = require('./zh_CN.json');
locales['zh_TW'] = require('./zh_TW.json');
stats['ar'] = {"percentDone":95};
stats['ar'] = {"percentDone":96};
stats['eu'] = {"percentDone":30};
stats['bs_BA'] = {"percentDone":74};
stats['bg_BG'] = {"percentDone":57};
stats['ca'] = {"percentDone":82};
stats['bs_BA'] = {"percentDone":75};
stats['bg_BG'] = {"percentDone":58};
stats['ca'] = {"percentDone":83};
stats['hr_HR'] = {"percentDone":96};
stats['cs_CZ'] = {"percentDone":85};
stats['da_DK'] = {"percentDone":95};
stats['de_DE'] = {"percentDone":94};
stats['et_EE'] = {"percentDone":56};
stats['cs_CZ'] = {"percentDone":86};
stats['da_DK'] = {"percentDone":96};
stats['de_DE'] = {"percentDone":95};
stats['et_EE'] = {"percentDone":57};
stats['en_GB'] = {"percentDone":100};
stats['en_US'] = {"percentDone":100};
stats['es_ES'] = {"percentDone":94};
stats['eo'] = {"percentDone":32};
stats['eo'] = {"percentDone":33};
stats['fi_FI'] = {"percentDone":94};
stats['fr_FR'] = {"percentDone":98};
stats['fr_FR'] = {"percentDone":99};
stats['gl_ES'] = {"percentDone":38};
stats['id_ID'] = {"percentDone":92};
stats['it_IT'] = {"percentDone":99};
stats['id_ID'] = {"percentDone":93};
stats['it_IT'] = {"percentDone":94};
stats['hu_HU'] = {"percentDone":88};
stats['nl_BE'] = {"percentDone":91};
stats['nl_NL'] = {"percentDone":94};
stats['nb_NO'] = {"percentDone":75};
stats['nl_BE'] = {"percentDone":92};
stats['nl_NL'] = {"percentDone":95};
stats['nb_NO'] = {"percentDone":76};
stats['fa'] = {"percentDone":71};
stats['pl_PL'] = {"percentDone":94};
stats['pt_BR'] = {"percentDone":94};
stats['pt_PT'] = {"percentDone":94};
stats['ro'] = {"percentDone":66};
stats['sl_SI'] = {"percentDone":95};
stats['sl_SI'] = {"percentDone":96};
stats['sv'] = {"percentDone":61};
stats['th_TH'] = {"percentDone":45};
stats['vi'] = {"percentDone":73};
stats['tr_TR'] = {"percentDone":94};
stats['uk_UA'] = {"percentDone":94};
stats['el_GR'] = {"percentDone":96};
stats['el_GR'] = {"percentDone":97};
stats['ru_RU'] = {"percentDone":94};
stats['sr_RS'] = {"percentDone":71};
stats['zh_CN'] = {"percentDone":99};
stats['zh_TW'] = {"percentDone":99};
stats['ja_JP'] = {"percentDone":96};
stats['ko'] = {"percentDone":99};
stats['zh_CN'] = {"percentDone":94};
stats['zh_TW'] = {"percentDone":92};
stats['ja_JP'] = {"percentDone":97};
stats['ko'] = {"percentDone":96};
module.exports = { locales: locales, stats: stats };
+1 -1
View File
@@ -1 +1 @@
{"OK":"OK","Cancel":"Annulla","The app is now going to close. Please relaunch it to complete the process.":"Ora l'applicazione si chiuderà. Per favore riavviala per completare il processo.","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"Scrivi il titolo di una nota o parte del suo contenuto per aprirla. Oppure digita # seguito dal nome di un'etichetta, oppure @ seguito dal nome di un taccuino. O scrivi : per cercare un comando.","Goto Anything...":"Cerca ovunque...","Command palette":"Comandi","Exporting to \"%s\" as \"%s\" format. Please wait...":"Esportazione da \"%s\" in formato \"%s\". Per favore attendere...","Could not export notes: %s":"Impossibile esportare le note: %s","Current version is up-to-date.":"La versione attuale è aggiornata.","%s (pre-release)":"%s (pre-rilascio)","An update is available, do you want to download it now?":"È disponibile un aggiornamento, vuoi scaricarlo ora?","Your version: %s":"La tua versione: %s","New version: %s":"Nuova versione: %s","Download":"Scarica","Skip this version":"Salta questa versione","Full changelog":"Changelog completo","The note \"%s\" has been successfully restored to the notebook \"%s\".":"La nota \"%s\" è stata ripristinata con successo nel taccuino \"%s\".","This note has no history":"Questa nota non ha cronologia","Unsupported link or message: %s":"Link o messaggio non supportato: %s","Restore":"Ripristina","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"Clicca \"%s\" per ripristinare la nota. Verrà copiata nel taccuino denominato \"%s\". La versione corrente della nota non verrà sostituita o modificata.","Back":"Indietro","Remove":"Rimuovi","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"Eliminare taccuino \"%s\"?\n\nAnche tutte le note e cartelle di questo taccuino saranno eliminati.","Delete":"Elimina","Remove tag \"%s\" from all notes?":"Rimuovere l'etichetta \"%s\" da tutte le note?","Remove this search from the sidebar?":"Rimuovere questa ricerca dalla barra laterale?","Export":"Esporta","All notes":"Tutte le note","Synchronise":"Sincronizza","Notebooks":"Taccuini","Tags":"Etichette","Decrypting items: %d/%d":"Decrittografia elementi: %d/%d","Fetching resources: %d/%d":"Recupero risorse: %d/%d","Sidebar":"Barra laterale","Focus":"Focus","Please select where the sync status should be exported to":"Per favore selezionare dove lo stato della sincronizzazione deve essere esportato","Retry All":"Riprova tutti","Retry":"Riprova","Export debug report":"Esporta il report di debug","strong text":"testo in grassetto","emphasised text":"testo sottolineato","Insert Hyperlink":"Inserire collegamento ipertestuale","List item":"Elemento della lista","Layout":"Disposizione","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"Questa nota non ha contenuto. Fai clic su \"%s\" per attivare l'editor e modificare la nota.","Cut":"Taglia","Copy":"Copia","Paste":"Incolla","Checkbox list":"Lista di caselle di spunta","Highlight":"Evidenzia","Strikethrough":"Barrato","Insert":"Inserisci","Superscript":"Apice","Subscript":"Pedice","Edit":"Modifica","Attach file":"Allega file","Code Block":"Blocco di codice","Inline Code":"Codice inline","Insert Date Time":"Inserisci data e ora","Drop notes or files here":"Trascina qui note o file","Code View":"Codice","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"Attendi che tutti gli allegati vengano scaricati e decrittografati. Puoi anche passare a %s per modificare la nota.","There was an error downloading this attachment:":"Errore nello scaricamento di questo allegato:","This attachment is not downloaded or not decrypted yet":"Questo allegato non è ancora stato scaricato o decriptato","Open...":"Apri...","Save as...":"Salva come...","Reveal file in folder":"Visualizza file nella cartella","Copy path to clipboard":"Copia il percorso negli appunti","Copy Link Address":"Copia l'indirizzo del link","to-do":"attività","note":"nota","Creating new %s...":"Creare nuovo %s...","Click to add tags...":"Clicca per aggiungere etichetta...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"Questo editor di testo RTF ha un certo numero di limitazioni e si raccomanda di conoscerle prima di utilizzarlo.","Read more about it":"Per saperne di più","Dismiss":"Non visualizzare più","The following attachments are being watched for changes:":"I seguenti allegati sono monitorati per eventuali modifiche:","The attachments will no longer be watched when you switch to a different note.":"Gli allegati non verranno più monitorati quando passerai ad una nota differente.","In: %s":"In: %s","Select all":"Seleziona tutto","Bold":Line truncated
{"OK":"OK","Cancel":"Annulla","The app is now going to close. Please relaunch it to complete the process.":"Ora l'applicazione si chiuderà. Per favore riavviala per completare il processo.","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"Scrivi il titolo di una nota o parte del suo contenuto per aprirla. Oppure digita # seguito dal nome di un'etichetta, oppure @ seguito dal nome di un taccuino. O scrivi : per cercare un comando.","Goto Anything...":"Cerca ovunque...","Command palette":"Comandi","Exporting to \"%s\" as \"%s\" format. Please wait...":"Esportazione da \"%s\" in formato \"%s\". Per favore attendere...","Could not export notes: %s":"Impossibile esportare le note: %s","Current version is up-to-date.":"La versione attuale è aggiornata.","%s (pre-release)":"%s (pre-rilascio)","An update is available, do you want to download it now?":"È disponibile un aggiornamento, vuoi scaricarlo ora?","Your version: %s":"La tua versione: %s","New version: %s":"Nuova versione: %s","Download":"Scarica","The note \"%s\" has been successfully restored to the notebook \"%s\".":"La nota \"%s\" è stata ripristinata con successo nel taccuino \"%s\".","This note has no history":"Questa nota non ha cronologia","Unsupported link or message: %s":"Link o messaggio non supportato: %s","Restore":"Ripristina","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"Clicca \"%s\" per ripristinare la nota. Verrà copiata nel taccuino denominato \"%s\". La versione corrente della nota non verrà sostituita o modificata.","Back":"Indietro","Remove":"Rimuovi","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"Eliminare taccuino \"%s\"?\n\nAnche tutte le note e cartelle di questo taccuino saranno eliminati.","Delete":"Elimina","Remove tag \"%s\" from all notes?":"Rimuovere l’etichetta \"%s\" da tutte le note?","Remove this search from the sidebar?":"Rimuovere questa ricerca dalla barra laterale?","Export":"Esporta","All notes":"Tutte le note","Synchronise":"Sincronizza","Notebooks":"Taccuini","Tags":"Etichette","Decrypting items: %d/%d":"Decrittografia elementi: %d/%d","Fetching resources: %d/%d":"Recupero risorse: %d/%d","Sidebar":"Barra laterale","Focus":"Focus","Please select where the sync status should be exported to":"Per favore selezionare dove lo stato della sincronizzazione deve essere esportato","Retry All":"Riprova tutti","Retry":"Riprova","strong text":"testo in grassetto","emphasised text":"testo sottolineato","Insert Hyperlink":"Inserire collegamento ipertestuale","List item":"Elemento della lista","Layout":"Disposizione","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"Questa nota non ha contenuto. Fai clic su \"%s\" per attivare l'editor e modificare la nota.","Cut":"Taglia","Copy":"Copia","Paste":"Incolla","Checkbox list":"Lista di caselle di spunta","Edit":"Modifica","Attach file":"Allega file","Code Block":"Blocco di codice","Inline Code":"Codice inline","Insert Date Time":"Inserisci data e ora","Drop notes or files here":"Trascina qui note o file","Code View":"Codice","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"Attendi che tutti gli allegati vengano scaricati e decrittografati. Puoi anche passare a %s per modificare la nota.","There was an error downloading this attachment:":"Errore nello scaricamento di questo allegato:","This attachment is not downloaded or not decrypted yet":"Questo allegato non è ancora stato scaricato o decriptato","Open...":"Apri...","Save as...":"Salva come...","Reveal file in folder":"Visualizza file nella cartella","Copy path to clipboard":"Copia il percorso negli appunti","Copy Link Address":"Copia l'indirizzo del link","to-do":"attività","note":"nota","Creating new %s...":"Creare nuovo %s...","Click to add tags...":"Clicca per aggiungere etichetta...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"Questo editor di testo RTF ha un certo numero di limitazioni e si raccomanda di conoscerle prima di utilizzarlo.","Read more about it":"Per saperne di più","Dismiss":"Non visualizzare più","The following attachments are being watched for changes:":"I seguenti allegati sono monitorati per eventuali modifiche:","The attachments will no longer be watched when you switch to a different note.":"Gli allegati non verranno più monitorati quando passerai ad una nota differente.","In: %s":"In: %s","Select all":"Seleziona tutto","Bold":"Grassetto","Italic":"Corsivo","Hyperlink":"Collegamento ipertestuale","Code":"Codice","Numbered List":"Elenco numerato","Bulleted List":"Elenco puntato","Checkbox":"Casella di controllo","Heading":"Intestazione","Horizontal Rule":"Riga orizzontLine truncated
+1 -1
View File
@@ -1 +1 @@
{"OK":"확인","Cancel":"취소","The app is now going to close. Please relaunch it to complete the process.":"애플리케이션이 곧 종료됩니다. 프로세스를 완료하려면 다시 실행해주세요.","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"노트 제목이나 내용의 일부를 입력하여 바로 이동하세요. 혹은 #으로 시작하는 태그 이름이나 @로 시작하는 노트북 이름을 입력하거나, :을 입력하여 명령을 검색하세요.","Goto Anything...":"가고자 하는 곳으로 이동...","Command palette":"명령 팔레트","Exporting to \"%s\" as \"%s\" format. Please wait...":"\"%s\"에 \"%s\" 포맷으로 내보내는 중입니다. 잠시만 기다려주세요...","Could not export notes: %s":"노트를 내보낼 수 없습니다: %s","Current version is up-to-date.":"현재 최신 버전을 사용 중입니다.","%s (pre-release)":"%s (사전 출시)","An update is available, do you want to download it now?":"업데이트가 있습니다. 지금 내려 받을까요?","Your version: %s":"현재 버전: %s","New version: %s":"새 버전: %s","Download":"다운로드","Skip this version":"이 버전 넘기기","Full changelog":"바뀐 내용을 모두 보기","The note \"%s\" has been successfully restored to the notebook \"%s\".":"노트 “%s”가 노트북 “%s\"에 성공적으로 복구되었습니다.","This note has no history":"이 노트의 변경 내역이 없습니다","Unsupported link or message: %s":"지원하지 않는 링크 또는 메시지: %s","Restore":"복구","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"\"%s\"를 클릭해서 노트를 복구하세요. 노트는 \"%s\" 이름을 가진 노트북에 복사됩니다. 노트의 현재 버전이 이동 혹은 수정되지는 않습니다.","Back":"뒤로 가기","Remove":"삭제","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"\"%s\" 노트북을 삭제할까요?\n\n이 노트북 안에 있는 모든 노트와 더불어 하위 노트북들도 함께 삭제됩니다.","Delete":"삭제","Remove tag \"%s\" from all notes?":"모든 노트에서 “%s” 태그를 제거할까요?","Remove this search from the sidebar?":"사이드바에서 이 검색 항목을 제거할까요?","Export":"내보내기","All notes":"모든 노트","Synchronise":"동기화","Notebooks":"노트북","Tags":"태그","Decrypting items: %d/%d":"복호화 항목: %d/%d","Fetching resources: %d/%d":"리소스 가져오는 중: %d/%d","Sidebar":"사이드바","Focus":"강조","Please select where the sync status should be exported to":"동기화 상태를 내보낼 대상을 선택하세요","Retry All":"전부 재시도","Retry":"재시도","Advanced tools":"고급 도구","Export debug report":"디버깅 보고서 내보내기","strong text":"굵게","emphasised text":"강조","Insert Hyperlink":"하이퍼링크 삽입","List item":"목록 추가","Layout":"배치","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"비어있는 노트입니다. \"%s\" 항목을 선택해서 편집기를 전환하고 노트를 편집하세요.","Cut":"잘라내기","Copy":"복사","Paste":"붙여넣기","Checkbox list":"선택상자 목록","Highlight":"하이라이트","Strikethrough":"취소선","Insert":"삽입","Superscript":"윗첨자","Subscript":"아랫 첨자","Edit":"편집","Attach file":"파일 첨부","Code Block":"코드 블록","Inline Code":"인라인 코드","Insert Date Time":"날짜와 시간 입력","Drop notes or files here":"노트나 파일을 여기에 끌어놓습니다","Code View":"코드 보기","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"모든 첨부 파일들이 다운로드 및 복호화될 때까지 기다려주세요. %s를 통해 이 노트를 편집하도록 전환할 수 있습니다.","There was an error downloading this attachment:":"첨부 파일을 내려 받는 중에 오류가 발생했습니다:","This attachment is not downloaded or not decrypted yet":"이 첨부 파일은 아직 다운로드 또는 복호화되지 않았습니다","Open...":"열기...","Save as...":"다른 이름으로 저장...","Reveal file in folder":"폴더에 있는 파일 표시","Copy path to clipboard":"경로를 클립보드에 복사","Copy Link Address":"링크 주소 복사","to-do":"할 일","note":"노트","Creating new %s...":"%s 새로 만들기...","Click to add tags...":"태그를 추가하려면 클릭하세요...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"서식 있는 텍스트 편집기에는 몇가지의 제약 사항들이 있으며, 사용하기 전에 이러한Line truncated
{"OK":"확인","Cancel":"취소","The app is now going to close. Please relaunch it to complete the process.":"애플리케이션이 곧 종료됩니다. 프로세스를 완료하려면 다시 실행해주세요.","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"노트 제목이나 내용의 일부를 입력하여 바로 이동하세요. 혹은 #으로 시작하는 태그 이름이나 @로 시작하는 노트북 이름을 입력하거나, :을 입력하여 명령을 검색하세요.","Goto Anything...":"가고자 하는 곳으로 이동...","Command palette":"명령 팔레트","Exporting to \"%s\" as \"%s\" format. Please wait...":"\"%s\"에 \"%s\" 포맷으로 내보내는 중입니다. 잠시만 기다려주세요...","Could not export notes: %s":"노트를 내보낼 수 없습니다: %s","Current version is up-to-date.":"현재 최신 버전을 사용 중입니다.","%s (pre-release)":"%s (프리-릴리즈)","An update is available, do you want to download it now?":"업데이트가 있습니다. 지금 내려 받을까요?","Your version: %s":"현재 버전: %s","New version: %s":"새 버전: %s","Download":"다운로드","The note \"%s\" has been successfully restored to the notebook \"%s\".":"노트 “%s”가 노트북 “%s\"에 성공적으로 복구되었습니다.","This note has no history":"이 노트엔 역사가 없습니다","Unsupported link or message: %s":"지원하지 않는 링크 또는 메시지: %s","Restore":"복구","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"\"%s\"를 클릭하여 노트를 복구하세요. 노트는 \"%s\" 이름을 가진 노트북에 복사될 것입니다. 현재 버전의 노트는 이동하거나 수정되지 않습니다.","Back":"뒤로 가기","Remove":"삭제","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"\"%s\" 노트북을 삭제할까요?\n\n이 노트북 안에 있는 모든 노트와 더불어 하위 노트북들도 함께 삭제됩니다.","Delete":"삭제","Remove tag \"%s\" from all notes?":"모든 노트에서 “%s” 태그를 제거할까요?","Remove this search from the sidebar?":"사이드바에서 이 검색 항목을 제거할까요?","Export":"내보내기","All notes":"모든 노트","Synchronise":"동기화","Notebooks":"노트북","Tags":"태그","Decrypting items: %d/%d":"복호화 항목: %d/%d","Fetching resources: %d/%d":"리소스 가져오는 중: %d/%d","Sidebar":"사이드바","Focus":"강조","Please select where the sync status should be exported to":"동기화 상태를 내보낼 대상을 선택하세요","Retry All":"전부 재시도","Retry":"재시도","strong text":"굵게","emphasised text":"강조","Insert Hyperlink":"하이퍼링크 삽입","List item":"목록 추가","Layout":"배치","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"비어있는 노트입니다. \"%s\" 항목을 선택해서 편집기를 전환하고 노트를 편집하세요.","Cut":"잘라내기","Copy":"복사","Paste":"붙여넣기","Checkbox list":"선택상자 목록","Highlight":"하이라이트","Strikethrough":"취소선","Insert":"삽입","Superscript":"윗첨자","Subscript":"아랫 첨자","Edit":"편집","Attach file":"파일 첨부","Code Block":"코드 블록","Inline Code":"인라인 코드","Insert Date Time":"날짜와 시간 입력","Drop notes or files here":"노트나 파일을 여기에 끌어놓습니다","Code View":"코드 보기","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"모든 첨부 파일들이 다운로드 및 복호화될 때까지 기다려주세요. %s를 통해 이 노트를 편집하도록 전환할 수 있습니다.","There was an error downloading this attachment:":"첨부 파일을 내려 받는 중에 오류가 발생했습니다:","This attachment is not downloaded or not decrypted yet":"이 첨부 파일은 아직 다운로드 또는 복호화 되지 않았습니다","Open...":"열기...","Save as...":"다른 이름으로 저장...","Reveal file in folder":"폴더에 있는 파일 표시","Copy path to clipboard":"경로를 클립보드에 복사","Copy Link Address":"링크 주소 복사","to-do":"할 일","note":"노트","Creating new %s...":"%s 새로 만들기...","Click to add tags...":"태그를 추가하려면 클릭하세요...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"서식 있는 텍스트 편집기에는 몇가지의 제한 사항들이 있으며, 사용하기 전에 이러한 제약 사항들을 미리 알고 있는 것이 좋습니다.","Read more about it":"더 자세히 알아보기","Dismiss":"취소","The following attachments are being watched foLine truncated
+1 -1
View File
@@ -1 +1 @@
{"OK":"确认","Cancel":"取消","The app is now going to close. Please relaunch it to complete the process.":"应用将要关闭。请重新启动它以完成此过程。","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"输入笔记标题或部分内容以转跳到它。或者输入 # 跟着一个标签名,或者输入 @ 跟着一个笔记本名字,或者输入 : 以搜索命令。","Goto Anything...":"跳转到任意内容...","Command palette":"命令面板","Exporting to \"%s\" as \"%s\" format. Please wait...":"导出到“%s”,格式为“%s”。请稍等...","Could not export notes: %s":"无法导出笔记:%s","Current version is up-to-date.":"当前版本为最新版。","%s (pre-release)":"%s(预发行版)","An update is available, do you want to download it now?":"有更新可用,是否现在进行下载?","Your version: %s":"您的版本:%s","New version: %s":"最新版本:%s","Download":"下载","Skip this version":"跳过该版本","Full changelog":"完整更新记录","The note \"%s\" has been successfully restored to the notebook \"%s\".":"笔记“%s”已成功恢复到笔记本“%s”中。","This note has no history":"此笔记没有历史记录","Unsupported link or message: %s":"不被支持的链接或信息:%s","Restore":"恢复","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"单击“%s”以恢复笔记。它将会被复制到名为“%s”的笔记本中。笔记的当前版本不会被替换或修改。","Back":"返回","Remove":"移除","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"是否删除笔记本“%s”?\n\n所有在该笔记本内的笔记和子笔记本也将同时被删除。","Delete":"删除","Remove tag \"%s\" from all notes?":"从所有笔记中删除标签“%s”?","Remove this search from the sidebar?":"从边栏中删除该项搜索?","Export":"导出","All notes":"全部笔记","Synchronise":"同步","Notebooks":"笔记本","Tags":"标签","Decrypting items: %d/%d":"正在解密条目:%d/%d","Fetching resources: %d/%d":"正在获取资源:%d/%d","Sidebar":"边栏","Focus":"聚焦于","Please select where the sync status should be exported to":"请选择同步状态的导出位置","Retry All":"全部重试","Retry":"重试","Advanced tools":"高级工具","Export debug report":"导出调试报告","strong text":"加粗文本","emphasised text":"强调文本","Insert Hyperlink":"插入超链接","List item":"列表项","Layout":"布局","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"该笔记没有任何内容。点击 \"%s\" 切换到编辑器并编辑笔记。","Cut":"剪切","Copy":"复制","Paste":"粘贴","Checkbox list":"复选框列表","Highlight":"突出显示","Strikethrough":"删除线","Insert":"插入","Superscript":"上标","Subscript":"订阅","Edit":"编辑","Attach file":"附加文件","Code Block":"代码块","Inline Code":"内联代码","Insert Date Time":"插入时间","Drop notes or files here":"将笔记或文件拖到此处","Code View":"代码视图","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"请等待所有附件均被下载并解密。您可以切换到 %s 编辑笔记。","There was an error downloading this attachment:":"下载此附件时出错:","This attachment is not downloaded or not decrypted yet":"该附件没有下载或者尚未解密","Open...":"打开...","Save as...":"另存为...","Reveal file in folder":"在文件夹中展示文件","Copy path to clipboard":"复制路径到剪贴板","Copy Link Address":"复制链接地址","to-do":"待办事项","note":"笔记","Creating new %s...":"新建 %s...","Click to add tags...":"单击以添加标签...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"此富文本编辑器有许多限制,建议在使用它之前注意它们。","Read more about it":"进一步了解","Dismiss":"忽略","The following attachments are being watched for changes:":"下列附件将被监控变化:","The attachments will no longer be watched when you switch to a different note.":"当您切换到其他笔记时,这些附件将不再被监控。","In: %s":"位于:%s","Select all":"全选","Bold":"加粗","Italic":"斜体","Hyperlink":"超链接","Code":"代码","Numbered List":"编号列表","Bulleted List":"项目符号列表","Checkbox":"复选框","Heading":"标题","Horizontal Rule":"水平线","Delete line":"删除行","Duplicate line":"复制行","Undo":"撤销","Redo":"恢复","Indent less":"减少缩进","Indent more":"增加缩进","Toggle comment":"添加/删除注释","Sort selected lines":"排序所选行","Swap Line truncated
{"OK":"确认","Cancel":"取消","The app is now going to close. Please relaunch it to complete the process.":"应用将要关闭。请重新启动它以完成此过程。","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"输入笔记标题或部分内容以转跳到它。或者输入 # 跟着一个标签名,或者输入 @ 跟着一个笔记本名字,或者输入 : 以搜索命令。","Goto Anything...":"跳转到任意内容...","Command palette":"命令面板","Exporting to \"%s\" as \"%s\" format. Please wait...":"导出到“%s”,格式为“%s”。请稍等...","Could not export notes: %s":"无法导出笔记:%s","Current version is up-to-date.":"当前版本为最新版。","%s (pre-release)":"%s(预发行版)","An update is available, do you want to download it now?":"有更新可用,是否现在进行下载?","Your version: %s":"您的版本:%s","New version: %s":"最新版本:%s","Download":"下载","The note \"%s\" has been successfully restored to the notebook \"%s\".":"笔记“%s”已成功恢复到笔记本“%s”中。","This note has no history":"此笔记没有历史记录","Unsupported link or message: %s":"不被支持的链接或信息:%s","Restore":"恢复","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"单击“%s”以恢复笔记。它将会被复制到名为“%s”的笔记本中。笔记的当前版本不会被替换或修改。","Back":"返回","Remove":"移除","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"是否删除笔记本“%s”?\n\n所有在该笔记本内的笔记和子笔记本也将同时被删除。","Delete":"删除","Remove tag \"%s\" from all notes?":"从所有笔记中删除标签“%s”?","Remove this search from the sidebar?":"从边栏中删除该项搜索?","Export":"导出","All notes":"全部笔记","Synchronise":"同步","Notebooks":"笔记本","Tags":"标签","Decrypting items: %d/%d":"正在解密条目:%d/%d","Fetching resources: %d/%d":"正在获取资源:%d/%d","Sidebar":"边栏","Focus":"聚焦于","Please select where the sync status should be exported to":"请选择同步状态的导出位置","Retry All":"全部重试","Retry":"重试","strong text":"加粗文本","emphasised text":"强调文本","Insert Hyperlink":"插入超链接","List item":"列表项","Layout":"布局","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"该笔记没有任何内容。点击 \"%s\" 切换到编辑器并编辑笔记。","Cut":"剪切","Copy":"复制","Paste":"粘贴","Checkbox list":"复选框列表","Edit":"编辑","Attach file":"附加文件","Code Block":"代码块","Inline Code":"内联代码","Insert Date Time":"插入时间","Drop notes or files here":"将笔记或文件拖到此处","Code View":"代码视图","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"请等待所有附件均被下载并解密。您可以切换到 %s 编辑笔记。","There was an error downloading this attachment:":"下载此附件时出错:","This attachment is not downloaded or not decrypted yet":"该附件没有下载或者尚未解密","Open...":"打开...","Save as...":"另存为...","Reveal file in folder":"在文件夹中展示文件","Copy path to clipboard":"复制路径到剪贴板","Copy Link Address":"复制链接地址","to-do":"待办事项","note":"笔记","Creating new %s...":"新建 %s...","Click to add tags...":"单击以添加标签...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"此富文本编辑器有许多限制,建议在使用它之前注意它们。","Read more about it":"进一步了解","Dismiss":"忽略","The following attachments are being watched for changes:":"下列附件将被监控变化:","The attachments will no longer be watched when you switch to a different note.":"当您切换到其他笔记时,这些附件将不再被监控。","In: %s":"位于:%s","Select all":"全选","Bold":"加粗","Italic":"斜体","Hyperlink":"超链接","Code":"代码","Numbered List":"编号列表","Bulleted List":"项目符号列表","Checkbox":"复选框","Heading":"标题","Horizontal Rule":"水平线","Delete line":"删除行","Undo":"撤销","Redo":"恢复","Indent less":"减少缩进","Indent more":"增加缩进","Toggle comment":"添加/删除注释","Sort selected lines":"排序所选行","Swap line up":"向上交换行","Swap line down":"向下交换行","Note title":"笔记标题","Search in current note":"在当前笔记内搜索","Note body":"笔记正文","ID":"ID","Created":"创建日期","Updated":"更新日期","Completed":"已完成","Location":"位置","URL":"URL","Note HistorLine truncated
+1 -1
View File
@@ -1 +1 @@
{"OK":"確定","Cancel":"取消","The app is now going to close. Please relaunch it to complete the process.":"程式即將關閉,請重新啟動以完成此過程。","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"輸入記事標題、部分記事內容、# 後輸入標籤名稱、@ 後輸入筆記本名稱等方式,可前往相應搜尋結果。或者可輸入 : 以搜尋其他指令。","Goto Anything...":"前往任意內容...","Command palette":"命令模式","Exporting to \"%s\" as \"%s\" format. Please wait...":"匯出到「%s」(格式:%s)。請稍候...","Could not export notes: %s":"無法匯出記事: %s","Current version is up-to-date.":"目前是最新版。","%s (pre-release)":"%s (發行前測試版)","An update is available, do you want to download it now?":"已有更新,您想立即下載嗎?","Your version: %s":"您目前的版本: %s","New version: %s":"最新版本: %s","Download":"下載","Skip this version":"這版不更新","Full changelog":"完整更新內容","The note \"%s\" has been successfully restored to the notebook \"%s\".":"已成功將「%s」還原至「%s」筆記本中。","This note has no history":"此記事沒有歷史紀錄","Unsupported link or message: %s":"未支援的鏈接或訊息: %s","Restore":"還原","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"點選「%s」將記事還原後複製到名為「%s」的記事本中。此記事當前的版本將保留,不會被取代或修改。","Back":"返回","Remove":"移除","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"確定刪除「%s」?\n\n此記事本中的所有記事和子記事本也將刪除。","Delete":"刪除","Remove tag \"%s\" from all notes?":"是否從所有記事中移除「%s」標籤?","Remove this search from the sidebar?":"是否從側邊欄中刪除此搜索?","Export":"匯出","All notes":"全部記事","Synchronise":"進行同步","Notebooks":"記事本","Tags":"標籤","Decrypting items: %d/%d":"解密項目:%d/%d 項","Fetching resources: %d/%d":"擷取資源檔案:%d/%d","Sidebar":"側邊欄","Focus":"焦點","Please select where the sync status should be exported to":"請選擇同步狀態的匯出位置","Retry All":"全部重試","Retry":"重試","Advanced tools":"進階工具","Export debug report":"匯出除錯報告","strong text":"粗體文字","emphasised text":"強調文字","Insert Hyperlink":"插入超連結","List item":"清單項目","Layout":"頁面佈局","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"此記事沒有內容。點擊「%s」切換到編輯模式並編輯記事。","Cut":"剪下","Copy":"複製","Paste":"貼上","Checkbox list":"核取方塊清單","Highlight":"強調標記","Strikethrough":"刪除線","Insert":"底線","Superscript":"上標","Subscript":"下標","Edit":"編輯","Attach file":"附加檔案","Code Block":"程式碼區塊","Inline Code":"內嵌程式碼","Insert Date Time":"插入日期時間","Drop notes or files here":"將記事或檔案拖曳至此處","Code View":"程式碼檢視","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"請等待所有附件下載並解密。您也可以切換到 %s 以編輯記事。","There was an error downloading this attachment:":"下載此附件時出現錯誤:","This attachment is not downloaded or not decrypted yet":"此附件尚未完成下載或解密","Open...":"開啟舊檔...","Save as...":"另存新檔...","Reveal file in folder":"在資料夾中顯示檔案","Copy path to clipboard":"複製路徑到剪貼簿","Copy Link Address":"複製鏈結位址","to-do":"待辦事項","note":"記事","Creating new %s...":"建立新 %s...","Click to add tags...":"新增標籤...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"這個編輯模式尚有許多限制,建議使用前先深入了解。","Read more about it":"進一步了解","Dismiss":"忽略","The following attachments are being watched for changes:":"已監控下列附件的變動:","The attachments will no longer be watched when you switch to a different note.":"切換到其他記事後,將不再監控這些附件的變動。","In: %s":"在: %s","Select all":"選擇全部","Bold":"粗體","Italic":"斜體","Hyperlink":"超連結","Code":"程式碼","Numbered List":"編號清單","Bulleted List":"項目清單","Checkbox":"核取方塊","Heading":"標題","Horizontal Rule":"水平線","Delete line":"刪除整行","Duplicate line":"複製本行","Undo":"復原","Redo":"恢復","Indent less":"減少縮排","Indent more":"增加縮排","Toggle comment":"切換注釋","Sort selected lines":"排序Line truncated
{"OK":"確定","Cancel":"取消","The app is now going to close. Please relaunch it to complete the process.":"應用將要關閉。請重新啟動它以完成此過程。","Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.":"輸入記事標題、部分記事內容、# 後輸入標籤名稱、@ 後輸入筆記本名稱等方式,可前往相應搜尋結果。或者可輸入 : 以搜尋其他指令。","Goto Anything...":"前往任意內容...","Command palette":"命令模式","Exporting to \"%s\" as \"%s\" format. Please wait...":"匯出到 \"%s\" 為 \"%s\" 格式。請稍候...","Could not export notes: %s":"無法匯出記事: %s","Current version is up-to-date.":"此版本是最新的。","%s (pre-release)":"%s (預發行)","An update is available, do you want to download it now?":"有可用的更新,您需要立即下載嗎?","Your version: %s":"您目前的版本: %s","New version: %s":"最新版本: %s","Download":"下載","The note \"%s\" has been successfully restored to the notebook \"%s\".":"記事 \"%s\" 已經被成功還原到筆記本 \"%s\".","This note has no history":"此記事沒有歷史紀錄","Unsupported link or message: %s":"不支援的鏈接或訊息: %s","Restore":"還原","Click \"%s\" to restore the note. It will be copied in the notebook named \"%s\". The current version of the note will not be replaced or modified.":"點選 \"%s\" 來還原記事。它將被複製到名為 \"%s\"的記事本中。目前記事的版本將不會被取代或修改。","Back":"返回","Remove":"移除","Delete notebook \"%s\"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.":"確定刪除記事本 \"%s\"?\n\n此記事本中的所有記事和子記事本也將被刪除。","Delete":"刪除","Remove tag \"%s\" from all notes?":"是否從所有記事中刪除此標籤 \"%s\"?","Remove this search from the sidebar?":"是否從側邊欄中刪除此搜索?","Export":"匯出","All notes":"全部記事","Synchronise":"進行同步","Notebooks":"記事本","Tags":"標籤","Decrypting items: %d/%d":"正在解密項目: %d/%d 項","Fetching resources: %d/%d":"擷取資源檔案: %d/%d","Sidebar":"側邊欄","Focus":"焦點","Please select where the sync status should be exported to":"請選擇將同步狀態導出到的位置","Retry All":"全部重試","Retry":"重試","strong text":"重要文字","emphasised text":"強調文字","Insert Hyperlink":"插入超連結","List item":"清單項目","Layout":"頁面佈局","This note has no content. Click on \"%s\" to toggle the editor and edit the note.":"此記事沒有內容。按一下 \"%s\" 切換到編輯模式並編輯記事。","Cut":"剪下","Copy":"複製","Paste":"貼上","Checkbox list":"核取方塊清單","Edit":"編輯","Attach file":"附加檔案","Code Block":"程式碼區塊","Inline Code":"內嵌程式碼","Insert Date Time":"插入日期時間","Drop notes or files here":"將記事或檔案拖曳至此處","Code View":"程式碼檢視","Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.":"請等待所有附件均被下載並解密。你還能在 %s 中編輯記事。","There was an error downloading this attachment:":"下載此附件時出現錯誤: ","This attachment is not downloaded or not decrypted yet":"此附件尚未被下載或是尚未解密完成","Open...":"開啟...","Save as...":"另存為...","Reveal file in folder":"在資料夾中展示檔案","Copy path to clipboard":"複製路徑到剪貼板","Copy Link Address":"複製鏈接位址","to-do":"待辦事項","note":"記事","Creating new %s...":"建立新 %s...","This Rich Text editor has a number of limitations and it is recommended to be aware of them before using it.":"此富文本編輯器有許多限制,建議在使用它之前注意它們","Read more about it":"進一步了解","Dismiss":"忽略","The following attachments are being watched for changes:":"下列附件將被監控變化:","The attachments will no longer be watched when you switch to a different note.":"當切換到其他記事時,這些附件將不再被監控。","In: %s":"在: %s","Select all":"選擇全部","Bold":"粗體","Italic":"斜體","Hyperlink":"超連結","Code":"引言","Numbered List":"編號清單","Bulleted List":"項目清單","Checkbox":"核取方塊","Heading":"標題","Horizontal Rule":"水平線","Delete line":"刪除整行","Undo":"復原","Redo":"恢復","Indent less":"減少縮排","Indent more":"增加縮排","Sort selected lines":"排序所選各行","Swap line up":"本行上移","Swap line down":"本行下移","Note title":"記事標題","Search in current note":"在目前記事中搜尋","Note body":"內容主體","ID":"ID","Created":"已建立","Updated":"已更新","Completed":"已完成","Location":"地理位置","URL":"網址","Note History":"記事本歷史紀錄","MarkupLine truncated
+7 -27
View File
@@ -7,18 +7,10 @@ const MarkdownIt = require('markdown-it');
const listRegex = /^(\s*)([*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
const emptyListRegex = /^(\s*)([*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s+)$/;
export enum MarkdownTableJustify {
Left = 'left',
Center = 'center',
Right = 'right,',
}
export interface MarkdownTableHeader {
name: string;
label: string;
filter?: Function;
disableEscape?: boolean;
justify?: MarkdownTableJustify;
}
export interface MarkdownTableRow {
@@ -128,38 +120,26 @@ const markdownUtils = {
createMarkdownTable(headers: MarkdownTableHeader[], rows: MarkdownTableRow[]): string {
const output = [];
const minCellWidth = 5;
const headersMd = [];
const lineMd = [];
for (let i = 0; i < headers.length; i++) {
const h = headers[i];
headersMd.push(stringPadding(h.label, minCellWidth, ' ', stringPadding.RIGHT));
const justify = h.justify ? h.justify : MarkdownTableJustify.Left;
if (justify === MarkdownTableJustify.Left) {
lineMd.push('-----');
} else if (justify === MarkdownTableJustify.Center) {
lineMd.push(':---:');
} else {
lineMd.push('----:');
}
headersMd.push(stringPadding(h.label, 3, ' ', stringPadding.RIGHT));
lineMd.push('---');
}
output.push(`| ${headersMd.join(' | ')} |`);
output.push(`| ${lineMd.join(' | ')} |`);
output.push(headersMd.join(' | '));
output.push(lineMd.join(' | '));
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const rowMd = [];
for (let j = 0; j < headers.length; j++) {
const h = headers[j];
const value = (h.filter ? h.filter(row[h.name]) : row[h.name]) || '';
const valueMd = h.disableEscape ? value : markdownUtils.escapeTableCell(value);
rowMd.push(stringPadding(valueMd, minCellWidth, ' ', stringPadding.RIGHT));
const valueMd = markdownUtils.escapeTableCell(h.filter ? h.filter(row[h.name]) : row[h.name]);
rowMd.push(stringPadding(valueMd, 3, ' ', stringPadding.RIGHT));
}
output.push(`| ${rowMd.join(' | ')} |`);
output.push(rowMd.join(' | '));
}
return output.join('\n');
+2 -14
View File
@@ -284,20 +284,8 @@ describe('models_Note', function() {
expect(externalToInternal).toBe(input);
}
{
const result = await Note.replaceResourceExternalToInternalLinks(`[](joplin://${note1.id})`);
expect(result).toBe(`[](:/${note1.id})`);
}
{
// This is a regular file path that contains the resourceDirName
// inside but it shouldn't be changed.
//
// https://github.com/laurent22/joplin/issues/5034
const noChangeInput = `[docs](file:///c:/foo/${resourceDirName}/docs)`;
const result = await Note.replaceResourceExternalToInternalLinks(noChangeInput, { useAbsolutePaths: false });
expect(result).toBe(noChangeInput);
}
const result = await Note.replaceResourceExternalToInternalLinks(`[](joplin://${note1.id})`);
expect(result).toBe(`[](:/${note1.id})`);
}));
it('should perform natural sorting', (async () => {
+2 -2
View File
@@ -208,9 +208,9 @@ export default class Note extends BaseItem {
for (const basePath of pathsToTry) {
const reStrings = [
// Handles file://path/to/abcdefg.jpg?t=12345678
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9]{32}\\.[a-zA-Z0-9]+\\?t=[0-9]+`,
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9.]+\\?t=[0-9]+`,
// Handles file://path/to/abcdefg.jpg
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9]{32}\\.[a-zA-Z0-9]+`,
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9.]+`,
];
for (const reString of reStrings) {
const re = new RegExp(reString, 'gi');
+14 -47
View File
@@ -476,12 +476,20 @@ class Setting extends BaseModel {
description: () => emptyDirWarning,
storage: SettingStorage.File,
},
'sync.9.userContentPath': {
value: '',
type: SettingItemType.String,
public: false,
storage: SettingStorage.Database,
},
// 'sync.9.directory': {
// value: 'Apps/Joplin',
// type: SettingItemType.String,
// section: 'sync',
// show: (settings: any) => {
// return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer');
// },
// filter: value => {
// return value ? ltrimSlashes(rtrimSlashes(value)) : '';
// },
// public: true,
// label: () => _('Joplin Server Directory'),
// storage: SettingStorage.File,
// },
'sync.9.username': {
value: '',
type: SettingItemType.String,
@@ -505,45 +513,6 @@ class Setting extends BaseModel {
secure: true,
},
// Although sync.10.path is essentially a constant, we still define
// it here so that both Joplin Server and Joplin Cloud can be
// handled in the same consistent way. Also having it a setting
// means it can be set to something else for development.
'sync.10.path': {
value: 'https://api.joplincloud.com',
type: SettingItemType.String,
public: false,
storage: SettingStorage.Database,
},
'sync.10.userContentPath': {
value: 'https://joplinusercontent.com',
type: SettingItemType.String,
public: false,
storage: SettingStorage.Database,
},
'sync.10.username': {
value: '',
type: SettingItemType.String,
section: 'sync',
show: (settings: any) => {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinCloud');
},
public: true,
label: () => _('Joplin Cloud email'),
storage: SettingStorage.File,
},
'sync.10.password': {
value: '',
type: SettingItemType.String,
section: 'sync',
show: (settings: any) => {
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinCloud');
},
public: true,
label: () => _('Joplin Cloud password'),
secure: true,
},
'sync.5.syncTargets': { value: {}, type: SettingItemType.Object, public: false },
'sync.resourceDownloadMode': {
@@ -570,7 +539,6 @@ class Setting extends BaseModel {
'sync.4.auth': { value: '', type: SettingItemType.String, public: false },
'sync.7.auth': { value: '', type: SettingItemType.String, public: false },
'sync.9.auth': { value: '', type: SettingItemType.String, public: false },
'sync.10.auth': { value: '', type: SettingItemType.String, public: false },
'sync.1.context': { value: '', type: SettingItemType.String, public: false },
'sync.2.context': { value: '', type: SettingItemType.String, public: false },
'sync.3.context': { value: '', type: SettingItemType.String, public: false },
@@ -580,7 +548,6 @@ class Setting extends BaseModel {
'sync.7.context': { value: '', type: SettingItemType.String, public: false },
'sync.8.context': { value: '', type: SettingItemType.String, public: false },
'sync.9.context': { value: '', type: SettingItemType.String, public: false },
'sync.10.context': { value: '', type: SettingItemType.String, public: false },
'sync.maxConcurrentConnections': { value: 5, type: SettingItemType.Int, storage: SettingStorage.File, public: true, advanced: true, section: 'sync', label: () => _('Max concurrent connections'), minimum: 1, maximum: 20, step: 1 },
-18
View File
@@ -51,20 +51,11 @@ describe('models_Tag', function() {
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id });
const todo1 = await Note.save({ title: 'todo 1', parent_id: folder1.id, is_todo: 1, todo_completed: 1590085027710 });
await Tag.setNoteTagsByTitles(note1.id, ['un']);
await Tag.setNoteTagsByTitles(note2.id, ['un']);
await Tag.setNoteTagsByTitles(todo1.id, ['un']);
let tags = await Tag.allWithNotes();
expect(tags.length).toBe(1);
expect(tags[0].note_count).toBe(3);
expect(tags[0].todo_completed_count).toBe(1);
await Note.delete(todo1.id);
tags = await Tag.allWithNotes();
expect(tags.length).toBe(1);
expect(tags[0].note_count).toBe(2);
await Note.delete(note1.id);
@@ -83,8 +74,6 @@ describe('models_Tag', function() {
const folder1 = await Folder.save({ title: 'folder1' });
const note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
const note2 = await Note.save({ title: 'ma 2nd note', parent_id: folder1.id });
const todo1 = await Note.save({ title: 'todo 2', parent_id: folder1.id, is_todo: 1, todo_completed: 1590085027710 });
const todo2 = await Note.save({ title: 'todo 2', parent_id: folder1.id, is_todo: 1 });
const tag = await Tag.save({ title: 'mytag' });
await Tag.addNote(tag.id, note1.id);
@@ -94,12 +83,6 @@ describe('models_Tag', function() {
await Tag.addNote(tag.id, note2.id);
tagWithCount = await Tag.loadWithCount(tag.id);
expect(tagWithCount.note_count).toBe(2);
await Tag.addNote(tag.id, todo1.id);
await Tag.addNote(tag.id, todo2.id);
tagWithCount = await Tag.loadWithCount(tag.id);
expect(tagWithCount.note_count).toBe(4);
expect(tagWithCount.todo_completed_count).toBe(1);
}));
it('should get common tags for set of notes', (async () => {
@@ -148,7 +131,6 @@ describe('models_Tag', function() {
expect(commonTagIds.includes(tagb.id)).toBe(true);
commonTags = await Tag.commonTagsByNoteIds([note3.id]);
commonTagIds = commonTags.map(t => t.id);
expect(commonTags.length).toBe(3);
expect(commonTagIds.includes(taga.id)).toBe(true);
+19 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/lib",
"version": "2.0.2",
"version": "2.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -4059,6 +4059,24 @@
"verror": "1.10.0"
}
},
"keytar": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz",
"integrity": "sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==",
"dev": true,
"requires": {
"node-addon-api": "^3.0.0",
"prebuild-install": "^6.0.0"
},
"dependencies": {
"node-addon-api": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.0.tgz",
"integrity": "sha512-kcwSAWhPi4+QzAtsL2+2s/awvDo2GKLsvMCwNRxb5BUshteXU8U97NCyvQDsGKs/m0He9WcG4YWew/BnuLx++w==",
"dev": true
}
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+9 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@joplin/lib",
"version": "2.0.2",
"version": "2.0.0",
"description": "Joplin Core library",
"author": "Laurent Cozic",
"homepage": "",
@@ -16,20 +16,21 @@
"test-ci": "npm run test"
},
"devDependencies": {
"@types/fs-extra": "^9.0.6",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.6",
"clean-html": "^1.5.0",
"@types/fs-extra": "^9.0.6",
"jest": "^26.6.3",
"sharp": "^0.26.2",
"typescript": "^4.0.5"
"keytar": "^7.0.0",
"typescript": "^4.0.5",
"clean-html": "^1.5.0"
},
"dependencies": {
"@joplin/fork-htmlparser2": "^4.1.26",
"@joplin/fork-sax": "^1.2.30",
"@joplin/fork-htmlparser2": "^4.1.24",
"@joplin/fork-sax": "^1.2.28",
"@joplin/renderer": "^1.8.2",
"@joplin/turndown": "^4.0.48",
"@joplin/turndown-plugin-gfm": "^1.0.30",
"@joplin/turndown": "^4.0.46",
"@joplin/turndown-plugin-gfm": "^1.0.28",
"async-mutex": "^0.1.3",
"aws-sdk": "^2.588.0",
"base-64": "^0.1.0",
+17 -25
View File
@@ -140,28 +140,6 @@ export default class ReportService {
return output;
}
private addRetryAllHandler(section: ReportSection): ReportSection {
const retryHandlers: Function[] = [];
for (let i = 0; i < section.body.length; i++) {
const item: RerportItemOrString = section.body[i];
if (typeof item !== 'string' && item.canRetry) {
retryHandlers.push(item.retryHandler);
}
}
if (retryHandlers.length) {
section.canRetryAll = true;
section.retryAllHandler = async () => {
for (const retryHandler of retryHandlers) {
await retryHandler();
}
};
}
return section;
}
async status(syncTarget: number): Promise<ReportSection[]> {
const r = await this.syncStatus(syncTarget);
const sections: ReportSection[] = [];
@@ -197,8 +175,6 @@ export default class ReportService {
section.body.push({ type: ReportItemType.CloseList });
section = this.addRetryAllHandler(section);
sections.push(section);
}
@@ -224,7 +200,23 @@ export default class ReportService {
});
}
section = this.addRetryAllHandler(section);
const retryHandlers: Function[] = [];
for (let i = 0; i < section.body.length; i++) {
const item: RerportItemOrString = section.body[i];
if (typeof item !== 'string' && item.canRetry) {
retryHandlers.push(item.retryHandler);
}
}
if (retryHandlers.length > 1) {
section.canRetryAll = true;
section.retryAllHandler = async () => {
for (const retryHandler of retryHandlers) {
await retryHandler();
}
};
}
sections.push(section);
}
+2 -19
View File
@@ -9,7 +9,6 @@ import shim from '../shim';
import BaseService from './BaseService';
import { _ } from '../locale';
import { ItemChangeEntity, NoteEntity, RevisionEntity } from './database/types';
const { substrWithEllipsis } = require('../string-utils');
const { sprintf } = require('sprintf-js');
const { wrapError } = require('../errorUtils');
@@ -231,23 +230,7 @@ export default class RevisionService extends BaseService {
return folder;
}
// reverseRevIndex = 0 means restoring the latest version. reverseRevIndex =
// 1 means the version before that, etc.
public async restoreNoteById(noteId: string, reverseRevIndex: number): Promise<NoteEntity> {
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, noteId);
if (!revisions.length) throw new Error(`No revision for note "${noteId}"`);
const revIndex = revisions.length - 1 - reverseRevIndex;
const note = await this.revisionNote(revisions, revIndex);
return this.importRevisionNote(note);
}
public restoreSuccessMessage(note: NoteEntity): string {
return _('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(note.title, 0, 32), this.restoreFolderTitle());
}
async importRevisionNote(note: NoteEntity): Promise<NoteEntity> {
async importRevisionNote(note: NoteEntity) {
const toImport = Object.assign({}, note);
delete toImport.id;
delete toImport.updated_time;
@@ -259,7 +242,7 @@ export default class RevisionService extends BaseService {
toImport.parent_id = folder.id;
return Note.save(toImport);
await Note.save(toImport);
}
async maintenance() {
@@ -77,6 +77,6 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau
folderIsShareRootAndOwnedByUser: commandFolder ? isRootSharedFolder(commandFolder) && isSharedFolderOwner(state, commandFolder.id) : false,
folderIsShared: commandFolder ? !!commandFolder.share_id : false,
joplinServerConnected: [9, 10].includes(state.settings['sync.target']),
joplinServerConnected: state.settings['sync.target'] === 9,
};
}
@@ -1,7 +1,8 @@
import KeychainService from '@joplin/lib/services/keychain/KeychainService';
import shim from '@joplin/lib/shim';
import Setting from '@joplin/lib/models/Setting';
import { db, setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
import KeychainService from '../../services/keychain/KeychainService';
import shim from '../../shim';
import Setting from '../../models/Setting';
const { db, setupDatabaseAndSynchronizer, switchClient } = require('../../testing/test-utils.js');
function describeIfCompatible(name: string, fn: any, elseFn: any) {
if (['win32', 'darwin'].includes(shim.platformName())) {
+5 -64
View File
@@ -1,21 +1,8 @@
import Logger from '../../Logger';
import shim from '../../shim';
import { PluginManifest } from './utils/types';
const md5 = require('md5');
const compareVersions = require('compare-versions');
const logger = Logger.create('RepositoryApi');
interface ReleaseAsset {
name: string;
browser_download_url: string;
}
interface Release {
upload_url: string;
assets: ReleaseAsset[];
}
export default class RepositoryApi {
// As a base URL, this class can support either a remote repository or a
@@ -27,7 +14,6 @@ export default class RepositoryApi {
// Later on, other repo types could be supported.
private baseUrl_: string;
private tempDir_: string;
private release_: Release = null;
private manifests_: PluginManifest[] = null;
public constructor(baseUrl: string, tempDir: string) {
@@ -35,12 +21,7 @@ export default class RepositoryApi {
this.tempDir_ = tempDir;
}
public async initialize() {
await this.loadManifests();
await this.loadRelease();
}
private async loadManifests() {
public async loadManifests() {
const manifestsText = await this.fetchText('manifests.json');
try {
const manifests = JSON.parse(manifestsText);
@@ -53,27 +34,6 @@ export default class RepositoryApi {
}
}
private get githubApiUrl(): string {
// https://github.com/joplin/plugins
// https://api.github.com/repos/joplin/plugins/releases
return this.baseUrl_.replace(/^(https:\/\/)(github\.com\/)(.*)$/, '$1api.$2repos/$3');
}
private async loadRelease() {
this.release_ = null;
if (this.isLocalRepo) return;
try {
const response = await fetch(`${this.githubApiUrl}/releases`);
const releases = await response.json();
if (!releases.length) throw new Error('No release was found');
this.release_ = releases[0];
} catch (error) {
logger.warn('Could not load release - files will be downloaded from the repository directly:', error);
}
}
private get isLocalRepo(): boolean {
return this.baseUrl_.indexOf('http') !== 0;
}
@@ -86,34 +46,15 @@ export default class RepositoryApi {
}
}
private assetFileUrl(pluginId: string): string {
if (this.release_) {
const asset = this.release_.assets.find(asset => {
const s = asset.name.split('@');
s.pop();
const id = s.join('@');
return id === pluginId;
});
if (asset) return asset.browser_download_url;
logger.warn(`Could not get plugin from release: ${pluginId}`);
}
// If we couldn't get the plugin file from the release, get it directly
// from the repository instead.
return this.repoFileUrl(`plugins/${pluginId}/plugin.jpl`);
}
private repoFileUrl(relativePath: string): string {
private fileUrl(relativePath: string): string {
return `${this.contentBaseUrl}/${relativePath}`;
}
private async fetchText(path: string): Promise<string> {
if (this.isLocalRepo) {
return shim.fsDriver().readFile(this.repoFileUrl(path), 'utf8');
return shim.fsDriver().readFile(this.fileUrl(path), 'utf8');
} else {
return shim.fetchText(this.repoFileUrl(path));
return shim.fetchText(this.fileUrl(path));
}
}
@@ -145,7 +86,7 @@ export default class RepositoryApi {
const manifest = manifests.find(m => m.id === pluginId);
if (!manifest) throw new Error(`No manifest for plugin ID "${pluginId}"`);
const fileUrl = this.assetFileUrl(manifest.id); // this.repoFileUrl(`plugins/${manifest.id}/plugin.jpl`);
const fileUrl = this.fileUrl(`plugins/${manifest.id}/plugin.jpl`);
const hash = md5(Date.now() + Math.random());
const targetPath = `${this.tempDir_}/${hash}_${manifest.id}.jpl`;
@@ -21,34 +21,6 @@ import { Command } from './types';
*
* To view what arguments are supported, you can open any of these files
* and look at the `execute()` command.
*
* ## Executing editor commands
*
* There might be a situation where you want to invoke editor commands
* without using a {@link JoplinContentScripts | contentScript}. For this
* reason Joplin provides the built in `editor.execCommand` command.
*
* `editor.execCommand` should work with any core command in both the
* [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and
* [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,
* as well as most functions calls directly on a CodeMirror editor object (extensions).
*
* * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)
* * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)
*
* `editor.execCommand` supports adding arguments for the commands.
*
* ```typescript
* await joplin.commands.execute('editor.execCommand', {
* name: 'madeUpCommand', // CodeMirror and TinyMCE
* args: [], // CodeMirror and TinyMCE
* ui: false, // TinyMCE only
* value: '', // TinyMCE only
* });
* ```
*
* [View the example using the CodeMirror editor](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts)
*
*/
export default class JoplinCommands {
+4 -2
View File
@@ -48,8 +48,10 @@ export interface Command {
* Currently the supported context variables aren't documented, but you can
* find the list below:
*
* - [Global When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts)
* - [Desktop app When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts)
* - [Global When
* Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts).
* - [Desktop app When
* Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts).
*
* Note: Commands are enabled by default unless you use this property.
*/
+1 -1
View File
@@ -28,7 +28,7 @@ export default async function(request: Request) {
options.caseInsensitive = true;
results = await ModelClass.all(options);
} else {
results = await SearchEngineUtils.notesForQuery(query, false, defaultLoadOptions(request, ModelType.Note));
results = await SearchEngineUtils.notesForQuery(query, defaultLoadOptions(request, ModelType.Note));
}
return collectionToPaginatedResults(modelType, results, request);
@@ -386,7 +386,6 @@ describe('services_SearchEngine', function() {
expect((await engine.search('测试')).length).toBe(1);
expect((await engine.search('测试'))[0].fields).toEqual(['body']);
expect((await engine.search('测试*'))[0].fields).toEqual(['body']);
expect((await engine.search('any:1 type:todo 测试')).length).toBe(1);
}));
it('should support queries with Japanese characters', (async () => {
@@ -399,7 +398,7 @@ describe('services_SearchEngine', function() {
expect((await engine.search('できません')).length).toBe(1);
expect((await engine.search('できません*'))[0].fields.sort()).toEqual(['body', 'title']); // usually assume that keyword was matched in body
expect((await engine.search('テスト'))[0].fields.sort()).toEqual(['body']);
expect((await engine.search('any:1 type:todo テスト')).length).toBe(1);
}));
it('should support queries with Korean characters', (async () => {
@@ -410,7 +409,6 @@ describe('services_SearchEngine', function() {
expect((await engine.search('이것은')).length).toBe(1);
expect((await engine.search('말')).length).toBe(1);
expect((await engine.search('any:1 type:todo 말')).length).toBe(1);
}));
it('should support queries with Thai characters', (async () => {
@@ -421,7 +419,28 @@ describe('services_SearchEngine', function() {
expect((await engine.search('นี่คือค')).length).toBe(1);
expect((await engine.search('ไทย')).length).toBe(1);
expect((await engine.search('any:1 type:todo ไทย')).length).toBe(1);
}));
it('should support field restricted queries with Chinese characters', (async () => {
let rows;
const n1 = await Note.save({ title: '你好', body: '我是法国人' });
await engine.syncTables();
expect((await engine.search('title:你好*')).length).toBe(1);
expect((await engine.search('title:你好*'))[0].fields).toEqual(['title']);
expect((await engine.search('body:法国人')).length).toBe(1);
expect((await engine.search('body:法国人'))[0].fields).toEqual(['body']);
expect((await engine.search('body:你好')).length).toBe(0);
expect((await engine.search('title:你好 body:法国人')).length).toBe(1);
expect((await engine.search('title:你好 body:法国人'))[0].fields.sort()).toEqual(['body', 'title']);
expect((await engine.search('title:你好 body:bla')).length).toBe(0);
expect((await engine.search('title:你好 我是')).length).toBe(1);
expect((await engine.search('title:你好 我是'))[0].fields.sort()).toEqual(['body', 'title']);
expect((await engine.search('title:bla 我是')).length).toBe(0);
// For non-alpha char, only the first field is looked at, the following ones are ignored
// expect((await engine.search('title:你好 title:hello')).length).toBe(1);
}));
it('should parse normal query strings', (async () => {
@@ -17,7 +17,6 @@ export default class SearchEngine {
public static relevantFields = 'id, title, body, user_created_time, user_updated_time, is_todo, todo_completed, todo_due, parent_id, latitude, longitude, altitude, source_url';
public static SEARCH_TYPE_AUTO = 'auto';
public static SEARCH_TYPE_BASIC = 'basic';
public static SEARCH_TYPE_NONLATIN_SCRIPT = 'nonlatin';
public static SEARCH_TYPE_FTS = 'fts';
public dispatch: Function = (_o: any) => {};
@@ -534,7 +533,6 @@ export default class SearchEngine {
determineSearchType_(query: string, preferredSearchType: any) {
if (preferredSearchType === SearchEngine.SEARCH_TYPE_BASIC) return SearchEngine.SEARCH_TYPE_BASIC;
if (preferredSearchType === SearchEngine.SEARCH_TYPE_NONLATIN_SCRIPT) return SearchEngine.SEARCH_TYPE_NONLATIN_SCRIPT;
// If preferredSearchType is "fts" we auto-detect anyway
// because it's not always supported.
@@ -549,15 +547,10 @@ export default class SearchEngine {
const textQuery = allTerms.filter(x => x.name === 'text' || x.name == 'title' || x.name == 'body').map(x => x.value).join(' ');
const st = scriptType(textQuery);
if (!Setting.value('db.ftsEnabled')) {
if (!Setting.value('db.ftsEnabled') || ['ja', 'zh', 'ko', 'th'].indexOf(st) >= 0) {
return SearchEngine.SEARCH_TYPE_BASIC;
}
// Non-alphabetical languages aren't support by SQLite FTS (except with extensions which are not available in all platforms)
if (['ja', 'zh', 'ko', 'th'].indexOf(st) >= 0) {
return SearchEngine.SEARCH_TYPE_NONLATIN_SCRIPT;
}
return SearchEngine.SEARCH_TYPE_FTS;
}
@@ -572,6 +565,7 @@ export default class SearchEngine {
const parsedQuery = await this.parseQuery(searchString);
if (searchType === SearchEngine.SEARCH_TYPE_BASIC) {
// Non-alphabetical languages aren't support by SQLite FTS (except with extensions which are not available in all platforms)
searchString = this.normalizeText_(searchString);
const rows = await this.basicSearch(searchString);
@@ -585,11 +579,10 @@ export default class SearchEngine {
// when searching.
// https://github.com/laurent22/joplin/issues/1075#issuecomment-459258856
const useFts = searchType === SearchEngine.SEARCH_TYPE_FTS;
try {
const { query, params } = queryBuilder(parsedQuery.allTerms, useFts);
const { query, params } = queryBuilder(parsedQuery.allTerms);
const rows = await this.db().selectAll(query, params);
this.processResults_(rows, parsedQuery, !useFts);
this.processResults_(rows, parsedQuery);
return rows;
} catch (error) {
this.logger().warn(`Cannot execute MATCH query: ${searchString}: ${error.message}`);
@@ -26,21 +26,12 @@ describe('services_SearchEngineUtils', function() {
Setting.setValue('showCompletedTodos', true);
const rows = await SearchEngineUtils.notesForQuery('abcd', true, null, searchEngine);
const rows = await SearchEngineUtils.notesForQuery('abcd', null, searchEngine);
expect(rows.length).toBe(3);
expect(rows.map(r=>r.id)).toContain(note1.id);
expect(rows.map(r=>r.id)).toContain(todo1.id);
expect(rows.map(r=>r.id)).toContain(todo2.id);
const options: any = {};
options.fields = ['id', 'title'];
const rows2 = await SearchEngineUtils.notesForQuery('abcd', true, options, searchEngine);
expect(rows2.length).toBe(3);
expect(rows2.map(r=>r.id)).toContain(note1.id);
expect(rows2.map(r=>r.id)).toContain(todo1.id);
expect(rows2.map(r=>r.id)).toContain(todo2.id);
}));
it('hide completed', (async () => {
@@ -52,35 +43,11 @@ describe('services_SearchEngineUtils', function() {
Setting.setValue('showCompletedTodos', false);
const rows = await SearchEngineUtils.notesForQuery('abcd', true, null, searchEngine);
const rows = await SearchEngineUtils.notesForQuery('abcd', null, searchEngine);
expect(rows.length).toBe(2);
expect(rows.map(r=>r.id)).toContain(note1.id);
expect(rows.map(r=>r.id)).toContain(todo1.id);
const options: any = {};
options.fields = ['id', 'title'];
const rows2 = await SearchEngineUtils.notesForQuery('abcd', true, options, searchEngine);
expect(rows2.length).toBe(2);
expect(rows2.map(r=>r.id)).toContain(note1.id);
expect(rows2.map(r=>r.id)).toContain(todo1.id);
}));
it('show completed (!applyUserSettings)', (async () => {
const note1 = await Note.save({ title: 'abcd', body: 'body 1' });
const todo1 = await Note.save({ title: 'abcd', body: 'todo 1', is_todo: 1 });
await Note.save({ title: 'qwer', body: 'body 2' });
const todo2 = await Note.save({ title: 'abcd', body: 'todo 2', is_todo: 1, todo_completed: 1590085027710 });
await searchEngine.syncTables();
Setting.setValue('showCompletedTodos', false);
const rows = await SearchEngineUtils.notesForQuery('abcd', false, null, searchEngine);
expect(rows.length).toBe(3);
expect(rows.map(r=>r.id)).toContain(note1.id);
expect(rows.map(r=>r.id)).toContain(todo1.id);
expect(rows.map(r=>r.id)).toContain(todo2.id);
}));
});
});
@@ -3,7 +3,7 @@ import Note from '../../models/Note';
import Setting from '../../models/Setting';
export default class SearchEngineUtils {
static async notesForQuery(query: string, applyUserSettings: boolean, options: any = null, searchEngine: SearchEngine = null) {
static async notesForQuery(query: string, options: any = null, searchEngine: SearchEngine = null) {
if (!options) options = {};
if (!searchEngine) {
@@ -30,20 +30,6 @@ export default class SearchEngineUtils {
idWasAutoAdded = true;
}
// Add fields is_todo and todo_completed for showCompletedTodos filtering.
// Also remember that the field was auto-added so that it can be removed afterwards.
let isTodoAutoAdded = false;
if (fields.indexOf('is_todo') < 0) {
fields.push('is_todo');
isTodoAutoAdded = true;
}
let isTodoCompletedAutoAdded = false;
if (fields.indexOf('todo_completed') < 0) {
fields.push('todo_completed');
isTodoCompletedAutoAdded = true;
}
const previewOptions = Object.assign({}, {
order: [],
fields: fields,
@@ -52,22 +38,20 @@ export default class SearchEngineUtils {
const notes = await Note.previews(null, previewOptions);
// Filter completed todos
let filteredNotes = [...notes];
if (applyUserSettings && !Setting.value('showCompletedTodos')) {
filteredNotes = notes.filter(note => note.is_todo === 0 || (note.is_todo === 1 && note.todo_completed === 0));
}
// By default, the notes will be returned in reverse order
// or maybe random order so sort them here in the correct order
// (search engine returns the results in order of relevance).
const sortedNotes = [];
for (let i = 0; i < filteredNotes.length; i++) {
const idx = noteIds.indexOf(filteredNotes[i].id);
sortedNotes[idx] = filteredNotes[i];
for (let i = 0; i < notes.length; i++) {
const idx = noteIds.indexOf(notes[i].id);
sortedNotes[idx] = notes[i];
if (idWasAutoAdded) delete sortedNotes[idx].id;
if (isTodoCompletedAutoAdded) delete sortedNotes[idx].is_todo;
if (isTodoAutoAdded) delete sortedNotes[idx].todo_completed;
}
// Filter completed todos
let filteredNotes = [...sortedNotes];
if (!Setting.value('showCompletedTodos')) {
filteredNotes = sortedNotes.filter(note => note.is_todo === 0 || (note.is_todo === 1 && note.todo_completed === 0));
}
// Note that when the search engine index is somehow corrupted, it might contain
@@ -76,9 +60,9 @@ export default class SearchEngineUtils {
// issue: https://discourse.joplinapp.org/t/how-to-recover-corrupted-database/9367
if (noteIds.length !== notes.length) {
// remove null objects
return sortedNotes.filter(n => n);
return filteredNotes.filter(n => n);
} else {
return sortedNotes;
return filteredNotes;
}
}
File diff suppressed because it is too large. Load diff
@@ -21,7 +21,7 @@ enum Requirement {
INCLUSION = 'INCLUSION',
}
const _notebookFilter = (notebooks: string[], requirement: Requirement, conditions: string[], params: string[], withs: string[], useFts: boolean) => {
const _notebookFilter = (notebooks: string[], requirement: Requirement, conditions: string[], params: string[], withs: string[]) => {
if (notebooks.length === 0) return;
const likes = [];
@@ -50,13 +50,12 @@ const _notebookFilter = (notebooks: string[], requirement: Requirement, conditio
ON folders.parent_id=${viewName}.id
)`;
const tableName = useFts ? 'notes_normalized' : 'notes';
const where = `
AND ROWID ${requirement === Requirement.EXCLUSION ? 'NOT' : ''} IN (
SELECT ${tableName}.ROWID
SELECT notes_normalized.ROWID
FROM ${viewName}
JOIN ${tableName}
ON ${viewName}.id=${tableName}.parent_id
JOIN notes_normalized
ON ${viewName}.id=notes_normalized.parent_id
)`;
@@ -66,12 +65,12 @@ const _notebookFilter = (notebooks: string[], requirement: Requirement, conditio
};
const notebookFilter = (terms: Term[], conditions: string[], params: string[], withs: string[], useFts: boolean) => {
const notebookFilter = (terms: Term[], conditions: string[], params: string[], withs: string[]) => {
const notebooksToInclude = terms.filter(x => x.name === 'notebook' && !x.negated).map(x => x.value);
_notebookFilter(notebooksToInclude, Requirement.INCLUSION, conditions, params, withs, useFts);
_notebookFilter(notebooksToInclude, Requirement.INCLUSION, conditions, params, withs);
const notebooksToExclude = terms.filter(x => x.name === 'notebook' && x.negated).map(x => x.value);
_notebookFilter(notebooksToExclude, Requirement.EXCLUSION, conditions, params, withs, useFts);
_notebookFilter(notebooksToExclude, Requirement.EXCLUSION, conditions, params, withs);
};
@@ -88,8 +87,7 @@ const filterByTableName = (
noteIDs: string,
requirement: Requirement,
withs: string[],
tableName: string,
useFts: boolean
tableName: string
) => {
const operator: Operation = getOperator(requirement, relation);
@@ -146,14 +144,13 @@ const filterByTableName = (
}
// Get the ROWIDs that satisfy the condition so we can filter the result
const targetTableName = useFts ? 'notes_normalized' : 'notes';
const whereCondition = `
${relation} ROWID ${(relation === 'AND' && requirement === 'EXCLUSION') ? 'NOT' : ''}
IN (
SELECT ${targetTableName}.ROWID
SELECT notes_normalized.ROWID
FROM notes_with_${requirement}_${tableName}
JOIN ${targetTableName}
ON notes_with_${requirement}_${tableName}.id=${targetTableName}.id
JOIN notes_normalized
ON notes_with_${requirement}_${tableName}.id=notes_normalized.id
)`;
withs.push(withCondition);
@@ -162,7 +159,7 @@ const filterByTableName = (
};
const resourceFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[], useFts: boolean) => {
const resourceFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[]) => {
const tableName = 'resources';
const resourceIDs = `
@@ -180,15 +177,15 @@ const resourceFilter = (terms: Term[], conditions: string[], params: string[], r
const excludedResources = terms.filter(x => x.name === 'resource' && x.negated);
if (requiredResources.length > 0) {
filterByTableName(requiredResources, conditions, params, relation, noteIDsWithResource, Requirement.INCLUSION, withs, tableName, useFts);
filterByTableName(requiredResources, conditions, params, relation, noteIDsWithResource, Requirement.INCLUSION, withs, tableName);
}
if (excludedResources.length > 0) {
filterByTableName(excludedResources, conditions, params, relation, noteIDsWithResource, Requirement.EXCLUSION, withs, tableName, useFts);
filterByTableName(excludedResources, conditions, params, relation, noteIDsWithResource, Requirement.EXCLUSION, withs, tableName);
}
};
const tagFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[], useFts: boolean) => {
const tagFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[]) => {
const tableName = 'tags';
const tagIDs = `
@@ -206,32 +203,30 @@ const tagFilter = (terms: Term[], conditions: string[], params: string[], relati
const excludedTags = terms.filter(x => x.name === 'tag' && x.negated);
if (requiredTags.length > 0) {
filterByTableName(requiredTags, conditions, params, relation, noteIDsWithTag, Requirement.INCLUSION, withs, tableName, useFts);
filterByTableName(requiredTags, conditions, params, relation, noteIDsWithTag, Requirement.INCLUSION, withs, tableName);
}
if (excludedTags.length > 0) {
filterByTableName(excludedTags, conditions, params, relation, noteIDsWithTag, Requirement.EXCLUSION, withs, tableName, useFts);
filterByTableName(excludedTags, conditions, params, relation, noteIDsWithTag, Requirement.EXCLUSION, withs, tableName);
}
};
const genericFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, fieldName: string, useFts: boolean) => {
const genericFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, fieldName: string) => {
if (fieldName === 'iscompleted' || fieldName === 'type') {
// Faster query when values can only take two distinct values
biConditionalFilter(terms, conditions, relation, fieldName, useFts);
biConditionalFilter(terms, conditions, relation, fieldName);
return;
}
const tableName = useFts ? 'notes_normalized' : 'notes';
const getCondition = (term: Term) => {
if (fieldName === 'sourceurl') {
return `${tableName}.source_url ${term.negated ? 'NOT' : ''} LIKE ?`;
return `notes_normalized.source_url ${term.negated ? 'NOT' : ''} LIKE ?`;
} else if (fieldName === 'date' && term.name === 'due') {
return `todo_due ${term.negated ? '<' : '>='} ?`;
} else if (fieldName === 'id') {
return `id ${term.negated ? 'NOT' : ''} LIKE ?`;
} else {
return `${tableName}.${fieldName === 'date' ? `user_${term.name}_time` : `${term.name}`} ${term.negated ? '<' : '>='} ?`;
return `notes_normalized.${fieldName === 'date' ? `user_${term.name}_time` : `${term.name}`} ${term.negated ? '<' : '>='} ?`;
}
};
@@ -239,16 +234,16 @@ const genericFilter = (terms: Term[], conditions: string[], params: string[], re
conditions.push(`
${relation} ( ${term.name === 'due' ? 'is_todo IS 1 AND ' : ''} ROWID IN (
SELECT ROWID
FROM ${tableName}
FROM notes_normalized
WHERE ${getCondition(term)}
))`);
params.push(term.value);
});
};
const biConditionalFilter = (terms: Term[], conditions: string[], relation: Relation, filterName: string, useFts: boolean) => {
const biConditionalFilter = (terms: Term[], conditions: string[], relation: Relation, filterName: string) => {
const getCondition = (filterName: string , value: string, relation: Relation) => {
const tableName = useFts ? (relation === 'AND' ? 'notes_fts' : 'notes_normalized') : 'notes';
const tableName = (relation === 'AND') ? 'notes_fts' : 'notes_normalized';
if (filterName === 'type') {
return `${tableName}.is_todo IS ${value === 'todo' ? 1 : 0}`;
} else if (filterName === 'iscompleted') {
@@ -267,44 +262,39 @@ const biConditionalFilter = (terms: Term[], conditions: string[], relation: Rela
AND ${getCondition(filterName, value, relation)}`);
}
if (relation === 'OR') {
if (useFts) {
conditions.push(`
OR ROWID IN (
SELECT ROWID
FROM notes_normalized
WHERE ${getCondition(filterName, value, relation)}
)`);
} else {
conditions.push(`
OR ${getCondition(filterName, value, relation)}`);
}
conditions.push(`
OR ROWID IN (
SELECT ROWID
FROM notes_normalized
WHERE ${getCondition(filterName, value, relation)}
)`);
}
});
};
const noteIdFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, useFts: boolean) => {
const noteIdFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation) => {
const noteIdTerms = terms.filter(x => x.name === 'id');
genericFilter(noteIdTerms, conditions, params, relation, 'id', useFts);
genericFilter(noteIdTerms, conditions, params, relation, 'id');
};
const typeFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, useFts: boolean) => {
const typeFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation) => {
const typeTerms = terms.filter(x => x.name === 'type');
genericFilter(typeTerms, conditions, params, relation, 'type', useFts);
genericFilter(typeTerms, conditions, params, relation, 'type');
};
const completedFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, useFts: boolean) => {
const completedFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation) => {
const completedTerms = terms.filter(x => x.name === 'iscompleted');
genericFilter(completedTerms, conditions, params, relation, 'iscompleted', useFts);
genericFilter(completedTerms, conditions, params, relation, 'iscompleted');
};
const locationFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation, useFts: boolean) => {
const locationFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation) => {
const locationTerms = terms.filter(x => x.name === 'latitude' || x.name === 'longitude' || x.name === 'altitude');
genericFilter(locationTerms, conditons, params, relation, 'location', useFts);
genericFilter(locationTerms, conditons, params, relation, 'location');
};
const dateFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation, useFts: boolean) => {
const dateFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation) => {
const getUnixMs = (date: string): string => {
const yyyymmdd = /^[0-9]{8}$/;
const yyyymm = /^[0-9]{6}$/;
@@ -331,61 +321,44 @@ const dateFilter = (terms: Term[], conditons: string[], params: string[], relati
const dateTerms = terms.filter(x => x.name === 'created' || x.name === 'updated' || x.name === 'due');
const unixDateTerms = dateTerms.map(term => { return { ...term, value: getUnixMs(term.value) }; });
genericFilter(unixDateTerms, conditons, params, relation, 'date', useFts);
genericFilter(unixDateTerms, conditons, params, relation, 'date');
};
const sourceUrlFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation, useFts: boolean) => {
const sourceUrlFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation) => {
const urlTerms = terms.filter(x => x.name === 'sourceurl');
genericFilter(urlTerms, conditons, params, relation, 'sourceurl', useFts);
genericFilter(urlTerms, conditons, params, relation, 'sourceurl');
};
const trimQuotes = (str: string) => str.startsWith('"') && str.endsWith('"') ? str.substr(1, str.length - 2) : str;
const textFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, useFts: boolean) => {
const createLikeMatch = (term: Term, negate: boolean) => {
const query = `${relation} ${negate ? 'NOT' : ''} (
${(term.name === 'text' || term.name === 'body') ? 'notes.body LIKE ? ' : ''}
${term.name === 'text' ? 'OR' : ''}
${(term.name === 'text' || term.name === 'title') ? 'notes.title LIKE ? ' : ''})`;
conditions.push(query);
const param = `%${trimQuotes(term.value).replace(/\*/, '%')}%`;
params.push(param);
if (term.name === 'text') params.push(param);
};
const textFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation) => {
const addExcludeTextConditions = (excludedTerms: Term[], conditions: string[], params: string[], relation: Relation) => {
if (useFts) {
const type = excludedTerms[0].name === 'text' ? '' : `.${excludedTerms[0].name}`;
if (relation === 'AND') {
conditions.push(`
AND ROWID NOT IN (
SELECT ROWID
FROM notes_fts
WHERE notes_fts${type} MATCH ?
)`);
params.push(excludedTerms.map(x => x.value).join(' OR '));
}
if (relation === 'OR') {
excludedTerms.forEach(term => {
conditions.push(`
OR ROWID IN (
SELECT *
FROM (
SELECT ROWID
FROM notes_fts
EXCEPT
SELECT ROWID
FROM notes_fts
WHERE notes_fts${type} MATCH ?
)
)`);
params.push(term.value);
});
}
} else {
const type = excludedTerms[0].name === 'text' ? '' : `.${excludedTerms[0].name}`;
if (relation === 'AND') {
conditions.push(`
AND ROWID NOT IN (
SELECT ROWID
FROM notes_fts
WHERE notes_fts${type} MATCH ?
)`);
params.push(excludedTerms.map(x => x.value).join(' OR '));
}
if (relation === 'OR') {
excludedTerms.forEach(term => {
createLikeMatch(term, true);
conditions.push(`
OR ROWID IN (
SELECT *
FROM (
SELECT ROWID
FROM notes_fts
EXCEPT
SELECT ROWID
FROM notes_fts
WHERE notes_fts${type} MATCH ?
)
)`);
params.push(term.value);
});
}
};
@@ -394,19 +367,13 @@ const textFilter = (terms: Term[], conditions: string[], params: string[], relat
const includedTerms = allTerms.filter(x => !x.negated);
if (includedTerms.length > 0) {
if (useFts) {
conditions.push(`${relation} notes_fts MATCH ?`);
const termsToMatch = includedTerms.map(term => {
if (term.name === 'text') return term.value;
else return `${term.name}:${term.value}`;
});
const matchQuery = (relation === 'OR') ? termsToMatch.join(' OR ') : termsToMatch.join(' ');
params.push(matchQuery);
} else {
includedTerms.forEach(term => {
createLikeMatch(term, false);
});
}
conditions.push(`${relation} notes_fts MATCH ?`);
const termsToMatch = includedTerms.map(term => {
if (term.name === 'text') return term.value;
else return `${term.name}:${term.value}`;
});
const matchQuery = (relation === 'OR') ? termsToMatch.join(' OR ') : termsToMatch.join(' ');
params.push(matchQuery);
}
const excludedTextTerms = allTerms.filter(x => x.name === 'text' && x.negated);
@@ -437,48 +404,47 @@ const getConnective = (terms: Term[], relation: Relation): string => {
return (!notebookTerm && (relation === 'OR')) ? 'ROWID=-1' : '1'; // ROWID=-1 acts as 0 (something always false)
};
export default function queryBuilder(terms: Term[], useFts: boolean) {
export default function queryBuilder(terms: Term[]) {
const queryParts: string[] = [];
const params: string[] = [];
const withs: string[] = [];
const relation: Relation = getDefaultRelation(terms);
const tableName = useFts ? 'notes_fts' : 'notes';
queryParts.push(`
SELECT
${tableName}.id,
${tableName}.title,
${useFts ? 'offsets(notes_fts) AS offsets, matchinfo(notes_fts, \'pcnalx\') AS matchinfo,' : ''}
${tableName}.user_created_time,
${tableName}.user_updated_time,
${tableName}.is_todo,
${tableName}.todo_completed,
${tableName}.parent_id
FROM ${tableName}
notes_fts.id,
notes_fts.title,
offsets(notes_fts) AS offsets,
matchinfo(notes_fts, 'pcnalx') AS matchinfo,
notes_fts.user_created_time,
notes_fts.user_updated_time,
notes_fts.is_todo,
notes_fts.todo_completed,
notes_fts.parent_id
FROM notes_fts
WHERE ${getConnective(terms, relation)}`);
noteIdFilter(terms, queryParts, params, relation, useFts);
noteIdFilter(terms, queryParts, params, relation);
notebookFilter(terms, queryParts, params, withs, useFts);
notebookFilter(terms, queryParts, params, withs);
tagFilter(terms, queryParts, params, relation, withs, useFts);
tagFilter(terms, queryParts, params, relation, withs);
resourceFilter(terms, queryParts, params, relation, withs, useFts);
resourceFilter(terms, queryParts, params, relation, withs);
textFilter(terms, queryParts, params, relation, useFts);
textFilter(terms, queryParts, params, relation);
typeFilter(terms, queryParts, params, relation, useFts);
typeFilter(terms, queryParts, params, relation);
completedFilter(terms, queryParts, params, relation, useFts);
completedFilter(terms, queryParts, params, relation);
dateFilter(terms, queryParts, params, relation, useFts);
dateFilter(terms, queryParts, params, relation);
locationFilter(terms, queryParts, params, relation, useFts);
locationFilter(terms, queryParts, params, relation);
sourceUrlFilter(terms, queryParts, params, relation, useFts);
sourceUrlFilter(terms, queryParts, params, relation);
let query;
if (withs.length > 0) {
+5 -8
View File
@@ -22,7 +22,7 @@ export default class ShareService {
}
public get enabled(): boolean {
return [9, 10].includes(Setting.value('sync.target')); // Joplin Server, Joplin Cloud targets
return Setting.value('sync.target') === 9; // Joplin Server target
}
private get store(): Store<any> {
@@ -36,13 +36,10 @@ export default class ShareService {
private api(): JoplinServerApi {
if (this.api_) return this.api_;
const syncTargetId = Setting.value('sync.target');
this.api_ = new JoplinServerApi({
baseUrl: () => Setting.value(`sync.${syncTargetId}.path`),
userContentBaseUrl: () => Setting.value(`sync.${syncTargetId}.userContentPath`),
username: () => Setting.value(`sync.${syncTargetId}.username`),
password: () => Setting.value(`sync.${syncTargetId}.password`),
baseUrl: () => Setting.value('sync.9.path'),
username: () => Setting.value('sync.9.username'),
password: () => Setting.value('sync.9.password'),
});
return this.api_;
@@ -137,7 +134,7 @@ export default class ShareService {
}
public shareUrl(share: StateShare): string {
return `${this.api().userContentBaseUrl()}/shares/${share.id}`;
return `${this.api().baseUrl()}/shares/${share.id}`;
}
public get shares() {
Loaded 100 of 260 files, more files were not shown because too many files have changed in this diff. Show more