1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-02-10 08:14:27 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Laurent Cozic
ac5f6c4cd6 Added openProfileDirectory directory command and menu item 2020-10-21 22:01:17 +01:00
109 changed files with 588 additions and 1516 deletions

View File

@@ -105,11 +105,7 @@ 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
@@ -126,7 +122,6 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/menuCommandNames.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js

5
.gitignore vendored
View File

@@ -99,11 +99,7 @@ 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
@@ -120,7 +116,6 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/menuCommandNames.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js

View File

@@ -48,11 +48,7 @@ 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
@@ -69,7 +65,6 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/menuCommandNames.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js

View File

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

View File

@@ -45,12 +45,10 @@ class Command extends BaseCommand {
const startDecryption = async () => {
this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.'));
while (true) {
try {
const result = await DecryptionWorker.instance().start();
if (result.error) throw result.error;
const line = [];
line.push(_('Decrypted items: %d', result.decryptedItemCount));
if (result.skippedItemCount) line.push(_('Skipped items: %d (use --retry-failed-items to retry decrypting them)', result.skippedItemCount));

View File

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

View File

@@ -17,23 +17,12 @@ 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

@@ -9,12 +9,12 @@
# Germán Martín <gmag11@gmail.com>, 2019.
# Fernando Pindado <fpindado@gmail.com>, 2020.
# Andros Fenollosa <andros@fenollosa.email>, 2020.
# Mario Campo <mario.campo@gmail.com>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Mario Campo <mario.campo@gmail.com>\n"
"Last-Translator: Andros Fenollosa <andros@fenollosa.email>\n"
"Language-Team: Spanish <lucas.vieites@gmail.com>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
@@ -279,13 +279,13 @@ msgstr "Las notas han sido importadas: %s"
#: ElectronClient/gui/ConfigScreen.min.js:84
#: ElectronClient/gui/ConfigScreen/ConfigScreen.js:99
msgid "This will open a new screen. Save your current changes?"
msgstr "Esto abrirá una nueva pantalla. ¿Quieres guardar los cambios actuales?"
msgstr "Esto abrirá una nueva pantalla. ¿Quieres salvar los cambios actuales?"
#: ElectronClient/gui/ConfigScreen.min.js:172
#: ElectronClient/gui/ConfigScreen/ConfigScreen.js:162
#: ReactNativeClient/lib/components/screens/config.js:307
msgid "Check synchronisation configuration"
msgstr "Comprobar sincronización de la configuración"
msgstr "Comprobar sincronización"
#: ElectronClient/gui/ConfigScreen.min.js:181
#: ElectronClient/gui/ConfigScreen/ConfigScreen.js:167
@@ -1050,7 +1050,7 @@ msgstr "Descifrando elementos: %d/%d"
#: ReactNativeClient/lib/components/side-menu-content.js:330
#, javascript-format
msgid "Fetching resources: %d/%d"
msgstr "Obteniendo recursos: %d/%d"
msgstr "Obteniendo refuersos: %d/%d"
#: ElectronClient/gui/SideBar/commands/focusElementSideBar.js:16
msgid "Sidebar"

View File

@@ -15,8 +15,6 @@ 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
@@ -124,6 +122,7 @@ 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 "
@@ -131,7 +130,7 @@ msgid ""
msgstr ""
"題名や本文の一部を入力してノートを検索し、そのノートに移動できます。また、「#"
"タグ名」で該当タグ付きノートの一覧に、「@ノートブック名」で該当ノートブックに"
"移動できます。あるいは「:」でコマンドを検索して実行できます。"
"移動できます。"
#: ElectronClient/plugins/GotoAnything.js:456
#: ElectronClient/plugins/GotoAnything.min.js:499
@@ -141,8 +140,9 @@ 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,8 +818,9 @@ 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:"
@@ -1413,9 +1414,9 @@ msgstr ""
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:63
#: ReactNativeClient/lib/services/KeymapService.js:144
#, javascript-format
#, fuzzy, javascript-format
msgid "Error: %s"
msgstr "エラー: %s"
msgstr "エラー"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:126
msgid "Command"
@@ -1485,8 +1486,9 @@ 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"
@@ -1498,13 +1500,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"
@@ -2227,7 +2229,7 @@ msgid "Cannot load \"%s\" module for format \"%s\" and output \"%s\""
msgstr "\"%s\" モジュール (\"%s / %s\" フォーマット用) を読み込めません"
#: ReactNativeClient/lib/services/interop/InteropService.js:152
#, javascript-format
#, fuzzy, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\" and target \"%s\""
msgstr "\"%s\" モジュール (\"%s / %s\" フォーマット用) を読み込めません"
@@ -2245,24 +2247,26 @@ 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
#, javascript-format
#, fuzzy, javascript-format
msgid "\"%s\" is missing the required \"%s\" property."
msgstr "\"%s\" は必須の \"%s\" プロパティを持っていません。"
msgstr ""
"キーマップアイテム %s は必須の \"command\" プロパティを持っていません。"
#: ReactNativeClient/lib/services/KeymapService.js:245
#: ReactNativeClient/lib/services/KeymapService.js:252
msgid "accelerator"
msgstr "ショートカットキー"
msgstr ""
#: ReactNativeClient/lib/services/KeymapService.js:252
#, javascript-format
#, fuzzy, javascript-format
msgid "Invalid %s: %s."
msgstr "無効な %s: %s"
msgstr "無効な入力:%s"
#: ReactNativeClient/lib/services/KeymapService.js:270
#, javascript-format
@@ -2729,13 +2733,11 @@ 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
@@ -2996,8 +2998,9 @@ 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."
@@ -3209,8 +3212,9 @@ msgid "AWS S3 bucket"
msgstr "AWS S3 バケット"
#: ReactNativeClient/lib/models/Setting.js:226
#, fuzzy
msgid "AWS S3 URL"
msgstr "AWS S3 URL"
msgstr "AWS S3"
#: ReactNativeClient/lib/models/Setting.js:237
msgid "AWS key"

View File

@@ -15,8 +15,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.1\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
@@ -123,14 +121,14 @@ 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 "
"commands."
msgstr ""
"노트 제목이나 내용의 일부를 입력하여 바로 이동하세요. 혹은 #으로 시작하는 태"
"그 이름이나 @로 시작하는 노트북 이름을 입력하거나, :을 입력하여 명령을 검색하"
"세요.\""
"노트 제목을 적거나 혹은 건너뛰세요. 혹은 #으로 시작하는 태그 이름이나, @로 시"
"작하는 노트북 이름을 적으세요."
#: ElectronClient/plugins/GotoAnything.js:456
#: ElectronClient/plugins/GotoAnything.min.js:499
@@ -140,8 +138,9 @@ msgstr "가고자 하는 곳으로 이동..."
#: 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 ""
@@ -2710,14 +2709,11 @@ 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
@@ -2975,8 +2971,9 @@ 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."
@@ -3185,8 +3182,9 @@ msgid "AWS S3 bucket"
msgstr "AWS S3 버킷"
#: ReactNativeClient/lib/models/Setting.js:226
#, fuzzy
msgid "AWS S3 URL"
msgstr "AWS S3 URL"
msgstr "AWS S3"
#: ReactNativeClient/lib/models/Setting.js:237
msgid "AWS key"

View File

@@ -141,7 +141,7 @@ msgstr "Ir para qualquer coisa..."
#: ElectronClient/plugins/GotoAnything.js:463
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:28
msgid "Command palette"
msgstr "Paleta de Comandos"
msgstr ""
#: ElectronClient/plugins/GotoAnything.min.js:459
msgid ""
@@ -549,8 +549,6 @@ msgid ""
"The attachments will no longer be watched when you switch to a different "
"note."
msgstr ""
"Os anexos não serão mais observados quando você mudar para uma nota"
"diferente."
#: ElectronClient/gui/NoteEditor/NoteEditor.js:387
#, javascript-format
@@ -667,9 +665,6 @@ msgid ""
"may take a few minutes to complete and the app needs to be restarted. To "
"proceed please click on the link."
msgstr ""
"O alvo da sincroniação precisa ser atualizado antes que o Joplin possa sincronizar. A operação"
"pode levar alguns minutos para completar e o app terá que ser reiniciado. Para"
"proceder, por favor, clique neste link."
#: ElectronClient/gui/MainScreen/MainScreen.js:416
#: ElectronClient/gui/MainScreen/MainScreen.min.js:306
@@ -1416,15 +1411,13 @@ msgstr "Anexos da nota"
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
msgid "Press the shortcut"
msgstr "Digite o atalho"
msgstr ""
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
msgid ""
"Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the "
"shortcut."
msgstr ""
"Digite o atalho e então aperte ENTER. Ou, pressione BARRA DE ESPAÇO para limpar o"
"atalho"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:63
#: ReactNativeClient/lib/services/KeymapService.js:144
@@ -1434,7 +1427,7 @@ msgstr "Erro"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:126
msgid "Command"
msgstr "Command"
msgstr ""
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:127
#, fuzzy
@@ -1494,7 +1487,6 @@ msgstr "Estatísticas"
msgid ""
"The app is now going to close. Please relaunch it to complete the process."
msgstr ""
"O app irá fechar agora. Por favor, reabra-o para completar o processo."
#: ElectronClient/app.js:340
#, javascript-format
@@ -1522,7 +1514,7 @@ msgstr "Encerrar edição externa"
#: ElectronClient/commands/toggleExternalEditing.js:37
msgid "Stop"
msgstr "Parar"
msgstr ""
#: ElectronClient/commands/stopExternalEditing.js:18
msgid "Stop external editing"
@@ -1749,7 +1741,7 @@ msgstr ""
#: CliClient/app/command-sync.js:35
msgid "Upgrade the sync target to the latest version."
msgstr "Atualizar o alvo de sincronização para a última versão."
msgstr ""
#: CliClient/app/command-sync.js:105
#, javascript-format

View File

@@ -1,6 +1,6 @@
{
"name": "joplin",
"version": "1.3.3",
"version": "1.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -23,9 +23,9 @@
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="
},
"abab": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz",
"integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg=="
},
"abbrev": {
"version": "1.1.1",
@@ -33,9 +33,9 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
"integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA=="
},
"acorn-globals": {
"version": "4.3.4",
@@ -47,9 +47,9 @@
},
"dependencies": {
"acorn": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
"integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ=="
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA=="
}
}
},
@@ -2964,9 +2964,9 @@
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
},
"highlight.js": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
},
"homedir-polyfill": {
"version": "1.0.3",
@@ -3227,11 +3227,6 @@
"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",
@@ -3655,9 +3650,9 @@
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
},
"joplin-turndown": {
"version": "4.0.30",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.30.tgz",
"integrity": "sha512-OrGdNTsjI6/cbx/es9Hl0YI3YTql4SopduFcYCnWTZgqT0SJqILnF2JQxSNnbPnkSDIIRdNOG4+iNzlY6bS1nw==",
"version": "4.0.29",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.29.tgz",
"integrity": "sha512-rVGu8u4TpSRETo59/jiVW9iaXnpdxxpBHjb7nyCflkDfWhg1Kska4uagBQGw7cD2yxw7mB2YUIB/fAgtlIzcDQ==",
"requires": {
"css": "^2.2.4",
"html-entities": "^1.2.1",
@@ -5573,6 +5568,13 @@
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
"requires": {
"lodash": "^4.17.19"
},
"dependencies": {
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
}
}
},
"request-promise-native": {

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",
"postinstall": "npm run build && patch-package --patch-dir ../patches/shared && patch-package --patch-dir ../patches/node",
"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.3",
"version": "1.3.0",
"bin": {
"joplin": "./main.js"
},
@@ -52,14 +52,13 @@
"font-awesome-filetypes": "^2.1.0",
"form-data": "^2.1.4",
"fs-extra": "^5.0.0",
"highlight.js": "^10.2.1",
"highlight.js": "10.1.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.30",
"joplin-turndown": "^4.0.29",
"joplin-turndown-plugin-gfm": "^1.0.12",
"json-stringify-safe": "^5.0.1",
"jssha": "^2.3.0",

View File

@@ -3,7 +3,6 @@ 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);
@@ -20,20 +19,6 @@ 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
@@ -53,7 +38,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 beautifyHtml(await enexXmlToHtml(enexInput, options.resources));
const actualOutput = await enexXmlToHtml(enexInput, options.resources);
expect(actualOutput).toEqual(expectedOutput);
}));

