1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-20 00:46:28 +02:00

Compare commits

..

33 Commits

Author SHA1 Message Date
Laurent Cozic
093ef3d55a Fixing url import 2020-10-28 17:49:11 +00:00
Laurent Cozic
a6748bafb3 All: Fixes #3992: Update highlight.js to fix freeze for certain code blocks 2020-10-28 17:31:54 +00:00
Laurent Cozic
b52f6eb77c All: Fixes #3955: Fixed sync issue when importing ENEX files that contain new line characters in the source URL attribute 2020-10-28 17:21:41 +00:00
Laurent Cozic
98c933fdb7 All: Fixed OneDrive authentication 2020-10-28 15:50:34 +00:00
Laurent Cozic
ece7ffadd6 Desktop: Fixes #3986: Handle gzipped CSS files when importing from clipper 2020-10-28 15:47:36 +00:00
Laurent Cozic
04cfd07176 macOS: Regression: Restore Edit menu in config screens so that Copy/Cut/Paste shortcuts work 2020-10-28 11:23:57 +00:00
Laurent Cozic
db2282a351 Desktop: Fix syntax of imported resources when importing ENEX as HTML 2020-10-28 11:11:04 +00:00
Laurent Cozic
0ec3d6ca9d Clipper: Upgraded clipper to support API pagination
This version of the clipper will support both new and old Joplin
clients, so as to ease the transition.
2020-10-27 00:38:39 +00:00
Laurent Cozic
6a068a90b2 Desktop: Regression: Keyboard shortcut would not save in some cases 2020-10-25 17:46:41 +00:00
Laurent Cozic
9a6f6c8b39 Desktop: Regression: Restore "New sub-notebook" command 2020-10-25 17:29:52 +00:00
Laurent Cozic
52d5c32950 Plugins: Add the openNote, openFolder and openTag commands 2020-10-25 17:22:59 +00:00
Laurent Cozic
de47cff86d Fixed case 2020-10-24 12:07:48 +01:00
Laurent Cozic
a459174f98 Desktop: Fix: Command Palette click did not work 2020-10-24 11:46:02 +01:00
Laurent Cozic
75d5aa3a77 Tools: Fixed linter errors 2020-10-24 00:14:30 +01:00
Laurent Cozic
c254ca524f CLI v1.3.3 2020-10-23 17:01:13 +01:00
Laurent Cozic
51934b8d8d Cli: Added missing "immer" package 2020-10-23 17:00:20 +01:00
Laurent Cozic
a5dd686bb2 CLI v1.3.2 2020-10-23 16:58:28 +01:00
Laurent Cozic
4cbfd04522 Cli: Trying to fix build 2020-10-23 16:56:34 +01:00
Laurent Cozic
6d5d9323bd CLI v1.3.1 2020-10-23 16:04:34 +01:00
Laurent Cozic
76063a6284 Android release v1.3.9 2020-10-23 16:03:13 +01:00
Laurent Cozic
4119924e57 Electron release v1.3.9 2020-10-23 15:57:29 +01:00
Laurent Cozic
537336754c All: Sort tags in a case-insensitive way 2020-10-23 15:48:11 +01:00
Laurent Cozic
06f73919bd Desktop: Fixed Cut menu item and test units 2020-10-23 13:21:37 +01:00
Laurent Cozic
3f3e46081c Merge branch 'dev' of github.com:laurent22/joplin into dev 2020-10-22 16:34:45 +01:00
Laurent Cozic
68e4b4eaad macOS: Fixes #3404: Show context menu option to copy a link 2020-10-22 16:32:13 +01:00
Gen Neko
9dcb4b51e5 All: Translation: Update ja_JP.po (#3967) 2020-10-22 11:05:28 -04:00
Laurent Cozic
8543849ea1 Tools: Fixed tests 2020-10-22 15:55:29 +01:00
Laurent Cozic
6ce5240e12 Plugins: Fixed tests 2020-10-22 14:51:59 +01:00
Rory O’Kane
5bc25aefce Desktop: Make “update is available” dialog box easier to use (#3877) 2020-10-22 12:25:06 +01:00
Naveen M V
b737ca7471 All: Fix search filters when language is in Korean or with accents (#3947) 2020-10-22 12:16:47 +01:00
Laurent Cozic
a5d7366f94 Desktop: Fix invalid tag state issue when importing notes or syncing 2020-10-22 11:21:16 +01:00
Laurent Cozic
98f822d89c Cleaned up plugin doc 2020-10-21 22:52:58 +01:00
Laurent Cozic
c33a8250ee Desktop: Added openProfileDirectory command and menu item 2020-10-21 22:10:21 +01:00
75 changed files with 639 additions and 295 deletions

View File

@@ -105,7 +105,11 @@ ElectronClient/gui/MainScreen/commands/hideModalMessage.js
ElectronClient/gui/MainScreen/commands/moveToFolder.js
ElectronClient/gui/MainScreen/commands/newFolder.js
ElectronClient/gui/MainScreen/commands/newNote.js
ElectronClient/gui/MainScreen/commands/newSubFolder.js
ElectronClient/gui/MainScreen/commands/newTodo.js
ElectronClient/gui/MainScreen/commands/openFolder.js
ElectronClient/gui/MainScreen/commands/openNote.js
ElectronClient/gui/MainScreen/commands/openTag.js
ElectronClient/gui/MainScreen/commands/print.js
ElectronClient/gui/MainScreen/commands/renameFolder.js
ElectronClient/gui/MainScreen/commands/renameTag.js

4
.gitignore vendored
View File

@@ -99,7 +99,11 @@ ElectronClient/gui/MainScreen/commands/hideModalMessage.js
ElectronClient/gui/MainScreen/commands/moveToFolder.js
ElectronClient/gui/MainScreen/commands/newFolder.js
ElectronClient/gui/MainScreen/commands/newNote.js
ElectronClient/gui/MainScreen/commands/newSubFolder.js
ElectronClient/gui/MainScreen/commands/newTodo.js
ElectronClient/gui/MainScreen/commands/openFolder.js
ElectronClient/gui/MainScreen/commands/openNote.js
ElectronClient/gui/MainScreen/commands/openTag.js
ElectronClient/gui/MainScreen/commands/print.js
ElectronClient/gui/MainScreen/commands/renameFolder.js
ElectronClient/gui/MainScreen/commands/renameTag.js

View File

@@ -48,7 +48,11 @@ ElectronClient/gui/MainScreen/commands/hideModalMessage.js
ElectronClient/gui/MainScreen/commands/moveToFolder.js
ElectronClient/gui/MainScreen/commands/newFolder.js
ElectronClient/gui/MainScreen/commands/newNote.js
ElectronClient/gui/MainScreen/commands/newSubFolder.js
ElectronClient/gui/MainScreen/commands/newTodo.js
ElectronClient/gui/MainScreen/commands/openFolder.js
ElectronClient/gui/MainScreen/commands/openNote.js
ElectronClient/gui/MainScreen/commands/openTag.js
ElectronClient/gui/MainScreen/commands/print.js
ElectronClient/gui/MainScreen/commands/renameFolder.js
ElectronClient/gui/MainScreen/commands/renameTag.js

View File

@@ -22,4 +22,5 @@ yarn-error.log
tests/support/dropbox-auth.txt
tests/support/nextcloud-auth.json
tests/support/onedrive-auth.txt
build/
build/
patches/

View File

@@ -54,15 +54,25 @@ export default class PluginRunner extends BasePluginRunner {
};
}
async run(plugin:Plugin, sandbox:Global) {
const vmSandbox = vm.createContext(this.newSandboxProxy(plugin.id, sandbox));
async run(plugin:Plugin, sandbox:Global):Promise<void> {
return new Promise((resolve:Function, reject:Function) => {
const onStarted = () => {
plugin.off('started', onStarted);
resolve();
};
try {
vm.runInContext(plugin.scriptText, vmSandbox);
} catch (error) {
this.logger().error(`In plugin ${plugin.id}:`, error);
return;
}
plugin.on('started', onStarted);
const vmSandbox = vm.createContext(this.newSandboxProxy(plugin.id, sandbox));
try {
vm.runInContext(plugin.scriptText, vmSandbox);
} catch (error) {
reject(error);
// this.logger().error(`In plugin ${plugin.id}:`, error);
// return;
}
});
}
}

View File

@@ -17,12 +17,23 @@ tasks.prepareBuild = {
excluded: ['node_modules'],
});
await utils.copyDir(`${__dirname}/locales-build`, `${buildDir}/locales`);
await utils.copyDir(`${__dirname}/../patches`, `${buildDir}/patches`);
await tasks.copyLib.fn();
await utils.copyFile(`${__dirname}/package.json`, `${buildDir}/package.json`);
await utils.copyFile(`${__dirname}/package-lock.json`, `${buildDir}/package-lock.json`);
await utils.copyFile(`${__dirname}/gulpfile.js`, `${buildDir}/gulpfile.js`);
// Import all the patches inside the CliClient directory
// and build file. Needs to be in CliClient dir for when running
// in dev mode, and in build dir for production.
const localPatchDir = `${buildDir}/patches`;
await fs.remove(localPatchDir);
await fs.mkdirp(localPatchDir);
await utils.copyDir(`${__dirname}/../patches/shared`, `${localPatchDir}`, { delete: false });
await utils.copyDir(`${__dirname}/../patches/node`, `${localPatchDir}`, { delete: false });
await fs.remove(`${__dirname}/patches`);
await utils.copyDir(`${localPatchDir}`, `${__dirname}/patches`);
const packageRaw = await fs.readFile(`${buildDir}/package.json`);
const package = JSON.parse(packageRaw.toString());
package.scripts.postinstall = 'patch-package';

View File

@@ -15,6 +15,8 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 1.8.4\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: ElectronClient/services/plugins/UserWebviewDialogButtonBar.js:20
#: ElectronClient/checkForUpdates.js:139
@@ -122,7 +124,6 @@ msgid "Could not export notes: %s"
msgstr "ノートをエクスポートできませんでした: %s"
#: ElectronClient/plugins/GotoAnything.js:431
#, fuzzy
msgid ""
"Type a note title or part of its content to jump to it. Or type # followed "
"by a tag name, or @ followed by a notebook name. Or type : to search for "
@@ -130,7 +131,7 @@ msgid ""
msgstr ""
"題名や本文の一部を入力してノートを検索し、そのノートに移動できます。また、「#"
"タグ名」で該当タグ付きノートの一覧に、「@ノートブック名」で該当ノートブックに"
"移動できます。"
"移動できます。あるいは「:」でコマンドを検索して実行できます。"
#: ElectronClient/plugins/GotoAnything.js:456
#: ElectronClient/plugins/GotoAnything.min.js:499
@@ -140,9 +141,8 @@ msgstr "Goto Anything..."
#: ElectronClient/plugins/GotoAnything.js:463
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:28
#, fuzzy
msgid "Command palette"
msgstr "コマンド"
msgstr "コマンドパレット"
#: ElectronClient/plugins/GotoAnything.min.js:459
msgid ""
@@ -818,9 +818,8 @@ msgid "New note"
msgstr "新しいノート"
#: ElectronClient/gui/MainScreen/commands/toggleEditors.js:18
#, fuzzy
msgid "Toggle editors"
msgstr "エディターレイアウトの表示切り替え"
msgstr "エディター切り替え"
#: ElectronClient/gui/MainScreen/commands/selectTemplate.js:24
msgid "Template file:"
@@ -1414,9 +1413,9 @@ msgstr ""
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:63
#: ReactNativeClient/lib/services/KeymapService.js:144
#, fuzzy, javascript-format
#, javascript-format
msgid "Error: %s"
msgstr "エラー"
msgstr "エラー: %s"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:126
msgid "Command"
@@ -1486,9 +1485,8 @@ msgid "Open %s"
msgstr "%s を開く"
#: ElectronClient/commands/copyDevCommand.js:18
#, fuzzy
msgid "Copy dev mode command to clipboard"
msgstr "クリップボードにパスを保存"
msgstr "開発モードコマンドをクリップボードにコピー"
#: ElectronClient/commands/startExternalEditing.js:20
msgid "Edit in external editor"
@@ -1500,13 +1498,13 @@ msgid "Error opening note in editor: %s"
msgstr "次のエディターで開く際にエラー: %s"
#: ElectronClient/commands/toggleExternalEditing.js:18
#, fuzzy
msgid "Toggle external editing"
msgstr "外部エディターでの編集を終了"
msgstr "外部エディターでの編集を開始・終了"
# Intentionally chose the word "Finish" instead of "Stop". Hope this string won't be used in more generic contexts.
#: ElectronClient/commands/toggleExternalEditing.js:37
msgid "Stop"
msgstr ""
msgstr "終了"
#: ElectronClient/commands/stopExternalEditing.js:18
msgid "Stop external editing"
@@ -2229,7 +2227,7 @@ msgid "Cannot load \"%s\" module for format \"%s\" and output \"%s\""
msgstr "\"%s\" モジュール (\"%s / %s\" フォーマット用) を読み込めません"
#: ReactNativeClient/lib/services/interop/InteropService.js:152
#, fuzzy, javascript-format
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\" and target \"%s\""
msgstr "\"%s\" モジュール (\"%s / %s\" フォーマット用) を読み込めません"
@@ -2247,26 +2245,24 @@ msgid "Restored Notes"
msgstr "復元されたノート"
#: ReactNativeClient/lib/services/KeymapService.js:240
#, fuzzy
msgid "command"
msgstr "コマンド"
#: ReactNativeClient/lib/services/KeymapService.js:240
#: ReactNativeClient/lib/services/KeymapService.js:245
#, fuzzy, javascript-format
#, javascript-format
msgid "\"%s\" is missing the required \"%s\" property."
msgstr ""
"キーマップアイテム %s は必須の \"command\" プロパティを持っていません。"
msgstr "\"%s\" は必須の \"%s\" プロパティを持っていません。"
#: ReactNativeClient/lib/services/KeymapService.js:245
#: ReactNativeClient/lib/services/KeymapService.js:252
msgid "accelerator"
msgstr ""
msgstr "ショートカットキー"
#: ReactNativeClient/lib/services/KeymapService.js:252
#, fuzzy, javascript-format
#, javascript-format
msgid "Invalid %s: %s."
msgstr "無効な入力:%s"
msgstr "無効な %s: %s"
#: ReactNativeClient/lib/services/KeymapService.js:270
#, javascript-format
@@ -2733,11 +2729,13 @@ msgid ""
"\n"
"You may turn off this option at any time in the Configuration screen."
msgstr ""
"ノートに位置情報を保存するには、ユーザーの許可が必要です。\n"
"\n"
"位置情報を保存するかどうかは、設定画面でいつでも変更できます。"
#: ReactNativeClient/lib/components/screens/Note.js:341
#, fuzzy
msgid "Permission needed"
msgstr "カメラ使用の許可"
msgstr "許可が必要"
#: ReactNativeClient/lib/components/screens/Note.js:501
#: ReactNativeClient/lib/shim-init-node.js:91
@@ -2998,9 +2996,8 @@ msgstr ""
"を確認してください。次が報告されたエラーです:"
#: ReactNativeClient/lib/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:28
#, fuzzy
msgid "Open"
msgstr "開く..."
msgstr "開く"
#: ReactNativeClient/lib/components/note-list.js:97
msgid "You currently have no notebooks."
@@ -3212,9 +3209,8 @@ msgid "AWS S3 bucket"
msgstr "AWS S3 バケット"
#: ReactNativeClient/lib/models/Setting.js:226
#, fuzzy
msgid "AWS S3 URL"
msgstr "AWS S3"
msgstr "AWS S3 URL"
#: ReactNativeClient/lib/models/Setting.js:237
msgid "AWS key"

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.3.0",
"version": "1.3.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2964,9 +2964,9 @@
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
},
"highlight.js": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
},
"homedir-polyfill": {
"version": "1.0.3",
@@ -3227,6 +3227,11 @@
"file-type": "^4.1.0"
}
},
"immer": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/immer/-/immer-7.0.14.tgz",
"integrity": "sha512-BxCs6pJwhgSEUEOZjywW7OA8DXVzfHjkBelSEl0A+nEu0+zS4cFVdNOONvt55N4WOm8Pu4xqSPYxhm1Lv2iBBA=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",

