You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-30 20:39:46 +02:00
Compare commits
88 Commits
server-v2.
...
v2.0.8
Author | SHA1 | Date | |
---|---|---|---|
|
b81c300907 | ||
|
1ded589eeb | ||
|
315216132f | ||
|
2eaa821272 | ||
|
7c93e268e4 | ||
|
d0c4de92e2 | ||
|
91ce465535 | ||
|
4098c01e7c | ||
|
e617e6fab3 | ||
|
5fd6571bf1 | ||
|
00dc1d881b | ||
|
c37eb56ed7 | ||
|
b2b6ad479a | ||
|
0e4c545e14 | ||
|
bbae1aef28 | ||
|
cf86ffc36e | ||
|
9d80a79cda | ||
|
ca487ade9a | ||
|
75b66a9fff | ||
|
56fdf97693 | ||
|
ce02a30441 | ||
|
a058e09183 | ||
|
594084e274 | ||
|
5614eb9442 | ||
|
7a3a2084db | ||
|
95d7ccccea | ||
|
f7a7009b3c | ||
|
de7579a14e | ||
|
c8d7ecbf6c | ||
|
3c41b45e8e | ||
|
62a371b9f3 | ||
|
5528ab7cc8 | ||
|
824afd4809 | ||
|
8ed1330d68 | ||
|
fec5d4b335 | ||
|
e7b9103bfc | ||
|
dd1c9e3c2a | ||
|
7c45b95f6f | ||
|
a7e67952b8 | ||
|
1b7d40387d | ||
|
7921e70c4f | ||
|
8afac643ba | ||
|
23cfbc2367 | ||
|
de45740129 | ||
|
a04d8ef441 | ||
|
db7b802803 | ||
|
75d79f373a | ||
|
e8a02c26d0 | ||
|
147b6b13ab | ||
|
a496a3d90d | ||
|
69a8ada2ec | ||
|
87257870f4 | ||
|
21ea3253db | ||
|
770af6a53b | ||
|
c88e4f6628 | ||
|
2f79492192 | ||
|
69aa749205 | ||
|
87a5f18c7b | ||
|
1d2a3a97d2 | ||
|
42891e37a1 | ||
|
fe802b8ebc | ||
|
3cb6d4568c | ||
|
a9f0a75d9d | ||
|
07d30eb5d2 | ||
|
8f6a47536c | ||
|
d8d83b236e | ||
|
a355600e76 | ||
|
2a58664735 | ||
|
89bc181072 | ||
|
ab7380a09f | ||
|
f8a26cf8f9 | ||
|
3505a2a973 | ||
|
5f94de0f24 | ||
|
6811ea1eb9 | ||
|
7be59a7435 | ||
|
c0683ca4c3 | ||
|
2b286410f6 | ||
|
907ac7c1f8 | ||
|
8bc27021db | ||
|
41ed66d323 | ||
|
0ef7e98479 | ||
|
161c77cb48 | ||
|
50d17bfb36 | ||
|
ee0f23718b | ||
|
cfe4546a0b | ||
|
f45e0d106f | ||
|
12a66342db | ||
|
f2b17560e6 |
@@ -140,6 +140,9 @@ 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
|
||||
@@ -338,6 +341,9 @@ 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
|
||||
@@ -821,6 +827,9 @@ 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
|
||||
@@ -1475,6 +1484,9 @@ 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
|
||||
@@ -1613,4 +1625,7 @@ 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
|
||||
|
128
.github/scripts/run_ci.sh
vendored
Executable file
128
.github/scripts/run_ci.sh
vendored
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/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_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
|
||||
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
|
38
.github/workflows/github-actions-main.yml
vendored
Normal file
38
.github/workflows/github-actions-main.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
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 }}
|
||||
IS_CONTINUOUS_INTEGRATION: 1
|
||||
run: |
|
||||
"${GITHUB_WORKSPACE}/.github/scripts/run_ci.sh"
|
15
.gitignore
vendored
15
.gitignore
vendored
@@ -126,6 +126,9 @@ 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
|
||||
@@ -324,6 +327,9 @@ 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
|
||||
@@ -807,6 +813,9 @@ 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
|
||||
@@ -1461,6 +1470,9 @@ 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
|
||||
@@ -1599,4 +1611,7 @@ 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
.travis.yml
138
.travis.yml
@@ -1,138 +0,0 @@
|
||||
# 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
|
BIN
Assets/WebsiteAssets/images/sponsors/Tranio.png
Normal file
BIN
Assets/WebsiteAssets/images/sponsors/Tranio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
4
BUILD.md
4
BUILD.md
@@ -1,5 +1,3 @@
|
||||
[](https://travis-ci.org/laurent22/joplin) [](https://ci.appveyor.com/project/laurent22/joplin)
|
||||
|
||||
# Building the applications
|
||||
|
||||
The Joplin source code is hosted on a [monorepo](https://en.wikipedia.org/wiki/Monorepo) managed by Lerna. The usage of Lerna is mostly transparent as the needed commands have been moved to the root package.json and thus are invoked for example when running `npm install` or `npm run watch`. The main thing to know about Lerna is that it links the packages in the monorepo using `npm link`, so if you check the node_modules directory you will see links instead of actual directories for certain packages. This is something to keep in mind as these links can cause issues in some cases.
|
||||
@@ -64,7 +62,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/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).
|
||||
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).
|
||||
|
||||
## Watching files
|
||||
|
||||
|
228
README.md
228
README.md
@@ -64,17 +64,19 @@ 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://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>
|
||||
|
||||
* * *
|
||||
|
||||
| | | |
|
||||
| :---: | :---: | :---: |
|
||||
| <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)
|
||||
<!-- 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 -->
|
||||
|
||||
<!-- TOC -->
|
||||
# Table of contents
|
||||
@@ -407,6 +409,12 @@ 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
|
||||
@@ -511,47 +519,47 @@ Current translations:
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
| Language | Po File | Last translator | Percent done
|
||||
---|---|---|---|---
|
||||
 | Arabic | [ar](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ar.po) | [Whaell O](mailto:Whaell@protonmail.com) | 96%
|
||||
 | Basque | [eu](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eu.po) | juan.abasolo@ehu.eus | 30%
|
||||
 | 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%
|
||||
 | Bulgarian (България) | [bg_BG](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/bg_BG.po) | | 58%
|
||||
 | Catalan | [ca](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ca.po) | jmontane, 2019 | 83%
|
||||
 | Croatian (Hrvatska) | [hr_HR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/hr_HR.po) | [Milo Ivir](mailto:mail@milotype.de) | 96%
|
||||
 | 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%
|
||||
 | 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%
|
||||
 | Deutsch (Deutschland) | [de_DE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/de_DE.po) | [Atalanttore](mailto:atalanttore@googlemail.com) | 95%
|
||||
 | Eesti Keel (Eesti) | [et_EE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/et_EE.po) | | 57%
|
||||
 | English (United Kingdom) | [en_GB](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_GB.po) | | 100%
|
||||
 | English (United States of America) | [en_US](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/en_US.po) | | 100%
|
||||
 | 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%
|
||||
 | Esperanto | [eo](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/eo.po) | Marton Paulo | 33%
|
||||
 | Finnish (Suomi) | [fi_FI](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fi_FI.po) | mrkaato | 94%
|
||||
 | Français (France) | [fr_FR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fr_FR.po) | Laurent Cozic | 99%
|
||||
 | 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%
|
||||
 | 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%
|
||||
 | Italiano (Italia) | [it_IT](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/it_IT.po) | [Alessandro Bernardello](mailto:mailfilledwithspam@gmail.com) | 94%
|
||||
 | 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%
|
||||
 | Nederlands (België, Belgique, Belgien) | [nl_BE](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/nl_BE.po) | | 92%
|
||||
 | 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%
|
||||
 | 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%
|
||||
 | Persian | [fa](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/fa.po) | [Kourosh Firoozbakht](mailto:kourox@protonmail.com) | 71%
|
||||
 | Polski (Polska) | [pl_PL](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/pl_PL.po) | [konhi](mailto:hello.konhi@gmail.com) | 94%
|
||||
 | 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%
|
||||
 | 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%
|
||||
 | Română | [ro](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ro.po) | [Cristi Duluta](mailto:cristi.duluta@gmail.com) | 66%
|
||||
 | 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%
|
||||
 | Svenska | [sv](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sv.po) | [Jonatan Nyberg](mailto:jonatan@autistici.org) | 61%
|
||||
 | Thai (ประเทศไทย) | [th_TH](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/th_TH.po) | | 45%
|
||||
 | Tiếng Việt | [vi](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/vi.po) | | 73%
|
||||
 | 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%
|
||||
 | Ukrainian (Україна) | [uk_UA](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/uk_UA.po) | [Vyacheslav Andreykiv](mailto:vandreykiv@gmail.com) | 94%
|
||||
 | Ελληνικά (Ελλάδα) | [el_GR](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/el_GR.po) | [Harris Arvanitis](mailto:xaris@tuta.io) | 97%
|
||||
 | Русский (Россия) | [ru_RU](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ru_RU.po) | [Sergey Segeda](mailto:thesermanarm@gmail.com) | 94%
|
||||
 | српски језик (Србија) | [sr_RS](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/sr_RS.po) | | 71%
|
||||
 | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_CN.po) | [Yang Zhang](mailto:zyangmath@gmail.com) | 94%
|
||||
 | 中文 (繁體) | [zh_TW](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/zh_TW.po) | [Yaoze Ye](mailto:yaozeye@yahoo.co.jp) | 92%
|
||||
 | 日本語 (日本) | [ja_JP](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ja_JP.po) | [genneko](mailto:genneko217@gmail.com) | 97%