View File

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

View File

@@ -1,25 +1,14 @@
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(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'));
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');
expectThrow(() => fsDriver.resolveRelativePathWithinDir('/test/temp', '../myfile.txt'));
expectThrow(() => fsDriver.resolveRelativePathWithinDir('/test/temp', './mydir/../../test.txt'));

View File

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

View File

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

View File

@@ -1 +0,0 @@
<img src="https://images.macrumors.com/filters:quality(90)/article/test.jpg?high"/>

View File

@@ -1 +0,0 @@
![](https://images.macrumors.com/filters:quality%2890%29/article/test.jpg?high)

View File

@@ -1 +0,0 @@
<img src="https://example.com/test.jpg" title="hello"/>

View File

@@ -1 +0,0 @@
![](https://example.com/test.jpg "hello")

View File

@@ -1 +0,0 @@
<img src="https://example.com/test.jpg" title='hello &amp; "(ouch)"'/>

View File

@@ -1 +0,0 @@
![](https://example.com/test.jpg "hello & &quot;&#40;ouch&#41;&quot;")

View File

@@ -104,14 +104,4 @@ 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

@@ -3,7 +3,6 @@ require('app-module-path').addPath(__dirname);
const { tempFilePath } = require('test-utils.js');
const KeymapService = require('lib/services/KeymapService').default;
const keymapService = KeymapService.instance();
keymapService.initialize([]);
describe('services_KeymapService', () => {
describe('validateAccelerator', () => {
@@ -32,7 +31,7 @@ describe('services_KeymapService', () => {
};
Object.entries(testCases).forEach(([platform, accelerators]) => {
keymapService.initialize([], platform);
keymapService.initialize(platform);
accelerators.forEach(accelerator => {
expect(() => keymapService.validateAccelerator(accelerator)).not.toThrow();
});
@@ -70,7 +69,7 @@ describe('services_KeymapService', () => {
};
Object.entries(testCases).forEach(([platform, accelerators]) => {
keymapService.initialize([], platform);
keymapService.initialize(platform);
accelerators.forEach(accelerator => {
expect(() => keymapService.validateAccelerator(accelerator)).toThrow();
});
@@ -82,12 +81,12 @@ describe('services_KeymapService', () => {
beforeEach(() => keymapService.initialize());
it('should allow registering new commands', async () => {
keymapService.initialize([], 'linux');
keymapService.initialize('linux');
keymapService.registerCommandAccelerator('myCustomCommand', 'Ctrl+Shift+Alt+B');
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Ctrl+Shift+Alt+B');
// Check that macOS key conversion is working
keymapService.initialize([], 'darwin');
keymapService.initialize('darwin');
keymapService.registerCommandAccelerator('myCustomCommand', 'CmdOrCtrl+Shift+Alt+B');
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Cmd+Shift+Option+B');
keymapService.setAccelerator('myCustomCommand', 'Cmd+Shift+Option+X');
@@ -96,7 +95,7 @@ describe('services_KeymapService', () => {
const keymapFilePath = tempFilePath('json');
await keymapService.saveCustomKeymap(keymapFilePath);
keymapService.initialize([], 'darwin');
keymapService.initialize('darwin');
await keymapService.loadCustomKeymap(keymapFilePath);
expect(keymapService.getAccelerator('myCustomCommand')).toEqual('Cmd+Shift+Option+X');
@@ -107,17 +106,17 @@ describe('services_KeymapService', () => {
beforeEach(() => keymapService.initialize());
it('should return the platform-specific default Accelerator', () => {
keymapService.initialize([], 'darwin');
keymapService.initialize('darwin');
expect(keymapService.getAccelerator('newNote')).toEqual('Cmd+N');
expect(keymapService.getAccelerator('synchronize')).toEqual('Cmd+S');
expect(keymapService.getAccelerator('textSelectAll')).toEqual('Cmd+A');
expect(keymapService.getAccelerator('textBold')).toEqual('Cmd+B');
keymapService.initialize([], 'linux');
keymapService.initialize('linux');
expect(keymapService.getAccelerator('newNote')).toEqual('Ctrl+N');
expect(keymapService.getAccelerator('synchronize')).toEqual('Ctrl+S');
keymapService.initialize([], 'win32');
keymapService.initialize('win32');
expect(keymapService.getAccelerator('textSelectAll')).toEqual('Ctrl+A');
expect(keymapService.getAccelerator('textBold')).toEqual('Ctrl+B');
});
@@ -131,7 +130,7 @@ describe('services_KeymapService', () => {
beforeEach(() => keymapService.initialize());
it('should update the Accelerator', () => {
keymapService.initialize(['print'], 'darwin');
keymapService.initialize('darwin');
const testCases_Darwin = [
{ command: 'newNote', accelerator: 'Ctrl+Option+Shift+N' },
{ command: 'synchronize', accelerator: 'F11' },
@@ -148,7 +147,7 @@ describe('services_KeymapService', () => {
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
keymapService.initialize(['print'], 'linux');
keymapService.initialize('linux');
const testCases_Linux = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: 'F15' },
@@ -168,7 +167,7 @@ describe('services_KeymapService', () => {
});
describe('getDefaultAccelerator', () => {
beforeEach(() => keymapService.initialize(['print', 'linux']));
beforeEach(() => keymapService.initialize());
it('should return the default accelerator', () => {
const testCases = [
@@ -197,7 +196,7 @@ describe('services_KeymapService', () => {
beforeEach(() => keymapService.initialize());
it('should update the keymap', () => {
keymapService.initialize([], 'darwin');
keymapService.initialize('darwin');
const customKeymapItems_Darwin = [
{ command: 'newNote', accelerator: 'Option+Shift+Cmd+N' },
{ command: 'synchronize', accelerator: 'Ctrl+F11' },
@@ -218,7 +217,7 @@ describe('services_KeymapService', () => {
expect(keymapService.getAccelerator(command)).toEqual(accelerator);
});
keymapService.initialize([], 'win32');
keymapService.initialize('win32');
const customKeymapItems_Win32 = [
{ command: 'newNote', accelerator: 'Ctrl+Alt+Shift+N' },
{ command: 'synchronize', accelerator: 'Ctrl+F11' },

View File

@@ -2,10 +2,12 @@ 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, createTempDir } = require('test-utils.js');
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, expectThrow } = require('test-utils.js');
const Note = require('lib/models/Note');
const Folder = require('lib/models/Folder');
@@ -63,9 +65,6 @@ 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');
}));
@@ -155,14 +154,12 @@ describe('services_PluginService', function() {
}));
it('should register a Markdown-it plugin', asyncTest(async () => {
const tempDir = await createTempDir();
const contentScriptPath = `${tempDir}/markdownItTestPlugin.js`;
const contentScriptPath = `${Setting.value('tempDir')}/markdownItTestPlugin${uuid.createNano()}.js`;
await shim.fsDriver().copy(`${testPluginDir}/content_script/src/markdownItTestPlugin.js`, contentScriptPath);
const service = newPluginService();
const plugin = await service.loadPluginFromString('example', tempDir, `
const plugin = await service.loadPluginFromString('example', Setting.value('tempDir'), `
/* joplin-manifest:
{
"manifest_version": 1,
@@ -176,7 +173,7 @@ describe('services_PluginService', function() {
joplin.plugins.register({
onStart: async function() {
await joplin.plugins.registerContentScript('markdownItPlugin', 'justtesting', './markdownItTestPlugin.js');
await joplin.plugins.registerContentScript('markdownItPlugin', 'justtesting', '${contentScriptPath}');
},
});
`);
@@ -201,7 +198,7 @@ describe('services_PluginService', function() {
expect(result.html.includes('JUST TESTING: something')).toBe(true);
await shim.fsDriver().remove(tempDir);
await shim.fsDriver().remove(contentScriptPath);
}));
});

View File

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

View File

@@ -80,9 +80,9 @@ EncryptionService.fsDriver_ = fsDriver;
FileApiDriverLocal.fsDriver_ = fsDriver;
const logDir = `${__dirname}/../tests/logs`;
const baseTempDir = `${__dirname}/../tests/tmp`;
const tempDir = `${__dirname}/../tests/tmp`;
fs.mkdirpSync(logDir, 0o755);
fs.mkdirpSync(baseTempDir, 0o755);
fs.mkdirpSync(tempDir, 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', baseTempDir);
Setting.setConstant('tempDir', tempDir);
Setting.setConstant('env', 'dev');
BaseService.logger_ = logger;
@@ -634,12 +634,6 @@ 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();
@@ -718,4 +712,4 @@ class TestApp extends BaseApplication {
}
}
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 };
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 };

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Joplin Web Clipper [DEV]",
"version": "1.3.1",
"version": "1.3.0",
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
"homepage_url": "https://joplinapp.org",
"content_security_policy": "script-src 'self'; object-src 'self'",

View File

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

View File

@@ -1,9 +1,5 @@
'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.items ? folders.items : folders });
this.dispatch({ type: 'FOLDERS_SET', folders: folders });
const tags = await this.clipperApiExec('GET', 'tags');
this.dispatch({ type: 'TAGS_SET', tags: tags.items ? tags.items : tags });
this.dispatch({ type: 'TAGS_SET', tags: tags });
bridge().restoreState();
return;
@@ -245,7 +245,7 @@ class Bridge {
}
async folderTree() {
return this.clipperApiExec('GET', 'folders', { as_tree: 1 });
return this.clipperApiExec('GET', 'folders');
}
async storageSet(keys) {

View File

@@ -14,7 +14,6 @@ import Setting from 'lib/models/Setting';
import actionApi from 'lib/services/rest/actionApi.desktop';
import BaseApplication from 'lib/BaseApplication';
import { _, setLocale } from 'lib/locale';
import menuCommandNames from './gui/menuCommandNames';
require('app-module-path').addPath(__dirname);
@@ -47,7 +46,6 @@ 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'),
@@ -63,9 +61,6 @@ 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'),
@@ -496,6 +491,14 @@ class Application extends BaseApplication {
const filename = Setting.custom_css_files.JOPLIN_APP;
await CssUtils.injectCustomStyles(`${dir}/${filename}`);
const keymapService = KeymapService.instance();
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (err) {
reg.logger().error(err.message);
}
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
@@ -526,21 +529,9 @@ class Application extends BaseApplication {
CommandService.instance().registerDeclaration(declaration);
}
const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only
// those can have a shortcut associated with them.
keymapService.initialize(menuCommandNames());
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (error) {
reg.logger().error(error);
}
// Since the settings need to be loaded before the store is
// created, it will never receive the SETTING_UPDATE_ALL even,
// which mean state.settings will not be initialised. So we
// manually call dispatchUpdateAll() to force an update.
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
await FoldersScreenUtils.refreshFolders();

View File

@@ -149,10 +149,8 @@ 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?')}`,
detail: `${_('Your version: %s', packageInfo.version)}\n${_('New version: %s', newVersionString)}${releaseNotes}`,
buttons: [_('Download'), _('Cancel')].concat(truncateReleaseNotes ? [_('Full Release Notes')] : []),
cancelId: 1,
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')] : []),
});
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, position: 'absolute', top: 0 }}>
<div style={styles.warning}>
<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

@@ -5,7 +5,7 @@ import { _ } from 'lib/locale';
const commandService = CommandService.instance();
const getLabel = (commandName: string):string => {
const getLabel = (commandName: string) => {
if (commandService.exists(commandName)) return commandService.label(commandName, true);
// Some commands are not registered in CommandService at the moment

View File

@@ -1,22 +1,11 @@
import { useState, useEffect } from 'react';
import KeymapService, { KeymapItem } from 'lib/services/KeymapService';
import getLabel from './getLabel';
import KeymapService, { KeymapItem } from '../../../lib/services/KeymapService';
const keymapService = KeymapService.instance();
// This custom hook provides a synchronized snapshot of the keymap residing at KeymapService
// All the logic regarding altering and interacting with the keymap is isolated from the components
function allKeymapItems() {
const output = keymapService.getKeymapItems().slice();
output.sort((a:KeymapItem, b:KeymapItem) => {
return getLabel(a.command).toLocaleLowerCase() < getLabel(b.command).toLocaleLowerCase() ? -1 : +1;
});
return output;
}
const useKeymap = (): [
KeymapItem[],
Error,
@@ -24,7 +13,7 @@ const useKeymap = (): [
(commandName: string, accelerator: string) => void,
(commandName: string) => void
] => {
const [keymapItems, setKeymapItems] = useState<KeymapItem[]>(() => allKeymapItems());
const [keymapItems, setKeymapItems] = useState<KeymapItem[]>(() => keymapService.getKeymapItems());
const [keymapError, setKeymapError] = useState<Error>(null);
const [mustSave, setMustSave] = useState(false);
@@ -53,7 +42,7 @@ const useKeymap = (): [
const overrideKeymapItems = (customKeymapItems: KeymapItem[]) => {
const oldKeymapItems = [...customKeymapItems];
keymapService.resetKeymap(); // Start with a fresh keymap
keymapService.initialize(); // Start with a fresh keymap
try {
// First, try to update the in-memory keymap of KeymapService
@@ -81,8 +70,7 @@ const useKeymap = (): [
await keymapService.saveCustomKeymap();
setKeymapError(null);
} catch (err) {
const error = new Error(`Could not save file: ${err.message}`);
setKeymapError(error);
setKeymapError(err);
}
}

View File

@@ -48,7 +48,6 @@ const commands = [
require('./commands/moveToFolder'),
require('./commands/newNote'),
require('./commands/newFolder'),
require('./commands/newSubFolder'),
require('./commands/newTodo'),
require('./commands/print'),
require('./commands/renameFolder'),
@@ -64,9 +63,6 @@ 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

@@ -1,17 +0,0 @@
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

@@ -1,16 +0,0 @@
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

@@ -1,26 +0,0 @@
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

@@ -1,16 +0,0 @@
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,9 +8,8 @@ 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 key={`${brIndex++}`}/>;
if (!line.trim()) return <br/>;
return <div key={line} className="text">{line}</div>;
});

View File

@@ -14,7 +14,6 @@ import InteropServiceHelper from '../InteropServiceHelper';
import { _ } from 'lib/locale';
import { MenuItem, MenuItemLocation } from 'lib/services/plugins/api/types';
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
import menuCommandNames from './menuCommandNames';
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
@@ -87,7 +86,38 @@ interface Props {
pluginMenus: any[],
}
const commandNames:string[] = menuCommandNames();
const commandNames:string[] = [
'focusElementSideBar',
'focusElementNoteList',
'focusElementNoteTitle',
'focusElementNoteBody',
'exportPdf',
'newNote',
'newTodo',
'newFolder',
'print',
'synchronize',
'textCopy',
'textCut',
'textPaste',
'textSelectAll',
'textBold',
'textItalic',
'textLink',
'textCode',
'insertDateTime',
'attachFile',
'focusSearch',
'showLocalSearch',
'toggleSideBar',
'toggleNoteList',
'toggleVisiblePanes',
'toggleExternalEditing',
'setTags',
'showNoteContentProperties',
'copyDevCommand',
'openProfileDirectory',
];
function menuItemSetChecked(id:string, checked:boolean) {
const menu = Menu.getApplicationMenu();
@@ -218,8 +248,10 @@ function useMenu(props:Props) {
menuItemDic.focusElementNoteBody,
];
let toolsItems:any[] = [];
const importItems = [];
const exportItems = [];
const toolsItemsFirst = [];
const templateItems:any[] = [];
const ioService = InteropService.instance();
const ioModules = ioService.modules();
@@ -266,18 +298,15 @@ function useMenu(props:Props) {
},
};
const separator = () => {
return {
type: 'separator',
};
};
const newNoteItem = menuItemDic.newNote;
const newTodoItem = menuItemDic.newTodo;
const newFolderItem = menuItemDic.newFolder;
const newSubFolderItem = menuItemDic.newSubFolder;
const printItem = menuItemDic.print;
toolsItemsFirst.push(syncStatusItem, {
type: 'separator',
});
templateItems.push({
label: _('Create note from template'),
click: () => {
@@ -311,22 +340,18 @@ function useMenu(props:Props) {
},
});
let toolsItems:any[] = [];
// we need this workaround, because on macOS the menu is different
const toolsItemsWindowsLinux:any[] = [
{
label: _('Options'),
accelerator: keymapService.getAccelerator('config'),
click: () => {
props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
},
const toolsItemsWindowsLinux:any[] = toolsItemsFirst.concat([{
label: _('Options'),
visible: !shim.isMac(),
accelerator: !shim.isMac() && keymapService.getAccelerator('config'),
click: () => {
props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
},
separator(),
];
} as any]);
// the following menu items will be available for all OS under Tools
const toolsItemsAll = [{
@@ -398,9 +423,7 @@ function useMenu(props:Props) {
},
shim.isMac() ? noItem : newNoteItem,
shim.isMac() ? noItem : newTodoItem,
shim.isMac() ? noItem : newFolderItem,
shim.isMac() ? noItem : newSubFolderItem,
{
shim.isMac() ? noItem : newFolderItem, {
type: 'separator',
visible: shim.isMac() ? false : true,
}, {
@@ -424,7 +447,9 @@ function useMenu(props:Props) {
menuItemDic.synchronize,
shim.isMac() ? noItem : printItem, {
shim.isMac() ? syncStatusItem : noItem, {
type: 'separator',
}, shim.isMac() ? noItem : printItem, {
type: 'separator',
platforms: ['darwin'],
},
@@ -448,9 +473,7 @@ function useMenu(props:Props) {
submenu: [
newNoteItem,
newTodoItem,
newFolderItem,
newSubFolderItem,
{
newFolderItem, {
label: _('Close Window'),
platforms: ['darwin'],
accelerator: shim.isMac() && keymapService.getAccelerator('closeWindow'),
@@ -489,6 +512,12 @@ function useMenu(props:Props) {
});
}
const separator = () => {
return {
type: 'separator',
};
};
const rootMenus:any = {
edit: {
id: 'edit',
@@ -551,6 +580,11 @@ function useMenu(props:Props) {
},
},
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
separator(),
{
label: _('Actual Size'),
click: () => {
@@ -583,18 +617,6 @@ function useMenu(props:Props) {
accelerator: 'CommandOrControl+-',
}],
},
go: {
label: _('&Go'),
submenu: [
menuItemDic.historyBackward,
menuItemDic.historyForward,
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
],
},
note: {
label: _('&Note'),
submenu: [
@@ -627,8 +649,6 @@ function useMenu(props:Props) {
click: () => _checkForUpdates(),
},
separator(),
syncStatusItem,
separator(),
{
id: 'help:toggleDevTools',
label: _('Toggle development tools'),
@@ -683,7 +703,6 @@ function useMenu(props:Props) {
const pluginMenuItems = PluginManager.instance().menuItems();
for (const item of pluginMenuItems) {
const itemParent = rootMenus[item.parent] ? rootMenus[item.parent] : 'tools';
itemParent.submenu.push(separator());
itemParent.submenu.push(item);
}
}
@@ -716,7 +735,6 @@ function useMenu(props:Props) {
rootMenus.file,
rootMenus.edit,
rootMenus.view,
rootMenus.go,
rootMenus.note,
rootMenus.tools,
rootMenus.help,
@@ -724,21 +742,52 @@ function useMenu(props:Props) {
if (shim.isMac()) template.splice(0, 0, rootMenus.macOsApp);
// TODO
// function isEmptyMenu(template:any[]) {
// for (let i = 0; i < template.length; i++) {
// const t = template[i];
// if (t.type !== 'separator') return false;
// }
// return true;
// }
// function removeUnwantedItems(template:any[], screen:string) {
// const platform = shim.platformName();
// let output = [];
// for (let i = 0; i < template.length; i++) {
// const t = Object.assign({}, template[i]);
// if (t.screens && t.screens.indexOf(screen) < 0) continue;
// if (t.platforms && t.platforms.indexOf(platform) < 0) continue;
// if (t.submenu) t.submenu = removeUnwantedItems(t.submenu, screen);
// if (('submenu' in t) && isEmptyMenu(t.submenu)) continue;
// output.push(t);
// }
// // Remove empty separator for now empty sections
// const temp = [];
// let previous = null;
// for (let i = 0; i < output.length; i++) {
// const t = Object.assign({}, output[i]);
// if (t.type === 'separator') {
// if (!previous) continue;
// if (previous.type === 'separator') continue;
// }
// temp.push(t);
// previous = t;
// }
// output = temp;
// return output;
// }
if (props.routeName !== 'Main') {
setMenu(Menu.buildFromTemplate([
{
label: _('&File'),
submenu: [quitMenuItem],
},
{
label: _('&Edit'),
submenu: [
menuItemDic.textCopy,
menuItemDic.textCut,
menuItemDic.textPaste,
menuItemDic.textSelectAll,
],
},
]));
} else {
setMenu(Menu.buildFromTemplate(template));

View File

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

View File

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

View File

@@ -1,37 +0,0 @@
export default function() {
return [
'attachFile',
'copyDevCommand',
'exportPdf',
'focusElementNoteBody',
'focusElementNoteList',
'focusElementNoteTitle',
'focusElementSideBar',
'focusSearch',
'historyBackward',
'historyForward',
'insertDateTime',
'newFolder',
'newNote',
'newSubFolder',
'newTodo',
'openProfileDirectory',
'print',
'setTags',
'showLocalSearch',
'showNoteContentProperties',
'synchronize',
'textBold',
'textCode',
'textCopy',
'textCut',
'textItalic',
'textLink',
'textPaste',
'textSelectAll',
'toggleExternalEditing',
'toggleNoteList',
'toggleSideBar',
'toggleVisiblePanes',
];
}

View File

@@ -368,12 +368,9 @@
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.15",
"version": "1.3.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -644,9 +644,9 @@
"dev": true
},
"abab": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz",
"integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg=="
},
"abbrev": {
"version": "1.1.1",
@@ -654,9 +654,9 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
"integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA=="
},
"acorn-globals": {
"version": "4.3.4",
@@ -668,9 +668,9 @@
},
"dependencies": {
"acorn": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz",
"integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ=="
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA=="
}
}
},
@@ -6770,9 +6770,9 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"highlight.js": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
},
"hoist-non-react-statics": {
"version": "2.5.0",
@@ -7312,9 +7312,9 @@
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
},
"joplin-turndown": {
"version": "4.0.30",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.30.tgz",
"integrity": "sha512-OrGdNTsjI6/cbx/es9Hl0YI3YTql4SopduFcYCnWTZgqT0SJqILnF2JQxSNnbPnkSDIIRdNOG4+iNzlY6bS1nw==",
"version": "4.0.29",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.29.tgz",
"integrity": "sha512-rVGu8u4TpSRETo59/jiVW9iaXnpdxxpBHjb7nyCflkDfWhg1Kska4uagBQGw7cD2yxw7mB2YUIB/fAgtlIzcDQ==",
"requires": {
"css": "^2.2.4",
"html-entities": "^1.2.1",
@@ -7387,9 +7387,9 @@
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "6.12.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -7398,9 +7398,9 @@
}
},
"aws4": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz",
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
},
"fast-deep-equal": {
"version": "3.1.3",
@@ -7408,11 +7408,11 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"requires": {
"ajv": "^6.12.3",
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
}
},
@@ -10276,6 +10276,13 @@
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
"requires": {
"lodash": "^4.17.19"
},
"dependencies": {
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
}
}
},
"request-promise-native": {

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.15",
"version": "1.3.8",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {
@@ -139,13 +139,13 @@
"form-data": "^2.3.2",
"formatcoords": "^1.1.3",
"fs-extra": "^5.0.0",
"highlight.js": "^10.2.1",
"highlight.js": "^10.1.1",
"html-entities": "^1.2.1",
"html-minifier": "^4.0.0",
"htmlparser2": "^4.1.0",
"image-type": "^3.0.0",
"immer": "^7.0.5",
"joplin-turndown": "^4.0.30",
"joplin-turndown": "^4.0.29",
"joplin-turndown-plugin-gfm": "^1.0.12",
"json-stringify-safe": "^5.0.1",
"jssha": "^2.3.1",

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 = Number(event.currentTarget.getAttribute('data-type'));
const itemType = event.currentTarget.getAttribute('data-type');
this.gotoItem({
id: itemId,
@@ -557,7 +557,7 @@ GotoAnything.manifest = {
menuItems: [
{
name: 'main',
parent: 'go',
parent: 'tools',
label: _('Goto Anything...'),
accelerator: () => KeymapService.instance().getAccelerator('gotoAnything'),
screens: ['Main'],

View File

@@ -2,5 +2,4 @@ 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.3.10/joplin-v1.3.10.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.3.10/joplin-v1.3.10-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.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)
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 2097598
versionName "1.3.10"
versionCode 2097596
versionName "1.3.8"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -21,8 +21,6 @@ export default function useOnMessage(onCheckboxChange:Function, noteBody:string,
onJoplinLinkClick(msg);
} else if (msg.startsWith('error:')) {
console.error(`Webview injected script error: ${msg}`);
} else {
onJoplinLinkClick(msg);
}
}, [onCheckboxChange, noteBody, onMarkForDownload, onJoplinLinkClick, onResourceLongPress]);
}

View File

@@ -116,7 +116,6 @@ export default function useSource(noteBody:string, noteMarkupLanguage:number, th
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
${assetsToHeaders(result.pluginAssets, { asHtml: true })}
</head>

View File

@@ -211,9 +211,7 @@ class NoteScreenComponent extends BaseScreenComponent {
};
this.useBetaEditor = () => {
// Disable for now
return false;
// return Setting.value('editor.beta') && Platform.OS !== 'android';
return Setting.value('editor.beta') && Platform.OS !== 'android';
};
this.takePhoto_onPress = this.takePhoto_onPress.bind(this);

View File

@@ -51,10 +51,7 @@ shared.renderFolders = function(props, renderItem) {
shared.renderTags = function(props, renderItem) {
const tags = props.tags.slice();
tags.sort((a, b) => {
// 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;
return a.title < b.title ? -1 : +1;
});
const tagItems = [];
const order = [];
@@ -69,6 +66,21 @@ 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,14 +195,13 @@ 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(resolvedBaseDir) !== 0) throw new Error(`Resolved path for relative path "${relativePath}" is not within base directory "${baseDir}" (Was resolved to ${resolvedPath})`);
if (resolvedPath.indexOf(baseDir) !== 0) throw new Error('Resolved path for relative path "' + relativePath + '" is not within base directory "' + baseDir + '"');
return resolvedPath;
}

View File

@@ -42,7 +42,6 @@ class HtmlUtils {
return selfClosingElements.includes(tagName.toLowerCase());
}
// Returns the **encoded** URLs, so to be useful they should be decoded again before use.
extractImageUrls(html) {
if (!html) return [];

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 ? noteAttributes.author.trim() : '';
note.author = noteAttributes.author;
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.trim()}` : 'evernote';
note.source_url = noteAttributes['source-url'] ? noteAttributes['source-url'].trim() : '';
note.source = noteAttributes.source ? `evernote.${noteAttributes.source}` : 'evernote';
note.source_url = noteAttributes['source-url'] ? noteAttributes['source-url'] : '';
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 ? noteResource.mime.trim() : '',
title: noteResource.filename ? noteResource.filename.trim() : '',
filename: noteResource.filename ? noteResource.filename.trim() : '',
mime: noteResource.mime,
title: noteResource.filename ? noteResource.filename : '',
filename: noteResource.filename ? noteResource.filename : '',
});
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');
@@ -8,54 +8,6 @@ const mhchemModule = require('./katex_mhchem.js');
// to serialize them with json-stringify-safe
const stringifySafe = require('json-stringify-safe');
function stringifyKatexOptions(options:any) {
if (!options) return '';
const newOptions = { ...options };
// The Katex macro structure is extremely verbose and slow to cache,
// so we need bespoke code to serialize it.
// macros:
// \hb: {tokens: Array(1), numArgs: 0}
// \d: {tokens: Array(7), numArgs: 1}
// \e:
// tokens: Array(11)
// 0: Token {text: "}", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 1: Token {text: "1", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 2: Token {text: "#", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 3: Token {text: "{", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 4: Token {text: "^", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 5: Token {text: "}", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 6: Token {text: "e", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 7: Token {text: " ", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 8: Token {text: "m", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 9: Token {text: " ", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// 10: Token {text: "{", loc: SourceLocation, noexpand: undefined, treatAsRelax: undefined}
// numArgs: 1
// \dv: {tokens: Array(15), numArgs: 2}
// \ddv: {tokens: Array(19), numArgs: 2}
// \pdv: {tokens: Array(15), numArgs: 2}
// \pddv: {tokens: Array(15), numArgs: 2}
// \abs: {tokens: Array(10), numArgs: 1}
// \inflim: {tokens: Array(10), numArgs: 0}
// \infint: {tokens: Array(2), numArgs: 0}
// \prob: {tokens: Array(12), numArgs: 1}
// \expval: {tokens: Array(14), numArgs: 2}
// \wf: {tokens: Array(6), numArgs: 0}
if (options.macros) {
const toSerialize:any = {};
for (const k of Object.keys(options.macros)) {
const macroText:string[] = options.macros[k].tokens.map((t:any) => t.text);
toSerialize[k] = `${macroText.join('')}_${options.macros[k].numArgs}`;
}
newOptions.macros = toSerialize;
}
return stringifySafe(newOptions);
}
katex = mhchemModule(katex);
function katexStyle() {
@@ -91,13 +43,14 @@ function katexStyle() {
// Test if potential opening or closing delimieter
// Assumes that there is a "$" at state.src[pos]
function isValidDelim(state:any, pos:number) {
const max = state.posMax;
let can_open = true,
let prevChar,
nextChar,
max = state.posMax,
can_open = true,
can_close = true;
const prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
const nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
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
@@ -115,7 +68,7 @@ function isValidDelim(state:any, pos:number) {
}
function math_inline(state:any, silent:boolean) {
let match, token, res, pos;
let start, match, token, res, pos;
if (state.src[state.pos] !== '$') {
return false;
@@ -134,7 +87,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.
const start = state.pos + 1;
start = state.pos + 1;
match = start;
while ((match = state.src.indexOf('$', match)) !== -1) {
// Found potential $, look for escapes, pos will point to
@@ -195,6 +148,7 @@ 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];
@@ -246,7 +200,7 @@ function math_block(state:any, start:number, end:number, silent:boolean) {
state.line = next + 1;
const token = state.push('math_block', 'math', 0);
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];
@@ -257,13 +211,13 @@ function math_block(state:any, start:number, end:number, silent:boolean) {
const cache_:any = {};
function renderToStringWithCache(latex:string, katexOptions:any) {
const cacheKey = md5(escape(latex) + escape(stringifyKatexOptions(katexOptions)));
const cacheKey = md5(escape(latex) + escape(stringifySafe(katexOptions)));
if (cacheKey in cache_) {
return cache_[cacheKey];
} else {
const beforeMacros = stringifyKatexOptions(katexOptions.macros);
const beforeMacros = stringifySafe(katexOptions.macros);
const output = katex.renderToString(latex, katexOptions);
const afterMacros = stringifyKatexOptions(katexOptions.macros);
const afterMacros = stringifySafe(katexOptions.macros);
// Don't cache the formulas that add macros, otherwise
// they won't be added on second run.
@@ -279,7 +233,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;

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@ function mermaidReady() {
// <h1 id="mermaid">Mermaid</h1>
//
// And that's going to make the lib set the `mermaid` object to the H1 element.
// So below, we double-check that what we have really is an instance of the library.
// So below, we double-check that what we have really is an instance of the library.
return typeof mermaid !== 'undefined' && mermaid !== null && typeof mermaid === 'object' && !!mermaid.init;
}

View File

@@ -87,16 +87,11 @@ class HtmlUtils {
return tagStack[tagStack.length - 1];
};
// The BASE tag allows changing the base URL from which files are
// loaded, and that can break several plugins, such as Katex (which
// needs to load CSS files using a relative URL). For that reason
// it is disabled. More info:
// https://github.com/laurent22/joplin/issues/3021
//
// "link" can be used to escape the parser and inject JavaScript.
// Adding "meta" too for the same reason as it shouldn't be used in
// notes anyway.
const disallowedTags = ['script', 'iframe', 'frameset', 'frame', 'object', 'base', 'embed', 'link', 'meta'];
// The BASE tag allows changing the base URL from which files are loaded, and
// that can break several plugins, such as Katex (which needs to load CSS
// files using a relative URL). For that reason it is disabled.
// More info: https://github.com/laurent22/joplin/issues/3021
const disallowedTags = ['script', 'iframe', 'frameset', 'frame', 'object', 'base', 'embed'];
const parser = new htmlparser2.Parser({

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.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
},
"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.2.1",
"highlight.js": "10.1.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');
const output = includeDir ? path : basename(path);
let output = includeDir ? path : basename(path);
if (output.indexOf('.') < 0) return output;
const splitted = output.split('.');

View File

@@ -30,13 +30,6 @@ const markdownUtils = {
return url;
},
unescapeLinkUrl(url:string) {
url = url.replace(/%28/g, '(');
url = url.replace(/%29/g, ')');
url = url.replace(/%20/g, ' ');
return url;
},
prependBaseUrl(md:string, baseUrl:string) {
// eslint-disable-next-line no-useless-escape
return md.replace(/(\]\()([^\s\)]+)(.*?\))/g, (_match:any, before:string, url:string, after:string) => {
@@ -44,7 +37,6 @@ const markdownUtils = {
});
},
// Returns the **encoded** URLs, so to be useful they should be decoded again before use.
extractImageUrls(md:string) {
const markdownIt = new MarkdownIt();
setupLinkify(markdownIt); // Necessary to support file:/// links

View File

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

View File

@@ -508,16 +508,11 @@ class Setting extends BaseModel {
'folders.sortOrder.reverse': { value: false, type: SettingItemType.Bool, public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
trackLocation: { value: true, type: SettingItemType.Bool, section: 'note', public: true, label: () => _('Save geo-location with notes') },
// 2020-10-29: For now disable the beta editor due to
// underlying bugs in the TextInput component which we cannot
// fix. Also the editor crashes in Android and in some cases in
// iOS.
// https://discourse.joplinapp.org/t/anyone-using-the-beta-editor-on-ios/11658/9
'editor.beta': {
value: false,
type: SettingItemType.Bool,
section: 'note',
public: false, // mobilePlatform === 'ios',
public: mobilePlatform === 'ios',
appTypes: ['mobile'],
label: () => 'Opt-in to the editor beta',
description: () => 'This beta adds list continuation, Markdown preview, and Markdown shortcuts. If you find bugs, please report them in the Discourse forum.',

View File

@@ -66,23 +66,10 @@ class Tag extends BaseItem {
note_id: noteId,
});
// 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,
});
}
this.dispatch({
type: 'TAG_UPDATE_ONE',
item: await Tag.loadWithCount(tagId),
});
return output;
}

View File

@@ -3,7 +3,6 @@ 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
@@ -91,16 +90,16 @@ class OneDriveApi {
}
async execTokenRequest(code, redirectUri) {
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 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 r = await shim.fetch(this.tokenBaseUrl(), {
method: 'POST',
body: urlUtils.objectToQueryString(body),
body: body,
headers: {
['Content-Type']: 'application/x-www-form-urlencoded',
},
@@ -367,21 +366,19 @@ class OneDriveApi {
throw new Error(_('Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.'));
}
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 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 response = await shim.fetch(this.tokenBaseUrl(), {
const options = {
method: 'POST',
body: urlUtils.objectToQueryString(body),
headers: {
['Content-Type']: 'application/x-www-form-urlencoded',
},
});
body: body,
};
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');
const output = includeDir ? path : basename(path);
let 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=':/${id}' ${attributesToStr(attributes)}>`,
`<a href='joplin://${id}' ${attributesToStr(attributes)}>`,
` ${attributes.alt || src}`,
'</a>',
].join('');

View File

@@ -11,7 +11,6 @@ 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 {
@@ -144,17 +143,8 @@ export default class CommandService extends BaseService {
return output;
}
public commandNames(publicOnly:boolean = false) {
if (publicOnly) {
const output = [];
for (const name in this.commands_) {
if (!this.isPublic(name)) continue;
output.push(name);
}
return output;
} else {
return Object.keys(this.commands_);
}
public commandNames() {
return Object.keys(this.commands_);
}
public commandByName(name:string, options:CommandByNameOptions = null):Command {
@@ -213,20 +203,10 @@ 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);
if (!command.runtime) throw new Error(`Cannot execute a command without a runtime: ${commandName}`);
return command.runtime.execute(this.createContext(), ...args);
return command.runtime.execute({ state: this.store_.getState() }, ...args);
}
public scheduleExecute(commandName:string, args:any) {
@@ -239,10 +219,6 @@ export default class CommandService extends BaseService {
return stateToWhenClauseContext(this.store_.getState());
}
public isPublic(commandName:string) {
return !!this.label(commandName);
}
// When looping on commands and checking their enabled state, the whenClauseContext
// should be specified (created using currentWhenClauseContext) to avoid having
// to re-create it on each call.

View File

@@ -106,9 +106,8 @@ class DecryptionWorker {
if (!('errorHandler' in options)) options.errorHandler = 'log';
if (this.state_ !== 'idle') {
const msg = `DecryptionWorker: cannot start because state is "${this.state_}"`;
this.logger().debug(msg);
return { error: new Error(msg) };
this.logger().debug(`DecryptionWorker: cannot start because state is "${this.state_}"`);
return;
}
// Note: the logic below is an optimisation to avoid going through the loop if no master key exists
@@ -116,8 +115,7 @@ class DecryptionWorker {
// "throw" and "dispatch" logic.
const loadedMasterKeyCount = await this.encryptionService().loadedMasterKeysCount();
if (!loadedMasterKeyCount) {
const msg = 'DecryptionWorker: cannot start because no master key is currently loaded.';
this.logger().info(msg);
this.logger().info('DecryptionWorker: cannot start because no master key is currently loaded.');
const ids = await MasterKey.allIds();
if (ids.length) {
@@ -132,7 +130,7 @@ class DecryptionWorker {
});
}
}
return { error: new Error(msg) };
return;
}
this.logger().info('DecryptionWorker: starting decryption...');

View File

@@ -16,6 +16,7 @@ const defaultKeymapItems = {
{ accelerator: 'Cmd+N', command: 'newNote' },
{ accelerator: 'Cmd+T', command: 'newTodo' },
{ accelerator: 'Cmd+S', command: 'synchronize' },
{ accelerator: '', command: 'print' },
{ accelerator: 'Cmd+H', command: 'hideApp' },
{ accelerator: 'Cmd+Q', command: 'quit' },
{ accelerator: 'Cmd+,', command: 'config' },
@@ -50,6 +51,7 @@ const defaultKeymapItems = {
{ accelerator: 'Ctrl+N', command: 'newNote' },
{ accelerator: 'Ctrl+T', command: 'newTodo' },
{ accelerator: 'Ctrl+S', command: 'synchronize' },
{ accelerator: '', command: 'print' },
{ accelerator: 'Ctrl+Q', command: 'quit' },
{ accelerator: 'Ctrl+Alt+I', command: 'insertTemplate' },
{ accelerator: 'Ctrl+C', command: 'textCopy' },
@@ -101,42 +103,29 @@ export default class KeymapService extends BaseService {
super();
this.lastSaveTime_ = Date.now();
// By default, initialize for the current platform
// Manual initialization allows testing for other platforms
this.initialize();
}
public get lastSaveTime():number {
return this.lastSaveTime_;
}
// `additionalDefaultCommandNames` will be added to the default keymap
// **except** if they are already in it. Basically this is a mechanism
// to add all the commands from the command service to the default
// keymap.
public initialize(additionalDefaultCommandNames:string[] = [], platform: string = shim.platformName()) {
public initialize(platform: string = shim.platformName()) {
this.platform = platform;
switch (platform) {
case 'darwin':
this.defaultKeymapItems = defaultKeymapItems.darwin.slice();
this.defaultKeymapItems = defaultKeymapItems.darwin;
this.modifiersRegExp = modifiersRegExp.darwin;
break;
default:
this.defaultKeymapItems = defaultKeymapItems.default.slice();
this.defaultKeymapItems = defaultKeymapItems.default;
this.modifiersRegExp = modifiersRegExp.default;
}
for (const name of additionalDefaultCommandNames) {
if (this.defaultKeymapItems.find((item:KeymapItem) => item.command === name)) continue;
this.defaultKeymapItems.push({
command: name,
accelerator: null,
});
}
this.resetKeymap();
}
// Reset keymap back to its default values
public resetKeymap() {
this.keymap = {};
for (let i = 0; i < this.defaultKeymapItems.length; i++) {
// Keep the original defaultKeymapItems array untouched
@@ -151,9 +140,7 @@ export default class KeymapService extends BaseService {
if (await shim.fsDriver().exists(customKeymapPath)) {
this.logger().info(`KeymapService: Loading keymap from file: ${customKeymapPath}`);
const customKeymapFile = (await shim.fsDriver().readFile(customKeymapPath, 'utf-8')).trim();
if (!customKeymapFile) return;
const customKeymapFile = await shim.fsDriver().readFile(customKeymapPath, 'utf-8');
// Custom keymaps are supposed to contain an array of keymap items
this.overrideKeymap(JSON.parse(customKeymapFile));
}
@@ -196,8 +183,8 @@ export default class KeymapService extends BaseService {
if (!commandName) throw new Error('Cannot register an accelerator without a command name');
const validatedAccelerator = accelerator ? this.convertToPlatform(accelerator) : null;
if (validatedAccelerator) this.validateAccelerator(validatedAccelerator);
const validatedAccelerator = this.convertToPlatform(accelerator);
this.validateAccelerator(validatedAccelerator);
this.keymap[commandName] = {
command: commandName,
@@ -277,7 +264,7 @@ export default class KeymapService extends BaseService {
// Throws whenever there are duplicate Accelerators used in the keymap
this.validateKeymap();
} catch (err) {
this.resetKeymap(); // Discard all the changes if there are any issues
this.initialize(); // Discard all the changes if there are any issues
throw err;
}
}

View File

@@ -73,20 +73,6 @@ export default class ResourceEditWatcher {
return this.eventEmitter_.removeListener(eventName, callback);
}
externalApi() {
return {
openAndWatch: async ({ resourceId }:any) => {
return this.openAndWatch(resourceId);
},
stopWatching: async ({ resourceId }:any) => {
return this.stopWatching(resourceId);
},
isWatched: async ({ resourceId }:any) => {
return !!this.watchedItemByResourceId(resourceId);
},
};
}
private watch(fileToWatch:string) {
if (!this.chokidar_) return;

View File

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

View File

@@ -4,7 +4,6 @@ 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
@@ -30,7 +29,6 @@ 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;
@@ -39,7 +37,6 @@ export default class Plugin {
this.scriptText_ = scriptText;
this.logger_ = logger;
this.dispatch_ = dispatch;
this.eventEmitter_ = new EventEmitter();
}
public get id():string {
@@ -62,25 +59,11 @@ export default class Plugin {
return this.baseDir_;
}
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) {
public 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,7 +45,6 @@ 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,30 +320,36 @@ export type Path = string[];
export enum ContentScriptType {
/**
* Registers a new Markdown-It plugin, which should follow the template below.
* Registers a new Markdown-It plugin, which should follow this template:
*
* ```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) {
* // ...
* },
* assets: {
* // ...
* },
*
* // 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: {},
* }
* }
* }
* ```
*
* - 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:
* 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:
*
* ```javascript
* module.exports = {

View File

@@ -1,26 +1,22 @@
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];
const contentScripts = plugin.contentScripts[ContentScriptType.MarkdownItPlugin];
if (!contentScripts) continue;
for (const scriptType in plugin.contentScripts) {
const contentScripts = plugin.contentScripts[scriptType];
for (const contentScript of contentScripts) {
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}`);
const loadedModule = require(contentScript.path).default;
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,
});
output.push({
id: contentScript.id,
module: loadedModule({}),
});
}
}
}

View File

@@ -2,7 +2,6 @@ import Setting from 'lib/models/Setting';
import Logger from 'lib/Logger';
import shim from 'lib/shim';
import uuid from 'lib/uuid';
import markdownUtils from 'lib/markdownUtils';
const { ltrimSlashes } = require('lib/path-utils');
const { Database } = require('lib/database.js');
@@ -632,9 +631,6 @@ export default class Api {
async downloadImage_(url:string /* , allowFileProtocolImages */) {
const tempDir = Setting.value('tempDir');
// The URL we get to download have been extracted from the Markdown document
url = markdownUtils.unescapeLinkUrl(url);
const isDataUrl = url && url.toLowerCase().indexOf('data:') === 0;
const name = isDataUrl ? md5(`${Math.random()}_${Date.now()}`) : filename(url);

View File

@@ -1,9 +1,7 @@
import ResourceEditWatcher from 'lib/services/ResourceEditWatcher/index';
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
export default {
externalEditWatcher: () => ExternalEditWatcher.instance().externalApi(),
resourceEditWatcher: () => ResourceEditWatcher.instance().externalApi(),
};

View File

@@ -577,22 +577,6 @@ 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,
@@ -651,15 +635,7 @@ class SearchEngine {
// If preferredSearchType is "fts" we auto-detect anyway
// because it's not always supported.
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);
const st = scriptType(query);
if (!Setting.value('db.ftsEnabled') || ['ja', 'zh', 'ko', 'th'].indexOf(st) >= 0) {
return SearchEngine.SEARCH_TYPE_BASIC;
@@ -677,11 +653,12 @@ 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,43 +13,6 @@ 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 = () => {
@@ -402,25 +365,9 @@ 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(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));
}
file.close(() => {
resolve(makeResponse(response));
});
});
});

View File

@@ -36,31 +36,21 @@ function shimInit() {
};
shim.fetch = async function(url, options = null) {
// 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
// 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
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,18 +91,4 @@ 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.2.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz",
"integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g=="
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.1.tgz",
"integrity": "sha512-b4L09127uVa+9vkMgPpdUQP78ickGbHEQTWeBrQFTJZ4/n2aihWOGS0ZoUqAwjVmfjhq/C76HRzkqwZhK4sBbg=="
},
"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.2.1",
"highlight.js": "10.1.1",
"html-entities": "^1.2.1",
"htmlparser2": "^4.1.0",
"immer": "^7.0.9",

View File

@@ -589,7 +589,7 @@ async function readmeFileTitle(sourcePath) {
const r = md.match(/(^|\n)# (.*)/);
if (!r) {
throw new Error(`Could not determine title for Markdown file: ${sourcePath}`);
throw new Error('Could not determine title for Markdown file: ', sourcePath);
} else {
return r[2];
}

View File

@@ -10,13 +10,12 @@ const { execCommand, githubUsername } = require('./tool-utils.js');
// From https://stackoverflow.com/a/6234804/561309
function escapeHtml(unsafe) {
// We only escape <> as this is enough for Markdown
return unsafe
// .replace(/&/g, '&amp;')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
// .replace(/"/g, '&quot;')
// .replace(/'/g, '&#039;');
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
async function gitLog(sinceTag) {

View File

@@ -95,29 +95,33 @@
</aside>
<div class="tsd-comment tsd-typography">
<div class="lead">
<p>Registers a new Markdown-It plugin, which should follow the template below.</p>
<p>Registers a new Markdown-It plugin, which should follow this template:</p>
</div>
<pre><code class="language-javascript"><span class="hljs-built_in">module</span>.exports = {
<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>
<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-attr">assets</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>: {},
}
}
}</code></pre>
<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>
<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>
<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> {

Some files were not shown because too many files have changed in this diff Show More