View File

@@ -6,7 +6,7 @@
"scripts": {
"test": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --fail-fast=true --config=tests/support/jasmine.json",
"test-ci": "gulp buildTests -L && node node_modules/jasmine/bin/jasmine.js --config=tests/support/jasmine.json",
"postinstall": "npm run build && patch-package --patch-dir ../patches/shared && patch-package --patch-dir ../patches/node",
"postinstall": "npm run build && patch-package --patch-dir ./patches",
"build": "gulp build",
"start": "gulp build -L && node 'build/main.js' --stack-trace-enabled --log-level debug --env dev"
},
@@ -28,7 +28,7 @@
],
"owner": "Laurent Cozic"
},
"version": "1.3.0",
"version": "1.3.3",
"bin": {
"joplin": "./main.js"
},
@@ -52,12 +52,13 @@
"font-awesome-filetypes": "^2.1.0",
"form-data": "^2.1.4",
"fs-extra": "^5.0.0",
"highlight.js": "10.1.1",
"highlight.js": "^10.2.1",
"html-entities": "^1.2.1",
"html-minifier": "^3.5.15",
"htmlparser2": "^4.1.0",
"image-data-uri": "^2.0.0",
"image-type": "^3.0.0",
"immer": "^7.0.14",
"joplin-turndown": "^4.0.29",
"joplin-turndown-plugin-gfm": "^1.0.12",
"json-stringify-safe": "^5.0.1",

View File