|
||||
 | 한국어 | [ko](https://github.com/laurent22/joplin/blob/dev/packages/tools/locales/ko.po) | [Ji-Hyeon Gim](mailto:potatogim@potatogim.net) | 96%
|
||||
<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%
|
||||
<!-- LOCALE-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# Contributors
|
||||
@@ -561,52 +569,80 @@ Thank you to everyone who've contributed to Joplin's source code!
|
||||
<!-- CONTRIBUTORS-TABLE-AUTO-GENERATED -->
|
||||
| | | | | |
|
||||
| :---: | :---: | :---: | :---: | :---: |
|
||||
| <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) | | | |
|
||||
| <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) | | |
|
||||
<!-- CONTRIBUTORS-TABLE-AUTO-GENERATED -->
|
||||
|
||||
# Known bugs
|
||||
@@ -618,7 +654,7 @@ Thank you to everyone who've contributed to Joplin's source code!
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2020 Laurent Cozic
|
||||
Copyright (c) 2016-2021 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:
|
||||
|
||||
|
@@ -86,6 +86,28 @@
|
||||
</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">'editor.execCommand'</span>, {
|
||||
<span class="hljs-attr">name</span>: <span class="hljs-string">'madeUpCommand'</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">''</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>
|
||||
<!--
|
||||
|
@@ -149,10 +149,8 @@
|
||||
<p>Currently the supported context variables aren't documented, but you can
|
||||
find the list below:</p>
|
||||
<ul>
|
||||
<li>[Global When
|
||||
Clauses](<a href="https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts">https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts</a>).</li>
|
||||
<li>[Desktop app When
|
||||
Clauses](<a href="https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts">https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts</a>).</li>
|
||||
<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>Note: Commands are enabled by default unless you use this property.</p>
|
||||
</div>
|
||||
|
@@ -405,6 +405,12 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog.md
|
||||
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&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>
|
||||
|
@@ -405,6 +405,40 @@ https://github.com/laurent22/joplin/blob/dev/readme/changelog_server.md
|
||||
<p><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&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>
|
||||
|
BIN
docs/images/sponsors/Tranio.png
Normal file
BIN
docs/images/sponsors/Tranio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
830
docs/index.html
830
docs/index.html
File diff suppressed because it is too large
Load Diff
@@ -99,17 +99,17 @@
|
||||
"sync.9.path": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"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/"
|
||||
"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/"
|
||||
},
|
||||
"sync.9.username": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Joplin Server email"
|
||||
"description": "Joplin Cloud email"
|
||||
},
|
||||
"sync.9.password": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Joplin Server password",
|
||||
"description": "Joplin Cloud password",
|
||||
"$comment": "private"
|
||||
},
|
||||
"sync.5.syncTargets": {
|
||||
|
@@ -415,15 +415,15 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Total Windows downloads</td>
|
||||
<td>1,425,567</td>
|
||||
<td>1,444,540</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total macOs downloads</td>
|
||||
<td>554,909</td>
|
||||
<td>561,465</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Linux downloads</td>
|
||||
<td>463,554</td>
|
||||
<td>473,228</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Windows %</td>
|
||||
@@ -453,92 +453,100 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.4">v2.0.4</a> (p)</td>
|
||||
<td>2021-06-02T12:54:17Z</td>
|
||||
<td>898</td>
|
||||
<td>267</td>
|
||||
<td>242</td>
|
||||
<td>1,407</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.2">v2.0.2</a> (p)</td>
|
||||
<td>2021-05-21T18:07:48Z</td>
|
||||
<td>594</td>
|
||||
<td>179</td>
|
||||
<td>448</td>
|
||||
<td>1,221</td>
|
||||
<td>1,953</td>
|
||||
<td>470</td>
|
||||
<td>1,554</td>
|
||||
<td>3,977</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v2.0.1">v2.0.1</a> (p)</td>
|
||||
<td>2021-05-15T13:22:58Z</td>
|
||||
<td>770</td>
|
||||
<td>243</td>
|
||||
<td>984</td>
|
||||
<td>1,997</td>
|
||||
<td>784</td>
|
||||
<td>245</td>
|
||||
<td>994</td>
|
||||
<td>2,023</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.5">v1.8.5</a></td>
|
||||
<td>2021-05-10T11:58:14Z</td>
|
||||
<td>11,870</td>
|
||||
<td>6,670</td>
|
||||
<td>5,753</td>
|
||||
<td>24,293</td>
|
||||
<td>27,272</td>
|
||||
<td>12,591</td>
|
||||
<td>13,983</td>
|
||||
<td>53,846</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.4">v1.8.4</a> (p)</td>
|
||||
<td>2021-05-09T18:05:05Z</td>
|
||||
<td>623</td>
|
||||
<td>656</td>
|
||||
<td>120</td>
|
||||
<td>433</td>
|
||||
<td>1,176</td>
|
||||
<td>1,209</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.3">v1.8.3</a> (p)</td>
|
||||
<td>2021-05-04T10:38:16Z</td>
|
||||
<td>1,049</td>
|
||||
<td>290</td>
|
||||
<td>1,280</td>
|
||||
<td>293</td>
|
||||
<td>912</td>
|
||||
<td>2,251</td>
|
||||
<td>2,485</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.2">v1.8.2</a> (p)</td>
|
||||
<td>2021-04-25T10:50:51Z</td>
|
||||
<td>1,445</td>
|
||||
<td>1,473</td>
|
||||
<td>421</td>
|
||||
<td>1,261</td>
|
||||
<td>3,127</td>
|
||||
<td>3,155</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.8.1">v1.8.1</a> (p)</td>
|
||||
<td>2021-03-29T10:46:41Z</td>
|
||||
<td>3,003</td>
|
||||
<td>3,025</td>
|
||||
<td>805</td>
|
||||
<td>2,418</td>
|
||||
<td>6,226</td>
|
||||
<td>2,419</td>
|
||||
<td>6,249</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.11">v1.7.11</a></td>
|
||||
<td>2021-02-03T12:50:01Z</td>
|
||||
<td>113,794</td>
|
||||
<td>42,526</td>
|
||||
<td>64,040</td>
|
||||
<td>220,360</td>
|
||||
<td>113,946</td>
|
||||
<td>42,556</td>
|
||||
<td>64,079</td>
|
||||
<td>220,581</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.10">v1.7.10</a></td>
|
||||
<td>2021-01-30T13:25:29Z</td>
|
||||
<td>13,825</td>
|
||||
<td>13,830</td>
|
||||
<td>4,831</td>
|
||||
<td>4,425</td>
|
||||
<td>23,081</td>
|
||||
<td>4,429</td>
|
||||
<td>23,090</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.9">v1.7.9</a> (p)</td>
|
||||
<td>2021-01-28T09:50:21Z</td>
|
||||
<td>480</td>
|
||||
<td>481</td>
|
||||
<td>123</td>
|
||||
<td>483</td>
|
||||
<td>1,086</td>
|
||||
<td>1,087</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.6">v1.7.6</a> (p)</td>
|
||||
<td>2021-01-27T10:36:05Z</td>
|
||||
<td>283</td>
|
||||
<td>284</td>
|
||||
<td>82</td>
|
||||
<td>277</td>
|
||||
<td>642</td>
|
||||
<td>643</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.5">v1.7.5</a> (p)</td>
|
||||
@@ -559,10 +567,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.8">v1.6.8</a></td>
|
||||
<td>2021-01-20T18:11:34Z</td>
|
||||
<td>18,064</td>
|
||||
<td>7,662</td>
|
||||
<td>7,578</td>
|
||||
<td>33,304</td>
|
||||
<td>18,148</td>
|
||||
<td>7,665</td>
|
||||
<td>7,581</td>
|
||||
<td>33,394</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.7.3">v1.7.3</a> (p)</td>
|
||||
@@ -575,50 +583,50 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.7">v1.6.7</a></td>
|
||||
<td>2021-01-11T23:20:33Z</td>
|
||||
<td>10,375</td>
|
||||
<td>4,617</td>
|
||||
<td>10,418</td>
|
||||
<td>4,619</td>
|
||||
<td>4,531</td>
|
||||
<td>19,523</td>
|
||||
<td>19,568</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.6">v1.6.6</a></td>
|
||||
<td>2021-01-09T16:15:31Z</td>
|
||||
<td>12,359</td>
|
||||
<td>12,362</td>
|
||||
<td>3,405</td>
|
||||
<td>4,776</td>
|
||||
<td>20,540</td>
|
||||
<td>20,543</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.5">v1.6.5</a> (p)</td>
|
||||
<td>2021-01-09T01:24:32Z</td>
|
||||
<td>553</td>
|
||||
<td>580</td>
|
||||
<td>57</td>
|
||||
<td>300</td>
|
||||
<td>910</td>
|
||||
<td>937</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.4">v1.6.4</a> (p)</td>
|
||||
<td>2021-01-07T19:11:32Z</td>
|
||||
<td>381</td>
|
||||
<td>72</td>
|
||||
<td>73</td>
|
||||
<td>197</td>
|
||||
<td>650</td>
|
||||
<td>651</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.2">v1.6.2</a> (p)</td>
|
||||
<td>2021-01-04T22:34:55Z</td>
|
||||
<td>664</td>
|
||||
<td>665</td>
|
||||
<td>221</td>
|
||||
<td>577</td>
|
||||
<td>1,462</td>
|
||||
<td>1,463</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.14">v1.5.14</a></td>
|
||||
<td>2020-12-30T01:48:46Z</td>
|
||||
<td>10,847</td>
|
||||
<td>5,191</td>
|
||||
<td>10,889</td>
|
||||
<td>5,192</td>
|
||||
<td>5,512</td>
|
||||
<td>21,550</td>
|
||||
<td>21,593</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.6.1">v1.6.1</a> (p)</td>
|
||||
@@ -639,18 +647,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.12">v1.5.12</a></td>
|
||||
<td>2020-12-28T15:14:08Z</td>
|
||||
<td>2,374</td>
|
||||
<td>2,378</td>
|
||||
<td>1,762</td>
|
||||
<td>911</td>
|
||||
<td>5,047</td>
|
||||
<td>5,051</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.11">v1.5.11</a></td>
|
||||
<td>2020-12-27T19:54:07Z</td>
|
||||
<td>13,999</td>
|
||||
<td>14,009</td>
|
||||
<td>4,605</td>
|
||||
<td>4,253</td>
|
||||
<td>22,857</td>
|
||||
<td>4,254</td>
|
||||
<td>22,868</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.10">v1.5.10</a> (p)</td>
|
||||
@@ -664,9 +672,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.9">v1.5.9</a> (p)</td>
|
||||
<td>2020-12-23T18:01:08Z</td>
|
||||
<td>321</td>
|
||||
<td>367</td>
|
||||
<td>399</td>
|
||||
<td>1,087</td>
|
||||
<td>368</td>
|
||||
<td>400</td>
|
||||
<td>1,089</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.5.8">v1.5.8</a> (p)</td>
|
||||
@@ -695,26 +703,26 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.19">v1.4.19</a></td>
|
||||
<td>2020-12-01T11:11:16Z</td>
|
||||
<td>25,492</td>
|
||||
<td>13,350</td>
|
||||
<td>11,610</td>
|
||||
<td>50,452</td>
|
||||
<td>25,530</td>
|
||||
<td>13,358</td>
|
||||
<td>11,615</td>
|
||||
<td>50,503</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.18">v1.4.18</a></td>
|
||||
<td>2020-11-28T12:21:41Z</td>
|
||||
<td>11,082</td>
|
||||
<td>3,870</td>
|
||||
<td>3,076</td>
|
||||
<td>18,028</td>
|
||||
<td>11,087</td>
|
||||
<td>3,871</td>
|
||||
<td>3,081</td>
|
||||
<td>18,039</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.16">v1.4.16</a></td>
|
||||
<td>2020-11-27T19:40:16Z</td>
|
||||
<td>1,452</td>
|
||||
<td>1,457</td>
|
||||
<td>822</td>
|
||||
<td>584</td>
|
||||
<td>2,858</td>
|
||||
<td>2,863</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.15">v1.4.15</a></td>
|
||||
@@ -727,18 +735,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.12">v1.4.12</a></td>
|
||||
<td>2020-11-23T18:58:07Z</td>
|
||||
<td>2,983</td>
|
||||
<td>2,994</td>
|
||||
<td>1,316</td>
|
||||
<td>1,287</td>
|
||||
<td>5,586</td>
|
||||
<td>5,597</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.11">v1.4.11</a> (p)</td>
|
||||
<td>2020-11-19T23:06:51Z</td>
|
||||
<td>946</td>
|
||||
<td>147</td>
|
||||
<td>974</td>
|
||||
<td>148</td>
|
||||
<td>574</td>
|
||||
<td>1,667</td>
|
||||
<td>1,696</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.10">v1.4.10</a> (p)</td>
|
||||
@@ -751,10 +759,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.9">v1.4.9</a> (p)</td>
|
||||
<td>2020-11-11T14:23:17Z</td>
|
||||
<td>497</td>
|
||||
<td>499</td>
|
||||
<td>133</td>
|
||||
<td>393</td>
|
||||
<td>1,023</td>
|
||||
<td>1,025</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.7">v1.4.7</a> (p)</td>
|
||||
@@ -767,10 +775,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.18">v1.3.18</a></td>
|
||||
<td>2020-11-06T12:07:02Z</td>
|
||||
<td>30,609</td>
|
||||
<td>30,668</td>
|
||||
<td>11,316</td>
|
||||
<td>10,495</td>
|
||||
<td>52,420</td>
|
||||
<td>52,479</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.17">v1.3.17</a> (p)</td>
|
||||
@@ -783,18 +791,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.4.6">v1.4.6</a> (p)</td>
|
||||
<td>2020-11-05T22:44:12Z</td>
|
||||
<td>339</td>
|
||||
<td>341</td>
|
||||
<td>86</td>
|
||||
<td>45</td>
|
||||
<td>470</td>
|
||||
<td>472</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.15">v1.3.15</a></td>
|
||||
<td>2020-11-04T12:22:50Z</td>
|
||||
<td>2,221</td>
|
||||
<td>2,223</td>
|
||||
<td>1,290</td>
|
||||
<td>836</td>
|
||||
<td>4,347</td>
|
||||
<td>4,349</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.11">v1.3.11</a> (p)</td>
|
||||
@@ -807,10 +815,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.10">v1.3.10</a> (p)</td>
|
||||
<td>2020-10-29T13:27:14Z</td>
|
||||
<td>368</td>
|
||||
<td>369</td>
|
||||
<td>107</td>
|
||||
<td>307</td>
|
||||
<td>782</td>
|
||||
<td>783</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.9">v1.3.9</a> (p)</td>
|
||||
@@ -848,9 +856,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.3">v1.3.3</a> (p)</td>
|
||||
<td>2020-10-17T10:56:57Z</td>
|
||||
<td>113</td>
|
||||
<td>36</td>
|
||||
<td>38</td>
|
||||
<td>25</td>
|
||||
<td>174</td>
|
||||
<td>176</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.2">v1.3.2</a> (p)</td>
|
||||
@@ -864,17 +872,17 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.3.1">v1.3.1</a> (p)</td>
|
||||
<td>2020-10-11T15:10:18Z</td>
|
||||
<td>77</td>
|
||||
<td>45</td>
|
||||
<td>35</td>
|
||||
<td>157</td>
|
||||
<td>46</td>
|
||||
<td>36</td>
|
||||
<td>159</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.6">v1.2.6</a></td>
|
||||
<td>2020-10-09T13:56:59Z</td>
|
||||
<td>44,164</td>
|
||||
<td>44,215</td>
|
||||
<td>17,713</td>
|
||||
<td>14,024</td>
|
||||
<td>75,901</td>
|
||||
<td>14,026</td>
|
||||
<td>75,954</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.4">v1.2.4</a> (p)</td>
|
||||
@@ -895,18 +903,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.2.2">v1.2.2</a> (p)</td>
|
||||
<td>2020-09-22T20:31:55Z</td>
|
||||
<td>777</td>
|
||||
<td>779</td>
|
||||
<td>199</td>
|
||||
<td>631</td>
|
||||
<td>1,607</td>
|
||||
<td>1,609</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.4">v1.1.4</a></td>
|
||||
<td>2020-09-21T11:20:09Z</td>
|
||||
<td>27,572</td>
|
||||
<td>13,489</td>
|
||||
<td>27,592</td>
|
||||
<td>13,490</td>
|
||||
<td>7,740</td>
|
||||
<td>48,801</td>
|
||||
<td>48,822</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.3">v1.1.3</a> (p)</td>
|
||||
@@ -927,42 +935,42 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.1.1">v1.1.1</a> (p)</td>
|
||||
<td>2020-09-11T23:32:47Z</td>
|
||||
<td>519</td>
|
||||
<td>521</td>
|
||||
<td>195</td>
|
||||
<td>342</td>
|
||||
<td>1,056</td>
|
||||
<td>1,058</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.245">v1.0.245</a></td>
|
||||
<td>2020-09-09T12:56:10Z</td>
|
||||
<td>21,148</td>
|
||||
<td>21,177</td>
|
||||
<td>9,999</td>
|
||||
<td>5,634</td>
|
||||
<td>36,781</td>
|
||||
<td>36,810</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.242">v1.0.242</a></td>
|
||||
<td>2020-09-04T22:00:34Z</td>
|
||||
<td>12,439</td>
|
||||
<td>12,446</td>
|
||||
<td>6,418</td>
|
||||
<td>3,015</td>
|
||||
<td>21,872</td>
|
||||
<td>21,879</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.241">v1.0.241</a></td>
|
||||
<td>2020-09-04T18:06:00Z</td>
|
||||
<td>23,628</td>
|
||||
<td>5,748</td>
|
||||
<td>4,994</td>
|
||||
<td>34,370</td>
|
||||
<td>23,665</td>
|
||||
<td>5,749</td>
|
||||
<td>4,995</td>
|
||||
<td>34,409</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.239">v1.0.239</a> (p)</td>
|
||||
<td>2020-09-01T21:56:36Z</td>
|
||||
<td>599</td>
|
||||
<td>601</td>
|
||||
<td>226</td>
|
||||
<td>400</td>
|
||||
<td>1,225</td>
|
||||
<td>1,227</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.237">v1.0.237</a> (p)</td>
|
||||
@@ -983,26 +991,26 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.235">v1.0.235</a> (p)</td>
|
||||
<td>2020-08-18T22:08:01Z</td>
|
||||
<td>1,671</td>
|
||||
<td>1,673</td>
|
||||
<td>489</td>
|
||||
<td>920</td>
|
||||
<td>3,080</td>
|
||||
<td>3,082</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.234">v1.0.234</a> (p)</td>
|
||||
<td>2020-08-17T23:13:02Z</td>
|
||||
<td>536</td>
|
||||
<td>538</td>
|
||||
<td>125</td>
|
||||
<td>100</td>
|
||||
<td>761</td>
|
||||
<td>763</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.233">v1.0.233</a></td>
|
||||
<td>2020-08-01T14:51:15Z</td>
|
||||
<td>43,098</td>
|
||||
<td>43,153</td>
|
||||
<td>18,188</td>
|
||||
<td>12,358</td>
|
||||
<td>73,644</td>
|
||||
<td>73,699</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.232">v1.0.232</a> (p)</td>
|
||||
@@ -1015,10 +1023,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.227">v1.0.227</a></td>
|
||||
<td>2020-07-07T20:44:54Z</td>
|
||||
<td>40,384</td>
|
||||
<td>15,273</td>
|
||||
<td>9,627</td>
|
||||
<td>65,284</td>
|
||||
<td>40,414</td>
|
||||
<td>15,274</td>
|
||||
<td>9,629</td>
|
||||
<td>65,317</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.226">v1.0.226</a> (p)</td>
|
||||
@@ -1031,10 +1039,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.224">v1.0.224</a></td>
|
||||
<td>2020-06-20T22:26:08Z</td>
|
||||
<td>24,774</td>
|
||||
<td>24,788</td>
|
||||
<td>11,005</td>
|
||||
<td>6,006</td>
|
||||
<td>41,785</td>
|
||||
<td>41,799</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.223">v1.0.223</a> (p)</td>
|
||||
@@ -1055,18 +1063,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.220">v1.0.220</a></td>
|
||||
<td>2020-06-13T18:26:22Z</td>
|
||||
<td>31,712</td>
|
||||
<td>31,734</td>
|
||||
<td>9,916</td>
|
||||
<td>6,411</td>
|
||||
<td>48,039</td>
|
||||
<td>48,061</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.218">v1.0.218</a></td>
|
||||
<td>2020-06-07T10:43:34Z</td>
|
||||
<td>14,535</td>
|
||||
<td>14,536</td>
|
||||
<td>6,968</td>
|
||||
<td>2,954</td>
|
||||
<td>24,457</td>
|
||||
<td>24,458</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.217">v1.0.217</a> (p)</td>
|
||||
@@ -1079,18 +1087,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.216">v1.0.216</a></td>
|
||||
<td>2020-05-24T14:21:01Z</td>
|
||||
<td>37,277</td>
|
||||
<td>14,268</td>
|
||||
<td>37,327</td>
|
||||
<td>14,269</td>
|
||||
<td>10,177</td>
|
||||
<td>61,722</td>
|
||||
<td>61,773</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.214">v1.0.214</a> (p)</td>
|
||||
<td>2020-05-21T17:15:15Z</td>
|
||||
<td>6,529</td>
|
||||
<td>6,545</td>
|
||||
<td>3,466</td>
|
||||
<td>760</td>
|
||||
<td>10,755</td>
|
||||
<td>10,771</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.212">v1.0.212</a> (p)</td>
|
||||
@@ -1119,18 +1127,18 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.207">v1.0.207</a> (p)</td>
|
||||
<td>2020-05-10T16:37:35Z</td>
|
||||
<td>1,187</td>
|
||||
<td>1,188</td>
|
||||
<td>263</td>
|
||||
<td>1,016</td>
|
||||
<td>2,466</td>
|
||||
<td>2,467</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.201">v1.0.201</a></td>
|
||||
<td>2020-04-15T22:55:13Z</td>
|
||||
<td>53,311</td>
|
||||
<td>53,324</td>
|
||||
<td>20,043</td>
|
||||
<td>18,180</td>
|
||||
<td>91,534</td>
|
||||
<td>91,547</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.200">v1.0.200</a></td>
|
||||
@@ -1143,98 +1151,98 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.199">v1.0.199</a></td>
|
||||
<td>2020-04-10T18:41:58Z</td>
|
||||
<td>19,339</td>
|
||||
<td>19,347</td>
|
||||
<td>5,884</td>
|
||||
<td>3,788</td>
|
||||
<td>29,011</td>
|
||||
<td>29,019</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.197">v1.0.197</a></td>
|
||||
<td>2020-03-30T17:21:22Z</td>
|
||||
<td>22,280</td>
|
||||
<td>22,290</td>
|
||||
<td>9,540</td>
|
||||
<td>5,726</td>
|
||||
<td>37,546</td>
|
||||
<td>5,734</td>
|
||||
<td>37,564</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.195">v1.0.195</a></td>
|
||||
<td>2020-03-22T19:56:12Z</td>
|
||||
<td>18,890</td>
|
||||
<td>7,948</td>
|
||||
<td>18,892</td>
|
||||
<td>7,949</td>
|
||||
<td>4,506</td>
|
||||
<td>31,344</td>
|
||||
<td>31,347</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.194">v1.0.194</a> (p)</td>
|
||||
<td>2020-03-14T00:00:32Z</td>
|
||||
<td>1,285</td>
|
||||
<td>1,375</td>
|
||||
<td>511</td>
|
||||
<td>3,171</td>
|
||||
<td>1,377</td>
|
||||
<td>513</td>
|
||||
<td>3,175</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.193">v1.0.193</a></td>
|
||||
<td>2020-03-08T08:58:53Z</td>
|
||||
<td>28,641</td>
|
||||
<td>28,642</td>
|
||||
<td>10,907</td>
|
||||
<td>7,392</td>
|
||||
<td>46,940</td>
|
||||
<td>7,393</td>
|
||||
<td>46,942</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.192">v1.0.192</a> (p)</td>
|
||||
<td>2020-03-06T23:27:52Z</td>
|
||||
<td>472</td>
|
||||
<td>473</td>
|
||||
<td>122</td>
|
||||
<td>89</td>
|
||||
<td>683</td>
|
||||
<td>684</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.190">v1.0.190</a> (p)</td>
|
||||
<td>2020-03-06T01:22:22Z</td>
|
||||
<td>373</td>
|
||||
<td>374</td>
|
||||
<td>90</td>
|
||||
<td>85</td>
|
||||
<td>548</td>
|
||||
<td>549</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.189">v1.0.189</a> (p)</td>
|
||||
<td>2020-03-04T17:27:15Z</td>
|
||||
<td>342</td>
|
||||
<td>343</td>
|
||||
<td>96</td>
|
||||
<td>90</td>
|
||||
<td>528</td>
|
||||
<td>529</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.187">v1.0.187</a> (p)</td>
|
||||
<td>2020-03-01T12:31:06Z</td>
|
||||
<td>919</td>
|
||||
<td>920</td>
|
||||
<td>230</td>
|
||||
<td>263</td>
|
||||
<td>1,412</td>
|
||||
<td>1,413</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.179">v1.0.179</a></td>
|
||||
<td>2020-01-24T22:42:41Z</td>
|
||||
<td>71,023</td>
|
||||
<td>28,545</td>
|
||||
<td>22,534</td>
|
||||
<td>122,102</td>
|
||||
<td>71,040</td>
|
||||
<td>28,550</td>
|
||||
<td>22,535</td>
|
||||
<td>122,125</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.178">v1.0.178</a></td>
|
||||
<td>2020-01-20T19:06:45Z</td>
|
||||
<td>17,539</td>
|
||||
<td>17,540</td>
|
||||
<td>5,962</td>
|
||||
<td>2,584</td>
|
||||
<td>26,085</td>
|
||||
<td>26,086</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.177">v1.0.177</a> (p)</td>
|
||||
<td>2019-12-30T14:40:40Z</td>
|
||||
<td>1,943</td>
|
||||
<td>1,944</td>
|
||||
<td>438</td>
|
||||
<td>678</td>
|
||||
<td>3,059</td>
|
||||
<td>679</td>
|
||||
<td>3,061</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.176">v1.0.176</a> (p)</td>
|
||||
@@ -1247,42 +1255,42 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.175">v1.0.175</a></td>
|
||||
<td>2019-12-08T11:48:47Z</td>
|
||||
<td>72,519</td>
|
||||
<td>16,905</td>
|
||||
<td>72,538</td>
|
||||
<td>16,906</td>
|
||||
<td>16,509</td>
|
||||
<td>105,933</td>
|
||||
<td>105,953</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.174">v1.0.174</a></td>
|
||||
<td>2019-11-12T18:20:58Z</td>
|
||||
<td>30,401</td>
|
||||
<td>30,407</td>
|
||||
<td>11,722</td>
|
||||
<td>8,221</td>
|
||||
<td>50,344</td>
|
||||
<td>50,350</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.173">v1.0.173</a></td>
|
||||
<td>2019-11-11T08:33:35Z</td>
|
||||
<td>5,072</td>
|
||||
<td>5,074</td>
|
||||
<td>2,077</td>
|
||||
<td>743</td>
|
||||
<td>7,892</td>
|
||||
<td>7,894</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.170">v1.0.170</a></td>
|
||||
<td>2019-10-13T22:13:04Z</td>
|
||||
<td>27,413</td>
|
||||
<td>27,424</td>
|
||||
<td>8,752</td>
|
||||
<td>7,675</td>
|
||||
<td>43,840</td>
|
||||
<td>43,851</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.169">v1.0.169</a></td>
|
||||
<td>2019-09-27T18:35:13Z</td>
|
||||
<td>17,097</td>
|
||||
<td>17,098</td>
|
||||
<td>5,921</td>
|
||||
<td>3,754</td>
|
||||
<td>26,772</td>
|
||||
<td>26,773</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.168">v1.0.168</a></td>
|
||||
@@ -1295,10 +1303,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.167">v1.0.167</a></td>
|
||||
<td>2019-09-10T08:48:37Z</td>
|
||||
<td>16,790</td>
|
||||
<td>16,791</td>
|
||||
<td>5,704</td>
|
||||
<td>3,703</td>
|
||||
<td>26,197</td>
|
||||
<td>26,198</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.166">v1.0.166</a></td>
|
||||
@@ -1311,34 +1319,34 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.165">v1.0.165</a></td>
|
||||
<td>2019-08-14T21:46:29Z</td>
|
||||
<td>18,898</td>
|
||||
<td>18,903</td>
|
||||
<td>6,972</td>
|
||||
<td>5,462</td>
|
||||
<td>31,332</td>
|
||||
<td>31,337</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.161">v1.0.161</a></td>
|
||||
<td>2019-07-13T18:30:00Z</td>
|
||||
<td>19,285</td>
|
||||
<td>19,287</td>
|
||||
<td>6,352</td>
|
||||
<td>4,136</td>
|
||||
<td>29,773</td>
|
||||
<td>29,775</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.160">v1.0.160</a></td>
|
||||
<td>2019-06-15T00:21:40Z</td>
|
||||
<td>30,531</td>
|
||||
<td>7,745</td>
|
||||
<td>30,535</td>
|
||||
<td>7,746</td>
|
||||
<td>8,101</td>
|
||||
<td>46,377</td>
|
||||
<td>46,382</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.159">v1.0.159</a></td>
|
||||
<td>2019-06-08T00:00:19Z</td>
|
||||
<td>5,194</td>
|
||||
<td>2,178</td>
|
||||
<td>1,112</td>
|
||||
<td>8,484</td>
|
||||
<td>1,113</td>
|
||||
<td>8,485</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.158">v1.0.158</a></td>
|
||||
@@ -1425,8 +1433,8 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td>2019-03-10T20:59:58Z</td>
|
||||
<td>13,629</td>
|
||||
<td>4,171</td>
|
||||
<td>3,223</td>
|
||||
<td>21,023</td>
|
||||
<td>3,227</td>
|
||||
<td>21,027</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.139">v1.0.139</a> (p)</td>
|
||||
@@ -1440,9 +1448,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.138">v1.0.138</a> (p)</td>
|
||||
<td>2019-03-03T17:23:00Z</td>
|
||||
<td>150</td>
|
||||
<td>86</td>
|
||||
<td>87</td>
|
||||
<td>84</td>
|
||||
<td>320</td>
|
||||
<td>321</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.137">v1.0.137</a> (p)</td>
|
||||
@@ -1455,10 +1463,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.135">v1.0.135</a></td>
|
||||
<td>2019-02-27T23:36:57Z</td>
|
||||
<td>12,514</td>
|
||||
<td>12,515</td>
|
||||
<td>3,958</td>
|
||||
<td>4,077</td>
|
||||
<td>20,549</td>
|
||||
<td>20,550</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.134">v1.0.134</a></td>
|
||||
@@ -1472,17 +1480,17 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.132">v1.0.132</a></td>
|
||||
<td>2019-02-26T23:02:05Z</td>
|
||||
<td>1,088</td>
|
||||
<td>451</td>
|
||||
<td>452</td>
|
||||
<td>95</td>
|
||||
<td>1,634</td>
|
||||
<td>1,635</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.127">v1.0.127</a></td>
|
||||
<td>2019-02-14T23:12:48Z</td>
|
||||
<td>9,785</td>
|
||||
<td>3,171</td>
|
||||
<td>9,786</td>
|
||||
<td>3,172</td>
|
||||
<td>2,929</td>
|
||||
<td>15,885</td>
|
||||
<td>15,887</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.126">v1.0.126</a> (p)</td>
|
||||
@@ -1504,9 +1512,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.120">v1.0.120</a></td>
|
||||
<td>2019-01-10T21:42:53Z</td>
|
||||
<td>15,605</td>
|
||||
<td>5,201</td>
|
||||
<td>5,202</td>
|
||||
<td>6,517</td>
|
||||
<td>27,323</td>
|
||||
<td>27,324</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.119">v1.0.119</a></td>
|
||||
@@ -1536,9 +1544,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.116">v1.0.116</a></td>
|
||||
<td>2018-11-20T19:09:24Z</td>
|
||||
<td>3,474</td>
|
||||
<td>1,121</td>
|
||||
<td>1,122</td>
|
||||
<td>714</td>
|
||||
<td>5,309</td>
|
||||
<td>5,310</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.115">v1.0.115</a></td>
|
||||
@@ -1559,10 +1567,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.111">v1.0.111</a></td>
|
||||
<td>2018-09-30T20:15:09Z</td>
|
||||
<td>12,041</td>
|
||||
<td>3,307</td>
|
||||
<td>3,668</td>
|
||||
<td>19,016</td>
|
||||
<td>12,042</td>
|
||||
<td>3,308</td>
|
||||
<td>3,669</td>
|
||||
<td>19,019</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.110">v1.0.110</a></td>
|
||||
@@ -1688,9 +1696,9 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.93">v1.0.93</a></td>
|
||||
<td>2018-05-14T11:36:01Z</td>
|
||||
<td>1,791</td>
|
||||
<td>1,157</td>
|
||||
<td>1,158</td>
|
||||
<td>759</td>
|
||||
<td>3,707</td>
|
||||
<td>3,708</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.91">v1.0.91</a></td>
|
||||
@@ -1719,10 +1727,10 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.83">v1.0.83</a></td>
|
||||
<td>2018-04-04T19:43:58Z</td>
|
||||
<td>4,886</td>
|
||||
<td>4,892</td>
|
||||
<td>2,532</td>
|
||||
<td>2,658</td>
|
||||
<td>10,076</td>
|
||||
<td>10,082</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v1.0.82">v1.0.82</a></td>
|
||||
@@ -2001,8 +2009,8 @@ https://github.com/laurent22/joplin/blob/dev/readme/stats.md
|
||||
<td>2017-11-24T14:27:49Z</td>
|
||||
<td>150</td>
|
||||
<td>696</td>
|
||||
<td>6,461</td>
|
||||
<td>7,307</td>
|
||||
<td>6,463</td>
|
||||
<td>7,309</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="https://github.com/laurent22/joplin/releases/tag/v0.10.23">v0.10.23</a></td>
|
||||
|
@@ -33,8 +33,7 @@ module.exports = {
|
||||
'<rootDir>/node_modules/',
|
||||
'<rootDir>/tests/support/',
|
||||
'<rootDir>/build/',
|
||||
'<rootDir>/tests/test-utils.js',
|
||||
'<rootDir>/tests/test-utils-synchronizer.js',
|
||||
'<rootDir>/tests/testUtils.js',
|
||||
'<rootDir>/tests/tmp/',
|
||||
'<rootDir>/tests/test data/',
|
||||
],
|
||||
|
105
packages/app-cli/package-lock.json
generated
105
packages/app-cli/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "1.8.1",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1806,80 +1806,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -2403,27 +2329,6 @@
|
||||
"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",
|
||||
@@ -2545,11 +2450,6 @@
|
||||
"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",
|
||||
@@ -4666,7 +4566,8 @@
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
$katexcode$
|
||||
Hello World:$katexcode$
|
File diff suppressed because one or more lines are too long
@@ -1,3 +1,5 @@
|
||||
Hello World :
|
||||
|
||||
$$
|
||||
katexcode
|
||||
\sqrt{3x}
|
||||
$$
|
@@ -4,7 +4,7 @@ import { setupDatabaseAndSynchronizer, switchClient, supportDir, createTempDir }
|
||||
|
||||
async function newRepoApi(): Promise<RepositoryApi> {
|
||||
const repo = new RepositoryApi(`${supportDir}/pluginRepo`, await createTempDir());
|
||||
await repo.loadManifests();
|
||||
await repo.initialize();
|
||||
return repo;
|
||||
}
|
||||
|
||||
|
@@ -96,6 +96,7 @@ 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'),
|
||||
|
20
packages/app-desktop/commands/restoreNoteRevision.ts
Normal file
20
packages/app-desktop/commands/restoreNoteRevision.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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().loadManifests();
|
||||
await repoApi().initialize();
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
loadError = error;
|
||||
|
@@ -526,6 +526,14 @@ function useMenu(props: Props) {
|
||||
click: () => { bridge().electronApp().hide(); },
|
||||
} : noItem,
|
||||
|
||||
shim.isMac() ? {
|
||||
role: 'hideothers',
|
||||
} : noItem,
|
||||
|
||||
shim.isMac() ? {
|
||||
role: 'unhide',
|
||||
} : noItem,
|
||||
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
|
@@ -224,10 +224,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
textHeading: () => addListItem('## ', ''),
|
||||
textHorizontalRule: () => addListItem('* * *'),
|
||||
'editor.execCommand': (value: CommandValue) => {
|
||||
if (editorRef.current[value.name]) {
|
||||
if (!('args' in value)) value.args = [];
|
||||
if (!('args' in value)) value.args = [];
|
||||
|
||||
if (editorRef.current[value.name]) {
|
||||
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,6 +20,7 @@ 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';
|
||||
@@ -107,6 +108,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
useJoplinMode(CodeMirror);
|
||||
const pluginOptions: any = useExternalPlugins(CodeMirror, props.plugins);
|
||||
useKeymap(CodeMirror);
|
||||
useJoplinCommands(CodeMirror);
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return editor;
|
||||
|
@@ -2,19 +2,76 @@ import { modifyListLines } from './useCursorUtils';
|
||||
|
||||
describe('useCursorUtils', () => {
|
||||
|
||||
const listWithDashes = `- item1
|
||||
- item2
|
||||
- item3`;
|
||||
const listWithDashes = [
|
||||
'- item1',
|
||||
'- item2',
|
||||
'- item3',
|
||||
];
|
||||
|
||||
const listNoDashes = `item1
|
||||
item2
|
||||
item3`;
|
||||
const listWithNoPrefixes = [
|
||||
'item1',
|
||||
'item2',
|
||||
'item3',
|
||||
];
|
||||
|
||||
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')));
|
||||
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 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')));
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
@@ -1,20 +1,27 @@
|
||||
import markdownUtils from '@joplin/lib/markdownUtils';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
export function modifyListLines(lines: string[],num: number,listSymbol: string) {
|
||||
export function modifyListLines(lines: string[], num: number, listSymbol: string) {
|
||||
const isNotNumbered = num === 1;
|
||||
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 (!line.startsWith(listSymbol)) {
|
||||
if (num) {
|
||||
if (num) {
|
||||
const lineNum = markdownUtils.olLineNumber(line);
|
||||
if (!lineNum && isNotNumbered) {
|
||||
lines[j] = `${num.toString()}. ${line}`;
|
||||
num++;
|
||||
} else {
|
||||
lines[j] = listSymbol + line;
|
||||
const listToken = markdownUtils.extractListToken(line);
|
||||
lines[j] = line.substr(listToken.length, line.length - listToken.length);
|
||||
}
|
||||
} else {
|
||||
lines[j] = line.substr(listSymbol.length, line.length - listSymbol.length);
|
||||
if (!line.startsWith(listSymbol)) {
|
||||
lines[j] = listSymbol + line;
|
||||
} else {
|
||||
lines[j] = line.substr(listSymbol.length, line.length - listSymbol.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
|
@@ -0,0 +1,7 @@
|
||||
// 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,6 +9,7 @@ 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) => {
|
||||
@@ -51,8 +52,14 @@ 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
|
||||
require('electron').shell.openPath(urlDecode(msg));
|
||||
// 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);
|
||||
} 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, substrWithEllipsis } = require('@joplin/lib/string-utils');
|
||||
const { urlDecode } = 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(_('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(this.state.note.title, 0, 32), RevisionService.instance().restoreFolderTitle()));
|
||||
alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
|
||||
}
|
||||
|
||||
backButton_click() {
|
||||
|
@@ -449,7 +449,11 @@ class SidebarComponent extends React.Component<Props, State> {
|
||||
|
||||
renderTag(tag: any, selected: boolean) {
|
||||
const anchorRef = this.anchorItemRef('tag', tag.id);
|
||||
const noteCount = Setting.value('showNoteCounts') ? this.renderNoteCount(tag.note_count) : '';
|
||||
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);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledListItem selected={selected}
|
||||
|
@@ -147,7 +147,10 @@ function StatusScreen(props: Props) {
|
||||
}
|
||||
|
||||
if (section.canRetryAll) {
|
||||
itemsHtml.push(renderSectionRetryAllHtml(section.title, section.retryAllHandler));
|
||||
itemsHtml.push(renderSectionRetryAllHtml(section.title, async () => {
|
||||
await section.retryAllHandler();
|
||||
void resfreshScreen();
|
||||
}));
|
||||
}
|
||||
|
||||
return <div key={key}>{itemsHtml}</div>;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -134,7 +133,7 @@ export default class NoteListUtils {
|
||||
})
|
||||
);
|
||||
|
||||
if (Setting.value('sync.target') === SyncTargetJoplinServer.id()) {
|
||||
if ([9, 10].includes(Setting.value('sync.target'))) {
|
||||
menu.append(
|
||||
new MenuItem(
|
||||
menuUtils.commandToStatefulMenuItem('showShareNoteDialog', noteIds.slice())
|
||||
|
2
packages/app-desktop/package-lock.json
generated
2
packages/app-desktop/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.8",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.8",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
|
@@ -47,6 +47,12 @@ interface State {
|
||||
listType: number;
|
||||
showHelp: boolean;
|
||||
resultsInBody: boolean;
|
||||
commandArgs: string[];
|
||||
}
|
||||
|
||||
interface CommandQuery {
|
||||
name: string;
|
||||
args: string[];
|
||||
}
|
||||
|
||||
class GotoAnything {
|
||||
@@ -87,6 +93,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
listType: BaseModel.TYPE_NOTE,
|
||||
showHelp: false,
|
||||
resultsInBody: false,
|
||||
commandArgs: [],
|
||||
};
|
||||
|
||||
this.styles_ = {};
|
||||
@@ -250,6 +257,15 @@ 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;
|
||||
|
||||
@@ -260,13 +276,16 @@ 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 query = this.state.query.substr(1);
|
||||
listType = BaseModel.TYPE_COMMAND;
|
||||
keywords = [query];
|
||||
const commandQuery = this.parseCommandQuery(this.state.query.substr(1));
|
||||
|
||||
const commandResults = CommandService.instance().searchCommands(query, true);
|
||||
listType = BaseModel.TYPE_COMMAND;
|
||||
keywords = [commandQuery.name];
|
||||
commandArgs = commandQuery.args;
|
||||
|
||||
const commandResults = CommandService.instance().searchCommands(commandQuery.name, true);
|
||||
|
||||
results = commandResults.map((result: CommandSearchResult) => {
|
||||
return {
|
||||
@@ -367,6 +386,7 @@ 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -379,7 +399,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
});
|
||||
|
||||
if (item.type === BaseModel.TYPE_COMMAND) {
|
||||
void CommandService.instance().execute(item.id);
|
||||
void CommandService.instance().execute(item.id, ...item.commandArgs);
|
||||
void focusEditorIfEditorCommand(item.id, CommandService.instance());
|
||||
return;
|
||||
}
|
||||
@@ -426,6 +446,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
id: itemId,
|
||||
parent_id: parentId,
|
||||
type: itemType,
|
||||
commandArgs: this.state.commandArgs,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -466,7 +487,7 @@ class Dialog extends React.PureComponent<Props, State> {
|
||||
selectedItem() {
|
||||
const index = this.selectedItemIndex();
|
||||
if (index < 0) return null;
|
||||
return this.state.results[index];
|
||||
return { ...this.state.results[index], commandArgs: this.state.commandArgs };
|
||||
}
|
||||
|
||||
input_onKeyDown(event: any) {
|
||||
|
@@ -30,7 +30,7 @@ if [ "$RESET_ALL" == "1" ]; then
|
||||
echo "config sync.9.username $USER_EMAIL" >> "$CMD_FILE"
|
||||
echo "config sync.9.password 123456" >> "$CMD_FILE"
|
||||
|
||||
if [ "$1" == "1" ]; then
|
||||
if [ "$USER_NUM" == "1" ]; then
|
||||
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api-joplincloud.local:22300/api/debug
|
||||
|
||||
echo 'mkbook "shared"' >> "$CMD_FILE"
|
||||
|
@@ -27,7 +27,7 @@ module.exports = async function() {
|
||||
// Use stdio: 'pipe' so that execSync doesn't print error directly to stdout
|
||||
branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
|
||||
hash = execSync('git log --pretty="%h" -1', { stdio: 'pipe' }).toString().trim();
|
||||
// The builds in CI are done from a 'detached HEAD' state, thus the branch name will be 'HEAD' for Travis builds.
|
||||
// The builds in CI are done from a 'detached HEAD' state, thus the branch name will be 'HEAD' for CI builds.
|
||||
} catch (err) {
|
||||
// Don't display error object as it's a "fatal" error, but
|
||||
// not for us, since is it not critical information
|
||||
|
@@ -25,8 +25,8 @@ module.exports = async function(params) {
|
||||
|
||||
console.info('Checking if notarization should be done...');
|
||||
|
||||
if (!process.env.TRAVIS || !process.env.TRAVIS_TAG) {
|
||||
console.info(`Either not running in CI or not processing a tag - skipping notarization. process.env.TRAVIS = ${process.env.TRAVIS}; process.env.TRAVIS_TAG = ${process.env.TRAVIS}`);
|
||||
if (!process.env.IS_CONTINUOUS_INTEGRATION || !process.env.GIT_TAG_NAME) {
|
||||
console.info(`Either not running in CI or not processing a tag - skipping notarization. process.env.IS_CONTINUOUS_INTEGRATION = ${process.env.IS_CONTINUOUS_INTEGRATION}; process.env.GIT_TAG_NAME = ${process.env.GIT_TAG_NAME}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,9 +45,8 @@ module.exports = async function(params) {
|
||||
|
||||
console.log(`Notarizing ${appId} found at ${appPath}`);
|
||||
|
||||
// Every x seconds we print something to stdout, otherwise Travis will
|
||||
// timeout the task after 10 minutes, and Apple notarization can take more
|
||||
// time.
|
||||
// Every x seconds we print something to stdout, otherwise CI may timeout
|
||||
// the task after 10 minutes, and Apple notarization can take more time.
|
||||
const waitingIntervalId = setInterval(() => {
|
||||
console.log('.');
|
||||
}, 60000);
|
||||
|
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -44,6 +45,7 @@ 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;
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,63 @@
|
||||
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);
|
||||
notes = await SearchEngineUtils.notesForQuery(query, true);
|
||||
} else {
|
||||
const p = query.split(' ');
|
||||
const temp = [];
|
||||
|
@@ -25,6 +25,7 @@ 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');
|
||||
@@ -90,6 +91,7 @@ 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';
|
||||
|
2
packages/fork-htmlparser2/package-lock.json
generated
2
packages/fork-htmlparser2/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"version": "4.1.24",
|
||||
"version": "4.1.26",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"description": "Fast & forgiving HTML/XML/RSS parser",
|
||||
"version": "4.1.24",
|
||||
"version": "4.1.26",
|
||||
"author": "Felix Boehm <me@feedic.com>",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
12
packages/fork-sax/package-lock.json
generated
12
packages/fork-sax/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-sax",
|
||||
"version": "1.2.28",
|
||||
"version": "1.2.30",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -143,6 +143,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -175,9 +181,11 @@
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz",
|
||||
"integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^0.3.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
|
@@ -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.28",
|
||||
"version": "1.2.30",
|
||||
"main": "lib/sax.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
2
packages/generator-joplin/package-lock.json
generated
2
packages/generator-joplin/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "generator-joplin",
|
||||
"version": "1.8.1",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Setting from './models/Setting';
|
||||
import Setting, { Env } from './models/Setting';
|
||||
import Logger, { TargetType, LoggerWrapper } from './Logger';
|
||||
import shim from './shim';
|
||||
import BaseService from './services/BaseService';
|
||||
@@ -46,6 +46,7 @@ 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');
|
||||
|
||||
@@ -312,7 +313,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);
|
||||
notes = await SearchEngineUtils.notesForQuery(search.query_pattern, true);
|
||||
const parsedQuery = await SearchEngine.instance().parseQuery(search.query_pattern);
|
||||
highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
||||
} else if (parentType === BaseModel.TYPE_SMART_FILTER) {
|
||||
@@ -691,6 +692,7 @@ export default class BaseApplication {
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
|
||||
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
|
||||
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
|
||||
|
||||
try {
|
||||
await shim.fsDriver().remove(tempDir);
|
||||
@@ -763,6 +765,13 @@ 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
|
||||
|
@@ -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];
|
||||
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];
|
||||
|
||||
let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion);
|
||||
|
||||
@@ -876,6 +876,18 @@ 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);
|
||||
|
@@ -1,13 +0,0 @@
|
||||
"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
|
@@ -10,6 +10,7 @@ const logger = Logger.create('JoplinServerApi');
|
||||
|
||||
interface Options {
|
||||
baseUrl(): string;
|
||||
userContentBaseUrl(): string;
|
||||
username(): string;
|
||||
password(): string;
|
||||
env?: Env;
|
||||
@@ -47,7 +48,7 @@ export default class JoplinServerApi {
|
||||
this.options_ = options;
|
||||
|
||||
if (options.env === Env.Dev) {
|
||||
this.debugRequests_ = true;
|
||||
// this.debugRequests_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,15 +56,24 @@ 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_;
|
||||
|
||||
this.session_ = await this.exec('POST', 'api/sessions', null, {
|
||||
email: this.options_.username(),
|
||||
password: this.options_.password(),
|
||||
});
|
||||
try {
|
||||
this.session_ = await this.exec('POST', 'api/sessions', null, {
|
||||
email: this.options_.username(),
|
||||
password: this.options_.password(),
|
||||
});
|
||||
|
||||
return this.session_;
|
||||
return this.session_;
|
||||
} catch (error) {
|
||||
logger.error('Could not acquire session:', error.details, '\n', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async sessionId() {
|
||||
@@ -77,7 +87,7 @@ export default class JoplinServerApi {
|
||||
|
||||
public static connectionErrorMessage(error: any) {
|
||||
const msg = error && error.message ? error.message : 'Unknown error';
|
||||
return _('Could not connect to Joplin Cloud. Please check the Synchronisation options in the config screen. Full error was:\n\n%s', msg);
|
||||
return _('Could not connect to Joplin Server. Please check the Synchronisation options in the config screen. Full error was:\n\n%s', msg);
|
||||
}
|
||||
|
||||
private requestToCurl_(url: string, options: any) {
|
||||
@@ -136,6 +146,8 @@ export default class JoplinServerApi {
|
||||
url += stringify(query);
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
if (this.debugRequests_) {
|
||||
logger.debug(this.requestToCurl_(url, fetchOptions));
|
||||
@@ -160,16 +172,19 @@ export default class JoplinServerApi {
|
||||
const responseText = await response.text();
|
||||
|
||||
if (this.debugRequests_) {
|
||||
logger.debug('Response', responseText);
|
||||
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.
|
||||
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}`);
|
||||
return new JoplinError(message, code, `${method} ${path}: ${message} (${code}): ${shortResponseText()}`);
|
||||
};
|
||||
|
||||
let responseJson_: any = null;
|
||||
@@ -195,7 +210,21 @@ export default class JoplinServerApi {
|
||||
throw newError(`${json.error}`, json.code ? json.code : response.status);
|
||||
}
|
||||
|
||||
throw newError('Unknown error', 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;
|
||||
|
59
packages/lib/SyncTargetJoplinCloud.ts
Normal file
59
packages/lib/SyncTargetJoplinCloud.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
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'));
|
||||
}
|
||||
}
|
@@ -5,13 +5,38 @@ 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() {
|
||||
@@ -27,7 +52,7 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
|
||||
}
|
||||
|
||||
public static label() {
|
||||
return `${_('Joplin Cloud')} (Beta)`;
|
||||
return `${_('Joplin Server')} (Beta)`;
|
||||
}
|
||||
|
||||
public async isAuthenticated() {
|
||||
@@ -38,30 +63,16 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
|
||||
return super.fileApi();
|
||||
}
|
||||
|
||||
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) {
|
||||
public static async checkConfig(options: FileApiOptions, syncTargetId: number = null) {
|
||||
const output = {
|
||||
ok: false,
|
||||
errorMessage: '',
|
||||
};
|
||||
|
||||
syncTargetId = syncTargetId === null ? SyncTargetJoplinServer.id() : syncTargetId;
|
||||
|
||||
try {
|
||||
const fileApi = await SyncTargetJoplinServer.newFileApi_(options);
|
||||
const fileApi = await newFileApi(syncTargetId, options);
|
||||
fileApi.requestRepeatCount_ = 0;
|
||||
|
||||
await fileApi.put('testing.txt', 'testing');
|
||||
@@ -78,15 +89,12 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
|
||||
}
|
||||
|
||||
protected async initFileApi() {
|
||||
const fileApi = await SyncTargetJoplinServer.newFileApi_({
|
||||
return initFileApi(SyncTargetJoplinServer.id(), this.logger(), {
|
||||
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() {
|
||||
|
@@ -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}`);
|
||||
throw new Error(`Name not found: ${name}. Was the sync target registered?`);
|
||||
}
|
||||
|
||||
static idToMetadata(id) {
|
||||
|
@@ -926,6 +926,7 @@ 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)) {
|
||||
|
@@ -80,7 +80,19 @@ export default class FileApiDriverJoplinServer {
|
||||
const response = await this.api().exec('GET', `${this.apiFilePath_(path)}/delta`, query);
|
||||
const stats = response.items
|
||||
.filter((item: any) => {
|
||||
return item.item_name.indexOf('locks/') !== 0 && item.item_name.indexOf('temp/') !== 0;
|
||||
// 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;
|
||||
})
|
||||
.map((item: any) => {
|
||||
return this.metadataToStat_(item, item.item_name, item.type === 3, '');
|
||||
@@ -171,6 +183,12 @@ export default class FileApiDriverJoplinServer {
|
||||
}
|
||||
|
||||
public async clearRoot(path: string) {
|
||||
await this.delete(path);
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
@@ -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":96};
|
||||
stats['ar'] = {"percentDone":95};
|
||||
stats['eu'] = {"percentDone":30};
|
||||
stats['bs_BA'] = {"percentDone":75};
|
||||
stats['bg_BG'] = {"percentDone":58};
|
||||
stats['ca'] = {"percentDone":83};
|
||||
stats['bs_BA'] = {"percentDone":74};
|
||||
stats['bg_BG'] = {"percentDone":57};
|
||||
stats['ca'] = {"percentDone":82};
|
||||
stats['hr_HR'] = {"percentDone":96};
|
||||
stats['cs_CZ'] = {"percentDone":86};
|
||||
stats['da_DK'] = {"percentDone":96};
|
||||
stats['de_DE'] = {"percentDone":95};
|
||||
stats['et_EE'] = {"percentDone":57};
|
||||
stats['cs_CZ'] = {"percentDone":85};
|
||||
stats['da_DK'] = {"percentDone":95};
|
||||
stats['de_DE'] = {"percentDone":94};
|
||||
stats['et_EE'] = {"percentDone":56};
|
||||
stats['en_GB'] = {"percentDone":100};
|
||||
stats['en_US'] = {"percentDone":100};
|
||||
stats['es_ES'] = {"percentDone":94};
|
||||
stats['eo'] = {"percentDone":33};
|
||||
stats['eo'] = {"percentDone":32};
|
||||
stats['fi_FI'] = {"percentDone":94};
|
||||
stats['fr_FR'] = {"percentDone":99};
|
||||
stats['fr_FR'] = {"percentDone":98};
|
||||
stats['gl_ES'] = {"percentDone":38};
|
||||
stats['id_ID'] = {"percentDone":93};
|
||||
stats['it_IT'] = {"percentDone":94};
|
||||
stats['id_ID'] = {"percentDone":92};
|
||||
stats['it_IT'] = {"percentDone":99};
|
||||
stats['hu_HU'] = {"percentDone":88};
|
||||
stats['nl_BE'] = {"percentDone":92};
|
||||
stats['nl_NL'] = {"percentDone":95};
|
||||
stats['nb_NO'] = {"percentDone":76};
|
||||
stats['nl_BE'] = {"percentDone":91};
|
||||
stats['nl_NL'] = {"percentDone":94};
|
||||
stats['nb_NO'] = {"percentDone":75};
|
||||
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":96};
|
||||
stats['sl_SI'] = {"percentDone":95};
|
||||
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":97};
|
||||
stats['el_GR'] = {"percentDone":96};
|
||||
stats['ru_RU'] = {"percentDone":94};
|
||||
stats['sr_RS'] = {"percentDone":71};
|
||||
stats['zh_CN'] = {"percentDone":94};
|
||||
stats['zh_TW'] = {"percentDone":92};
|
||||
stats['ja_JP'] = {"percentDone":97};
|
||||
stats['ko'] = {"percentDone":96};
|
||||
stats['zh_CN'] = {"percentDone":99};
|
||||
stats['zh_TW'] = {"percentDone":99};
|
||||
stats['ja_JP'] = {"percentDone":96};
|
||||
stats['ko'] = {"percentDone":99};
|
||||
module.exports = { locales: locales, stats: stats };
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,10 +7,18 @@ 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 {
|
||||
@@ -120,26 +128,38 @@ 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, 3, ' ', stringPadding.RIGHT));
|
||||
lineMd.push('---');
|
||||
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('----:');
|
||||
}
|
||||
}
|
||||
|
||||
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 valueMd = markdownUtils.escapeTableCell(h.filter ? h.filter(row[h.name]) : row[h.name]);
|
||||
rowMd.push(stringPadding(valueMd, 3, ' ', stringPadding.RIGHT));
|
||||
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));
|
||||
}
|
||||
output.push(rowMd.join(' | '));
|
||||
output.push(`| ${rowMd.join(' | ')} |`);
|
||||
}
|
||||
|
||||
return output.join('\n');
|
||||
|
@@ -284,8 +284,20 @@ describe('models_Note', function() {
|
||||
expect(externalToInternal).toBe(input);
|
||||
}
|
||||
|
||||
const result = await Note.replaceResourceExternalToInternalLinks(`[](joplin://${note1.id})`);
|
||||
expect(result).toBe(`[](:/${note1.id})`);
|
||||
{
|
||||
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);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should perform natural sorting', (async () => {
|
||||
|
@@ -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.]+\\?t=[0-9]+`,
|
||||
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9]{32}\\.[a-zA-Z0-9]+\\?t=[0-9]+`,
|
||||
// Handles file://path/to/abcdefg.jpg
|
||||
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9.]+`,
|
||||
`${pregQuote(`${basePath}/`)}[a-zA-Z0-9]{32}\\.[a-zA-Z0-9]+`,
|
||||
];
|
||||
for (const reString of reStrings) {
|
||||
const re = new RegExp(reString, 'gi');
|
||||
|
@@ -472,24 +472,16 @@ class Setting extends BaseModel {
|
||||
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer');
|
||||
},
|
||||
public: true,
|
||||
label: () => _('Joplin Cloud URL'),
|
||||
label: () => _('Joplin Server URL'),
|
||||
description: () => emptyDirWarning,
|
||||
storage: SettingStorage.File,
|
||||
},
|
||||
// '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 Cloud Directory'),
|
||||
// storage: SettingStorage.File,
|
||||
// },
|
||||
'sync.9.userContentPath': {
|
||||
value: '',
|
||||
type: SettingItemType.String,
|
||||
public: false,
|
||||
storage: SettingStorage.Database,
|
||||
},
|
||||
'sync.9.username': {
|
||||
value: '',
|
||||
type: SettingItemType.String,
|
||||
@@ -498,7 +490,7 @@ class Setting extends BaseModel {
|
||||
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer');
|
||||
},
|
||||
public: true,
|
||||
label: () => _('Joplin Cloud email'),
|
||||
label: () => _('Joplin Server email'),
|
||||
storage: SettingStorage.File,
|
||||
},
|
||||
'sync.9.password': {
|
||||
@@ -509,6 +501,45 @@ class Setting extends BaseModel {
|
||||
return settings['sync.target'] == SyncTargetRegistry.nameToId('joplinServer');
|
||||
},
|
||||
public: true,
|
||||
label: () => _('Joplin Server password'),
|
||||
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,
|
||||
},
|
||||
@@ -539,6 +570,7 @@ 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 },
|
||||
@@ -548,6 +580,7 @@ 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 },
|
||||
|
||||
|
@@ -51,11 +51,20 @@ 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);
|
||||
@@ -74,6 +83,8 @@ 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);
|
||||
|
||||
@@ -83,6 +94,12 @@ 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 () => {
|
||||
@@ -131,6 +148,7 @@ 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);
|
||||
|
2
packages/lib/package-lock.json
generated
2
packages/lib/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.2",
|
||||
"description": "Joplin Core library",
|
||||
"author": "Laurent Cozic",
|
||||
"homepage": "",
|
||||
@@ -16,20 +16,20 @@
|
||||
"test-ci": "npm run test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"clean-html": "^1.5.0",
|
||||
"jest": "^26.6.3",
|
||||
"sharp": "^0.26.2",
|
||||
"typescript": "^4.0.5",
|
||||
"clean-html": "^1.5.0"
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.24",
|
||||
"@joplin/fork-sax": "^1.2.28",
|
||||
"@joplin/fork-htmlparser2": "^4.1.26",
|
||||
"@joplin/fork-sax": "^1.2.30",
|
||||
"@joplin/renderer": "^1.8.2",
|
||||
"@joplin/turndown": "^4.0.46",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.28",
|
||||
"@joplin/turndown": "^4.0.48",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.30",
|
||||
"async-mutex": "^0.1.3",
|
||||
"aws-sdk": "^2.588.0",
|
||||
"base-64": "^0.1.0",
|
||||
|
@@ -140,6 +140,28 @@ 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[] = [];
|
||||
@@ -175,6 +197,8 @@ export default class ReportService {
|
||||
|
||||
section.body.push({ type: ReportItemType.CloseList });
|
||||
|
||||
section = this.addRetryAllHandler(section);
|
||||
|
||||
sections.push(section);
|
||||
}
|
||||
|
||||
@@ -200,23 +224,7 @@ export default class ReportService {
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
section = this.addRetryAllHandler(section);
|
||||
|
||||
sections.push(section);
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ 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');
|
||||
|
||||
@@ -230,7 +231,23 @@ export default class RevisionService extends BaseService {
|
||||
return folder;
|
||||
}
|
||||
|
||||
async importRevisionNote(note: NoteEntity) {
|
||||
// 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> {
|
||||
const toImport = Object.assign({}, note);
|
||||
delete toImport.id;
|
||||
delete toImport.updated_time;
|
||||
@@ -242,7 +259,7 @@ export default class RevisionService extends BaseService {
|
||||
|
||||
toImport.parent_id = folder.id;
|
||||
|
||||
await Note.save(toImport);
|
||||
return 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: state.settings['sync.target'] === 9,
|
||||
joplinServerConnected: [9, 10].includes(state.settings['sync.target']),
|
||||
};
|
||||
}
|
||||
|
@@ -1,8 +1,21 @@
|
||||
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
|
||||
@@ -14,6 +27,7 @@ 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) {
|
||||
@@ -21,7 +35,12 @@ export default class RepositoryApi {
|
||||
this.tempDir_ = tempDir;
|
||||
}
|
||||
|
||||
public async loadManifests() {
|
||||
public async initialize() {
|
||||
await this.loadManifests();
|
||||
await this.loadRelease();
|
||||
}
|
||||
|
||||
private async loadManifests() {
|
||||
const manifestsText = await this.fetchText('manifests.json');
|
||||
try {
|
||||
const manifests = JSON.parse(manifestsText);
|
||||
@@ -34,6 +53,27 @@ 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;
|
||||
}
|
||||
@@ -46,15 +86,34 @@ export default class RepositoryApi {
|
||||
}
|
||||
}
|
||||
|
||||
private fileUrl(relativePath: string): string {
|
||||
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 {
|
||||
return `${this.contentBaseUrl}/${relativePath}`;
|
||||
}
|
||||
|
||||
private async fetchText(path: string): Promise<string> {
|
||||
if (this.isLocalRepo) {
|
||||
return shim.fsDriver().readFile(this.fileUrl(path), 'utf8');
|
||||
return shim.fsDriver().readFile(this.repoFileUrl(path), 'utf8');
|
||||
} else {
|
||||
return shim.fetchText(this.fileUrl(path));
|
||||
return shim.fetchText(this.repoFileUrl(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +145,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.fileUrl(`plugins/${manifest.id}/plugin.jpl`);
|
||||
const fileUrl = this.assetFileUrl(manifest.id); // this.repoFileUrl(`plugins/${manifest.id}/plugin.jpl`);
|
||||
const hash = md5(Date.now() + Math.random());
|
||||
const targetPath = `${this.tempDir_}/${hash}_${manifest.id}.jpl`;
|
||||
|
||||
|
@@ -21,6 +21,34 @@ 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 {
|
||||
|
||||
|
@@ -28,7 +28,7 @@ export default async function(request: Request) {
|
||||
options.caseInsensitive = true;
|
||||
results = await ModelClass.all(options);
|
||||
} else {
|
||||
results = await SearchEngineUtils.notesForQuery(query, defaultLoadOptions(request, ModelType.Note));
|
||||
results = await SearchEngineUtils.notesForQuery(query, false, defaultLoadOptions(request, ModelType.Note));
|
||||
}
|
||||
|
||||
return collectionToPaginatedResults(modelType, results, request);
|
||||
|
@@ -386,6 +386,7 @@ 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 () => {
|
||||
@@ -398,7 +399,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 () => {
|
||||
@@ -409,6 +410,7 @@ 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 () => {
|
||||
@@ -419,28 +421,7 @@ describe('services_SearchEngine', function() {
|
||||
|
||||
expect((await engine.search('นี่คือค')).length).toBe(1);
|
||||
expect((await engine.search('ไทย')).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);
|
||||
expect((await engine.search('any:1 type:todo ไทย')).length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should parse normal query strings', (async () => {
|
||||
|
@@ -17,6 +17,7 @@ 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) => {};
|
||||
@@ -533,6 +534,7 @@ 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.
|
||||
@@ -547,10 +549,15 @@ 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') || ['ja', 'zh', 'ko', 'th'].indexOf(st) >= 0) {
|
||||
if (!Setting.value('db.ftsEnabled')) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -565,7 +572,6 @@ 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);
|
||||
|
||||
@@ -579,10 +585,11 @@ 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);
|
||||
const { query, params } = queryBuilder(parsedQuery.allTerms, useFts);
|
||||
const rows = await this.db().selectAll(query, params);
|
||||
this.processResults_(rows, parsedQuery);
|
||||
this.processResults_(rows, parsedQuery, !useFts);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
this.logger().warn(`Cannot execute MATCH query: ${searchString}: ${error.message}`);
|
||||
|
@@ -26,12 +26,21 @@ describe('services_SearchEngineUtils', function() {
|
||||
|
||||
Setting.setValue('showCompletedTodos', true);
|
||||
|
||||
const rows = await SearchEngineUtils.notesForQuery('abcd', null, searchEngine);
|
||||
const rows = await SearchEngineUtils.notesForQuery('abcd', true, 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 () => {
|
||||
@@ -43,11 +52,35 @@ describe('services_SearchEngineUtils', function() {
|
||||
|
||||
Setting.setValue('showCompletedTodos', false);
|
||||
|
||||
const rows = await SearchEngineUtils.notesForQuery('abcd', null, searchEngine);
|
||||
const rows = await SearchEngineUtils.notesForQuery('abcd', true, 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, options: any = null, searchEngine: SearchEngine = null) {
|
||||
static async notesForQuery(query: string, applyUserSettings: boolean, options: any = null, searchEngine: SearchEngine = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
if (!searchEngine) {
|
||||
@@ -30,6 +30,20 @@ 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,
|
||||
@@ -38,20 +52,22 @@ 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 < notes.length; i++) {
|
||||
const idx = noteIds.indexOf(notes[i].id);
|
||||
sortedNotes[idx] = notes[i];
|
||||
for (let i = 0; i < filteredNotes.length; i++) {
|
||||
const idx = noteIds.indexOf(filteredNotes[i].id);
|
||||
sortedNotes[idx] = filteredNotes[i];
|
||||
if (idWasAutoAdded) delete sortedNotes[idx].id;
|
||||
}
|
||||
|
||||
// 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));
|
||||
if (isTodoCompletedAutoAdded) delete sortedNotes[idx].is_todo;
|
||||
if (isTodoAutoAdded) delete sortedNotes[idx].todo_completed;
|
||||
}
|
||||
|
||||
// Note that when the search engine index is somehow corrupted, it might contain
|
||||
@@ -60,9 +76,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 filteredNotes.filter(n => n);
|
||||
return sortedNotes.filter(n => n);
|
||||
} else {
|
||||
return filteredNotes;
|
||||
return sortedNotes;
|
||||
}
|
||||
|
||||
}
|
||||
|
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[]) => {
|
||||
const _notebookFilter = (notebooks: string[], requirement: Requirement, conditions: string[], params: string[], withs: string[], useFts: boolean) => {
|
||||
if (notebooks.length === 0) return;
|
||||
|
||||
const likes = [];
|
||||
@@ -50,12 +50,13 @@ 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 notes_normalized.ROWID
|
||||
SELECT ${tableName}.ROWID
|
||||
FROM ${viewName}
|
||||
JOIN notes_normalized
|
||||
ON ${viewName}.id=notes_normalized.parent_id
|
||||
JOIN ${tableName}
|
||||
ON ${viewName}.id=${tableName}.parent_id
|
||||
)`;
|
||||
|
||||
|
||||
@@ -65,12 +66,12 @@ const _notebookFilter = (notebooks: string[], requirement: Requirement, conditio
|
||||
|
||||
};
|
||||
|
||||
const notebookFilter = (terms: Term[], conditions: string[], params: string[], withs: string[]) => {
|
||||
const notebookFilter = (terms: Term[], conditions: string[], params: string[], withs: string[], useFts: boolean) => {
|
||||
const notebooksToInclude = terms.filter(x => x.name === 'notebook' && !x.negated).map(x => x.value);
|
||||
_notebookFilter(notebooksToInclude, Requirement.INCLUSION, conditions, params, withs);
|
||||
_notebookFilter(notebooksToInclude, Requirement.INCLUSION, conditions, params, withs, useFts);
|
||||
|
||||
const notebooksToExclude = terms.filter(x => x.name === 'notebook' && x.negated).map(x => x.value);
|
||||
_notebookFilter(notebooksToExclude, Requirement.EXCLUSION, conditions, params, withs);
|
||||
_notebookFilter(notebooksToExclude, Requirement.EXCLUSION, conditions, params, withs, useFts);
|
||||
};
|
||||
|
||||
|
||||
@@ -87,7 +88,8 @@ const filterByTableName = (
|
||||
noteIDs: string,
|
||||
requirement: Requirement,
|
||||
withs: string[],
|
||||
tableName: string
|
||||
tableName: string,
|
||||
useFts: boolean
|
||||
) => {
|
||||
const operator: Operation = getOperator(requirement, relation);
|
||||
|
||||
@@ -144,13 +146,14 @@ 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 notes_normalized.ROWID
|
||||
SELECT ${targetTableName}.ROWID
|
||||
FROM notes_with_${requirement}_${tableName}
|
||||
JOIN notes_normalized
|
||||
ON notes_with_${requirement}_${tableName}.id=notes_normalized.id
|
||||
JOIN ${targetTableName}
|
||||
ON notes_with_${requirement}_${tableName}.id=${targetTableName}.id
|
||||
)`;
|
||||
|
||||
withs.push(withCondition);
|
||||
@@ -159,7 +162,7 @@ const filterByTableName = (
|
||||
};
|
||||
|
||||
|
||||
const resourceFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[]) => {
|
||||
const resourceFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[], useFts: boolean) => {
|
||||
const tableName = 'resources';
|
||||
|
||||
const resourceIDs = `
|
||||
@@ -177,15 +180,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);
|
||||
filterByTableName(requiredResources, conditions, params, relation, noteIDsWithResource, Requirement.INCLUSION, withs, tableName, useFts);
|
||||
}
|
||||
|
||||
if (excludedResources.length > 0) {
|
||||
filterByTableName(excludedResources, conditions, params, relation, noteIDsWithResource, Requirement.EXCLUSION, withs, tableName);
|
||||
filterByTableName(excludedResources, conditions, params, relation, noteIDsWithResource, Requirement.EXCLUSION, withs, tableName, useFts);
|
||||
}
|
||||
};
|
||||
|
||||
const tagFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[]) => {
|
||||
const tagFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, withs: string[], useFts: boolean) => {
|
||||
const tableName = 'tags';
|
||||
|
||||
const tagIDs = `
|
||||
@@ -203,30 +206,32 @@ 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);
|
||||
filterByTableName(requiredTags, conditions, params, relation, noteIDsWithTag, Requirement.INCLUSION, withs, tableName, useFts);
|
||||
}
|
||||
|
||||
if (excludedTags.length > 0) {
|
||||
filterByTableName(excludedTags, conditions, params, relation, noteIDsWithTag, Requirement.EXCLUSION, withs, tableName);
|
||||
filterByTableName(excludedTags, conditions, params, relation, noteIDsWithTag, Requirement.EXCLUSION, withs, tableName, useFts);
|
||||
}
|
||||
};
|
||||
|
||||
const genericFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, fieldName: string) => {
|
||||
const genericFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, fieldName: string, useFts: boolean) => {
|
||||
if (fieldName === 'iscompleted' || fieldName === 'type') {
|
||||
// Faster query when values can only take two distinct values
|
||||
biConditionalFilter(terms, conditions, relation, fieldName);
|
||||
biConditionalFilter(terms, conditions, relation, fieldName, useFts);
|
||||
return;
|
||||
}
|
||||
|
||||
const tableName = useFts ? 'notes_normalized' : 'notes';
|
||||
|
||||
const getCondition = (term: Term) => {
|
||||
if (fieldName === 'sourceurl') {
|
||||
return `notes_normalized.source_url ${term.negated ? 'NOT' : ''} LIKE ?`;
|
||||
return `${tableName}.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 `notes_normalized.${fieldName === 'date' ? `user_${term.name}_time` : `${term.name}`} ${term.negated ? '<' : '>='} ?`;
|
||||
return `${tableName}.${fieldName === 'date' ? `user_${term.name}_time` : `${term.name}`} ${term.negated ? '<' : '>='} ?`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -234,16 +239,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 notes_normalized
|
||||
FROM ${tableName}
|
||||
WHERE ${getCondition(term)}
|
||||
))`);
|
||||
params.push(term.value);
|
||||
});
|
||||
};
|
||||
|
||||
const biConditionalFilter = (terms: Term[], conditions: string[], relation: Relation, filterName: string) => {
|
||||
const biConditionalFilter = (terms: Term[], conditions: string[], relation: Relation, filterName: string, useFts: boolean) => {
|
||||
const getCondition = (filterName: string , value: string, relation: Relation) => {
|
||||
const tableName = (relation === 'AND') ? 'notes_fts' : 'notes_normalized';
|
||||
const tableName = useFts ? (relation === 'AND' ? 'notes_fts' : 'notes_normalized') : 'notes';
|
||||
if (filterName === 'type') {
|
||||
return `${tableName}.is_todo IS ${value === 'todo' ? 1 : 0}`;
|
||||
} else if (filterName === 'iscompleted') {
|
||||
@@ -262,39 +267,44 @@ const biConditionalFilter = (terms: Term[], conditions: string[], relation: Rela
|
||||
AND ${getCondition(filterName, value, relation)}`);
|
||||
}
|
||||
if (relation === 'OR') {
|
||||
conditions.push(`
|
||||
OR ROWID IN (
|
||||
SELECT ROWID
|
||||
FROM notes_normalized
|
||||
WHERE ${getCondition(filterName, value, relation)}
|
||||
)`);
|
||||
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)}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const noteIdFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation) => {
|
||||
const noteIdFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, useFts: boolean) => {
|
||||
const noteIdTerms = terms.filter(x => x.name === 'id');
|
||||
genericFilter(noteIdTerms, conditions, params, relation, 'id');
|
||||
genericFilter(noteIdTerms, conditions, params, relation, 'id', useFts);
|
||||
};
|
||||
|
||||
|
||||
const typeFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation) => {
|
||||
const typeFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, useFts: boolean) => {
|
||||
const typeTerms = terms.filter(x => x.name === 'type');
|
||||
genericFilter(typeTerms, conditions, params, relation, 'type');
|
||||
genericFilter(typeTerms, conditions, params, relation, 'type', useFts);
|
||||
};
|
||||
|
||||
const completedFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation) => {
|
||||
const completedFilter = (terms: Term[], conditions: string[], params: string[], relation: Relation, useFts: boolean) => {
|
||||
const completedTerms = terms.filter(x => x.name === 'iscompleted');
|
||||
genericFilter(completedTerms, conditions, params, relation, 'iscompleted');
|
||||
genericFilter(completedTerms, conditions, params, relation, 'iscompleted', useFts);
|
||||
};
|
||||
|
||||
|
||||
const locationFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation) => {
|
||||
const locationFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation, useFts: boolean) => {
|
||||
const locationTerms = terms.filter(x => x.name === 'latitude' || x.name === 'longitude' || x.name === 'altitude');
|
||||
genericFilter(locationTerms, conditons, params, relation, 'location');
|
||||
genericFilter(locationTerms, conditons, params, relation, 'location', useFts);
|
||||
};
|
||||
|
||||
const dateFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation) => {
|
||||
const dateFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation, useFts: boolean) => {
|
||||
const getUnixMs = (date: string): string => {
|
||||
const yyyymmdd = /^[0-9]{8}$/;
|
||||
const yyyymm = /^[0-9]{6}$/;
|
||||
@@ -321,44 +331,61 @@ 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');
|
||||
genericFilter(unixDateTerms, conditons, params, relation, 'date', useFts);
|
||||
};
|
||||
|
||||
const sourceUrlFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation) => {
|
||||
const sourceUrlFilter = (terms: Term[], conditons: string[], params: string[], relation: Relation, useFts: boolean) => {
|
||||
const urlTerms = terms.filter(x => x.name === 'sourceurl');
|
||||
genericFilter(urlTerms, conditons, params, relation, 'sourceurl');
|
||||
genericFilter(urlTerms, conditons, params, relation, 'sourceurl', useFts);
|
||||
};
|
||||
|
||||
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) => {
|
||||
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 => {
|
||||
if (useFts) {
|
||||
const type = excludedTerms[0].name === 'text' ? '' : `.${excludedTerms[0].name}`;
|
||||
if (relation === 'AND') {
|
||||
conditions.push(`
|
||||
OR ROWID IN (
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT ROWID
|
||||
FROM notes_fts
|
||||
EXCEPT
|
||||
SELECT ROWID
|
||||
FROM notes_fts
|
||||
WHERE notes_fts${type} MATCH ?
|
||||
)
|
||||
AND ROWID NOT IN (
|
||||
SELECT ROWID
|
||||
FROM notes_fts
|
||||
WHERE notes_fts${type} MATCH ?
|
||||
)`);
|
||||
params.push(term.value);
|
||||
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 {
|
||||
excludedTerms.forEach(term => {
|
||||
createLikeMatch(term, true);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -367,13 +394,19 @@ const textFilter = (terms: Term[], conditions: string[], params: string[], relat
|
||||
|
||||
const includedTerms = allTerms.filter(x => !x.negated);
|
||||
if (includedTerms.length > 0) {
|
||||
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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const excludedTextTerms = allTerms.filter(x => x.name === 'text' && x.negated);
|
||||
@@ -404,47 +437,48 @@ 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[]) {
|
||||
export default function queryBuilder(terms: Term[], useFts: boolean) {
|
||||
const queryParts: string[] = [];
|
||||
const params: string[] = [];
|
||||
const withs: string[] = [];
|
||||
|
||||
const relation: Relation = getDefaultRelation(terms);
|
||||
|
||||
const tableName = useFts ? 'notes_fts' : 'notes';
|
||||
|
||||
queryParts.push(`
|
||||
SELECT
|
||||
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
|
||||
${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}
|
||||
WHERE ${getConnective(terms, relation)}`);
|
||||
|
||||
noteIdFilter(terms, queryParts, params, relation);
|
||||
noteIdFilter(terms, queryParts, params, relation, useFts);
|
||||
|
||||
notebookFilter(terms, queryParts, params, withs);
|
||||
notebookFilter(terms, queryParts, params, withs, useFts);
|
||||
|
||||
tagFilter(terms, queryParts, params, relation, withs);
|
||||
tagFilter(terms, queryParts, params, relation, withs, useFts);
|
||||
|
||||
resourceFilter(terms, queryParts, params, relation, withs);
|
||||
resourceFilter(terms, queryParts, params, relation, withs, useFts);
|
||||
|
||||
textFilter(terms, queryParts, params, relation);
|
||||
textFilter(terms, queryParts, params, relation, useFts);
|
||||
|
||||
|
||||
typeFilter(terms, queryParts, params, relation);
|
||||
typeFilter(terms, queryParts, params, relation, useFts);
|
||||
|
||||
completedFilter(terms, queryParts, params, relation);
|
||||
completedFilter(terms, queryParts, params, relation, useFts);
|
||||
|
||||
dateFilter(terms, queryParts, params, relation);
|
||||
dateFilter(terms, queryParts, params, relation, useFts);
|
||||
|
||||
locationFilter(terms, queryParts, params, relation);
|
||||
locationFilter(terms, queryParts, params, relation, useFts);
|
||||
|
||||
sourceUrlFilter(terms, queryParts, params, relation);
|
||||
sourceUrlFilter(terms, queryParts, params, relation, useFts);
|
||||
|
||||
let query;
|
||||
if (withs.length > 0) {
|
||||
|
@@ -22,7 +22,7 @@ export default class ShareService {
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return Setting.value('sync.target') === 9; // Joplin Server target
|
||||
return [9, 10].includes(Setting.value('sync.target')); // Joplin Server, Joplin Cloud targets
|
||||
}
|
||||
|
||||
private get store(): Store<any> {
|
||||
@@ -36,10 +36,13 @@ 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.9.path'),
|
||||
username: () => Setting.value('sync.9.username'),
|
||||
password: () => Setting.value('sync.9.password'),
|
||||
baseUrl: () => Setting.value(`sync.${syncTargetId}.path`),
|
||||
userContentBaseUrl: () => Setting.value(`sync.${syncTargetId}.userContentPath`),
|
||||
username: () => Setting.value(`sync.${syncTargetId}.username`),
|
||||
password: () => Setting.value(`sync.${syncTargetId}.password`),
|
||||
});
|
||||
|
||||
return this.api_;
|
||||
@@ -134,7 +137,7 @@ export default class ShareService {
|
||||
}
|
||||
|
||||
public shareUrl(share: StateShare): string {
|
||||
return `${this.api().baseUrl()}/shares/${share.id}`;
|
||||
return `${this.api().userContentBaseUrl()}/shares/${share.id}`;
|
||||
}
|
||||
|
||||
public get shares() {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import Setting from '../../models/Setting';
|
||||
import { allNotesFolders, remoteNotesAndFolders, localNotesFoldersSameAsRemote } from '../../testing/test-utils-synchronizer';
|
||||
|
||||
const { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSynchronizer, synchronizer, sleep, switchClient, syncTargetId, fileApi } = require('../../testing/test-utils.js');
|
||||
import { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSynchronizer, synchronizer, sleep, switchClient, syncTargetId, fileApi } from '../../testing/test-utils';
|
||||
import Folder from '../../models/Folder';
|
||||
import Note from '../../models/Note';
|
||||
import BaseItem from '../../models/BaseItem';
|
||||
|
@@ -1,15 +1,13 @@
|
||||
import time from '../../time';
|
||||
import shim from '../../shim';
|
||||
import Setting from '../../models/Setting';
|
||||
|
||||
const { synchronizerStart, allSyncTargetItemsEncrypted, kvStore, supportDir, setupDatabaseAndSynchronizer, synchronizer, fileApi, switchClient, encryptionService, loadEncryptionMasterKey, decryptionWorker, checkThrowAsync } = require('../../testing/test-utils.js');
|
||||
import { createFolderTree, syncTargetName, synchronizerStart, allSyncTargetItemsEncrypted, kvStore, supportDir, setupDatabaseAndSynchronizer, synchronizer, fileApi, switchClient, encryptionService, loadEncryptionMasterKey, decryptionWorker, checkThrowAsync } from '../../testing/test-utils';
|
||||
import Folder from '../../models/Folder';
|
||||
import Note from '../../models/Note';
|
||||
import Resource from '../../models/Resource';
|
||||
import ResourceFetcher from '../../services/ResourceFetcher';
|
||||
import MasterKey from '../../models/MasterKey';
|
||||
import BaseItem from '../../models/BaseItem';
|
||||
import { createFolderTree } from '../../testing/test-utils';
|
||||
|
||||
let insideBeforeEach = false;
|
||||
|
||||
@@ -415,6 +413,13 @@ describe('Synchronizer.e2ee', function() {
|
||||
}));
|
||||
|
||||
it('should not encrypt items that are shared by folder', (async () => {
|
||||
// We skip this test for Joplin Server because it's going to check if
|
||||
// the share_id refers to an existing share.
|
||||
if (syncTargetName() === 'joplinServer') {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Setting.setValue('encryption.enabled', true);
|
||||
await loadEncryptionMasterKey();
|
||||
|
||||
|
@@ -487,13 +487,12 @@ function shimInit(sharp = null, keytar = null, React = null, appVersion = null)
|
||||
maxSockets: 1,
|
||||
keepAliveMsecs: 5000,
|
||||
};
|
||||
if (url.startsWith('https')) {
|
||||
shim.httpAgent_ = new https.Agent(AgentSettings);
|
||||
} else {
|
||||
shim.httpAgent_ = new http.Agent(AgentSettings);
|
||||
}
|
||||
shim.httpAgent_ = {
|
||||
http: new http.Agent(AgentSettings),
|
||||
https: new https.Agent(AgentSettings),
|
||||
};
|
||||
}
|
||||
return shim.httpAgent_;
|
||||
return url.startsWith('https') ? shim.httpAgent_.https : shim.httpAgent_.http;
|
||||
};
|
||||
|
||||
shim.openOrCreateFile = (filepath, defaultContents) => {
|
||||
|
@@ -50,7 +50,8 @@ const WebDavApi = require('../WebDavApi');
|
||||
const DropboxApi = require('../DropboxApi');
|
||||
import JoplinServerApi from '../JoplinServerApi';
|
||||
import { FolderEntity } from '../services/database/types';
|
||||
import { credentialFile } from '../utils/credentialFiles';
|
||||
import { credentialFile, readCredentialFile } from '../utils/credentialFiles';
|
||||
import SyncTargetJoplinCloud from '../SyncTargetJoplinCloud';
|
||||
const { loadKeychainServiceAndSettings } = require('../services/SettingUtils');
|
||||
const md5 = require('md5');
|
||||
const S3 = require('aws-sdk/clients/s3');
|
||||
@@ -112,6 +113,7 @@ SyncTargetRegistry.addClass(SyncTargetNextcloud);
|
||||
SyncTargetRegistry.addClass(SyncTargetDropbox);
|
||||
SyncTargetRegistry.addClass(SyncTargetAmazonS3);
|
||||
SyncTargetRegistry.addClass(SyncTargetJoplinServer);
|
||||
SyncTargetRegistry.addClass(SyncTargetJoplinCloud);
|
||||
|
||||
let syncTargetName_ = '';
|
||||
let syncTargetId_: number = null;
|
||||
@@ -433,7 +435,8 @@ async function synchronizerStart(id: number = null, extraOptions: any = null) {
|
||||
if (id === null) id = currentClient_;
|
||||
|
||||
const contextKey = `sync.${syncTargetId()}.context`;
|
||||
const context = Setting.value(contextKey);
|
||||
const contextString = Setting.value(contextKey);
|
||||
const context = contextString ? JSON.parse(contextString) : {};
|
||||
|
||||
const options = Object.assign({}, extraOptions);
|
||||
if (context) options.context = context;
|
||||
@@ -569,13 +572,23 @@ async function initFileApi() {
|
||||
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('joplinServer')) {
|
||||
mustRunInBand();
|
||||
|
||||
const joplinServerAuth = JSON.parse(await readCredentialFile('joplin-server-test-units-2.json'));
|
||||
|
||||
// const joplinServerAuth = {
|
||||
// "email": "admin@localhost",
|
||||
// "password": "admin",
|
||||
// "baseUrl": "http://api-joplincloud.local:22300",
|
||||
// "userContentBaseUrl": ""
|
||||
// }
|
||||
|
||||
// Note that to test the API in parallel mode, you need to use Postgres
|
||||
// as database, as the SQLite database is not reliable when being
|
||||
// read/write from multiple processes at the same time.
|
||||
const api = new JoplinServerApi({
|
||||
baseUrl: () => 'http://localhost:22300',
|
||||
username: () => 'admin@localhost',
|
||||
password: () => 'admin',
|
||||
baseUrl: () => joplinServerAuth.baseUrl,
|
||||
userContentBaseUrl: () => joplinServerAuth.userContentBaseUrl,
|
||||
username: () => joplinServerAuth.email,
|
||||
password: () => joplinServerAuth.password,
|
||||
});
|
||||
|
||||
fileApi = new FileApi('', new FileApiDriverJoplinServer(api));
|
||||
|
@@ -1,54 +0,0 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.readCredentialFile = exports.credentialFile = exports.credentialDir = void 0;
|
||||
const fs = require('fs-extra');
|
||||
function credentialDir() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const username = require('os').userInfo().username;
|
||||
const toTry = [
|
||||
`c:/Users/${username}/joplin-credentials`,
|
||||
`/mnt/c/Users/${username}/joplin-credentials`,
|
||||
`/home/${username}/joplin-credentials`,
|
||||
`/Users/${username}/joplin-credentials`,
|
||||
];
|
||||
for (const dirPath of toTry) {
|
||||
if (yield fs.pathExists(dirPath))
|
||||
return dirPath;
|
||||
}
|
||||
throw new Error(`Could not find credential directory in any of these paths: ${JSON.stringify(toTry)}`);
|
||||
});
|
||||
}
|
||||
exports.credentialDir = credentialDir;
|
||||
function credentialFile(filename) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const rootDir = yield credentialDir();
|
||||
const output = `${rootDir}/${filename}`;
|
||||
if (!(yield fs.pathExists(output)))
|
||||
throw new Error(`No such file: ${output}`);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
exports.credentialFile = credentialFile;
|
||||
function readCredentialFile(filename, defaultValue = '') {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const filePath = yield credentialFile(filename);
|
||||
const r = yield fs.readFile(filePath);
|
||||
return r.toString();
|
||||
}
|
||||
catch (error) {
|
||||
return defaultValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.readCredentialFile = readCredentialFile;
|
||||
//# sourceMappingURL=credentialFiles.js.map
|
@@ -28,7 +28,10 @@ export async function readCredentialFile(filename: string, defaultValue: string
|
||||
try {
|
||||
const filePath = await credentialFile(filename);
|
||||
const r = await fs.readFile(filePath);
|
||||
return r.toString();
|
||||
// There's normally no reason to keep the last new line character and it
|
||||
// can cause problems in certain scripts, so trim it. Any other white
|
||||
// space should also not be relevant.
|
||||
return r.toString().trim();
|
||||
} catch (error) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
171
packages/plugin-repo-cli/commands/updateRelease.ts
Normal file
171
packages/plugin-repo-cli/commands/updateRelease.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { githubOauthToken } from '@joplin/tools/tool-utils';
|
||||
import { pathExists, readdir, readFile, stat, writeFile } from 'fs-extra';
|
||||
const ghReleaseAssets = require('gh-release-assets');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const apiBaseUrl = 'https://api.github.com/repos/joplin/plugins';
|
||||
|
||||
interface Args {
|
||||
pluginRepoDir: string;
|
||||
dryRun: boolean;
|
||||
}
|
||||
|
||||
interface PluginInfo {
|
||||
id: string;
|
||||
version: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
interface ReleaseAsset {
|
||||
id: number;
|
||||
name: string;
|
||||
download_count: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface Release {
|
||||
upload_url: string;
|
||||
assets: ReleaseAsset[];
|
||||
}
|
||||
|
||||
async function getRelease(): Promise<Release> {
|
||||
const response = await fetch(`${apiBaseUrl}/releases`);
|
||||
const releases = await response.json();
|
||||
if (!releases.length) throw new Error('No existing release');
|
||||
return releases[0];
|
||||
}
|
||||
|
||||
async function getPluginInfos(pluginRepoDir: string): Promise<PluginInfo[]> {
|
||||
const pluginDirs = await readdir(`${pluginRepoDir}/plugins`);
|
||||
const output: PluginInfo[] = [];
|
||||
|
||||
for (const pluginDir of pluginDirs) {
|
||||
const basePath = `${pluginRepoDir}/plugins/${pluginDir}`;
|
||||
const manifest = JSON.parse(await readFile(`${basePath}/manifest.json`, 'utf8'));
|
||||
output.push({
|
||||
id: manifest.id,
|
||||
version: manifest.version,
|
||||
path: `${basePath}/plugin.jpl`,
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function assetNameFromPluginInfo(pluginInfo: PluginInfo): string {
|
||||
return `${pluginInfo.id}@${pluginInfo.version}.jpl`;
|
||||
}
|
||||
|
||||
function pluginInfoFromAssetName(name: string): PluginInfo {
|
||||
let s = name.split('.');
|
||||
s.pop();
|
||||
s = s.join('.').split('@');
|
||||
return {
|
||||
id: s[0],
|
||||
version: s[1],
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteAsset(oauthToken: string, id: number) {
|
||||
await fetch(`${apiBaseUrl}/releases/assets/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `token ${oauthToken}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function uploadAsset(oauthToken: string, uploadUrl: string, pluginInfo: PluginInfo) {
|
||||
return new Promise((resolve: Function, reject: Function) => {
|
||||
ghReleaseAssets({
|
||||
url: uploadUrl,
|
||||
token: oauthToken,
|
||||
assets: [
|
||||
{
|
||||
name: assetNameFromPluginInfo(pluginInfo),
|
||||
path: pluginInfo.path,
|
||||
},
|
||||
],
|
||||
}, (error: Error, assets: any) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(assets);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function createStats(statFilePath: string, release: Release) {
|
||||
const output: Record<string, any> = await pathExists(statFilePath) ? JSON.parse(await readFile(statFilePath, 'utf8')) : {};
|
||||
|
||||
if (release.assets) {
|
||||
for (const asset of release.assets) {
|
||||
const pluginInfo = pluginInfoFromAssetName(asset.name);
|
||||
if (!output[pluginInfo.id]) output[pluginInfo.id] = {};
|
||||
|
||||
output[pluginInfo.id][pluginInfo.version] = {
|
||||
downloadCount: asset.download_count,
|
||||
createdAt: asset.created_at,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
async function saveStats(statFilePath: string, stats: any) {
|
||||
await writeFile(statFilePath, JSON.stringify(stats, null, '\t'));
|
||||
}
|
||||
|
||||
export default async function(args: Args) {
|
||||
const release = await getRelease();
|
||||
const statFilePath = `${args.pluginRepoDir}/stats.json`;
|
||||
const stats = await createStats(statFilePath, release);
|
||||
|
||||
// We save the stats:
|
||||
// - If the stat file doesn't exist
|
||||
// - Or every x hours
|
||||
// - Or before deleting an asset (so that we preserve the number of times a
|
||||
// particular version of a plugin has been downloaded).
|
||||
let statSaved = false;
|
||||
async function doSaveStats() {
|
||||
if (statSaved) return;
|
||||
console.info('Updating stats file...');
|
||||
await saveStats(statFilePath, stats);
|
||||
statSaved = true;
|
||||
}
|
||||
|
||||
if (!(await pathExists(statFilePath))) {
|
||||
await doSaveStats();
|
||||
} else {
|
||||
const fileInfo = await stat(statFilePath);
|
||||
if (Date.now() - fileInfo.mtime.getTime() >= 7 * 24 * 60 * 60 * 1000) {
|
||||
await doSaveStats();
|
||||
}
|
||||
}
|
||||
|
||||
const pluginInfos = await getPluginInfos(args.pluginRepoDir);
|
||||
const oauthToken = await githubOauthToken();
|
||||
|
||||
for (const pluginInfo of pluginInfos) {
|
||||
const assetName = assetNameFromPluginInfo(pluginInfo);
|
||||
|
||||
const otherVersionAssets = release.assets.filter(asset => {
|
||||
const info = pluginInfoFromAssetName(asset.name);
|
||||
return info.id === pluginInfo.id && info.version !== pluginInfo.version;
|
||||
});
|
||||
|
||||
for (const asset of otherVersionAssets) {
|
||||
console.info(`Deleting old asset ${asset.name}...`);
|
||||
await doSaveStats();
|
||||
await deleteAsset(oauthToken, asset.id);
|
||||
}
|
||||
|
||||
const existingAsset = release.assets.find(asset => asset.name === assetName);
|
||||
if (existingAsset) continue;
|
||||
console.info(`Uploading ${assetName}...`);
|
||||
await uploadAsset(oauthToken, release.upload_url, pluginInfo);
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ import checkIfPluginCanBeAdded from './lib/checkIfPluginCanBeAdded';
|
||||
import updateReadme from './lib/updateReadme';
|
||||
import { NpmPackage } from './lib/types';
|
||||
import gitCompareUrl from './lib/gitCompareUrl';
|
||||
import commandUpdateRelease from './commands/updateRelease';
|
||||
|
||||
function stripOffPackageOrg(name: string): string {
|
||||
const n = name.split('/');
|
||||
@@ -249,6 +250,14 @@ async function commandBuild(args: CommandBuildArgs) {
|
||||
await processNpmPackage(npmPackage, repoDir);
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
await commandUpdateRelease(args);
|
||||
if (!(await gitRepoClean())) {
|
||||
await execCommand2('git add -A', { showOutput: true });
|
||||
await execCommand2('git commit -m "Update stats"', { showOutput: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (!dryRun) await execCommand2('git push');
|
||||
}
|
||||
|
||||
@@ -263,6 +272,7 @@ async function main() {
|
||||
const commands: Record<string, Function> = {
|
||||
build: commandBuild,
|
||||
version: commandVersion,
|
||||
updateRelease: commandUpdateRelease,
|
||||
};
|
||||
|
||||
let selectedCommand: string = '';
|
||||
@@ -286,6 +296,8 @@ async function main() {
|
||||
|
||||
.command('version', 'Gives version info', () => {}, (args: any) => setSelectedCommand('version', args))
|
||||
|
||||
.command('update-release <plugin-repo-dir>', 'Update GitHub release', () => {}, (args: any) => setSelectedCommand('updateRelease', args))
|
||||
|
||||
.help()
|
||||
.argv;
|
||||
|
||||
|
179
packages/plugin-repo-cli/package-lock.json
generated
179
packages/plugin-repo-cli/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/plugin-repo-cli",
|
||||
"version": "1.8.2",
|
||||
"version": "2.0.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -907,6 +907,11 @@
|
||||
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
|
||||
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -1296,8 +1301,7 @@
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
@@ -1382,6 +1386,14 @@
|
||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
|
||||
"dev": true
|
||||
},
|
||||
"decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"requires": {
|
||||
"mimic-response": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
@@ -1470,6 +1482,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"duplexify": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz",
|
||||
"integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==",
|
||||
"requires": {
|
||||
"end-of-stream": "^1.4.1",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1",
|
||||
"stream-shift": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
@@ -1495,7 +1530,6 @@
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
@@ -1889,6 +1923,19 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"gh-release-assets": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/gh-release-assets/-/gh-release-assets-2.0.0.tgz",
|
||||
"integrity": "sha512-I+Gy+e86o7A6J7sJRX4uA3EvLlLFcXxsRre22YTJ5dzpl/elZA75bMWfoBd0WVY3Mp9M8KtROfn3zlzDkptyWw==",
|
||||
"requires": {
|
||||
"async": "^3.2.0",
|
||||
"mime": "^2.4.6",
|
||||
"progress-stream": "^2.0.0",
|
||||
"pumpify": "^2.0.1",
|
||||
"simple-get": "^4.0.0",
|
||||
"util-extend": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
@@ -2080,8 +2127,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ip-regex": {
|
||||
"version": "2.1.0",
|
||||
@@ -2254,8 +2300,7 @@
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
@@ -3225,6 +3270,11 @@
|
||||
"picomatch": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
|
||||
"integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.45.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
|
||||
@@ -3246,6 +3296,11 @@
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"dev": true
|
||||
},
|
||||
"mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@@ -3319,6 +3374,11 @@
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
},
|
||||
"node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@@ -3460,7 +3520,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -3626,6 +3685,20 @@
|
||||
"react-is": "^17.0.1"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"progress-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-2.0.0.tgz",
|
||||
"integrity": "sha1-+sY6Cz0R3qy7CWmrzJOyFLzhntU=",
|
||||
"requires": {
|
||||
"speedometer": "~1.0.0",
|
||||
"through2": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz",
|
||||
@@ -3646,12 +3719,21 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"pumpify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz",
|
||||
"integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==",
|
||||
"requires": {
|
||||
"duplexify": "^4.1.1",
|
||||
"inherits": "^2.0.3",
|
||||
"pump": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
@@ -3701,6 +3783,20 @@
|
||||
"type-fest": "^0.8.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"regex-not": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
|
||||
@@ -3873,8 +3969,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
@@ -4104,6 +4199,21 @@
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
||||
"dev": true
|
||||
},
|
||||
"simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz",
|
||||
"integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==",
|
||||
"requires": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"sisteransi": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||
@@ -4311,6 +4421,11 @@
|
||||
"integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==",
|
||||
"dev": true
|
||||
},
|
||||
"speedometer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.0.0.tgz",
|
||||
"integrity": "sha1-zWccsGdSwivKM3Di8zREC+T8YuI="
|
||||
},
|
||||
"split-string": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||
@@ -4387,6 +4502,11 @@
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||
"dev": true
|
||||
},
|
||||
"stream-shift": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
|
||||
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
|
||||
},
|
||||
"string-length": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz",
|
||||
@@ -4407,6 +4527,14 @@
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||
@@ -4485,6 +4613,15 @@
|
||||
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
|
||||
"dev": true
|
||||
},
|
||||
"through2": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
|
||||
"integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
|
||||
"requires": {
|
||||
"readable-stream": "~2.3.6",
|
||||
"xtend": "~4.0.1"
|
||||
}
|
||||
},
|
||||
"tmpl": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
|
||||
@@ -4687,6 +4824,16 @@
|
||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"util-extend": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
|
||||
"integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
@@ -4827,8 +4974,7 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"write-file-atomic": {
|
||||
"version": "3.0.3",
|
||||
@@ -4860,6 +5006,11 @@
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/plugin-repo-cli",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
@@ -21,6 +21,8 @@
|
||||
"@joplin/lib": "^1.8.2",
|
||||
"@joplin/tools": "^1.8.2",
|
||||
"fs-extra": "^9.0.1",
|
||||
"gh-release-assets": "^2.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"yargs": "^16.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
2
packages/renderer/package-lock.json
generated
2
packages/renderer/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.2",
|
||||
"description": "The Joplin note renderer, used the mobile and desktop application",
|
||||
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer",
|
||||
"main": "index.js",
|
||||
@@ -24,7 +24,7 @@
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.24",
|
||||
"@joplin/fork-htmlparser2": "^4.1.26",
|
||||
"font-awesome-filetypes": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"highlight.js": "^10.2.1",
|
||||
|
123
packages/server/package-lock.json
generated
123
packages/server/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -901,6 +901,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@koa/cors": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.1.0.tgz",
|
||||
"integrity": "sha512-7ulRC1da/rBa6kj6P4g2aJfnET3z8Uf3SWu60cjbtxTA5g8lxRdX/Bd2P92EagGwwAhANeNw8T8if99rJliR6Q==",
|
||||
"requires": {
|
||||
"vary": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"@rmp135/sql-ts": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@rmp135/sql-ts/-/sql-ts-1.7.0.tgz",
|
||||
@@ -1386,8 +1394,7 @@
|
||||
"@types/node": {
|
||||
"version": "12.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz",
|
||||
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w=="
|
||||
},
|
||||
"@types/nodemailer": {
|
||||
"version": "6.4.1",
|
||||
@@ -2034,6 +2041,11 @@
|
||||
"resolved": "https://registry.npmjs.org/bulma-prefers-dark/-/bulma-prefers-dark-0.1.0-beta.0.tgz",
|
||||
"integrity": "sha512-EeDW8pQrkYEOXo2l3WykfghbUzi8jlQWGI+Cu2HwmXwQHMcoGF6yiKYCNShttN+8z3atq8fLWh3B7pqXUV4fBA=="
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"cache-base": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
|
||||
@@ -2098,6 +2110,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -3222,8 +3243,7 @@
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
@@ -3246,6 +3266,16 @@
|
||||
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
||||
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"get-package-type": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
|
||||
@@ -3410,7 +3440,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
@@ -3420,6 +3449,11 @@
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
@@ -6335,6 +6369,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.10.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz",
|
||||
"integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw=="
|
||||
},
|
||||
"object-visit": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
|
||||
@@ -6869,6 +6908,44 @@
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"dev": true
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",
|
||||
"integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.3",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"http-errors": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
||||
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
@@ -7261,6 +7338,16 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
@@ -7647,6 +7734,25 @@
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||
},
|
||||
"stripe": {
|
||||
"version": "8.150.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-8.150.0.tgz",
|
||||
"integrity": "sha512-48YMLupzvDyVZUs37xUBd1SF0E3B77ahOTLhL7ycVwZqwjlQ30K7iHTejIAUdtEnWaNkaOz0LX6jHeR49IulRQ==",
|
||||
"requires": {
|
||||
"@types/node": ">=8.1.0",
|
||||
"qs": "^6.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
|
||||
"integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -7956,6 +8062,11 @@
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"unset-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",
|
||||
@@ -17,6 +17,7 @@
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@joplin/lib": "^1.0.9",
|
||||
"@joplin/renderer": "^1.7.4",
|
||||
"@koa/cors": "^3.1.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bulma": "^0.9.1",
|
||||
"bulma-prefers-dark": "^0.1.0-beta.0",
|
||||
@@ -29,14 +30,16 @@
|
||||
"markdown-it": "^12.0.4",
|
||||
"mustache": "^3.1.0",
|
||||
"nanoid": "^2.1.1",
|
||||
"node-cron": "^3.0.0",
|
||||
"node-env-file": "^0.1.8",
|
||||
"nodemailer": "^6.6.0",
|
||||
"nodemon": "^2.0.6",
|
||||
"pg": "^8.5.1",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"query-string": "^6.8.3",
|
||||
"raw-body": "^2.4.1",
|
||||
"sqlite3": "^4.1.0",
|
||||
"node-cron": "^3.0.0",
|
||||
"stripe": "^8.150.0",
|
||||
"yargs": "^14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user