Skip to content

Commit 68888ee

Browse files
committed
Merge branch 'develop'
2 parents e4e8e6c + 69cc2fb commit 68888ee

23 files changed

+769
-184
lines changed

.github/workflows/buildAndTest.yml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: Build and test
2+
on: [push, pull_request]
3+
jobs:
4+
buildAndTest:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v2
8+
- uses: actions/setup-node@v2
9+
with:
10+
node-version: "16"
11+
- name: Install dependencies
12+
run: npm install
13+
- name: Build
14+
run: npm run dist
15+
- name: Run test
16+
run: npm test

.husky/pre-push

-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
#!/bin/sh
22
. "$(dirname "$0")/_/husky.sh"
3-
4-
npm test

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## not released
44

5+
## v1.3.0 (2023-05-14)
6+
7+
- Add: German translation
8+
9+
## v1.2.2 (2023-01-06)
10+
11+
- Fix: `moveLogFile: ENOTDIR: not a directory` for certain backup settings
12+
- Add: DirectoryPath selector for backup path and tmp. export path selection on Joplin >= v2.10.4
13+
514
## v1.2.1 (2022-12-10)
615

716
- Fix: #47 Suppress repeating error message during automatic execution if the backup path is not accessible

__test__/backup.test.ts

+79-57
Original file line numberDiff line numberDiff line change
@@ -528,19 +528,23 @@ describe("Backup", function () {
528528
backupRetention: 1,
529529
password: null,
530530
singleJex: true,
531-
result: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
531+
result: path.join(testPath.backupBasePath),
532532
testFile: "testFile.txt",
533-
checkFile: path.join(testPath.backupBasePath, "JoplinBackup.7z"),
533+
checkFile: path.join(testPath.backupBasePath, "testFile.txt.7z"),
534534
saveBackupInfoCalled: 0,
535535
},
536536
{
537537
zipArchive: "yes",
538538
backupRetention: 2,
539539
password: null,
540540
singleJex: true,
541-
result: path.join(testPath.backupBasePath, "202101021630.7z"),
541+
result: path.join(testPath.backupBasePath, "202101021630"),
542542
testFile: "testFile.txt",
543-
checkFile: path.join(testPath.backupBasePath, "202101021630.7z"),
543+
checkFile: path.join(
544+
testPath.backupBasePath,
545+
"202101021630",
546+
"testFile.txt.7z"
547+
),
544548
saveBackupInfoCalled: 1,
545549
},
546550
{
@@ -801,86 +805,104 @@ describe("Backup", function () {
801805
expect(backup.log.transports.file.level).toBe("error");
802806
});
803807

804-
it(`move logfile`, async () => {
808+
describe("move logfile", function () {
805809
const testCases = [
806810
{
811+
description: "backupBasePath",
807812
zipArchive: "no",
808813
password: null,
809814
logDst: testPath.backupBasePath,
810-
testLogFile: path.join(testPath.backupBasePath, "backup.log"),
811815
},
812816
{
817+
description: "backupBasePath, password",
813818
zipArchive: "no",
814-
password: null,
815-
logDst: path.join(testPath.backupBasePath, "testDir"),
816-
testLogFile: path.join(
817-
testPath.backupBasePath,
818-
"testDir",
819-
"backup.log"
820-
),
819+
password: "secret",
820+
logDst: testPath.backupBasePath,
821821
},
822822
{
823+
description: "backupBasePath, zip, password",
823824
zipArchive: "yes",
824-
password: null,
825-
logDst: path.join(testPath.backupBasePath, "testDir"),
826-
testLogFile: path.join(
827-
testPath.backupBasePath,
828-
"testDir",
829-
"backup.log"
830-
),
825+
password: "secret",
826+
logDst: testPath.backupBasePath,
831827
},
832828
{
829+
description: "backupBasePath, zip one",
833830
zipArchive: "yesone",
834831
password: null,
835-
logDst: path.join(testPath.backupBasePath, "Backup.7z"),
836-
testLogFile: "backup.log",
832+
logDst: path.join(testPath.backupBasePath, "retention.7z"),
837833
},
838834
{
835+
description: "backupBasePath, zip one, password",
839836
zipArchive: "yesone",
840837
password: "secret",
841-
logDst: path.join(testPath.backupBasePath, "Backup.7z"),
842-
testLogFile: "backup.log",
838+
logDst: path.join(testPath.backupBasePath, "retention.7z"),
843839
},
844840
{
841+
description: "sub in backupBasePath",
842+
zipArchive: "no",
843+
password: null,
844+
logDst: path.join(testPath.backupBasePath, "retentionfolder"),
845+
},
846+
{
847+
description: "sub in backupBasePath, password",
845848
zipArchive: "no",
846849
password: "secret",
847-
logDst: testPath.backupBasePath,
848-
testLogFile: "backup.log",
850+
logDst: path.join(testPath.backupBasePath, "retentionfolder"),
851+
},
852+
{
853+
description: "sub in backupBasePath, zip",
854+
zipArchive: "yes",
855+
password: null,
856+
logDst: path.join(testPath.backupBasePath, "retentionfolder"),
857+
},
858+
{
859+
description: "sub in backupBasePath, password, zip",
860+
zipArchive: "yes",
861+
password: "secret",
862+
logDst: path.join(testPath.backupBasePath, "retentionfolder"),
849863
},
850864
];
851865

852-
backup.logFile = path.join(testPath.base, "test.log");
853866
for (const testCase of testCases) {
854-
await createTestStructure();
855-
if (testCase.zipArchive !== "yesone") {
856-
fs.emptyDirSync(testCase.logDst);
857-
}
858-
if (testCase.zipArchive === "yesone") {
859-
const dummyFile = path.join(testPath.base, "dummy");
860-
fs.writeFileSync(dummyFile, "dummy");
861-
await sevenZip.add(testCase.logDst, dummyFile, testCase.password);
862-
expect(fs.existsSync(dummyFile)).toBe(true);
863-
expect(fs.existsSync(testCase.logDst)).toBe(true);
864-
}
865-
866-
fs.writeFileSync(backup.logFile, "log");
867-
868-
backup.zipArchive = testCase.zipArchive;
869-
backup.password = testCase.password;
870-
871-
expect(fs.existsSync(backup.logFile)).toBe(true);
872-
expect(await backup.moveLogFile(testCase.logDst)).toBe(true);
873-
expect(fs.existsSync(backup.logFile)).toBe(false);
874-
875-
if (testCase.password !== null || testCase.zipArchive === "yesone") {
876-
const fileList = await sevenZip.list(
877-
testCase.logDst,
878-
testCase.password
879-
);
880-
expect(fileList.map((f) => f.file)).toContain(testCase.testLogFile);
881-
} else {
882-
expect(fs.existsSync(testCase.testLogFile)).toBe(true);
883-
}
867+
it(`${testCase.description}`, async () => {
868+
backup.logFile = path.join(testPath.base, "test.log");
869+
backup.zipArchive = testCase.zipArchive;
870+
backup.password = testCase.password;
871+
872+
await createTestStructure();
873+
874+
if (testCase.zipArchive === "yesone") {
875+
const dummyFile = path.join(testPath.base, "dummy");
876+
fs.writeFileSync(dummyFile, "dummy");
877+
expect(fs.existsSync(dummyFile)).toBe(true);
878+
await sevenZip.add(testCase.logDst, dummyFile, testCase.password);
879+
expect(fs.existsSync(testCase.logDst)).toBe(true);
880+
} else {
881+
fs.emptyDirSync(testCase.logDst);
882+
}
883+
884+
fs.writeFileSync(backup.logFile, "log");
885+
expect(fs.existsSync(backup.logFile)).toBe(true);
886+
887+
expect(await backup.moveLogFile(testCase.logDst)).toBe(true);
888+
expect(fs.existsSync(backup.logFile)).toBe(false);
889+
890+
let checkBackupLogFile = path.join(testCase.logDst, "backup.log");
891+
if (testCase.zipArchive === "yesone") {
892+
checkBackupLogFile = testCase.logDst;
893+
} else if (testCase.password !== null) {
894+
checkBackupLogFile = path.join(testCase.logDst, "backuplog.7z");
895+
}
896+
expect(fs.existsSync(checkBackupLogFile)).toBe(true);
897+
898+
if (testCase.password !== null || testCase.zipArchive === "yesone") {
899+
const fileList = await sevenZip.list(
900+
testCase.logDst,
901+
testCase.password
902+
);
903+
expect(fileList.map((f) => f.file)).toContain("backup.log");
904+
}
905+
});
884906
}
885907
});
886908
});

api/Joplin.d.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,19 @@ import JoplinViews from './JoplinViews';
88
import JoplinInterop from './JoplinInterop';
99
import JoplinSettings from './JoplinSettings';
1010
import JoplinContentScripts from './JoplinContentScripts';
11+
import JoplinClipboard from './JoplinClipboard';
12+
import JoplinWindow from './JoplinWindow';
1113
/**
1214
* This is the main entry point to the Joplin API. You can access various services using the provided accessors.
1315
*
14-
* **This is a beta API**
16+
* The API is now relatively stable and in general maintaining backward compatibility is a top priority, so you shouldn't except much breakages.
1517
*
16-
* Please note that the plugin API is relatively new and should be considered Beta state. Besides possible bugs, what it means is that there might be necessary breaking changes from one version to the next. Whenever such change is needed, best effort will be done to:
18+
* If a breaking change ever becomes needed, best effort will be done to:
1719
*
18-
* - Maintain backward compatibility;
19-
* - When possible, deprecate features instead of removing them;
20+
* - Deprecate features instead of removing them, so as to give you time to fix the issue;
2021
* - Document breaking changes in the changelog;
2122
*
22-
* So if you are developing a plugin, please keep an eye on the changelog as everything will be in there with information about how to update your code. There won't be any major API rewrite or architecture changes, but possibly small tweaks like function signature change, type change, etc.
23-
*
24-
* Eventually, the plugin API will be versioned to make this process smoother.
23+
* So if you are developing a plugin, please keep an eye on the changelog as everything will be in there with information about how to update your code.
2524
*/
2625
export default class Joplin {
2726
private data_;
@@ -33,8 +32,12 @@ export default class Joplin {
3332
private interop_;
3433
private settings_;
3534
private contentScripts_;
35+
private clipboard_;
36+
private window_;
3637
constructor(implementation: any, plugin: Plugin, store: any);
3738
get data(): JoplinData;
39+
get clipboard(): JoplinClipboard;
40+
get window(): JoplinWindow;
3841
get plugins(): JoplinPlugins;
3942
get workspace(): JoplinWorkspace;
4043
get contentScripts(): JoplinContentScripts;

api/JoplinClipboard.d.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export default class JoplinClipboard {
2+
private electronClipboard_;
3+
private electronNativeImage_;
4+
constructor(electronClipboard: any, electronNativeImage: any);
5+
readText(): Promise<string>;
6+
writeText(text: string): Promise<void>;
7+
readHtml(): Promise<string>;
8+
writeHtml(html: string): Promise<void>;
9+
/**
10+
* Returns the image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
11+
*/
12+
readImage(): Promise<string>;
13+
/**
14+
* Takes an image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
15+
*/
16+
writeImage(dataUrl: string): Promise<void>;
17+
/**
18+
* Returns the list available formats (mime types).
19+
*
20+
* For example [ 'text/plain', 'text/html' ]
21+
*/
22+
availableFormats(): Promise<string[]>;
23+
}

api/JoplinCommands.d.ts

+33-5
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,41 @@ import { Command } from './types';
1515
*
1616
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
1717
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
18-
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.ts)
18+
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)
1919
*
2020
* To view what arguments are supported, you can open any of these files
2121
* and look at the `execute()` command.
22+
*
23+
* ## Executing editor commands
24+
*
25+
* There might be a situation where you want to invoke editor commands
26+
* without using a {@link JoplinContentScripts | contentScript}. For this
27+
* reason Joplin provides the built in `editor.execCommand` command.
28+
*
29+
* `editor.execCommand` should work with any core command in both the
30+
* [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and
31+
* [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,
32+
* as well as most functions calls directly on a CodeMirror editor object (extensions).
33+
*
34+
* * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)
35+
* * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)
36+
*
37+
* `editor.execCommand` supports adding arguments for the commands.
38+
*
39+
* ```typescript
40+
* await joplin.commands.execute('editor.execCommand', {
41+
* name: 'madeUpCommand', // CodeMirror and TinyMCE
42+
* args: [], // CodeMirror and TinyMCE
43+
* ui: false, // TinyMCE only
44+
* value: '', // TinyMCE only
45+
* });
46+
* ```
47+
*
48+
* [View the example using the CodeMirror editor](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts)
49+
*
2250
*/
2351
export default class JoplinCommands {
24-
/**
52+
/**
2553
* <span class="platform-desktop">desktop</span> Executes the given
2654
* command.
2755
*
@@ -40,8 +68,8 @@ export default class JoplinCommands {
4068
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
4169
* ```
4270
*/
43-
execute(commandName: string, ...args: any[]): Promise<any | void>;
44-
/**
71+
execute(commandName: string, ...args: any[]): Promise<any | void>;
72+
/**
4573
* <span class="platform-desktop">desktop</span> Registers a new command.
4674
*
4775
* ```typescript
@@ -57,5 +85,5 @@ export default class JoplinCommands {
5785
* });
5886
* ```
5987
*/
60-
register(command: Command): Promise<void>;
88+
register(command: Command): Promise<void>;
6189
}

api/JoplinData.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ModelType } from '../../../BaseModel';
12
import { Path } from './types';
23
/**
34
* This module provides access to the Joplin data API: https://joplinapp.org/api/references/rest_api/
@@ -44,4 +45,6 @@ export default class JoplinData {
4445
post(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
4546
put(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
4647
delete(path: Path, query?: any): Promise<any>;
48+
itemType(itemId: string): Promise<ModelType>;
49+
resourcePath(resourceId: string): Promise<string>;
4750
}

api/JoplinFilters.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
* so for now disable filters.
66
*/
77
export default class JoplinFilters {
8-
on(name: string, callback: Function): Promise<void>;
9-
off(name: string, callback: Function): Promise<void>;
8+
on(name: string, callback: Function): Promise<void>;
9+
off(name: string, callback: Function): Promise<void>;
1010
}

api/JoplinInterop.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ import { ExportModule, ImportModule } from './types';
1212
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/api/references/rest_api/
1313
*/
1414
export default class JoplinInterop {
15-
registerExportModule(module: ExportModule): Promise<void>;
16-
registerImportModule(module: ImportModule): Promise<void>;
15+
registerExportModule(module: ExportModule): Promise<void>;
16+
registerImportModule(module: ImportModule): Promise<void>;
1717
}

api/JoplinSettings.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface ChangeEvent {
66
*/
77
keys: string[];
88
}
9-
export declare type ChangeHandler = (event: ChangeEvent)=> void;
9+
export declare type ChangeHandler = (event: ChangeEvent) => void;
1010
/**
1111
* This API allows registering new settings and setting sections, as well as getting and setting settings. Once a setting has been registered it will appear in the config screen and be editable by the user.
1212
*

0 commit comments

Comments
 (0)