You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-02-10 08:14:27 +02:00
Compare commits
1 Commits
v1.3.15
...
openProfil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac5f6c4cd6 |
@@ -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
5
.gitignore
vendored
@@ -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
|
||||
|
||||
5
.ignore
5
.ignore
@@ -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
|
||||
|
||||
3
CliClient/.gitignore
vendored
3
CliClient/.gitignore
vendored
@@ -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/
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
44
CliClient/package-lock.json
generated
44
CliClient/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<a href="https://images.macrumors.com/filters:quality(90)/article.html"/>testing</a>
|
||||
@@ -1 +0,0 @@
|
||||
[testing](https://images.macrumors.com/filters:quality%2890%29/article.html)
|
||||
@@ -1 +0,0 @@
|
||||
<img src="https://images.macrumors.com/filters:quality(90)/article/test.jpg?high"/>
|
||||
@@ -1 +0,0 @@
|
||||

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

|
||||
@@ -1 +0,0 @@
|
||||
<img src="https://example.com/test.jpg" title='hello & "(ouch)"'/>
|
||||
@@ -1 +0,0 @@
|
||||
"")
|
||||
@@ -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);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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'",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -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>;
|
||||
});
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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'); },
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
@@ -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', {
|
||||
|
||||
59
ElectronClient/package-lock.json
generated
59
ElectronClient/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -2,5 +2,4 @@ import { AppState } from '../../app';
|
||||
|
||||
export interface DesktopCommandContext {
|
||||
state: AppState,
|
||||
dispatch: Function,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 [];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
|
||||
@@ -363,4 +363,4 @@ export default function(theme:any) {
|
||||
`;
|
||||
|
||||
return [css];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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('.');
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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('.');
|
||||
|
||||
@@ -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('');
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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...');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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({}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
6
ReactNativeClient/package-lock.json
generated
6
ReactNativeClient/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
// .replace(/"/g, '"')
|
||||
// .replace(/'/g, ''');
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function gitLog(sinceTag) {
|
||||
|
||||
@@ -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 "context" 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'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'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'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'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
Reference in New Issue
Block a user