@@ -3,6 +3,7 @@ require('app-module-path').addPath(__dirname);
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const shim = require('lib/shim').default;
const { enexXmlToHtml } = require('lib/import-enex-html-gen.js');
const cleanHtml = require('clean-html');
process.on('unhandledRejection', (reason, p) => {
console.warn('Unhandled Rejection at: Promise', p, 'reason:', reason);
@@ -19,6 +20,20 @@ const audioResource = {
title: 'audio test',
};
// All the test HTML files are beautified ones, so we need to run
// this before the comparison. Before, beautifying was done by `enexXmlToHtml`
// but that was removed due to problems with the clean-html package.
const beautifyHtml = (html) => {
return new Promise((resolve) => {
try {
cleanHtml.clean(html, { wrap: 0 }, (...cleanedHtml) => resolve(cleanedHtml.join('')));
} catch (error) {
console.warn(`Could not clean HTML - the "unclean" version will be used: ${error.message}: ${html.trim().substr(0, 512).replace(/[\n\r]/g, ' ')}...`);
resolve([html].join(''));
}
});
};
/**
* Tests the importer for a single note, checking that the result of
* processing the given `.enex` input file matches the contents of the given
@@ -38,7 +53,7 @@ const compareOutputToExpected = (options) => {
it(testTitle, asyncTest(async () => {
const enexInput = await shim.fsDriver().readFile(inputFile);
const expectedOutput = await shim.fsDriver().readFile(outputFile);
const actualOutput = await enexXmlToHtml(enexInput, options.resources);
const actualOutput = await beautifyHtml(await enexXmlToHtml(enexInput, options.resources));
expect(actualOutput).toEqual(expectedOutput);
}));

View File

@@ -1,5 +1,5 @@
<en-note>
<div><a href="joplin://21ca2b948f222a38802940ec7e2e5de3" hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1">attachment-1</a></div>
<div><a href=":/21ca2b948f222a38802940ec7e2e5de3" hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1">attachment-1</a></div>
<div>
<br>
</div>

View File

@@ -1,14 +1,25 @@
import FsDriverNode from 'lib/fs-driver-node';
import shim from 'lib/shim';
const { expectThrow } = require('test-utils.js');
// On Windows, path.resolve is going to convert a path such as
// /tmp/file.txt to c:\tmp\file.txt
function platformPath(path:string) {
if (shim.isWindows()) {
return `C:${path.replace(/\//g, '\\')}`;
} else {
return path;
}
}
describe('fsDriver', function() {
it('should resolveRelativePathWithinDir', () => {
const fsDriver = new FsDriverNode();
expect(fsDriver.resolveRelativePathWithinDir('/test/temp', './my/file.txt')).toBe('/test/temp/my/file.txt');
expect(fsDriver.resolveRelativePathWithinDir('/', './test')).toBe('/test');
expect(fsDriver.resolveRelativePathWithinDir('/test', 'myfile.txt')).toBe('/test/myfile.txt');
expect(fsDriver.resolveRelativePathWithinDir('/test/temp', './mydir/../test.txt')).toBe('/test/temp/test.txt');
expect(fsDriver.resolveRelativePathWithinDir('/test/temp', './my/file.txt')).toBe(platformPath('/test/temp/my/file.txt'));
expect(fsDriver.resolveRelativePathWithinDir('/', './test')).toBe(platformPath('/test'));
expect(fsDriver.resolveRelativePathWithinDir('/test', 'myfile.txt')).toBe(platformPath('/test/myfile.txt'));
expect(fsDriver.resolveRelativePathWithinDir('/test/temp', './mydir/../test.txt')).toBe(platformPath('/test/temp/test.txt'));
expectThrow(() => fsDriver.resolveRelativePathWithinDir('/test/temp', '../myfile.txt'));
expectThrow(() => fsDriver.resolveRelativePathWithinDir('/test/temp', './mydir/../../test.txt'));

View File

@@ -0,0 +1 @@
<a href="https://images.macrumors.com/filters:quality(90)/article.html"/>testing</a>

View File

@@ -0,0 +1 @@
[testing](https://images.macrumors.com/filters:quality%2890%29/article.html)

View File

@@ -104,4 +104,14 @@ describe('models_BaseItem', function() {
expect(noteAfter).toEqual(noteBefore);
}));
it('should serialize and unserialize properties that contain new lines', asyncTest(async () => {
const note = await Note.save({ title: 'note', source_url: '\nhttps://joplinapp.org/\n' });
const noteBefore = await Note.load(note.id);
const serialized = await Note.serialize(noteBefore);
const noteAfter = await Note.unserialize(serialized);
expect(noteAfter).toEqual(noteBefore);
}));
});

View File

@@ -2,12 +2,10 @@ import PluginRunner from '../app/services/plugins/PluginRunner';
import PluginService from 'lib/services/plugins/PluginService';
import { ContentScriptType } from 'lib/services/plugins/api/types';
import MdToHtml from 'lib/joplin-renderer/MdToHtml';
import Setting from 'lib/models/Setting';
import shim from 'lib/shim';
import uuid from 'lib/uuid';
require('app-module-path').addPath(__dirname);
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, expectThrow } = require('test-utils.js');
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, expectThrow, createTempDir } = require('test-utils.js');
const Note = require('lib/models/Note');
const Folder = require('lib/models/Folder');
@@ -65,6 +63,9 @@ describe('services_PluginService', function() {
const allFolders = await Folder.all();
expect(allFolders.length).toBe(1);
// If you have an error here, it might mean you need to run `npm i` from
// the "withExternalModules" folder. Not clear exactly why.
expect(allFolders[0].title).toBe(' foo');
}));
@@ -154,12 +155,14 @@ describe('services_PluginService', function() {
}));
it('should register a Markdown-it plugin', asyncTest(async () => {
const contentScriptPath = `${Setting.value('tempDir')}/markdownItTestPlugin${uuid.createNano()}.js`;
const tempDir = await createTempDir();
const contentScriptPath = `${tempDir}/markdownItTestPlugin.js`;
await shim.fsDriver().copy(`${testPluginDir}/content_script/src/markdownItTestPlugin.js`, contentScriptPath);
const service = newPluginService();
const plugin = await service.loadPluginFromString('example', Setting.value('tempDir'), `
const plugin = await service.loadPluginFromString('example', tempDir, `
/* joplin-manifest:
{
"manifest_version": 1,
@@ -173,7 +176,7 @@ describe('services_PluginService', function() {
joplin.plugins.register({
onStart: async function() {
await joplin.plugins.registerContentScript('markdownItPlugin', 'justtesting', '${contentScriptPath}');
await joplin.plugins.registerContentScript('markdownItPlugin', 'justtesting', './markdownItTestPlugin.js');
},
});
`);
@@ -198,7 +201,7 @@ describe('services_PluginService', function() {
expect(result.html.includes('JUST TESTING: something')).toBe(true);
await shim.fsDriver().remove(contentScriptPath);
await shim.fsDriver().remove(tempDir);
}));
});

View File

@@ -1,7 +1,12 @@
import joplin from 'api';
import { ContentScriptType } from 'api/types';
joplin.plugins.register({
onStart: async function() {
await joplin.plugins.registerContentScript('markdownItPlugin', 'justtesting', './markdownItTestPlugin.js');
await joplin.plugins.registerContentScript(
ContentScriptType.MarkdownItPlugin,
'justtesting',
'./markdownItTestPlugin.js'
);
},
});

View File

@@ -80,9 +80,9 @@ EncryptionService.fsDriver_ = fsDriver;
FileApiDriverLocal.fsDriver_ = fsDriver;
const logDir = `${__dirname}/../tests/logs`;
const tempDir = `${__dirname}/../tests/tmp`;
const baseTempDir = `${__dirname}/../tests/tmp`;
fs.mkdirpSync(logDir, 0o755);
fs.mkdirpSync(tempDir, 0o755);
fs.mkdirpSync(baseTempDir, 0o755);
fs.mkdirpSync(`${__dirname}/data`);
SyncTargetRegistry.addClass(SyncTargetMemory);
@@ -146,7 +146,7 @@ BaseItem.loadClass('Revision', Revision);
Setting.setConstant('appId', 'net.cozic.joplintest-cli');
Setting.setConstant('appType', 'cli');
Setting.setConstant('tempDir', tempDir);
Setting.setConstant('tempDir', baseTempDir);
Setting.setConstant('env', 'dev');
BaseService.logger_ = logger;
@@ -634,6 +634,12 @@ function tempFilePath(ext) {
return `${Setting.value('tempDir')}/${md5(Date.now() + Math.random())}.${ext}`;
}
async function createTempDir() {
const tempDirPath = `${baseTempDir}/${uuid.createNano()}`;
await fs.mkdirp(tempDirPath);
return tempDirPath;
}
function mockDate(year, month, day, tick) {
const fixedDate = new Date(2020, 0, 1);
jasmine.clock().install();
@@ -712,4 +718,4 @@ class TestApp extends BaseApplication {
}
}
module.exports = { synchronizerStart, syncTargetName, setSyncTargetName, syncDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, mockDate, restoreDate, TestApp };
module.exports = { synchronizerStart, syncTargetName, setSyncTargetName, syncDir, createTempDir, isNetworkSyncTarget, kvStore, expectThrow, logger, expectNotThrow, resourceService, resourceFetcher, tempFilePath, allSyncTargetItemsEncrypted, msleep, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, checkThrow, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, currentClientId, id, ids, sortedIds, at, createNTestNotes, createNTestFolders, createNTestTags, mockDate, restoreDate, TestApp };

View File

@@ -55,7 +55,7 @@
},
"scripts": {
"start": "node scripts/start.js",
"build": "SKIP_PREFLIGHT_CHECK=true node scripts/build.js",
"build": "node scripts/build.js SKIP_PREFLIGHT_CHECK",
"test": "node scripts/test.js --env=jsdom",
"watch": "cra-build-watch",
"postinstall": "node postinstall.js && npm run build"

View File

@@ -1,5 +1,9 @@
'use strict';
if (process.argv && process.argv.indexOf('SKIP_PREFLIGHT_CHECK') >= 0) {
process.env.SKIP_PREFLIGHT_CHECK = 'true';
}
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';

View File

@@ -148,10 +148,10 @@ class Bridge {
this.dispatch({ type: 'CLIPPER_SERVER_SET', foundState: 'found', port: state.port });
const folders = await this.folderTree();
this.dispatch({ type: 'FOLDERS_SET', folders: folders });
this.dispatch({ type: 'FOLDERS_SET', folders: folders.items ? folders.items : folders });
const tags = await this.clipperApiExec('GET', 'tags');
this.dispatch({ type: 'TAGS_SET', tags: tags });
this.dispatch({ type: 'TAGS_SET', tags: tags.items ? tags.items : tags });
bridge().restoreState();
return;
@@ -245,7 +245,7 @@ class Bridge {
}
async folderTree() {
return this.clipperApiExec('GET', 'folders');
return this.clipperApiExec('GET', 'folders', { as_tree: 1 });
}
async storageSet(keys) {

View File

@@ -46,6 +46,7 @@ const commands = [
require('./gui/MainScreen/commands/moveToFolder'),
require('./gui/MainScreen/commands/newNote'),
require('./gui/MainScreen/commands/newFolder'),
require('./gui/MainScreen/commands/newSubFolder'),
require('./gui/MainScreen/commands/newTodo'),
require('./gui/MainScreen/commands/print'),
require('./gui/MainScreen/commands/renameFolder'),
@@ -61,6 +62,9 @@ const commands = [
require('./gui/MainScreen/commands/toggleSideBar'),
require('./gui/MainScreen/commands/toggleVisiblePanes'),
require('./gui/MainScreen/commands/toggleEditors'),
require('./gui/MainScreen/commands/openNote'),
require('./gui/MainScreen/commands/openFolder'),
require('./gui/MainScreen/commands/openTag'),
require('./gui/NoteEditor/commands/focusElementNoteBody'),
require('./gui/NoteEditor/commands/focusElementNoteTitle'),
require('./gui/NoteEditor/commands/showLocalSearch'),

View File

@@ -149,8 +149,10 @@ function checkForUpdates(inBackground, window, logFilePath, options) {
const result = await dialog.showMessageBox(parentWindow_, {
type: 'info',
message: `${_('An update is available, do you want to download it now?')}\n\n${_('Your version: %s', packageInfo.version)}\n${_('New version: %s', newVersionString)}${releaseNotes}`,
buttons: [_('Yes'), _('No')].concat(truncateReleaseNotes ? [_('Full Release Notes')] : []),
message: `${_('An update is available, do you want to download it now?')}`,
detail: `${_('Your version: %s', packageInfo.version)}\n${_('New version: %s', newVersionString)}${releaseNotes}`,
buttons: [_('Download'), _('Cancel')].concat(truncateReleaseNotes ? [_('Full Release Notes')] : []),
cancelId: 1,
});
const buttonIndex = result.response;

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { useState } from 'react';
import KeymapService, { KeymapItem } from '../../lib/services/KeymapService';
import KeymapService, { KeymapItem } from 'lib/services/KeymapService';
import { ShortcutRecorder } from './ShortcutRecorder';
import getLabel from './utils/getLabel';
import useKeymap from './utils/useKeymap';
@@ -106,7 +106,7 @@ export const KeymapConfigScreen = ({ themeId }: KeymapConfigScreenProps) => {
const renderError = (error: Error) => {
return (
<div style={styles.warning}>
<div style={{ ...styles.warning, position: 'absolute', top: 0 }}>
<p style={styles.text}>
<span>
{error.message}

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { useState, useEffect, KeyboardEvent } from 'react';
import KeymapService from '../../lib/services/KeymapService';
import KeymapService from 'lib/services/KeymapService';
import styles_ from './styles';
import { _ } from 'lib/locale';
@@ -41,7 +41,7 @@ export const ShortcutRecorder = ({ onSave, onReset, onCancel, onError, initialAc
}
}, [accelerator]);
const handleKeydown = (event: KeyboardEvent<HTMLDivElement>) => {
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
event.preventDefault();
const newAccelerator = keymapService.domToElectronAccelerator(event);
@@ -64,7 +64,7 @@ export const ShortcutRecorder = ({ onSave, onReset, onCancel, onError, initialAc
<input
value={accelerator}
placeholder={_('Press the shortcut')}
onKeyDown={handleKeydown}
onKeyDown={handleKeyDown}
style={styles.recorderInput}
title={_('Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the shortcut.')}
readOnly

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import KeymapService, { KeymapItem } from '../../../lib/services/KeymapService';
import KeymapService, { KeymapItem } from 'lib/services/KeymapService';
const keymapService = KeymapService.instance();
@@ -70,7 +70,8 @@ const useKeymap = (): [
await keymapService.saveCustomKeymap();
setKeymapError(null);
} catch (err) {
setKeymapError(err);
const error = new Error(`Could not save file: ${err.message}`);
setKeymapError(error);
}
}

View File

@@ -48,6 +48,7 @@ const commands = [
require('./commands/moveToFolder'),
require('./commands/newNote'),
require('./commands/newFolder'),
require('./commands/newSubFolder'),
require('./commands/newTodo'),
require('./commands/print'),
require('./commands/renameFolder'),
@@ -63,6 +64,9 @@ const commands = [
require('./commands/toggleNoteList'),
require('./commands/toggleSideBar'),
require('./commands/toggleVisiblePanes'),
require('./commands/openNote'),
require('./commands/openFolder'),
require('./commands/openTag'),
];
class MainScreenComponent extends React.Component<any, any> {

View File

@@ -0,0 +1,17 @@
import CommandService, { CommandContext, CommandDeclaration, CommandRuntime } from 'lib/services/CommandService';
import { _ } from 'lib/locale';
export const declaration:CommandDeclaration = {
name: 'newSubFolder',
label: () => _('New sub-notebook'),
iconName: 'fa-book',
};
export const runtime = ():CommandRuntime => {
return {
execute: async (context:CommandContext, parentId:string = null) => {
parentId = parentId || context.state.selectedFolderId;
return CommandService.instance().execute('newFolder', parentId);
},
};
};

View File

@@ -0,0 +1,16 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
export const declaration:CommandDeclaration = {
name: 'openFolder',
};
export const runtime = ():CommandRuntime => {
return {
execute: async (context:CommandContext, folderId:string) => {
context.dispatch({
type: 'FOLDER_SELECT',
id: folderId,
});
},
};
};

View File

@@ -0,0 +1,26 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
const Note = require('lib/models/Note');
const Folder = require('lib/models/Folder');
export const declaration:CommandDeclaration = {
name: 'openNote',
};
export const runtime = ():CommandRuntime => {
return {
execute: async (context:CommandContext, noteId:string, hash:string = null) => {
const note = await Note.load(noteId);
if (!note) throw new Error(`No such note: ${noteId}`);
const folder = await Folder.load(note.parent_id);
if (!folder) throw new Error(`Note parent notebook does not exist: ${JSON.stringify(note)}`);
context.dispatch({
type: 'FOLDER_AND_NOTE_SELECT',
folderId: folder.id,
noteId: note.id,
hash,
});
},
};
};

View File

@@ -0,0 +1,16 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
export const declaration:CommandDeclaration = {
name: 'openTag',
};
export const runtime = ():CommandRuntime => {
return {
execute: async (context:CommandContext, tagId:string) => {
context.dispatch({
type: 'TAG_SELECT',
id: tagId,
});
},
};
};

View File

@@ -8,8 +8,9 @@ export const declaration:CommandDeclaration = {
export const runtime = (comp:any):CommandRuntime => {
return {
execute: async (_context:CommandContext, message:string) => {
let brIndex = 1;
const lines = message.split('\n').map((line:string) => {
if (!line.trim()) return <br/>;
if (!line.trim()) return <br key={`${brIndex++}`}/>;
return <div key={line} className="text">{line}</div>;
});

View File

@@ -95,6 +95,7 @@ const commandNames:string[] = [
'newNote',
'newTodo',
'newFolder',
'newSubFolder',
'print',
'synchronize',
'textCopy',
@@ -301,6 +302,7 @@ function useMenu(props:Props) {
const newNoteItem = menuItemDic.newNote;
const newTodoItem = menuItemDic.newTodo;
const newFolderItem = menuItemDic.newFolder;
const newSubFolderItem = menuItemDic.newSubFolder;
const printItem = menuItemDic.print;
toolsItemsFirst.push(syncStatusItem, {
@@ -423,7 +425,9 @@ function useMenu(props:Props) {
},
shim.isMac() ? noItem : newNoteItem,
shim.isMac() ? noItem : newTodoItem,
shim.isMac() ? noItem : newFolderItem, {
shim.isMac() ? noItem : newFolderItem,
shim.isMac() ? noItem : newSubFolderItem,
{
type: 'separator',
visible: shim.isMac() ? false : true,
}, {
@@ -473,7 +477,9 @@ function useMenu(props:Props) {
submenu: [
newNoteItem,
newTodoItem,
newFolderItem, {
newFolderItem,
newSubFolderItem,
{
label: _('Close Window'),
platforms: ['darwin'],
accelerator: shim.isMac() && keymapService.getAccelerator('closeWindow'),
@@ -788,6 +794,15 @@ function useMenu(props:Props) {
label: _('&File'),
submenu: [quitMenuItem],
},
{
label: _('&Edit'),
submenu: [
menuItemDic.textCopy,
menuItemDic.textCut,
menuItemDic.textPaste,
menuItemDic.textSelectAll,
],
},
]));
} else {
setMenu(Menu.buildFromTemplate(template));

View File

@@ -43,6 +43,14 @@ async function resourceInfo(options:ContextMenuOptions):Promise<any> {
return { resource, resourcePath };
}
function handleCopyToClipboard(options:ContextMenuOptions) {
if (options.textToCopy) {
clipboard.writeText(options.textToCopy);
} else if (options.htmlToCopy) {
clipboard.writeHTML(options.htmlToCopy);
}
}
export function menuItems():ContextMenuItems {
return {
open: {
@@ -88,7 +96,7 @@ export function menuItems():ContextMenuItems {
cut: {
label: _('Cut'),
onAction: async (options:ContextMenuOptions) => {
clipboard.writeText(options.textToCopy);
handleCopyToClipboard(options);
options.insertContent('');
},
isActive: (_itemType:ContextMenuItemType, options:ContextMenuOptions) => !options.isReadOnly && (!!options.textToCopy || !!options.htmlToCopy),
@@ -96,11 +104,7 @@ export function menuItems():ContextMenuItems {
copy: {
label: _('Copy'),
onAction: async (options:ContextMenuOptions) => {
if (options.textToCopy) {
clipboard.writeText(options.textToCopy);
} else if (options.htmlToCopy) {
clipboard.writeHTML(options.htmlToCopy);
}
handleCopyToClipboard(options);
},
isActive: (_itemType:ContextMenuItemType, options:ContextMenuOptions) => !!options.textToCopy || !!options.htmlToCopy,
},
@@ -117,7 +121,7 @@ export function menuItems():ContextMenuItems {
onAction: async (options:ContextMenuOptions) => {
clipboard.writeText(options.linkToCopy !== null ? options.linkToCopy : options.textToCopy);
},
isActive: (itemType:ContextMenuItemType) => itemType === ContextMenuItemType.Link,
isActive: (itemType:ContextMenuItemType, options:ContextMenuOptions) => itemType === ContextMenuItemType.Link || !!options.linkToCopy,
},
};
}

View File

@@ -41,7 +41,7 @@ export default function useMessageHandler(scrollWhenReady:any, setScrollWhenRead
itemType: arg0 && arg0.type,
resourceId: arg0.resourceId,
textToCopy: arg0.textToCopy,
linkToCopy: null,
linkToCopy: arg0.linkToCopy || null,
htmlToCopy: '',
insertContent: () => { console.warn('insertContent() not implemented'); },
});

View File

@@ -368,9 +368,12 @@
const selectedText = window.getSelection().toString();
if (selectedText) {
const linkToCopy = event.target && event.target.getAttribute('href') ? event.target.getAttribute('href') : null;
ipcProxySendToHost('contextMenu', {
type: 'text',
textToCopy: selectedText,
linkToCopy: linkToCopy,
});
} else if (event.target.getAttribute('href')) {
ipcProxySendToHost('contextMenu', {

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.8",
"version": "1.3.9",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -6770,9 +6770,9 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"highlight.js": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
},
"hoist-non-react-statics": {
"version": "2.5.0",

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.8",
"version": "1.3.9",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -139,7 +139,7 @@
"form-data": "^2.3.2",
"formatcoords": "^1.1.3",
"fs-extra": "^5.0.0",
"highlight.js": "^10.1.1",
"highlight.js": "^10.2.1",
"html-entities": "^1.2.1",
"html-minifier": "^4.0.0",
"htmlparser2": "^4.1.0",

View File

@@ -421,7 +421,7 @@ class Dialog extends React.PureComponent<Props, State> {
listItem_onClick(event:any) {
const itemId = event.currentTarget.getAttribute('data-id');
const parentId = event.currentTarget.getAttribute('data-parent-id');
const itemType = event.currentTarget.getAttribute('data-type');
const itemType = Number(event.currentTarget.getAttribute('data-type'));
this.gotoItem({
id: itemId,

View File

@@ -2,4 +2,5 @@ import { AppState } from '../../app';
export interface DesktopCommandContext {
state: AppState,
dispatch: Function,
}

View File

@@ -28,7 +28,7 @@ Linux | <a href='https://github.com/laurent22/joplin/releases/download/
Operating System | Download | Alt. Download
-----------------|----------|----------------
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.2.6/joplin-v1.2.6.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.2.6/joplin-v1.2.6-32bit.apk)
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://joplinapp.org/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.9/joplin-v1.3.9.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.9/joplin-v1.3.9-32bit.apk)
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://joplinapp.org/images/BadgeIOS.png'/></a> | -
## Terminal application

View File

@@ -132,8 +132,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097596
versionName "1.3.8"
versionCode 2097597
versionName "1.3.9"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -51,7 +51,10 @@ shared.renderFolders = function(props, renderItem) {
shared.renderTags = function(props, renderItem) {
const tags = props.tags.slice();
tags.sort((a, b) => {
return a.title < b.title ? -1 : +1;
// Note: while newly created tags are normalized and lowercase
// imported tags might be any case, so we need to do case-insensitive
// sort.
return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1;
});
const tagItems = [];
const order = [];
@@ -66,21 +69,6 @@ shared.renderTags = function(props, renderItem) {
};
};
// shared.renderSearches = function(props, renderItem) {
// let searches = props.searches.slice();
// let searchItems = [];
// const order = [];
// for (let i = 0; i < searches.length; i++) {
// const search = searches[i];
// order.push(search.id);
// searchItems.push(renderItem(search, props.selectedSearchId == search.id && props.notesParentType == 'Search'));
// }
// return {
// items: searchItems,
// order: order,
// };
// }
shared.synchronize_press = async function(comp) {
const { reg } = require('lib/registry.js');

View File

@@ -195,13 +195,14 @@ export default class FsDriverNode extends FsDriverBase {
public resolve(path:string) {
return require('path').resolve(path);
}
// Resolves the provided relative path to an absolute path within baseDir. The function
// also checks that the absolute path is within baseDir, to avoid security issues.
// It is expected that baseDir is a safe path (not user-provided).
public resolveRelativePathWithinDir(baseDir:string, relativePath:string) {
const resolvedBaseDir = nodeResolve(baseDir);
const resolvedPath = nodeResolve(baseDir, relativePath);
if (resolvedPath.indexOf(baseDir) !== 0) throw new Error('Resolved path for relative path "' + relativePath + '" is not within base directory "' + baseDir + '"');
if (resolvedPath.indexOf(resolvedBaseDir) !== 0) throw new Error(`Resolved path for relative path "${relativePath}" is not within base directory "${baseDir}" (Was resolved to ${resolvedPath})`);
return resolvedPath;
}

View File

@@ -474,13 +474,13 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
note.latitude = noteAttributes.latitude;
note.longitude = noteAttributes.longitude;
note.altitude = noteAttributes.altitude;
note.author = noteAttributes.author;
note.author = noteAttributes.author ? noteAttributes.author.trim() : '';
note.is_todo = noteAttributes['reminder-order'] !== '0' && !!noteAttributes['reminder-order'];
note.todo_due = dateToTimestamp(noteAttributes['reminder-time'], true);
note.todo_completed = dateToTimestamp(noteAttributes['reminder-done-time'], true);
note.order = dateToTimestamp(noteAttributes['reminder-order'], true);
note.source = noteAttributes.source ? `evernote.${noteAttributes.source}` : 'evernote';
note.source_url = noteAttributes['source-url'] ? noteAttributes['source-url'] : '';
note.source = noteAttributes.source ? `evernote.${noteAttributes.source.trim()}` : 'evernote';
note.source_url = noteAttributes['source-url'] ? noteAttributes['source-url'].trim() : '';
noteAttributes = null;
} else if (n == 'resource') {
@@ -488,9 +488,9 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
id: noteResource.id,
dataFilePath: noteResource.dataFilePath,
dataEncoding: noteResource.dataEncoding,
mime: noteResource.mime,
title: noteResource.filename ? noteResource.filename : '',
filename: noteResource.filename ? noteResource.filename : '',
mime: noteResource.mime ? noteResource.mime.trim() : '',
title: noteResource.filename ? noteResource.filename.trim() : '',
filename: noteResource.filename ? noteResource.filename.trim() : '',
});
noteResource = null;

View File

@@ -1,6 +1,6 @@
// This plugin is used only on mobile, to highlight search results.
import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
import { RuleOptions } from 'lib/joplin-renderer/MdToHtml';
const stringUtils = require('../../stringUtils.js');
const md5 = require('md5');
@@ -67,4 +67,4 @@ function plugin(markdownIt:any, ruleOptions:RuleOptions) {
export default {
plugin,
}
};

View File

@@ -1,4 +1,4 @@
import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
import { RuleOptions } from 'lib/joplin-renderer/MdToHtml';
const htmlUtils = require('../../htmlUtils.js');
const utils = require('../../utils');
@@ -47,4 +47,4 @@ function plugin(markdownIt:any, ruleOptions:RuleOptions) {
markdownIt.renderer.rules.html_inline = handleImageTags(htmlInlineDefaultRender);
}
export default { plugin }
export default { plugin };

View File

@@ -1,4 +1,4 @@
import { RuleOptions } from "lib/joplin-renderer/MdToHtml";
import { RuleOptions } from 'lib/joplin-renderer/MdToHtml';
let katex = require('katex');
const md5 = require('md5');
@@ -43,14 +43,13 @@ function katexStyle() {
// Test if potential opening or closing delimieter
// Assumes that there is a "$" at state.src[pos]
function isValidDelim(state:any, pos:number) {
let prevChar,
nextChar,
max = state.posMax,
can_open = true,
const max = state.posMax;
let can_open = true,
can_close = true;
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
const prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
const nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
// Check non-whitespace conditions for opening and closing, and
// check that closing delimeter isn't followed by a number
@@ -68,7 +67,7 @@ function isValidDelim(state:any, pos:number) {
}
function math_inline(state:any, silent:boolean) {
let start, match, token, res, pos;
let match, token, res, pos;
if (state.src[state.pos] !== '$') {
return false;
@@ -87,7 +86,7 @@ function math_inline(state:any, silent:boolean) {
// This loop will assume that the first leading backtick can not
// be the first character in state.src, which is known since
// we have found an opening delimieter already.
start = state.pos + 1;
const start = state.pos + 1;
match = start;
while ((match = state.src.indexOf('$', match)) !== -1) {
// Found potential $, look for escapes, pos will point to
@@ -148,7 +147,6 @@ function math_block(state:any, start:number, end:number, silent:boolean) {
next,
lastPos,
found = false,
token,
pos = state.bMarks[start] + state.tShift[start],
max = state.eMarks[start];
@@ -200,7 +198,7 @@ function math_block(state:any, start:number, end:number, silent:boolean) {
state.line = next + 1;
token = state.push('math_block', 'math', 0);
const token = state.push('math_block', 'math', 0);
token.block = true;
token.content = (firstLine && firstLine.trim() ? `${firstLine}\n` : '') + state.getLines(start + 1, next, state.tShift[start], true) + (lastLine && lastLine.trim() ? lastLine : '');
token.map = [start, state.line];
@@ -233,7 +231,7 @@ export default {
// https://github.com/laurent22/joplin/issues/1105
if (!options.context.userData.__katex) options.context.userData.__katex = { macros: {} };
const katexOptions:any = {}
const katexOptions:any = {};
katexOptions.macros = options.context.userData.__katex.macros;
katexOptions.trust = true;

View File

@@ -363,4 +363,4 @@ export default function(theme:any) {
`;
return [css];
};
}

View File

@@ -616,9 +616,9 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"highlight.js": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
},
"html-entities": {
"version": "1.2.1",

View File

@@ -18,7 +18,7 @@
"base-64": "^0.1.0",
"font-awesome-filetypes": "^2.1.0",
"fs-extra": "^8.1.0",
"highlight.js": "10.1.1",
"highlight.js": "^10.2.1",
"html-entities": "^1.2.1",
"json-stringify-safe": "^5.0.1",
"katex": "^0.12.0",

View File

@@ -13,7 +13,7 @@ export function basename(path:string) {
export function filename(path:string, includeDir:boolean = false):string {
if (!path) throw new Error('Path is empty');
let output = includeDir ? path : basename(path);
const output = includeDir ? path : basename(path);
if (output.indexOf('.') < 0) return output;
const splitted = output.split('.');

View File

@@ -256,9 +256,11 @@ class BaseItem extends BaseModel {
propValue = JSON.stringify(propValue);
} else if (propValue === null || propValue === undefined) {
propValue = '';
} else {
propValue = `${propValue}`;
}
return propValue;
return propValue.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
}
static unserialize_format(type, propName, propValue) {
@@ -279,7 +281,7 @@ class BaseItem extends BaseModel {
propValue = Database.formatValue(ItemClass.fieldType(propName), propValue);
}
return propValue;
return typeof propValue === 'string' ? propValue.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : propValue;
}
static async serialize(item, shownKeys = null) {

View File

@@ -66,10 +66,23 @@ class Tag extends BaseItem {
note_id: noteId,
});
this.dispatch({
type: 'TAG_UPDATE_ONE',
item: await Tag.loadWithCount(tagId),
});
// While syncing or importing notes, the app might associate a tag ID with a note ID
// but the actual items might not have been downloaded yet, so
// check that we actually get some result before dispatching
// the action.
//
// Fixes: https://github.com/laurent22/joplin/issues/3958#issuecomment-714320526
//
// Also probably fixes the errors on GitHub about reducer
// items being undefined.
const tagWithCount = await Tag.loadWithCount(tagId);
if (tagWithCount) {
this.dispatch({
type: 'TAG_UPDATE_ONE',
item: tagWithCount,
});
}
return output;
}

View File

@@ -3,6 +3,7 @@ const { stringify } = require('query-string');
const { time } = require('lib/time-utils.js');
const Logger = require('lib/Logger').default;
const { _ } = require('lib/locale');
const urlUtils = require('lib/urlUtils.js');
class OneDriveApi {
// `isPublic` is to tell OneDrive whether the application is a "public" one (Mobile and desktop
@@ -90,16 +91,16 @@ class OneDriveApi {
}
async execTokenRequest(code, redirectUri) {
const body = new shim.FormData();
body.append('client_id', this.clientId());
if (!this.isPublic()) body.append('client_secret', this.clientSecret());
body.append('code', code);
body.append('redirect_uri', redirectUri);
body.append('grant_type', 'authorization_code');
const body = {};
body['client_id'] = this.clientId();
if (!this.isPublic()) body['client_secret'] = this.clientSecret();
body['code'] = code;
body['redirect_uri'] = redirectUri;
body['grant_type'] = 'authorization_code';
const r = await shim.fetch(this.tokenBaseUrl(), {
method: 'POST',
body: body,
body: urlUtils.objectToQueryString(body),
headers: {
['Content-Type']: 'application/x-www-form-urlencoded',
},
@@ -366,19 +367,21 @@ class OneDriveApi {
throw new Error(_('Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.'));
}
const body = new shim.FormData();
body.append('client_id', this.clientId());
if (!this.isPublic()) body.append('client_secret', this.clientSecret());
body.append('refresh_token', this.auth_.refresh_token);
body.append('redirect_uri', 'http://localhost:1917');
body.append('grant_type', 'refresh_token');
const body = {};
body['client_id'] = this.clientId();
if (!this.isPublic()) body['client_secret'] = this.clientSecret();
body['refresh_token'] = this.auth_.refresh_token;
body['redirect_uri'] = 'http://localhost:1917';
body['grant_type'] = 'refresh_token';
const options = {
const response = await shim.fetch(this.tokenBaseUrl(), {
method: 'POST',
body: body,
};
body: urlUtils.objectToQueryString(body),
headers: {
['Content-Type']: 'application/x-www-form-urlencoded',
},
});
const response = await shim.fetch(this.tokenBaseUrl(), options);
if (!response.ok) {
this.setAuth(null);
const msg = await response.text();

View File

@@ -17,7 +17,7 @@ export function basename(path:string) {
export function filename(path:string, includeDir:boolean = false) {
if (!path) throw new Error('Path is empty');
let output = includeDir ? path : basename(path);
const output = includeDir ? path : basename(path);
if (output.indexOf('.') < 0) return output;
const splitted = output.split('.');

View File

@@ -47,7 +47,7 @@ const attributesToStr = (attributes) =>
const attachmentElement = ({ src, attributes, id }) =>
[
`<a href='joplin://${id}' ${attributesToStr(attributes)}>`,
`<a href=':/${id}' ${attributesToStr(attributes)}>`,
` ${attributes.alt || src}`,
'</a>',
].join('');

View File

@@ -11,6 +11,7 @@ type EnabledCondition = string;
export interface CommandContext {
// The state may also be of type "AppState" (used by the desktop app), which inherits from "State" (used by all apps)
state: State,
dispatch: Function,
}
export interface CommandRuntime {
@@ -203,10 +204,20 @@ export default class CommandService extends BaseService {
delete command.runtime;
}
private createContext():CommandContext {
return {
state: this.store_.getState(),
dispatch: (action:any) => {
this.store_.dispatch(action);
},
};
}
public async execute(commandName:string, ...args:any[]):Promise<any | void> {
const command = this.commandByName(commandName);
this.logger().info('CommandService::execute:', commandName, args);
return command.runtime.execute({ state: this.store_.getState() }, ...args);
if (!command.runtime) throw new Error(`Cannot execute a command without a runtime: ${commandName}`);
return command.runtime.execute(this.createContext(), ...args);
}
public scheduleExecute(commandName:string, args:any) {

View File

@@ -51,7 +51,7 @@ const defaultKeymapItems = {
{ accelerator: 'Ctrl+N', command: 'newNote' },
{ accelerator: 'Ctrl+T', command: 'newTodo' },
{ accelerator: 'Ctrl+S', command: 'synchronize' },
{ accelerator: '', command: 'print' },
{ accelerator: null, command: 'print' },
{ accelerator: 'Ctrl+Q', command: 'quit' },
{ accelerator: 'Ctrl+Alt+I', command: 'insertTemplate' },
{ accelerator: 'Ctrl+C', command: 'textCopy' },

View File

@@ -4,7 +4,7 @@ import Global from './api/Global';
export default abstract class BasePluginRunner extends BaseService {
async run(plugin:Plugin, sandbox:Global) {
async run(plugin:Plugin, sandbox:Global):Promise<void> {
throw new Error(`Not implemented: ${plugin} / ${sandbox}`);
}

View File

@@ -4,6 +4,7 @@ import shim from 'lib/shim';
import { ViewHandle } from './utils/createViewHandle';
import { ContentScriptType } from './api/types';
import Logger from 'lib/Logger';
const EventEmitter = require('events');
interface ViewControllers {
[key:string]: ViewController
@@ -29,6 +30,7 @@ export default class Plugin {
private viewControllers_:ViewControllers = {};
private contentScripts_:ContentScripts = {};
private dispatch_:Function;
private eventEmitter_:any;
constructor(id:string, baseDir:string, manifest:PluginManifest, scriptText:string, logger:Logger, dispatch:Function) {
this.id_ = id;
@@ -37,6 +39,7 @@ export default class Plugin {
this.scriptText_ = scriptText;
this.logger_ = logger;
this.dispatch_ = dispatch;
this.eventEmitter_ = new EventEmitter();
}
public get id():string {
@@ -59,11 +62,25 @@ export default class Plugin {
return this.baseDir_;
}
public registerContentScript(type:ContentScriptType, id:string, path:string) {
on(eventName:string, callback:Function) {
return this.eventEmitter_.on(eventName, callback);
}
off(eventName:string, callback:Function) {
return this.eventEmitter_.removeListener(eventName, callback);
}
emit(eventName:string, event:any = null) {
return this.eventEmitter_.emit(eventName, event);
}
public async registerContentScript(type:ContentScriptType, id:string, path:string) {
if (!this.contentScripts_[type]) this.contentScripts_[type] = [];
const absolutePath = shim.fsDriver().resolveRelativePathWithinDir(this.baseDir, path);
if (!(await shim.fsDriver().exists(absolutePath))) throw new Error(`Could not find content script at path ${absolutePath}`);
this.contentScripts_[type].push({ id, path: absolutePath });
this.logger_.debug(`Plugin: ${this.id}: Registered content script: ${type}: ${id}: ${absolutePath}`);

View File

@@ -45,6 +45,7 @@ export default class JoplinPlugins {
this.logger.error(`In plugin ${this.plugin.id}:`, newError);
}).then(() => {
this.logger.info(`Finished running onStart handler: ${this.plugin.id} (Took ${Date.now() - startTime}ms)`);
this.plugin.emit('started');
});
}
}

View File

@@ -320,36 +320,30 @@ export type Path = string[];
export enum ContentScriptType {
/**
* Registers a new Markdown-It plugin, which should follow this template:
* Registers a new Markdown-It plugin, which should follow the template below.
*
* ```javascript
* // The module should export an object as below:
*
* module.exports = {
*
* // The "context" variable is currently unused but could be used later on to provide
* // access to your own plugin so that the content script and plugin can communicate.
* default: function(context) {
* return {
*
* // This is the actual Markdown-It plugin - check the [official doc](https://github.com/markdown-it/markdown-it) for more information
* // The `options` parameter is of type [RuleOptions](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml.ts), which
* // contains a number of options, mostly useful for Joplin's internal code.
* plugin: function(markdownIt, options) {
* // ...
* },
*
* // You may also specify additional assets such as JS or CSS that should be loaded in the rendered HTML document.
* // Check for example the Joplin [Mermaid plugin](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.ts) to
* // see how the data should be structured.
* assets: {},
* assets: {
* // ...
* },
* }
* }
* }
* ```
*
* To include a regular Markdown-It plugin, that doesn't make use of any Joplin-specific feature, you
* would simply create a file such as this:
* - The `context` argument is currently unused but could be used later on to provide access to your own plugin so that the content script and plugin can communicate.
*
* - The **required** `plugin` key is the actual Markdown-It plugin - check the [official doc](https://github.com/markdown-it/markdown-it) for more information. The `options` parameter is of type [RuleOptions](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml.ts), which contains a number of options, mostly useful for Joplin's internal code.
*
* - Using the **optional** `assets` key you may specify assets such as JS or CSS that should be loaded in the rendered HTML document. Check for example the Joplin [Mermaid plugin](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.ts) to see how the data should be structured.
*
* To include a regular Markdown-It plugin, that doesn't make use of any Joplin-specific features, you would simply create a file such as this:
*
* ```javascript
* module.exports = {

View File

@@ -1,22 +1,26 @@
import { PluginStates } from '../reducer';
import { ExtraRendererRule } from 'lib/joplin-renderer/MdToHtml';
import { ContentScriptType } from '../api/types';
export default function contentScriptsToRendererRules(plugins:PluginStates):ExtraRendererRule[] {
const output:ExtraRendererRule[] = [];
for (const pluginId in plugins) {
const plugin = plugins[pluginId];
for (const scriptType in plugin.contentScripts) {
const contentScripts = plugin.contentScripts[scriptType];
for (const contentScript of contentScripts) {
const contentScripts = plugin.contentScripts[ContentScriptType.MarkdownItPlugin];
if (!contentScripts) continue;
const loadedModule = require(contentScript.path).default;
for (const contentScript of contentScripts) {
const module = require(contentScript.path);
if (!module.default || typeof module.default !== 'function') throw new Error(`Content script must export a function under the "default" key: Plugin: ${pluginId}: Script: ${contentScript.id}`);
output.push({
id: contentScript.id,
module: loadedModule({}),
});
}
const loadedModule = module.default({});
if (!loadedModule.plugin) throw new Error(`Content script must export a "plugin" key: Plugin: ${pluginId}: Script: ${contentScript.id}`);
output.push({
id: contentScript.id,
module: loadedModule,
});
}
}

View File

@@ -577,6 +577,22 @@ class SearchEngine {
keys.push(col);
}
//
// The object "allTerms" is used for query construction purposes (this contains all the filter terms)
// Since this is used for the FTS match query, we need to normalize text, title and body terms.
// Note, we're not normalizing terms like tag because these are matched using SQL LIKE operator and so we must preserve their diacritics.
//
// The object "terms" only include text, title, body terms and is used for highlighting.
// By not normalizing the text, title, body in "terms", highlighting still works correctly for words with diacritics.
//
allTerms = allTerms.map(x => {
if (x.name === 'text' || x.name === 'title' || x.name === 'body') {
return Object.assign(x, { value: this.normalizeText_(x.value) });
}
return x;
});
return {
termCount: termCount,
keys: keys,
@@ -635,7 +651,15 @@ class SearchEngine {
// If preferredSearchType is "fts" we auto-detect anyway
// because it's not always supported.
const st = scriptType(query);
let allTerms = [];
try {
allTerms = filterParser(query);
} catch (error) {
console.warn(error);
}
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) {
return SearchEngine.SEARCH_TYPE_BASIC;
@@ -653,12 +677,11 @@ class SearchEngine {
fuzzy: Setting.value('db.fuzzySearchEnabled') === 1,
}, options);
searchString = this.normalizeText_(searchString);
const searchType = this.determineSearchType_(searchString, options);
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);
const parsedQuery = await this.parseQuery(searchString);
this.processResults_(rows, parsedQuery, true);

View File

@@ -13,6 +13,43 @@ const http = require('http');
const https = require('https');
const toRelative = require('relative');
const timers = require('timers');
const zlib = require('zlib');
function fileExists(filePath) {
try {
return fs.statSync(filePath).isFile();
} catch (err) {
return false;
}
}
const gunzipFile = function(source, destination) {
if (!fileExists(source)) {
throw new Error(`No such file: ${source}`);
}
return new Promise((resolve, reject) => {
// prepare streams
const src = fs.createReadStream(source);
const dest = fs.createWriteStream(destination);
// extract the archive
src.pipe(zlib.createGunzip()).pipe(dest);
// callback on extract completion
dest.on('close', function() {
resolve();
});
src.on('error', () => {
reject();
});
dest.on('error', () => {
reject();
});
});
};
function shimInit() {
shim.fsDriver = () => {
@@ -365,9 +402,25 @@ function shimInit() {
const request = http.request(requestOptions, function(response) {
response.pipe(file);
const isGzipped = response.headers['content-encoding'] === 'gzip';
file.on('finish', function() {
file.close(() => {
resolve(makeResponse(response));
file.close(async () => {
if (isGzipped) {
const gzipFilePath = `${filePath}.gzip`;
await shim.fsDriver().move(filePath, gzipFilePath);
try {
await gunzipFile(gzipFilePath, filePath);
resolve(makeResponse(response));
} catch (error) {
cleanUpOnError(error);
}
shim.fsDriver().remove(gzipFilePath);
} else {
resolve(makeResponse(response));
}
});
});
});

View File

@@ -36,21 +36,31 @@ function shimInit() {
};
shim.fetch = async function(url, options = null) {
// The native fetch() throws an uncatable error that crashes the app if calling it with an
// invalid URL such as '//.resource' or "http://ocloud. de" so detect if the URL is valid beforehand
// and throw a catchable error.
// Bug: https://github.com/facebook/react-native/issues/7436
// The native fetch() throws an uncatchable error that crashes the
// app if calling it with an invalid URL such as '//.resource' or
// "http://ocloud. de" so detect if the URL is valid beforehand and
// throw a catchable error. Bug:
// https://github.com/facebook/react-native/issues/7436
const validatedUrl = urlValidator.isUri(url);
if (!validatedUrl) throw new Error(`Not a valid URL: ${url}`);
return shim.fetchWithRetry(() => {
// If the request has a body and it's not a GET call, and it doesn't have a Content-Type header
// we display a warning, because it could trigger a "Network request failed" error.
// If the request has a body and it's not a GET call, and it
// doesn't have a Content-Type header we display a warning,
// because it could trigger a "Network request failed" error.
// https://github.com/facebook/react-native/issues/30176
if (options?.body && options?.method && options.method !== 'GET' && !options?.headers?.['Content-Type']) {
console.warn('Done a non-GET fetch call without a Content-Type header. It may make the request fail.', url, options);
}
// Among React Native `fetch()` many bugs, one of them is that
// it will truncate strings when they contain binary data.
// Browser fetch() or Node fetch() work fine but as always RN's
// one doesn't. There's no obvious way to fix this so we'll
// have to wait if it's eventually fixed upstream. See here for
// more info:
// https://github.com/laurent22/joplin/issues/3986#issuecomment-718019688
return fetch(validatedUrl, options);
}, options);
};

View File

@@ -91,4 +91,18 @@ urlUtils.extractResourceUrls = function(text) {
return output;
};
urlUtils.objectToQueryString = function(query) {
if (!query) return '';
let queryString = '';
const s = [];
for (const k in query) {
if (!query.hasOwnProperty(k)) continue;
s.push(`${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`);
}
queryString = s.join('&');
return queryString;
};
module.exports = urlUtils;

View File

@@ -4425,9 +4425,9 @@
}
},
"highlight.js": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
},
"hoist-non-react-statics": {
"version": "2.5.5",

View File

@@ -25,7 +25,7 @@
"events": "^1.1.1",
"font-awesome-filetypes": "^2.1.0",
"form-data": "^2.1.4",
"highlight.js": "10.1.1",
"highlight.js": "^10.2.1",
"html-entities": "^1.2.1",
"htmlparser2": "^4.1.0",
"immer": "^7.0.9",

View File

@@ -95,33 +95,29 @@
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Registers a new Markdown-It plugin, which should follow this template:</p>
<p>Registers a new Markdown-It plugin, which should follow the template below.</p>
</div>
<pre><code class="language-javascript"><span class="hljs-comment">// The module should export an object as below:</span>
<span class="hljs-built_in">module</span>.exports = {
<span class="hljs-comment">// The &quot;context&quot; variable is currently unused but could be used later on to provide</span>
<span class="hljs-comment">// access to your own plugin so that the content script and plugin can communicate.</span>
<pre><code class="language-javascript"><span class="hljs-built_in">module</span>.exports = {
<span class="hljs-attr">default</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">context</span>) </span>{
<span class="hljs-keyword">return</span> {
<span class="hljs-comment">// This is the actual Markdown-It plugin - check the [official doc](https://github.com/markdown-it/markdown-it) for more information</span>
<span class="hljs-comment">// The `options` parameter is of type [RuleOptions](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml.ts), which</span>
<span class="hljs-comment">// contains a number of options, mostly useful for Joplin&#x27;s internal code.</span>
<span class="hljs-attr">plugin</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">markdownIt, options</span>) </span>{
<span class="hljs-comment">// ...</span>
},
<span class="hljs-comment">// You may also specify additional assets such as JS or CSS that should be loaded in the rendered HTML document.</span>
<span class="hljs-comment">// Check for example the Joplin [Mermaid plugin](https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.ts) to</span>
<span class="hljs-comment">// see how the data should be structured.</span>
<span class="hljs-attr">assets</span>: {},
<span class="hljs-attr">assets</span>: {
<span class="hljs-comment">// ...</span>
},
}
}
}</code></pre>
<p>To include a regular Markdown-It plugin, that doesn&#39;t make use of any Joplin-specific feature, you
would simply create a file such as this:</p>
<ul>
<li><p>The <code>context</code> argument is currently unused but could be used later on to provide access to your own plugin so that the content script and plugin can communicate.</p>
</li>
<li><p>The <strong>required</strong> <code>plugin</code> key is the actual Markdown-It plugin - check the <a href="https://github.com/markdown-it/markdown-it">official doc</a> for more information. The <code>options</code> parameter is of type <a href="https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml.ts">RuleOptions</a>, which contains a number of options, mostly useful for Joplin&#39;s internal code.</p>
</li>
<li><p>Using the <strong>optional</strong> <code>assets</code> key you may specify assets such as JS or CSS that should be loaded in the rendered HTML document. Check for example the Joplin <a href="https://github.com/laurent22/joplin/blob/dev/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.ts">Mermaid plugin</a> to see how the data should be structured.</p>
</li>
</ul>
<p>To include a regular Markdown-It plugin, that doesn&#39;t make use of any Joplin-specific features, you would simply create a file such as this:</p>
<pre><code class="language-javascript"><span class="hljs-built_in">module</span>.exports = {
<span class="hljs-attr">default</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">context</span>) </span>{
<span class="hljs-keyword">return</span> {

View File

@@ -395,7 +395,7 @@ https://github.com/laurent22/joplin/blob/dev/readme/debugging.md
<p>It is possible to get the apps to display or log more information that might help debug various issues.</p>
<h2>Desktop application<a name="desktop-application" href="#desktop-application" class="heading-anchor">🔗</a></h2>
<ul>
<li>Add a file named &quot;flags.txt&quot; in the config directory (should be <code>~/.config/joplin-desktop</code> or <code>c:\Users\YOUR_NAME\.config\joplin-desktop</code>) with the following content: <code>--open-dev-tools --debug --log-level debug</code></li>
<li>Click on menu <strong>Help &gt; Open Profile Directory</strong> and add a file named &quot;flags.txt&quot; in your directory with the following content: <code>--open-dev-tools --debug --log-level debug</code></li>
<li>Restart the application</li>
<li>The development tools should now be opened. Click the &quot;Console&quot; tab</li>
<li>Now repeat the action that was causing problem. The console might output warnings or errors - please add them to the GitHub issue. Also open log.txt in the config folder and if there is any error or warning, please also add them to the issue.</li>
@@ -403,12 +403,11 @@ https://github.com/laurent22/joplin/blob/dev/readme/debugging.md
<h2>CLI application<a name="cli-application" href="#cli-application" class="heading-anchor">🔗</a></h2>
<ul>
<li>Start the app with <code>joplin --debug --log-level debug</code></li>
<li>Check the log.txt as specified above for the desktop application and attach the log to the GitHub issue (or just the warnings/errors if any)</li>
<li>Check log.txt as specified above for the desktop application and attach the log to the GitHub issue (or just the warnings/errors if any). The profile directory would be in <code>~/.config/joplin</code>.</li>
</ul>
<h2>Mobile application<a name="mobile-application" href="#mobile-application" class="heading-anchor">🔗</a></h2>
<ul>
<li>In the options, enable Advanced Option</li>
<li>Open the log in the top right hand corner menu and post a screenshot of any error/warning.</li>
<li>In the Configuration screen, press on the <strong>Log button</strong> and post a screenshot of any error/warning.</li>
</ul>
<h1>Creating a low-level bug report on Android<a name="creating-a-low-level-bug-report-on-android" href="#creating-a-low-level-bug-report-on-android" class="heading-anchor">🔗</a></h1>
<p><a href="https://developer.android.com/studio/debug/bug-report">https://developer.android.com/studio/debug/bug-report</a></p>

View File

@@ -791,9 +791,9 @@ Details:
</tr>
</tbody>
</table>
<p>Note: In CliClient you have to escape the query using <code>--</code> when using negated filters.<br>
<p>Note: In the CLI client you have to escape the query using <code>--</code> when using negated filters.<br>
Eg. <code>:search -- &quot;-tag:tag1&quot;</code>.</p>
<p>The filters are implicitely connected by and/or connective depending on the following rules;</p>
<p>The filters are implicitly connected by and/or connectives depending on the following rules:</p>
<ul>
<li>By default, all filters are connected by &quot;AND&quot;.</li>
<li>To override this default behaviour, use the <code>any</code> filter, in which case the search terms will be connected by &quot;OR&quot; instead.</li>
@@ -878,71 +878,71 @@ Eg. <code>:search -- &quot;-tag:tag1&quot;</code>.</p>
<td><img src="https://joplinapp.org/images/flags/country-4x3/arableague.png" alt=""></td>
<td>Arabic</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/ar.po">ar</a></td>
<td>أحمد باشا إبراهيم (<a href="mailto:fi_ahmed_bacha@esi.dz">fi_ahmed_bacha@esi.dz</a>)</td>
<td>80%</td>
<td><a href="mailto:fi_ahmed_bacha@esi.dz">أحمد باشا إبراهيم</a></td>
<td>79%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/es/basque_country.png" alt=""></td>
<td>Basque</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/eu.po">eu</a></td>
<td>juan.abasolo@ehu.eus</td>
<td>34%</td>
<td>33%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/ba.png" alt=""></td>
<td>Bosnian</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/bs_BA.po">bs_BA</a></td>
<td>Derviš T. (<a href="mailto:dervis.t@pm.me">dervis.t@pm.me</a>)</td>
<td>83%</td>
<td><a href="mailto:dervis.t@pm.me">Derviš T.</a></td>
<td>82%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/bg.png" alt=""></td>
<td>Bulgarian</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/bg_BG.po">bg_BG</a></td>
<td></td>
<td>66%</td>
<td>65%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/es/catalonia.png" alt=""></td>
<td>Catalan</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/ca.po">ca</a></td>
<td>jmontane, 2019</td>
<td>96%</td>
<td>95%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/hr.png" alt=""></td>
<td>Croatian</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/hr_HR.po">hr_HR</a></td>
<td>Hrvoje Mandić (<a href="mailto:trbuhom@net.hr">trbuhom@net.hr</a>)</td>
<td><a href="mailto:trbuhom@net.hr">Hrvoje Mandić</a></td>
<td>27%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/cz.png" alt=""></td>
<td>Czech</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/cs_CZ.po">cs_CZ</a></td>
<td>Lukas Helebrandt (<a href="mailto:lukas@aiya.cz">lukas@aiya.cz</a>)</td>
<td>82%</td>
<td><a href="mailto:lukas@aiya.cz">Lukas Helebrandt</a></td>
<td>99%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/dk.png" alt=""></td>
<td>Dansk</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/da_DK.po">da_DK</a></td>
<td>Morten Juhl-Johansen Zölde-Fejér (mjjzf@syntaktisk.</td>
<td>74%</td>
<td><a href="mailto:mjjzf@syntaktisk.">Morten Juhl-Johansen Zölde-Fejér</a></td>
<td>73%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/de.png" alt=""></td>
<td>Deutsch</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/de_DE.po">de_DE</a></td>
<td>Ettore Atalan (<a href="mailto:atalanttore@users.noreply.github.com">atalanttore@users.noreply.github.com</a>)</td>
<td>98%</td>
<td><a href="mailto:atalanttore@users.noreply.github.com">Ettore Atalan</a></td>
<td>97%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/ee.png" alt=""></td>
<td>Eesti Keel</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/et_EE.po">et_EE</a></td>
<td></td>
<td>66%</td>
<td>65%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/gb.png" alt=""></td>
@@ -962,98 +962,98 @@ Eg. <code>:search -- &quot;-tag:tag1&quot;</code>.</p>
<td><img src="https://joplinapp.org/images/flags/country-4x3/es.png" alt=""></td>
<td>Español</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/es_ES.po">es_ES</a></td>
<td>Fernando Pindado (<a href="mailto:fpindado@gmail.com">fpindado@gmail.com</a>)</td>
<td>95%</td>
<td><a href="mailto:andros@fenollosa.email">Andros Fenollosa</a></td>
<td>99%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/esperanto.png" alt=""></td>
<td>Esperanto</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/eo.po">eo</a></td>
<td>Marton Paulo</td>
<td>38%</td>
<td>37%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/fr.png" alt=""></td>
<td>Français</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/fr_FR.po">fr_FR</a></td>
<td>Laurent Cozic</td>
<td>99%</td>
<td>98%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/es/galicia.png" alt=""></td>
<td>Galician</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/gl_ES.po">gl_ES</a></td>
<td>Marcos Lans (<a href="mailto:marcoslansgarza@gmail.com">marcoslansgarza@gmail.com</a>)</td>
<td>43%</td>
<td><a href="mailto:marcoslansgarza@gmail.com">Marcos Lans</a></td>
<td>42%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/id.png" alt=""></td>
<td>Indonesian</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/id_ID.po">id_ID</a></td>
<td>Fathy AR (<a href="mailto:16875937+fathyar@users.noreply.github.com">16875937+fathyar@users.noreply.github.com</a>)</td>
<td>93%</td>
<td><a href="mailto:16875937+fathyar@users.noreply.github.com">Fathy AR</a></td>
<td>92%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/it.png" alt=""></td>
<td>Italiano</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/it_IT.po">it_IT</a></td>
<td>StarFang208</td>
<td>90%</td>
<td><a href="mailto:mailfilledwithspam@gmail.com">Alessandro Bernardello</a></td>
<td>98%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/be.png" alt=""></td>
<td>Nederlands</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/nl_BE.po">nl_BE</a></td>
<td></td>
<td>34%</td>
<td>33%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/nl.png" alt=""></td>
<td>Nederlands</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/nl_NL.po">nl_NL</a></td>
<td>MetBril (<a href="mailto:metbril@users.noreply.github.com">metbril@users.noreply.github.com</a>)</td>
<td>95%</td>
<td><a href="mailto:metbril@users.noreply.github.com">MetBril</a></td>
<td>94%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/no.png" alt=""></td>
<td>Norwegian</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/nb_NO.po">nb_NO</a></td>
<td>Mats Estensen (<a href="mailto:code@mxe.no">code@mxe.no</a>)</td>
<td>88%</td>
<td><a href="mailto:code@mxe.no">Mats Estensen</a></td>
<td>87%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/ir.png" alt=""></td>
<td>Persian</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/fa.po">fa</a></td>
<td>Kourosh Firoozbakht (<a href="mailto:kourox@protonmail.com">kourox@protonmail.com</a>)</td>
<td>83%</td>
<td><a href="mailto:kourox@protonmail.com">Kourosh Firoozbakht</a></td>
<td>82%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/pl.png" alt=""></td>
<td>Polski</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/pl_PL.po">pl_PL</a></td>
<td></td>
<td>98%</td>
<td>97%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/pt.png" alt=""></td>
<td>Português</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/pt_PT.po">pt_PT</a></td>
<td>Diogo Caveiro</td>
<td>88%</td>
<td><a href="mailto:jduar@protonmail.com">João Duarte</a></td>
<td>98%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/br.png" alt=""></td>
<td>Português (Brasil)</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/pt_BR.po">pt_BR</a></td>
<td>Renato Nunes Bastos (<a href="mailto:rnbastos@gmail.com">rnbastos@gmail.com</a>)</td>
<td>96%</td>
<td><a href="mailto:rnbastos@gmail.com">Renato Nunes Bastos</a></td>
<td>95%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/ro.png" alt=""></td>
<td>Română</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/ro.po">ro</a></td>
<td>Cristi Duluta (<a href="mailto:cristi.duluta@gmail.com">cristi.duluta@gmail.com</a>)</td>
<td><a href="mailto:cristi.duluta@gmail.com">Cristi Duluta</a></td>
<td>77%</td>
</tr>
<tr>
@@ -1067,7 +1067,7 @@ Eg. <code>:search -- &quot;-tag:tag1&quot;</code>.</p>
<td><img src="https://joplinapp.org/images/flags/country-4x3/se.png" alt=""></td>
<td>Svenska</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/sv.po">sv</a></td>
<td>Jonatan Nyberg (<a href="mailto:jonatan@autistici.org">jonatan@autistici.org</a>)</td>
<td><a href="mailto:jonatan@autistici.org">Jonatan Nyberg</a></td>
<td>70%</td>
</tr>
<tr>
@@ -1082,63 +1082,63 @@ Eg. <code>:search -- &quot;-tag:tag1&quot;</code>.</p>
<td>Tiếng Việt</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/vi.po">vi</a></td>
<td></td>
<td>85%</td>
<td>84%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/tr.png" alt=""></td>
<td>Türkçe</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/tr_TR.po">tr_TR</a></td>
<td>Arda Kılıçdağı (<a href="mailto:arda@kilicdagi.com">arda@kilicdagi.com</a>)</td>
<td>98%</td>
<td><a href="mailto:arda@kilicdagi.com">Arda Kılıçdağı</a></td>
<td>97%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/gr.png" alt=""></td>
<td>Ελληνικά</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/el_GR.po">el_GR</a></td>
<td>Harris Arvanitis (<a href="mailto:xaris@tuta.io">xaris@tuta.io</a>)</td>
<td>96%</td>
<td><a href="mailto:xaris@tuta.io">Harris Arvanitis</a></td>
<td>95%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/ru.png" alt=""></td>
<td>Русский</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/ru_RU.po">ru_RU</a></td>
<td>Sergey Segeda (<a href="mailto:thesermanarm@gmail.com">thesermanarm@gmail.com</a>)</td>
<td>95%</td>
<td><a href="mailto:thesermanarm@gmail.com">Sergey Segeda</a></td>
<td>94%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/rs.png" alt=""></td>
<td>српски језик</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/sr_RS.po">sr_RS</a></td>
<td></td>
<td>71%</td>
<td>70%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/cn.png" alt=""></td>
<td>中文 (简体)</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/zh_CN.po">zh_CN</a></td>
<td>WhiredPlanck (<a href="mailto:fungdaat31@outlook.com">fungdaat31@outlook.com</a>)</td>
<td>96%</td>
<td><a href="mailto:fungdaat31@outlook.com">WhiredPlanck</a></td>
<td>95%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/tw.png" alt=""></td>
<td>中文 (繁體)</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/zh_TW.po">zh_TW</a></td>
<td>Yaoze Ye (<a href="mailto:yaozeye@yahoo.co.jp">yaozeye@yahoo.co.jp</a>)</td>
<td>95%</td>
<td><a href="mailto:yaozeye@yahoo.co.jp">Yaoze Ye</a></td>
<td>94%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/jp.png" alt=""></td>
<td>日本語</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/ja_JP.po">ja_JP</a></td>
<td>genneko (<a href="mailto:genneko217@gmail.com">genneko217@gmail.com</a>)</td>
<td>98%</td>
<td><a href="mailto:genneko217@gmail.com">genneko</a></td>
<td>97%</td>
</tr>
<tr>
<td><img src="https://joplinapp.org/images/flags/country-4x3/kr.png" alt=""></td>
<td>한국어</td>
<td><a href="https://github.com/laurent22/joplin/blob/dev/CliClient/locales/ko.po">ko</a></td>
<td>Ji-Hyeon Gim (<a href="mailto:potatogim@potatogim.net">potatogim@potatogim.net</a>)</td>
<td>98%</td>
<td><a href="mailto:potatogim@potatogim.net">Ji-Hyeon Gim</a></td>
<td>99%</td>
</tr>
</tbody>
</table>

View File

@@ -1,5 +1,21 @@
# Joplin terminal app changelog
## [cli-v1.3.3](https://github.com/laurent22/joplin/releases/tag/cli-v1.3.3) - 2020-10-23T16:00:38Z
- Improved: Added support for a custom S3 URL (#3921) (#3691 by [@aaron](https://github.com/aaron))
- Improved: Allow setting note geolocation attributes via API (#3884)
- Improved: Import &lt;strike&gt;,&lt;s&gt; tags (strikethrough) from Evernote (#3936 by Ian Slinger)
- Improved: Removed OneDrive Dev sync target which was not really useful
- Improved: Sort search results by average of multiple criteria, including &#039;Sort notes by&#039; field setting (#3777 by [@shawnaxsom](https://github.com/shawnaxsom))
- Improved: Sort tags in a case-insensitive way
- Improved: Updated installation script with BSD support (#3930 by Andros Fenollosa)
- Fixed: Crash when trying to change app locale (#3847)
- Fixed: Fix search filters when language is in Korean or with accents (#3947 by Naveen M V)
- Fixed: Fixed freeze when importing ENEX as HTML, and fixed potential error when importing resources (#3958)
- Fixed: Fixed setting issue that would cause a password to be saved in plain text in the database, even when the keychain is working
- Fixed: Importing ENEX as HTML was importing as Markdown (#3923)
- Fixed: Regression: Fix export of pluginAssets when exporting to html/pdf (#3927 by Caleb John)
## [cli-v1.2.3](https://github.com/laurent22/joplin/releases/tag/cli-v1.2.3) - 2020-10-09T11:17:18Z
- Improved: Improved handling of database migration failures

View File

@@ -4,7 +4,7 @@ It is possible to get the apps to display or log more information that might hel
## Desktop application
- Add a file named "flags.txt" in the config directory (should be `~/.config/joplin-desktop` or `c:\Users\YOUR_NAME\.config\joplin-desktop`) with the following content: `--open-dev-tools --debug --log-level debug`
- Click on menu **Help &gt; Open Profile Directory** and add a file named "flags.txt" in your directory with the following content: `--open-dev-tools --debug --log-level debug`
- Restart the application
- The development tools should now be opened. Click the "Console" tab
- Now repeat the action that was causing problem. The console might output warnings or errors - please add them to the GitHub issue. Also open log.txt in the config folder and if there is any error or warning, please also add them to the issue.
@@ -12,12 +12,11 @@ It is possible to get the apps to display or log more information that might hel
## CLI application
- Start the app with `joplin --debug --log-level debug`
- Check the log.txt as specified above for the desktop application and attach the log to the GitHub issue (or just the warnings/errors if any)
- Check log.txt as specified above for the desktop application and attach the log to the GitHub issue (or just the warnings/errors if any). The profile directory would be in `~/.config/joplin`.
## Mobile application
- In the options, enable Advanced Option
- Open the log in the top right hand corner menu and post a screenshot of any error/warning.
- In the Configuration screen, press on the **Log button** and post a screenshot of any error/warning.
# Creating a low-level bug report on Android