116 | * `,
117 | *
118 | * onRenderNote: async (props: any) => {
119 | * const formattedDate = dayjs(props.note.created_time).format();
120 | * return {
121 | * // Also return the props, so that note.title is available from the
122 | * // template
123 | * ...props,
124 | * formattedDate,
125 | * }
126 | * },
127 | * ```
128 | */
129 | onRenderNote: OnRenderNoteHandler;
130 | /**
131 | * This handler allows adding some interacivity to the note renderer -
132 | * whenever an input element within the item is changed (for example, when a
133 | * checkbox is clicked, or a text input is changed), this `onChange` handler
134 | * is going to be called.
135 | *
136 | * You can inspect `event.elementId` to know which element had some changes,
137 | * and `event.value` to know the new value. `event.noteId` also tells you
138 | * what note is affected, so that you can potentially apply changes to it.
139 | *
140 | * You specify the element ID, by setting a `data-id` attribute on the
141 | * input.
142 | *
143 | * For example, if you have such a template:
144 | *
145 | * ```html
146 | *
147 | *
148 | *
149 | * ```
150 | *
151 | * The event handler will receive an event with `elementId` set to
152 | * `noteTitleInput`.
153 | */
154 | onChange?: OnChangeHandler;
155 | }
156 | export {};
157 |
--------------------------------------------------------------------------------
/api/noteListType.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable multiline-comment-style */
2 |
3 | import { Size } from './types';
4 |
5 | // AUTO-GENERATED by generate-database-type
6 | type ListRendererDatabaseDependency = 'folder.created_time' | 'folder.encryption_applied' | 'folder.encryption_cipher_text' | 'folder.icon' | 'folder.id' | 'folder.is_shared' | 'folder.master_key_id' | 'folder.parent_id' | 'folder.share_id' | 'folder.title' | 'folder.updated_time' | 'folder.user_created_time' | 'folder.user_data' | 'folder.user_updated_time' | 'folder.type_' | 'note.altitude' | 'note.application_data' | 'note.author' | 'note.body' | 'note.conflict_original_id' | 'note.created_time' | 'note.encryption_applied' | 'note.encryption_cipher_text' | 'note.id' | 'note.is_conflict' | 'note.is_shared' | 'note.is_todo' | 'note.latitude' | 'note.longitude' | 'note.markup_language' | 'note.master_key_id' | 'note.order' | 'note.parent_id' | 'note.share_id' | 'note.source' | 'note.source_application' | 'note.source_url' | 'note.title' | 'note.todo_completed' | 'note.todo_due' | 'note.updated_time' | 'note.user_created_time' | 'note.user_data' | 'note.user_updated_time' | 'note.type_';
7 | // AUTO-GENERATED by generate-database-type
8 |
9 | export enum ItemFlow {
10 | TopToBottom = 'topToBottom',
11 | LeftToRight = 'leftToRight',
12 | }
13 |
14 | export type RenderNoteView = Record;
15 |
16 | export interface OnChangeEvent {
17 | elementId: string;
18 | value: any;
19 | noteId: string;
20 | }
21 |
22 | export type OnRenderNoteHandler = (props: any)=> Promise;
23 | export type OnChangeHandler = (event: OnChangeEvent)=> Promise;
24 |
25 | /**
26 | * Most of these are the built-in note properties, such as `note.title`,
27 | * `note.todo_completed`, etc.
28 | *
29 | * Additionally, the `item.*` properties are specific to the rendered item. The
30 | * most important being `item.selected`, which you can use to display the
31 | * selected note in a different way.
32 | *
33 | * Finally some special properties are provided to make it easier to render
34 | * notes. In particular, if possible prefer `note.titleHtml` to `note.title`
35 | * since some important processing has already been done on the string, such as
36 | * handling the search highlighter and escaping. Since it's HTML and already
37 | * escaped you would insert it using `{{{titleHtml}}}` (triple-mustache syntax,
38 | * which disables escaping).
39 | *
40 | * `notes.tag` gives you the list of tags associated with the note.
41 | *
42 | * `note.isWatched` tells you if the note is currently opened in an external
43 | * editor. In which case you would generally display some indicator.
44 | */
45 | export type ListRendererDepependency =
46 | ListRendererDatabaseDependency |
47 | 'item.size.width' |
48 | 'item.size.height' |
49 | 'item.selected' |
50 | 'note.titleHtml' |
51 | 'note.isWatched' |
52 | 'note.tags';
53 |
54 | export interface ListRenderer {
55 | /**
56 | * It must be unique to your plugin.
57 | */
58 | id: string;
59 |
60 | /**
61 | * Can be top to bottom or left to right. Left to right gives you more
62 | * option to set the size of the items since you set both its width and
63 | * height.
64 | */
65 | flow: ItemFlow;
66 |
67 | /**
68 | * The size of each item must be specified in advance for performance
69 | * reasons, and cannot be changed afterwards. If the item flow is top to
70 | * bottom, you only need to specificy the item height (the width will be
71 | * ignored).
72 | */
73 | itemSize: Size;
74 |
75 | /**
76 | * The CSS is relative to the list item container. What will appear in the
77 | * page is essentially `.note-list-item { YOUR_CSS; }`. It means you can use
78 | * child combinator with guarantee it will only apply to your own items. In
79 | * this example, the styling will apply to `.note-list-item > .content`:
80 | *
81 | * ```css
82 | * > .content {
83 | * padding: 10px;
84 | * }
85 | * ```
86 | *
87 | * In order to get syntax highlighting working here, it's recommended
88 | * installing an editor extension such as [es6-string-html VSCode
89 | * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)
90 | */
91 | itemCss?: string;
92 |
93 | /**
94 | * List the dependencies that your plugin needs to render the note list
95 | * items. Only these will be passed to your `onRenderNote` handler. Ensure
96 | * that you do not add more than what you need since there is a performance
97 | * penalty for each property.
98 | */
99 | dependencies: ListRendererDepependency[];
100 |
101 | /**
102 | * This is the HTML template that will be used to render the note list item.
103 | * This is a [Mustache template](https://github.com/janl/mustache.js) and it
104 | * will receive the variable you return from `onRenderNote` as tags. For
105 | * example, if you return a property named `formattedDate` from
106 | * `onRenderNote`, you can insert it in the template using `Created date:
107 | * {{formattedDate}}`.
108 | *
109 | * In order to get syntax highlighting working here, it's recommended
110 | * installing an editor extension such as [es6-string-html VSCode
111 | * extension](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)
112 | */
113 | itemTemplate: string;
114 |
115 | /**
116 | * This user-facing text is used for example in the View menu, so that your
117 | * renderer can be selected.
118 | */
119 | label: ()=> Promise;
120 |
121 | /**
122 | * This is where most of the real-time processing will happen. When a note
123 | * is rendered for the first time and every time it changes, this handler
124 | * receives the properties specified in the `dependencies` property. You can
125 | * then process them, load any additional data you need, and once done you
126 | * need to return the properties that are needed in the `itemTemplate` HTML.
127 | * Again, to use the formatted date example, you could have such a renderer:
128 | *
129 | * ```typescript
130 | * dependencies: [
131 | * 'note.title',
132 | * 'note.created_time',
133 | * ],
134 | *
135 | * itemTemplate: // html
136 | * `
137 | *
141 | * `,
142 | *
143 | * onRenderNote: async (props: any) => {
144 | * const formattedDate = dayjs(props.note.created_time).format();
145 | * return {
146 | * // Also return the props, so that note.title is available from the
147 | * // template
148 | * ...props,
149 | * formattedDate,
150 | * }
151 | * },
152 | * ```
153 | */
154 | onRenderNote: OnRenderNoteHandler;
155 |
156 | /**
157 | * This handler allows adding some interacivity to the note renderer -
158 | * whenever an input element within the item is changed (for example, when a
159 | * checkbox is clicked, or a text input is changed), this `onChange` handler
160 | * is going to be called.
161 | *
162 | * You can inspect `event.elementId` to know which element had some changes,
163 | * and `event.value` to know the new value. `event.noteId` also tells you
164 | * what note is affected, so that you can potentially apply changes to it.
165 | *
166 | * You specify the element ID, by setting a `data-id` attribute on the
167 | * input.
168 | *
169 | * For example, if you have such a template:
170 | *
171 | * ```html
172 | *
173 | *
174 | *
175 | * ```
176 | *
177 | * The event handler will receive an event with `elementId` set to
178 | * `noteTitleInput`.
179 | */
180 | onChange?: OnChangeHandler;
181 | }
182 |
--------------------------------------------------------------------------------
/api/types.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable multiline-comment-style */
2 |
3 | // =================================================================
4 | // Command API types
5 | // =================================================================
6 |
7 | export interface Command {
8 | /**
9 | * Name of command - must be globally unique
10 | */
11 | name: string;
12 |
13 | /**
14 | * Label to be displayed on menu items or keyboard shortcut editor for example.
15 | * If it is missing, it's assumed it's a private command, to be called programmatically only.
16 | * In that case the command will not appear in the shortcut editor or command panel, and logically
17 | * should not be used as a menu item.
18 | */
19 | label?: string;
20 |
21 | /**
22 | * Icon to be used on toolbar buttons for example
23 | */
24 | iconName?: string;
25 |
26 | /**
27 | * Code to be ran when the command is executed. It may return a result.
28 | */
29 | execute(...args: any[]): Promise;
30 |
31 | /**
32 | * Defines whether the command should be enabled or disabled, which in turns
33 | * affects the enabled state of any associated button or menu item.
34 | *
35 | * The condition should be expressed as a "when-clause" (as in Visual Studio
36 | * Code). It's a simple boolean expression that evaluates to `true` or
37 | * `false`. It supports the following operators:
38 | *
39 | * Operator | Symbol | Example
40 | * -- | -- | --
41 | * Equality | == | "editorType == markdown"
42 | * Inequality | != | "currentScreen != config"
43 | * Or | \|\| | "noteIsTodo \|\| noteTodoCompleted"
44 | * And | && | "oneNoteSelected && !inConflictFolder"
45 | *
46 | * Joplin, unlike VSCode, also supports parenthesis, which allows creating
47 | * more complex expressions such as `cond1 || (cond2 && cond3)`. Only one
48 | * level of parenthesis is possible (nested ones aren't supported).
49 | *
50 | * Currently the supported context variables aren't documented, but you can
51 | * find the list below:
52 | *
53 | * - [Global When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts)
54 | * - [Desktop app When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts)
55 | *
56 | * Note: Commands are enabled by default unless you use this property.
57 | */
58 | enabledCondition?: string;
59 | }
60 |
61 | // =================================================================
62 | // Interop API types
63 | // =================================================================
64 |
65 | export enum FileSystemItem {
66 | File = 'file',
67 | Directory = 'directory',
68 | }
69 |
70 | export enum ImportModuleOutputFormat {
71 | Markdown = 'md',
72 | Html = 'html',
73 | }
74 |
75 | /**
76 | * Used to implement a module to export data from Joplin. [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export) for an example.
77 | *
78 | * In general, all the event handlers you'll need to implement take a `context` object as a first argument. This object will contain the export or import path as well as various optional properties, such as which notes or notebooks need to be exported.
79 | *
80 | * To get a better sense of what it will contain it can be useful to print it using `console.info(context)`.
81 | */
82 | export interface ExportModule {
83 | /**
84 | * The format to be exported, eg "enex", "jex", "json", etc.
85 | */
86 | format: string;
87 |
88 | /**
89 | * The description that will appear in the UI, for example in the menu item.
90 | */
91 | description: string;
92 |
93 | /**
94 | * Whether the module will export a single file or multiple files in a directory. It affects the open dialog that will be presented to the user when using your exporter.
95 | */
96 | target: FileSystemItem;
97 |
98 | /**
99 | * Only applies to single file exporters or importers
100 | * It tells whether the format can package multiple notes into one file.
101 | * For example JEX or ENEX can, but HTML cannot.
102 | */
103 | isNoteArchive: boolean;
104 |
105 | /**
106 | * The extensions of the files exported by your module. For example, it is `["htm", "html"]` for the HTML module, and just `["jex"]` for the JEX module.
107 | */
108 | fileExtensions?: string[];
109 |
110 | /**
111 | * Called when the export process starts.
112 | */
113 | onInit(context: ExportContext): Promise;
114 |
115 | /**
116 | * Called when an item needs to be processed. An "item" can be any Joplin object, such as a note, a folder, a notebook, etc.
117 | */
118 | onProcessItem(context: ExportContext, itemType: number, item: any): Promise;
119 |
120 | /**
121 | * Called when a resource file needs to be exported.
122 | */
123 | onProcessResource(context: ExportContext, resource: any, filePath: string): Promise;
124 |
125 | /**
126 | * Called when the export process is done.
127 | */
128 | onClose(context: ExportContext): Promise;
129 | }
130 |
131 | export interface ImportModule {
132 | /**
133 | * The format to be exported, eg "enex", "jex", "json", etc.
134 | */
135 | format: string;
136 |
137 | /**
138 | * The description that will appear in the UI, for example in the menu item.
139 | */
140 | description: string;
141 |
142 | /**
143 | * Only applies to single file exporters or importers
144 | * It tells whether the format can package multiple notes into one file.
145 | * For example JEX or ENEX can, but HTML cannot.
146 | */
147 | isNoteArchive: boolean;
148 |
149 | /**
150 | * The type of sources that are supported by the module. Tells whether the module can import files or directories or both.
151 | */
152 | sources: FileSystemItem[];
153 |
154 | /**
155 | * Tells the file extensions of the exported files.
156 | */
157 | fileExtensions?: string[];
158 |
159 | /**
160 | * Tells the type of notes that will be generated, either HTML or Markdown (default).
161 | */
162 | outputFormat?: ImportModuleOutputFormat;
163 |
164 | /**
165 | * Called when the import process starts. There is only one event handler within which you should import the complete data.
166 | */
167 | onExec(context: ImportContext): Promise;
168 | }
169 |
170 | export interface ExportOptions {
171 | format?: string;
172 | path?: string;
173 | sourceFolderIds?: string[];
174 | sourceNoteIds?: string[];
175 | // modulePath?: string;
176 | target?: FileSystemItem;
177 | }
178 |
179 | export interface ExportContext {
180 | destPath: string;
181 | options: ExportOptions;
182 |
183 | /**
184 | * You can attach your own custom data using this propery - it will then be passed to each event handler, allowing you to keep state from one event to the next.
185 | */
186 | userData?: any;
187 | }
188 |
189 | export interface ImportContext {
190 | sourcePath: string;
191 | options: any;
192 | warnings: string[];
193 | }
194 |
195 | // =================================================================
196 | // Misc types
197 | // =================================================================
198 |
199 | export interface Script {
200 | onStart?(event: any): Promise;
201 | }
202 |
203 | export interface Disposable {
204 | // dispose():void;
205 | }
206 |
207 | export enum ModelType {
208 | Note = 1,
209 | Folder = 2,
210 | Setting = 3,
211 | Resource = 4,
212 | Tag = 5,
213 | NoteTag = 6,
214 | Search = 7,
215 | Alarm = 8,
216 | MasterKey = 9,
217 | ItemChange = 10,
218 | NoteResource = 11,
219 | ResourceLocalState = 12,
220 | Revision = 13,
221 | Migration = 14,
222 | SmartFilter = 15,
223 | Command = 16,
224 | }
225 |
226 | export interface VersionInfo {
227 | version: string;
228 | profileVersion: number;
229 | syncVersion: number;
230 | }
231 |
232 | // =================================================================
233 | // Menu types
234 | // =================================================================
235 |
236 | export interface CreateMenuItemOptions {
237 | accelerator: string;
238 | }
239 |
240 | export enum MenuItemLocation {
241 | File = 'file',
242 | Edit = 'edit',
243 | View = 'view',
244 | Note = 'note',
245 | Tools = 'tools',
246 | Help = 'help',
247 |
248 | /**
249 | * @deprecated Do not use - same as NoteListContextMenu
250 | */
251 | Context = 'context',
252 |
253 | // If adding an item here, don't forget to update isContextMenuItemLocation()
254 |
255 | /**
256 | * When a command is called from the note list context menu, the
257 | * command will receive the following arguments:
258 | *
259 | * - `noteIds:string[]`: IDs of the notes that were right-clicked on.
260 | */
261 | NoteListContextMenu = 'noteListContextMenu',
262 |
263 | EditorContextMenu = 'editorContextMenu',
264 |
265 | /**
266 | * When a command is called from a folder context menu, the
267 | * command will receive the following arguments:
268 | *
269 | * - `folderId:string`: ID of the folder that was right-clicked on
270 | */
271 | FolderContextMenu = 'folderContextMenu',
272 |
273 | /**
274 | * When a command is called from a tag context menu, the
275 | * command will receive the following arguments:
276 | *
277 | * - `tagId:string`: ID of the tag that was right-clicked on
278 | */
279 | TagContextMenu = 'tagContextMenu',
280 | }
281 |
282 | export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
283 | return [
284 | MenuItemLocation.Context,
285 | MenuItemLocation.NoteListContextMenu,
286 | MenuItemLocation.EditorContextMenu,
287 | MenuItemLocation.FolderContextMenu,
288 | MenuItemLocation.TagContextMenu,
289 | ].includes(location);
290 | }
291 |
292 | export interface MenuItem {
293 | /**
294 | * Command that should be associated with the menu item. All menu item should
295 | * have a command associated with them unless they are a sub-menu.
296 | */
297 | commandName?: string;
298 |
299 | /**
300 | * Arguments that should be passed to the command. They will be as rest
301 | * parameters.
302 | */
303 | commandArgs?: any[];
304 |
305 | /**
306 | * Set to "separator" to create a divider line
307 | */
308 | type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio');
309 |
310 | /**
311 | * Accelerator associated with the menu item
312 | */
313 | accelerator?: string;
314 |
315 | /**
316 | * Menu items that should appear below this menu item. Allows creating a menu tree.
317 | */
318 | submenu?: MenuItem[];
319 |
320 | /**
321 | * Menu item label. If not specified, the command label will be used instead.
322 | */
323 | label?: string;
324 | }
325 |
326 | // =================================================================
327 | // View API types
328 | // =================================================================
329 |
330 | export interface ButtonSpec {
331 | id: ButtonId;
332 | title?: string;
333 | onClick?(): void;
334 | }
335 |
336 | export type ButtonId = string;
337 |
338 | export enum ToolbarButtonLocation {
339 | /**
340 | * This toolbar in the top right corner of the application. It applies to the note as a whole, including its metadata.
341 | */
342 | NoteToolbar = 'noteToolbar',
343 |
344 | /**
345 | * This toolbar is right above the text editor. It applies to the note body only.
346 | */
347 | EditorToolbar = 'editorToolbar',
348 | }
349 |
350 | export type ViewHandle = string;
351 |
352 | export interface EditorCommand {
353 | name: string;
354 | value?: any;
355 | }
356 |
357 | export interface DialogResult {
358 | id: ButtonId;
359 | formData?: any;
360 | }
361 |
362 | export interface Size {
363 | width?: number;
364 | height?: number;
365 | }
366 |
367 | export interface Rectangle {
368 | x?: number;
369 | y?: number;
370 | width?: number;
371 | height?: number;
372 | }
373 |
374 | // =================================================================
375 | // Settings types
376 | // =================================================================
377 |
378 | export enum SettingItemType {
379 | Int = 1,
380 | String = 2,
381 | Bool = 3,
382 | Array = 4,
383 | Object = 5,
384 | Button = 6,
385 | }
386 |
387 | export enum SettingItemSubType {
388 | FilePathAndArgs = 'file_path_and_args',
389 | FilePath = 'file_path', // Not supported on mobile!
390 | DirectoryPath = 'directory_path', // Not supported on mobile!
391 | }
392 |
393 | export enum AppType {
394 | Desktop = 'desktop',
395 | Mobile = 'mobile',
396 | Cli = 'cli',
397 | }
398 |
399 | export enum SettingStorage {
400 | Database = 1,
401 | File = 2,
402 | }
403 |
404 | // Redefine a simplified interface to mask internal details
405 | // and to remove function calls as they would have to be async.
406 | export interface SettingItem {
407 | value: any;
408 | type: SettingItemType;
409 |
410 | /**
411 | * Currently only used to display a file or directory selector. Always set
412 | * `type` to `SettingItemType.String` when using this property.
413 | */
414 | subType?: SettingItemSubType;
415 |
416 | label: string;
417 | description?: string;
418 |
419 | /**
420 | * A public setting will appear in the Configuration screen and will be
421 | * modifiable by the user. A private setting however will not appear there,
422 | * and can only be changed programmatically. You may use this to store some
423 | * values that you do not want to directly expose.
424 | */
425 | public: boolean;
426 |
427 | /**
428 | * You would usually set this to a section you would have created
429 | * specifically for the plugin.
430 | */
431 | section?: string;
432 |
433 | /**
434 | * To create a setting with multiple options, set this property to `true`.
435 | * That setting will render as a dropdown list in the configuration screen.
436 | */
437 | isEnum?: boolean;
438 |
439 | /**
440 | * This property is required when `isEnum` is `true`. In which case, it
441 | * should contain a map of value => label.
442 | */
443 | options?: Record;
444 |
445 | /**
446 | * Reserved property. Not used at the moment.
447 | */
448 | appTypes?: AppType[];
449 |
450 | /**
451 | * Set this to `true` to store secure data, such as passwords. Any such
452 | * setting will be stored in the system keychain if one is available.
453 | */
454 | secure?: boolean;
455 |
456 | /**
457 | * An advanced setting will be moved under the "Advanced" button in the
458 | * config screen.
459 | */
460 | advanced?: boolean;
461 |
462 | /**
463 | * Set the min, max and step values if you want to restrict an int setting
464 | * to a particular range.
465 | */
466 | minimum?: number;
467 | maximum?: number;
468 | step?: number;
469 |
470 | /**
471 | * Either store the setting in the database or in settings.json. Defaults to database.
472 | */
473 | storage?: SettingStorage;
474 | }
475 |
476 | export interface SettingSection {
477 | label: string;
478 | iconName?: string;
479 | description?: string;
480 | name?: string;
481 | }
482 |
483 | // =================================================================
484 | // Data API types
485 | // =================================================================
486 |
487 | /**
488 | * An array of at least one element and at most three elements.
489 | *
490 | * - **[0]**: Resource name (eg. "notes", "folders", "tags", etc.)
491 | * - **[1]**: (Optional) Resource ID.
492 | * - **[2]**: (Optional) Resource link.
493 | */
494 | export type Path = string[];
495 |
496 | // =================================================================
497 | // Content Script types
498 | // =================================================================
499 |
500 | export type PostMessageHandler = (message: any)=> Promise;
501 |
502 | /**
503 | * When a content script is initialised, it receives a `context` object.
504 | */
505 | export interface ContentScriptContext {
506 | /**
507 | * The plugin ID that registered this content script
508 | */
509 | pluginId: string;
510 |
511 | /**
512 | * The content script ID, which may be necessary to post messages
513 | */
514 | contentScriptId: string;
515 |
516 | /**
517 | * Can be used by CodeMirror content scripts to post a message to the plugin
518 | */
519 | postMessage: PostMessageHandler;
520 | }
521 |
522 | export interface ContentScriptModuleLoadedEvent {
523 | userData?: any;
524 | }
525 |
526 | export interface ContentScriptModule {
527 | onLoaded?: (event: ContentScriptModuleLoadedEvent)=> void;
528 | plugin: ()=> any;
529 | assets?: ()=> void;
530 | }
531 |
532 | export interface MarkdownItContentScriptModule extends Omit {
533 | plugin: (markdownIt: any, options: any)=> any;
534 | }
535 |
536 | export enum ContentScriptType {
537 | /**
538 | * Registers a new Markdown-It plugin, which should follow the template
539 | * below.
540 | *
541 | * ```javascript
542 | * module.exports = {
543 | * default: function(context) {
544 | * return {
545 | * plugin: function(markdownIt, pluginOptions) {
546 | * // ...
547 | * },
548 | * assets: {
549 | * // ...
550 | * },
551 | * }
552 | * }
553 | * }
554 | * ```
555 | *
556 | * See [the
557 | * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
558 | * for a simple Markdown-it plugin example.
559 | *
560 | * ## Exported members
561 | *
562 | * - The `context` argument is currently unused but could be used later on
563 | * to provide access to your own plugin so that the content script and
564 | * plugin can communicate.
565 | *
566 | * - The **required** `plugin` key is the actual Markdown-It plugin - check
567 | * the [official doc](https://github.com/markdown-it/markdown-it) for more
568 | * information.
569 | *
570 | * - Using the **optional** `assets` key you may specify assets such as JS
571 | * or CSS that should be loaded in the rendered HTML document. Check for
572 | * example the Joplin [Mermaid
573 | * plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
574 | * to see how the data should be structured.
575 | *
576 | * ## Getting the settings from the renderer
577 | *
578 | * You can access your plugin settings from the renderer by calling
579 | * `pluginOptions.settingValue("your-setting-key')`.
580 | *
581 | * ## Posting messages from the content script to your plugin
582 | *
583 | * The application provides the following function to allow executing
584 | * commands from the rendered HTML code:
585 | *
586 | * ```javascript
587 | * const response = await webviewApi.postMessage(contentScriptId, message);
588 | * ```
589 | *
590 | * - `contentScriptId` is the ID you've defined when you registered the
591 | * content script. You can retrieve it from the
592 | * {@link ContentScriptContext | context}.
593 | * - `message` can be any basic JavaScript type (number, string, plain
594 | * object), but it cannot be a function or class instance.
595 | *
596 | * When you post a message, the plugin can send back a `response` thus
597 | * allowing two-way communication:
598 | *
599 | * ```javascript
600 | * await joplin.contentScripts.onMessage(contentScriptId, (message) => {
601 | * // Process message
602 | * return response; // Can be any object, string or number
603 | * });
604 | * ```
605 | *
606 | * See {@link JoplinContentScripts.onMessage} for more details, as well as
607 | * the [postMessage
608 | * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
609 | *
610 | * ## Registering an existing Markdown-it plugin
611 | *
612 | * To include a regular Markdown-It plugin, that doesn't make use of any
613 | * Joplin-specific features, you would simply create a file such as this:
614 | *
615 | * ```javascript
616 | * module.exports = {
617 | * default: function(context) {
618 | * return {
619 | * plugin: require('markdown-it-toc-done-right');
620 | * }
621 | * }
622 | * }
623 | * ```
624 | */
625 | MarkdownItPlugin = 'markdownItPlugin',
626 |
627 | /**
628 | * Registers a new CodeMirror plugin, which should follow the template
629 | * below.
630 | *
631 | * ```javascript
632 | * module.exports = {
633 | * default: function(context) {
634 | * return {
635 | * plugin: function(CodeMirror) {
636 | * // ...
637 | * },
638 | * codeMirrorResources: [],
639 | * codeMirrorOptions: {
640 | * // ...
641 | * },
642 | * assets: {
643 | * // ...
644 | * },
645 | * }
646 | * }
647 | * }
648 | * ```
649 | *
650 | * - The `context` argument is currently unused but could be used later on
651 | * to provide access to your own plugin so that the content script and
652 | * plugin can communicate.
653 | *
654 | * - The `plugin` key is your CodeMirror plugin. This is where you can
655 | * register new commands with CodeMirror or interact with the CodeMirror
656 | * instance as needed.
657 | *
658 | * - The `codeMirrorResources` key is an array of CodeMirror resources that
659 | * will be loaded and attached to the CodeMirror module. These are made up
660 | * of addons, keymaps, and modes. For example, for a plugin that want's to
661 | * enable clojure highlighting in code blocks. `codeMirrorResources` would
662 | * be set to `['mode/clojure/clojure']`.
663 | *
664 | * - The `codeMirrorOptions` key contains all the
665 | * [CodeMirror](https://codemirror.net/doc/manual.html#config) options
666 | * that will be set or changed by this plugin. New options can alse be
667 | * declared via
668 | * [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption),
669 | * and then have their value set here. For example, a plugin that enables
670 | * line numbers would set `codeMirrorOptions` to `{'lineNumbers': true}`.
671 | *
672 | * - Using the **optional** `assets` key you may specify **only** CSS assets
673 | * that should be loaded in the rendered HTML document. Check for example
674 | * the Joplin [Mermaid
675 | * plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
676 | * to see how the data should be structured.
677 | *
678 | * One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions` keys
679 | * must be provided for the plugin to be valid. Having multiple or all
680 | * provided is also okay.
681 | *
682 | * See also the [demo
683 | * plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
684 | * for an example of all these keys being used in one plugin.
685 | *
686 | * ## Posting messages from the content script to your plugin
687 | *
688 | * In order to post messages to the plugin, you can use the postMessage
689 | * function passed to the {@link ContentScriptContext | context}.
690 | *
691 | * ```javascript
692 | * const response = await context.postMessage('messageFromCodeMirrorContentScript');
693 | * ```
694 | *
695 | * When you post a message, the plugin can send back a `response` thus
696 | * allowing two-way communication:
697 | *
698 | * ```javascript
699 | * await joplin.contentScripts.onMessage(contentScriptId, (message) => {
700 | * // Process message
701 | * return response; // Can be any object, string or number
702 | * });
703 | * ```
704 | *
705 | * See {@link JoplinContentScripts.onMessage} for more details, as well as
706 | * the [postMessage
707 | * demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
708 | *
709 | */
710 | CodeMirrorPlugin = 'codeMirrorPlugin',
711 | }
712 |
--------------------------------------------------------------------------------
/img/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
29 |
--------------------------------------------------------------------------------
/img/icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackGruber/joplin-plugin-backup/76e9913e321132c6a7da6cb48b73db092754ef82/img/icon_256.png
--------------------------------------------------------------------------------
/img/icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackGruber/joplin-plugin-backup/76e9913e321132c6a7da6cb48b73db092754ef82/img/icon_32.png
--------------------------------------------------------------------------------
/img/joplin_path_in_gui.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackGruber/joplin-plugin-backup/76e9913e321132c6a7da6cb48b73db092754ef82/img/joplin_path_in_gui.jpg
--------------------------------------------------------------------------------
/img/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackGruber/joplin-plugin-backup/76e9913e321132c6a7da6cb48b73db092754ef82/img/main.png
--------------------------------------------------------------------------------
/img/showcase1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackGruber/joplin-plugin-backup/76e9913e321132c6a7da6cb48b73db092754ef82/img/showcase1.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "joplin-plugin-backup",
3 | "version": "1.4.4",
4 | "scripts": {
5 | "dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive",
6 | "prepare": "npm run dist && husky install",
7 | "update": "npm install -g generator-joplin && yo joplin --node-package-manager npm --update --force",
8 | "release": "npm test && node ./node_modules/joplinplugindevtools/dist/createRelease.js",
9 | "preRelease": "npm test && node ./node_modules/joplinplugindevtools/dist/createRelease.js --prerelease",
10 | "gitRelease": "node ./node_modules/joplinplugindevtools/dist/createRelease.js --upload",
11 | "gitPreRelease": "node ./node_modules/joplinplugindevtools/dist/createRelease.js --upload --prerelease",
12 | "test": "jest",
13 | "updateVersion": "webpack --env joplin-plugin-config=updateVersion"
14 | },
15 | "license": "MIT",
16 | "keywords": [
17 | "joplin-plugin"
18 | ],
19 | "devDependencies": {
20 | "@types/jest": "^26.0.23",
21 | "@types/node": "^18.7.13",
22 | "axios": "^0.21.1",
23 | "chalk": "^4.1.0",
24 | "copy-webpack-plugin": "^11.0.0",
25 | "dotenv": "^10.0.0",
26 | "fs-extra": "^10.1.0",
27 | "glob": "^8.0.3",
28 | "husky": "^6.0.0",
29 | "jest": "^27.0.4",
30 | "jest-when": "^3.3.1",
31 | "joplinplugindevtools": "^1.0.16",
32 | "lint-staged": "^11.0.0",
33 | "mime": "^2.5.2",
34 | "on-build-webpack": "^0.1.0",
35 | "prettier": "2.3.0",
36 | "tar": "^6.1.11",
37 | "ts-jest": "^27.0.2",
38 | "ts-loader": "^9.3.1",
39 | "typescript": "^4.8.2",
40 | "webpack": "^5.74.0",
41 | "webpack-cli": "^4.10.0",
42 | "yargs": "^16.2.0",
43 | "@joplin/lib": "~2.9"
44 | },
45 | "browser": {
46 | "fs": false,
47 | "child_process": false
48 | },
49 | "dependencies": {
50 | "@types/i18n": "^0.13.6",
51 | "7zip-bin": "^5.1.1",
52 | "electron-log": "^4.3.1",
53 | "i18n": "^0.15.1",
54 | "moment": "^2.29.1",
55 | "node-7z": "^2.1.2"
56 | },
57 | "lint-staged": {
58 | "**/*": "prettier --write --ignore-unknown"
59 | },
60 | "jest": {
61 | "transform": {
62 | ".(ts|tsx)": "ts-jest"
63 | },
64 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
65 | "moduleFileExtensions": [
66 | "ts",
67 | "tsx",
68 | "js"
69 | ],
70 | "moduleNameMapper": {
71 | "^api$": "/node_modules/joplinplugindevtools/dist/apiMock.js",
72 | "^api/types$": "/api/types"
73 | }
74 | },
75 | "files": [
76 | "publish"
77 | ]
78 | }
79 |
--------------------------------------------------------------------------------
/plugin.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "extraScripts": []
3 | }
4 |
--------------------------------------------------------------------------------
/src/helper.ts:
--------------------------------------------------------------------------------
1 | import joplin from "api";
2 | import * as path from "path";
3 |
4 | export namespace helper {
5 | export async function validFileName(fileName: string) {
6 | var regChar = /[:*?"<>\/|\\]+/; // forbidden characters \ / : * ? " < > |
7 | var rexNames = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; // forbidden file names
8 |
9 | if (regChar.test(fileName) === true || rexNames.test(fileName) === true) {
10 | return false;
11 | } else {
12 | return true;
13 | }
14 | }
15 |
16 | export async function joplinVersionInfo(): Promise {
17 | try {
18 | return await joplin.versionInfo();
19 | } catch (error) {
20 | return null;
21 | }
22 | }
23 |
24 | // -2: Error
25 | // -1: Lower version
26 | // 0: Version equal
27 | // 1: Higer verison
28 | export async function versionCompare(
29 | version1: string,
30 | version2: string
31 | ): Promise {
32 | if (version1.trim() === "" || version2.trim() === "") {
33 | return -2;
34 | }
35 |
36 | const vArray1 = version1.split(".");
37 | const vArray2 = version2.split(".");
38 | let result = null;
39 |
40 | let maxIndex = -1;
41 | if (vArray1.length >= vArray2.length) {
42 | maxIndex = vArray1.length;
43 | } else {
44 | maxIndex = vArray2.length;
45 | }
46 |
47 | for (let index = 0; index < maxIndex; index++) {
48 | let check1 = 0;
49 | if (index < vArray1.length) {
50 | check1 = parseInt(vArray1[index]);
51 | }
52 |
53 | let check2 = 0;
54 | if (index < vArray2.length) {
55 | check2 = parseInt(vArray2[index]);
56 | }
57 |
58 | if (check1 > check2) {
59 | return 1;
60 | } else if (check1 === check2) {
61 | result = 0;
62 | } else {
63 | return -1;
64 | }
65 | }
66 |
67 | return result;
68 | }
69 |
70 | // Doesn't resolve simlinks
71 | // See https://stackoverflow.com/questions/44892672/how-to-check-if-two-paths-are-the-same-in-npm
72 | // for possible alternative implementations.
73 | export function isSubdirectoryOrEqual(
74 | parent: string,
75 | possibleChild: string,
76 |
77 | // Testing only
78 | pathModule: typeof path = path
79 | ) {
80 | // Appending path.sep to handle this case:
81 | // parent: /a/b/test
82 | // possibleChild: /a/b/test2
83 | // "/a/b/test2".startsWith("/a/b/test") -> true, but
84 | // "/a/b/test2/".startsWith("/a/b/test/") -> false
85 | //
86 | // Note that .resolve removes trailing slashes.
87 | //
88 | const normalizedParent = pathModule.resolve(parent) + pathModule.sep;
89 | const normalizedChild = pathModule.resolve(possibleChild) + pathModule.sep;
90 |
91 | return normalizedChild.startsWith(normalizedParent);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import joplin from "api";
2 | import { Backup } from "./Backup";
3 |
4 | const backup = new Backup();
5 |
6 | joplin.plugins.register({
7 | onStart: async function () {
8 | console.log("Start Backup Plugin");
9 | await backup.init();
10 |
11 | joplin.settings.onChange(async (event: any) => {
12 | if (event.keys.indexOf("backupInterval") !== -1) {
13 | console.log("Backup interval changed");
14 | await backup.startTimer();
15 | }
16 | });
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/src/locales/de_DE.json:
--------------------------------------------------------------------------------
1 | {
2 | "msg": {
3 | "backup": {
4 | "completed": "Backup wurde erstellt"
5 | },
6 | "error": {
7 | "PluginUpgrade": "Upgrade Fehler %s: %s",
8 | "folderCreation": "Fehler beim Ordner erstellen: %s",
9 | "ConfigureBackupPath": "Bitte einen Backup Pfad in `Joplin Tools > Options > Backup` konfigurieren",
10 | "PasswordMissMatch": "Passwörter stimmen nicht überein!",
11 | "BackupPathDontExist": "Der Backup Pfad '%s' existiert nicht!",
12 | "BackupAlreadyRunning": "Es läuft bereits ein Backup!",
13 | "Backup": "Backup Fehler für %s: %s",
14 | "fileCopy": "Fehler beim kopieren von Datei/Ordner in %s: %s",
15 | "deleteFile": "Fehler beim löschen von Datei/Ordner in %s: %s",
16 | "backupPathContainsImportantDir": "Der Sicherungspfad ist oder enthält ein wichtiges Verzeichnis (%s), das durch ein Backup überschrieben werden könnte. Ohne die Aktivierung der Unterordner-Einstellung ist dies nicht erlaubt!",
17 | "BackupSetNotSupportedChars": "Der Name des Backup-Sets enthält nicht zulässige Zeichen ( %s )!",
18 | "passwordDoubleQuotes": "Das Passwort enthält \" (Doppelte Anführungszeichen), diese sind wegen eines Bugs nicht erlaubt. Der Passwortschutz für die Backups wurde deaktivert!"
19 | }
20 | },
21 | "settings": {
22 | "section": {
23 | "label": "Backup"
24 | },
25 | "path": {
26 | "label": "Sicherungs Pfad",
27 | "description": "Speicherort für die Backups. Dieser Pfad ist exklusiv für die Joplin Backups wenn die Einstellungen für 'Erstellen eines Unterordners' deaktiviert wird, es dürfen sich dann keine anderen Daten darin befinden!"
28 | },
29 | "exportPath": {
30 | "label": "Temporärer Export Pfad",
31 | "description": "Temporärer Pfad für den Datenexport aus Joplin, bevor die Daten in den %s verschoben werden"
32 | },
33 | "backupRetention": {
34 | "label": "Behalte x Sicherungen",
35 | "description": "Wie viele Sicherungen aufbewahrt werden sollen. Wenn mehr als eine Version konfiguriert ist, werden die Ordner im Sicherungspfad entsprechend der Einstellung 'Sicherungsset Namen' erstellt"
36 | },
37 | "backupInterval": {
38 | "label": "Sicherungsinterval in Stunden",
39 | "description": "0 = Automatisches Sicherung ist deaktivert"
40 | },
41 | "onlyOnChange": {
42 | "label": "Nur bei änderung",
43 | "description": "Erstellt eine Sicherung im angegebenen Sicherungsintervall nur dann, wenn es eine Änderung in den Notizen, Tags, Dateien oder Notizbücher gab"
44 | },
45 | "usePassword": {
46 | "label": "Passwort geschütztes Sicherung",
47 | "description": "Die Sicherung wird mittels verschlüsseltem Archive geschützt"
48 | },
49 | "password": {
50 | "label": "Passwort",
51 | "description": "Wenn ein Passwort eingegeben wurde, sind die Sicherungen mit einem Passwort geschützt"
52 | },
53 | "passwordRepeat": {
54 | "label": "Passwort wiederholen",
55 | "description": "Wiederholen Sie das Passwort, um dieses zu bestätigen"
56 | },
57 | "fileLogLevel": {
58 | "label": "Protokollstufe",
59 | "description": "Protokollstufe für die Backup Logdatei",
60 | "value": {
61 | "false": "Aus",
62 | "verbose": "Ausführlich",
63 | "info": "Info",
64 | "warn": "Warnung",
65 | "error": "Fehler"
66 | }
67 | },
68 | "createSubfolder": {
69 | "label": "Erstellen eines Unterordners",
70 | "description": "Erstellt einen Unterordner im konfigurierten %s. Nur deaktivieren, wenn sich keine weiteren Daten im %s befinden!"
71 | },
72 | "createSubfolderPerProfile": {
73 | "label": "Unterordner für Joplin profile",
74 | "description": "Erstellt einen Unterordner innerhalb des Sicherungsverzeichnisses für das aktuelle Profil. Dadurch können mehrere Profile derselben Joplin Installation dasselbe Sicherungsverzeichnis verwenden, ohne dass Sicherungen anderer Profile überschrieben werden. Alle Profile, die dasselbe Sicherungsverzeichnis verwenden, müssen diese Einstellung aktiviert haben"
75 | },
76 | "zipArchive": {
77 | "label": "Erstelle ein Archive",
78 | "description": "Backup Daten in einem Archiv speichern, wenn ein Passwortschutz für die Sicherung eingestellt ist wird immer ein Archiv erstellt",
79 | "value": {
80 | "no": "Nein",
81 | "yes": "Ja",
82 | "yesone": "Ja, eine archive Datei"
83 | }
84 | },
85 | "compressionLevel": {
86 | "label": "ZIP Komprimierungsgrad",
87 | "description": "Komprimierungsgrad für das Archiv",
88 | "value": {
89 | "copy": "Kopieren (keine Komprimierung)",
90 | "fastest": "Am schnellsten",
91 | "fast": "Schnell",
92 | "normal": "Normal",
93 | "maximum": "Maximal",
94 | "ultra": "Ultra"
95 | }
96 | },
97 | "backupSetName": {
98 | "label": "Sicherungsset Namen",
99 | "description": "Name des Sicherungssatzes, wenn mehrere Sicherungen aufbewahrt werden sollen. Moment Token (https://momentjs.com/docs/#/displaying/format/) können mittels {TOKEN} verwendet werden"
100 | },
101 | "backupPlugins": {
102 | "label": "Plugins sichern",
103 | "description": "Plugin jpl Dateien mit sichern (Es werden keine Plugin Einstellungen gesichert!)"
104 | },
105 | "exportFormat": {
106 | "label": "Export Format",
107 | "description": "Joplin Datenexportformat während der Sicherung"
108 | },
109 | "singleJex": {
110 | "label": "Eine JEX Datei",
111 | "description": "Erstellt nur eine JEX Datei (Empfohlen, um den Verlust interner Notizverknüpfungen oder der Ordnerstruktur bei einer Wiederherstellung zu vermeiden!)"
112 | },
113 | "execFinishCmd": {
114 | "label": "Befehl nach der Sicherung",
115 | "description": "Befehl/Program nach der Sicherung ausführen"
116 | }
117 | },
118 | "backupReadme": "# Joplin Sicherung\n\nDieser Ordner enthält eine oder mehrere Sicherungen aus der Joplin Note Anwendung.\n\nSiehe [Backup documentation](https://joplinapp.org/plugins/plugin/io.github.jackgruber.backup/#restore) für Informationen wie eine Sicherung wieder hergestellt werden kann.",
119 | "command": {
120 | "createBackup": "Backup erstellen"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/locales/en_US.json:
--------------------------------------------------------------------------------
1 | {
2 | "msg": {
3 | "backup": {
4 | "completed": "Backup completed"
5 | },
6 | "error": {
7 | "PluginUpgrade": "Upgrade error %s: %s",
8 | "folderCreation": "Error on folder creation: %s",
9 | "ConfigureBackupPath": "Please configure backup path in Joplin Tools > Options > Backup",
10 | "PasswordMissMatch": "Passwords do not match!",
11 | "BackupPathDontExist": "The Backup path '%s' does not exist!",
12 | "BackupAlreadyRunning": "A backup is already running!",
13 | "Backup": "Backup error for %s: %s",
14 | "fileCopy": "Error on file/folder copy in %s: %s",
15 | "deleteFile": "Error on file/folder delete in %s: %s",
16 | "backupPathContainsImportantDir": "The backup path is or contains an important directory (%s) that could be overwritten by a backup. Without enabling the subfolder setting, this is not allowed!",
17 | "BackupSetNotSupportedChars": "Backup set name does contain not allowed characters ( %s )!",
18 | "passwordDoubleQuotes": "Password contains \" (double quotes), these are not allowed because of a bug. Password protection for the backup is deactivated!"
19 | }
20 | },
21 | "settings": {
22 | "section": {
23 | "label": "Backup"
24 | },
25 | "path": {
26 | "label": "Backup path",
27 | "description": "Storage location for the backups. This path is exclusive to Joplin backups when the 'Create Subfolder' setting is disabled: there should be no other data in it!"
28 | },
29 | "exportPath": {
30 | "label": "Temporary export path",
31 | "description": "Temporary path for data export from Joplin, before the data is moved to the backup path"
32 | },
33 | "backupRetention": {
34 | "label": "Keep x backups",
35 | "description": "How many backups should be kept. If more than one version configured, folders are created in the Backup Path according to 'Backup set name' setting"
36 | },
37 | "backupInterval": {
38 | "label": "Backup interval in hours",
39 | "description": "0 = disable automatic backup"
40 | },
41 | "onlyOnChange": {
42 | "label": "Only on change",
43 | "description": "Creates a backup at the specified backup interval only if there was a change to a `note`, `tag`, `resource` or `notebook`"
44 | },
45 | "usePassword": {
46 | "label": "Password protected backups",
47 | "description": "Protect the backups via encrypted archive"
48 | },
49 | "password": {
50 | "label": "Password",
51 | "description": "If a password has been entered, the backups are protected with a password"
52 | },
53 | "passwordRepeat": {
54 | "label": "Password (Repeat)",
55 | "description": "Repeat password to validate"
56 | },
57 | "fileLogLevel": {
58 | "label": "Log level",
59 | "description": "Log level for the backup log file",
60 | "value": {
61 | "false": "Off",
62 | "verbose": "Verbose",
63 | "info": "Info",
64 | "warn": "Warning",
65 | "error": "Error"
66 | }
67 | },
68 | "createSubfolder": {
69 | "label": "Create Subfolder",
70 | "description": "Create a subfolder in the configured {{backupPath}}. Deactivate only if there is no other data in the {{backupPath}}!"
71 | },
72 | "createSubfolderPerProfile": {
73 | "label": "Create subfolder for Joplin profile",
74 | "description": "Create a subfolder within the backup directory for the current profile. This allows multiple profiles from the same Joplin installation to use the same backup directory without overwriting backups made from other profiles. All profiles that use the same backup directory must have this setting enabled."
75 | },
76 | "zipArchive": {
77 | "label": "Create archive",
78 | "description": "Save backup data in an archive. If 'Password protected backups' is set, an archive is always created.",
79 | "value": {
80 | "no": "No",
81 | "yes": "Yes",
82 | "yesone": "Yes, one archive"
83 | }
84 | },
85 | "compressionLevel": {
86 | "label": "Compression level",
87 | "description": "Compression level for archive",
88 | "value": {
89 | "copy": "Copy (no compression)",
90 | "fastest": "Fastest",
91 | "fast": "Fast",
92 | "normal": "Normal",
93 | "maximum": "Maximum",
94 | "ultra": "Ultra"
95 | }
96 | },
97 | "backupSetName": {
98 | "label": "Backup set name",
99 | "description": "Name of the backup set if multiple backups are to be kept. Moment Token (https://momentjs.com/docs/#/displaying/format/) can be used with {TOKEN}"
100 | },
101 | "backupPlugins": {
102 | "label": "Backup plugins",
103 | "description": "Backup plugin jpl files (No plugin settings!)"
104 | },
105 | "exportFormat": {
106 | "label": "Export format",
107 | "description": "Joplin data export format during the backup"
108 | },
109 | "singleJex": {
110 | "label": "Single JEX",
111 | "description": "Create only one JEX file (Recommended to prevent the loss of internal note links or folder structure during a restore!)"
112 | },
113 | "execFinishCmd": {
114 | "label": "Command on Backup finish",
115 | "description": "Execute command when backup is finished"
116 | }
117 | },
118 | "backupReadme": "# Joplin Backup\n\nThis folder contains one or more backups of data from the Joplin note taking application.\n\nSee the [Backup documentation](https://joplinapp.org/plugins/plugin/io.github.jackgruber.backup/#restore) for information about how to restore from this backup.",
119 | "command": {
120 | "createBackup": "Create backup"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/locales/ro_MD.json:
--------------------------------------------------------------------------------
1 | {
2 | "msg": {
3 | "backup": {
4 | "completed": "Copie de siguranță finalizată"
5 | },
6 | "error": {
7 | "PluginUpgrade": "Eroare actualizare %s: %s",
8 | "folderCreation": "Eroare la crearea dosarului: %s",
9 | "ConfigureBackupPath": "Te rog configurează calea pentru copia de siguranță în Joplin din meniul Unelte > Opțiuni > Backup",
10 | "PasswordMissMatch": "Parolele nu se potrivesc!",
11 | "BackupPathDontExist": "Calea „%s” pentru copia de siguranță nu există!",
12 | "BackupAlreadyRunning": "O copiere de siguranță este deja în desfășurare!",
13 | "Backup": "Eroare copie de siguranță pentru %s: %s",
14 | "fileCopy": "Eroare la copierea fișierului/dosarului în %s: %s",
15 | "deleteFile": "Eroare la ștergerea fișierului/dosarului în %s: %s",
16 | "backupPathContainsImportantDir": "Calea pentru copia de siguranță este sau conține un dosar important (%s) care ar putea fi suprascris de o copie de siguranță. Fără a activa setarea pentru subdosare, acest lucru nu este permis!",
17 | "BackupSetNotSupportedChars": "Numele setului copiei de siguranță conține caractere nepermise ( %s )!",
18 | "passwordDoubleQuotes": "Parola conține \" (ghilimele). Acestea nu sînt permise din cauza unui defect. Protectția cu parolă a copiei de siguranță este dezactivată!"
19 | }
20 | },
21 | "settings": {
22 | "path": {
23 | "label": "Locație copie de siguranță",
24 | "description": "Calea către locația pentru stocarea cópiilor de siguranță. Această cale este folosită exclusiv de cópiile Joplin atunci cînd setarea „Creează subdosar” este dezactivată: nu ar trebui să fie niciun fel de alte date în ea!"
25 | },
26 | "exportPath": {
27 | "label": "Cale de export temporară",
28 | "description": "Cale temporară pentru exportul de date din Joplin, folosită înainte de mutarea datelor în calea copiei de siguranță."
29 | },
30 | "backupRetention": {
31 | "label": "Păstrează n cópii de siguranță",
32 | "description": "Cîte cópii de siguranță ar trebui păstrate. Dacă este configurată mai mult de o versiune, se vor creea dosare în calea dată în concordanță cu setarea „Nume set copie de siguranță”."
33 | },
34 | "backupInterval": {
35 | "label": "Interval de copiere în ore",
36 | "description": "0 = dezactivează copierea automată"
37 | },
38 | "onlyOnChange": {
39 | "label": "Doar la schimbare",
40 | "description": "Creează o copie de siguranță la intervalul de timp specificat doar dacă s-a detectat o schimbare la o notiță, etichetă, resursă sau caiet."
41 | },
42 | "usePassword": {
43 | "label": "Protejează cópiile de siguranță cu parolă",
44 | "description": "Realizează o arhivă criptată a cópiilor."
45 | },
46 | "password": {
47 | "label": "Parolă",
48 | "description": "Dacă este întrodusă o parolă, cópiile vor fi protejate."
49 | },
50 | "passwordRepeat": {
51 | "label": "Parolă (din nou)",
52 | "description": "Repetă parola pentru a valida."
53 | },
54 | "fileLogLevel": {
55 | "label": "Nivel juranlizare",
56 | "description": "Nivelul de severitate pentru jurnalul copiei de siguranță.",
57 | "value": {
58 | "false": "fără",
59 | "verbose": "detaliat",
60 | "info": "informativ",
61 | "warn": "atenționări",
62 | "error": "erori"
63 | }
64 | },
65 | "createSubfolder": {
66 | "label": "Creează subdosar",
67 | "description": "Creează un subdosar în calea copiei de siguranță date. Dezactivează doar dacă nu există alte date în calea copiei de siguranță!"
68 | },
69 | "createSubfolderPerProfile": {
70 | "label": "Creează subdosar pentru profilul Joplin",
71 | "description": "Creează un subdosar în dosarul copiei de siguranță pentru profilul curent. Acest lucru permite ca mai multe profile din instalarea curentă a Joplin să folosească același dosar de cópii de siguranță fără să suprascrie cópiile create de alte profile. Toate profilele care folosesc același dosar de cópii trebuie să aibă această opțiune activată."
72 | },
73 | "zipArchive": {
74 | "label": "Creează arhivă",
75 | "description": "Salvează datele copiate într-o arhivă. Dacă „Protejează cópiile de siguranță cu parolă” este activat, o arhivă este mereu creată.",
76 | "value": {
77 | "no": "nu",
78 | "yes": "da",
79 | "yesone": "da, o singură arhivă"
80 | }
81 | },
82 | "compressionLevel": {
83 | "label": "Nivel compresie",
84 | "description": "Gradul de compresie al arhivei",
85 | "value": {
86 | "copy": "fără compresie",
87 | "fastest": "cea mai rapidă",
88 | "fast": "rapidă",
89 | "normal": "normală",
90 | "maximum": "maximă",
91 | "ultra": "ultra"
92 | }
93 | },
94 | "backupSetName": {
95 | "label": "Nume set copie de siguranță",
96 | "description": "Numele unui set de cópii dacă mai multe cópii trebuie păstrate. Token-uri Moment (https://momentjs.com/docs/#/displaying/format/) pot fi utilizate cu {TOKEN}"
97 | },
98 | "backupPlugins": {
99 | "label": "Copiază plugin-urile",
100 | "description": "Copiază și fișierele jpl ale plugin-urilor (fără setări!)"
101 | },
102 | "exportFormat": {
103 | "label": "Format export",
104 | "description": "Formatul folosit la exportarea notițelor din Joplin"
105 | },
106 | "singleJex": {
107 | "label": "Un singur fișier JEX",
108 | "description": "Creează doar un singur fișier JEX (Recomandat pentru a preveni pierderile de date legate de legăturile dintre note și structura ierarhică în timpul restaurării!)"
109 | },
110 | "execFinishCmd": {
111 | "label": "Comandă la finalizarea copierii de siguranță",
112 | "description": "Execută o comandă la terminarea copierii"
113 | }
114 | },
115 | "backupReadme": "# Copie de siguranță Joplin\n\nAcest dosar conține una sau mai multe cópii de siguranță ale datelor din aplicația de notițe Joplin.\n\nVezi [documentația](https://joplinapp.org/plugins/plugin/io.github.jackgruber.backup/#restore) pentru informații legate de cum să restaurezi copia de siguranță.",
116 | "command": {
117 | "createBackup": "Creează o copie de siguranță"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/locales/ro_RO.json:
--------------------------------------------------------------------------------
1 | {
2 | "msg": {
3 | "backup": {
4 | "completed": "Copie de siguranță finalizată"
5 | },
6 | "error": {
7 | "PluginUpgrade": "Eroare actualizare %s: %s",
8 | "folderCreation": "Eroare la crearea dosarului: %s",
9 | "ConfigureBackupPath": "Te rog configurează calea pentru copia de siguranță în Joplin din meniul Unelte > Opțiuni > Backup",
10 | "PasswordMissMatch": "Parolele nu se potrivesc!",
11 | "BackupPathDontExist": "Calea „%s” pentru copia de siguranță nu există!",
12 | "BackupAlreadyRunning": "O copiere de siguranță este deja în desfășurare!",
13 | "Backup": "Eroare copie de siguranță pentru %s: %s",
14 | "fileCopy": "Eroare la copierea fișierului/dosarului în %s: %s",
15 | "deleteFile": "Eroare la ștergerea fișierului/dosarului în %s: %s",
16 | "backupPathContainsImportantDir": "Calea pentru copia de siguranță este sau conține un dosar important (%s) care ar putea fi suprascris de o copie de siguranță. Fără a activa setarea pentru subdosare, acest lucru nu este permis!",
17 | "BackupSetNotSupportedChars": "Numele setului copiei de siguranță conține caractere nepermise ( %s )!",
18 | "passwordDoubleQuotes": "Parola conține \" (ghilimele). Acestea nu sunt permise din cauza unui defect. Protectția cu parolă a copiei de siguranță este dezactivată!"
19 | }
20 | },
21 | "settings": {
22 | "path": {
23 | "label": "Locație copie de siguranță",
24 | "description": "Calea către locația pentru stocarea cópiilor de siguranță. Această cale este folosită exclusiv de cópiile Joplin atunci când setarea „Creează subdosar” este dezactivată: nu ar trebui să fie niciun fel de alte date în ea!"
25 | },
26 | "exportPath": {
27 | "label": "Cale de export temporară",
28 | "description": "Cale temporară pentru exportul de date din Joplin, folosită înainte de mutarea datelor în calea copiei de siguranță."
29 | },
30 | "backupRetention": {
31 | "label": "Păstrează n cópii de siguranță",
32 | "description": "Câte cópii de siguranță ar trebui păstrate. Dacă este configurată mai mult de o versiune, se vor creea dosare în calea dată în concordanță cu setarea „Nume set copie de siguranță”."
33 | },
34 | "backupInterval": {
35 | "label": "Interval de copiere în ore",
36 | "description": "0 = dezactivează copierea automată"
37 | },
38 | "onlyOnChange": {
39 | "label": "Doar la schimbare",
40 | "description": "Creează o copie de siguranță la intervalul de timp specificat doar dacă s-a detectat o schimbare la o notiță, etichetă, resursă sau caiet."
41 | },
42 | "usePassword": {
43 | "label": "Protejează cópiile de siguranță cu parolă",
44 | "description": "Realizează o arhivă criptată a cópiilor."
45 | },
46 | "password": {
47 | "label": "Parolă",
48 | "description": "Dacă este introdusă o parolă, cópiile vor fi protejate."
49 | },
50 | "passwordRepeat": {
51 | "label": "Parolă (din nou)",
52 | "description": "Repetă parola pentru a valida."
53 | },
54 | "fileLogLevel": {
55 | "label": "Nivel juranlizare",
56 | "description": "Nivelul de severitate pentru jurnalul copiei de siguranță.",
57 | "value": {
58 | "false": "fără",
59 | "verbose": "detaliat",
60 | "info": "informativ",
61 | "warn": "atenționări",
62 | "error": "erori"
63 | }
64 | },
65 | "createSubfolder": {
66 | "label": "Creează subdosar",
67 | "description": "Creează un subdosar în calea copiei de siguranță date. Dezactivează doar dacă nu există alte date în calea copiei de siguranță!"
68 | },
69 | "createSubfolderPerProfile": {
70 | "label": "Creează subdosar pentru profilul Joplin",
71 | "description": "Creează un subdosar în dosarul copiei de siguranță pentru profilul curent. Acest lucru permite ca mai multe profile din instalarea curentă a Joplin să folosească același dosar de cópii de siguranță fără să suprascrie cópiile create de alte profile. Toate profilele care folosesc același dosar de cópii trebuie să aibă această opțiune activată."
72 | },
73 | "zipArchive": {
74 | "label": "Creează arhivă",
75 | "description": "Salvează datele copiate într-o arhivă. Dacă „Protejează cópiile de siguranță cu parolă” este activat, o arhivă este mereu creată.",
76 | "value": {
77 | "no": "nu",
78 | "yes": "da",
79 | "yesone": "da, o singură arhivă"
80 | }
81 | },
82 | "compressionLevel": {
83 | "label": "Nivel compresie",
84 | "description": "Gradul de compresie al arhivei",
85 | "value": {
86 | "copy": "fără compresie",
87 | "fastest": "cea mai rapidă",
88 | "fast": "rapidă",
89 | "normal": "normală",
90 | "maximum": "maximă",
91 | "ultra": "ultra"
92 | }
93 | },
94 | "backupSetName": {
95 | "label": "Nume set copie de siguranță",
96 | "description": "Numele unui set de cópii dacă mai multe cópii trebuie păstrate. Token-uri Moment (https://momentjs.com/docs/#/displaying/format/) pot fi utilizate cu {TOKEN}"
97 | },
98 | "backupPlugins": {
99 | "label": "Copiază plugin-urile",
100 | "description": "Copiază și fișierele jpl ale plugin-urilor (fără setări!)"
101 | },
102 | "exportFormat": {
103 | "label": "Format export",
104 | "description": "Formatul folosit la exportarea notițelor din Joplin"
105 | },
106 | "singleJex": {
107 | "label": "Un singur fișier JEX",
108 | "description": "Creează doar un singur fișier JEX (Recomandat pentru a preveni pierderile de date legate de legăturile dintre note și structura ierarhică în timpul restaurării!)"
109 | },
110 | "execFinishCmd": {
111 | "label": "Comandă la finalizarea copierii de siguranță",
112 | "description": "Execută o comandă la terminarea copierii"
113 | }
114 | },
115 | "backupReadme": "# Copie de siguranță Joplin\n\nAcest dosar conține una sau mai multe cópii de siguranță ale datelor din aplicația de notițe Joplin.\n\nVezi [documentația](https://joplinapp.org/plugins/plugin/io.github.jackgruber.backup/#restore) pentru informații legate de cum să restaurezi copia de siguranță.",
116 | "command": {
117 | "createBackup": "Creează o copie de siguranță"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/locales/sk_SK.json:
--------------------------------------------------------------------------------
1 | {
2 | "msg": {
3 | "backup": {
4 | "completed": "Zálohovanie dokončené"
5 | },
6 | "error": {
7 | "PluginUpgrade": "Chyba aktualizácie %s: %s",
8 | "folderCreation": "Chyba pri vytváraní priečinka: %s",
9 | "ConfigureBackupPath": "Prosím, nastavte cestu zálohy Joplin v ponuke Nástroje > Možnosti > Zálohovanie",
10 | "PasswordMissMatch": "Heslá sa nezhodujú!",
11 | "BackupPathDontExist": "Cesta zálohy '%s' neexistuje!",
12 | "BackupAlreadyRunning": "Zálohovanie už prebieha!",
13 | "Backup": "Chyba zálohovania pre %s: %s",
14 | "fileCopy": "Chyba pri kopírovaní súboru/priečinka v %s: %s",
15 | "deleteFile": "Chyba pri mazaní súboru/priečinka v %s: %s",
16 | "backupPathContainsImportantDir": "Cesta zálohy je alebo obsahuje dôležitý priečinok (%s), ktorý by mohol byť prepísaný zálohou. Bez povolenia nastavenia podpriečinka toto nie je povolené!",
17 | "BackupSetNotSupportedChars": "Názov záložnej sady obsahuje nepovolené znaky ( %s )!",
18 | "passwordDoubleQuotes": "Heslo obsahuje \" (úvodzovky). Tieto nie sú povolené kvôli chybe. Ochrana heslom pre zálohu je deaktivovaná!"
19 | }
20 | },
21 | "settings": {
22 | "path": {
23 | "label": "Cesta zálohovania",
24 | "description": "Umiestnenie úložiska pre zálohy. Táto cesta je výhradne pre zálohy Joplin, keď je nastavenie 'Vytvoriť podpriečinok' zakázané: nemali by v ňom byť žiadne iné údaje!"
25 | },
26 | "exportPath": {
27 | "label": "Dočasná cesta exportu",
28 | "description": "Dočasná cesta pre export údajov z aplikácie Joplin pred presunom údajov do zálohovacej cesty"
29 | },
30 | "backupRetention": {
31 | "label": "Ponechať x záloh",
32 | "description": "Koľko záloh by sa malo zachovať. Ak je nastavená viac ako jedna verzia, priečinky sa vytvárajú v ceste zálohovania podľa nastavenia 'Názov záložnej sady'"
33 | },
34 | "backupInterval": {
35 | "label": "Interval zálohovania v hodinách",
36 | "description": "0 = vypnúť automatické zálohovanie"
37 | },
38 | "onlyOnChange": {
39 | "label": "Iba pri zmene",
40 | "description": "Vytvorí zálohu v zadanom intervale zálohovania iba vtedy, ak došlo v `poznámke`, `štítku`, `zdroji` alebo `zápisníku` ku zmene"
41 | },
42 | "usePassword": {
43 | "label": "Zálohy chránené heslom",
44 | "description": "Chrániť zálohy prostredníctvom šifrovaného archívu"
45 | },
46 | "password": {
47 | "label": "Heslo",
48 | "description": "Ak bolo zadané heslo, zálohy sú chránené heslom"
49 | },
50 | "passwordRepeat": {
51 | "label": "Heslo (opakovať)",
52 | "description": "Zopakovať heslo pre overenie"
53 | },
54 | "fileLogLevel": {
55 | "label": "Úroveň záznamu",
56 | "description": "Úroveň záznamu pre súbor záznamu zálohy",
57 | "value": {
58 | "false": "Vypnuté",
59 | "verbose": "Podrobné",
60 | "info": "Informácie",
61 | "warn": "Upozornenie",
62 | "error": "Chyba"
63 | }
64 | },
65 | "createSubfolder": {
66 | "label": "Vytvoriť podpriečinok",
67 | "description": "Vytvoriť podpriečinok v nastavenej {backupPath}. Deaktivujte iba vtedy, ak v {backupPath} nie sú žiadne iné údaje!"
68 | },
69 | "createSubfolderPerProfile": {
70 | "label": "Vytvoriť podpriečinok pre profil Joplin",
71 | "description": "Vytvoriť podpriečinok v rámci priečinku zálohy pre aktuálny profil. To umožňuje viacerým profilom z rovnakej inštalácie Joplin používať rovnaký priečinok zálohy bez prepisovania záloh vytvorených z iných profilov. Všetky profily, ktoré používajú rovnaký priečinok zálohy, musia mať toto nastavenie povolené."
72 | },
73 | "zipArchive": {
74 | "label": "Vytvoriť archív",
75 | "description": "Uložiť zálohované údaje do archívu. Ak sú nastavené 'Zálohy chránené heslom', archív sa vždy vytvorí.",
76 | "value": {
77 | "no": "Nie",
78 | "yes": "Áno",
79 | "yesone": "Áno, jeden archív"
80 | }
81 | },
82 | "compressionLevel": {
83 | "label": "Úroveň kompresie",
84 | "description": "Úroveň kompresie pre archív",
85 | "value": {
86 | "copy": "Kopírovať (žiadna kompresia)",
87 | "fastest": "Najrýchlejšia",
88 | "fast": "Rýchla",
89 | "normal": "Normálna",
90 | "maximum": "Maximálna",
91 | "ultra": "Ultra"
92 | }
93 | },
94 | "backupSetName": {
95 | "label": "Názov záložnej sady",
96 | "description": "Názov záložnej sady, ak sa má uchovávať viacero záloh. Moment Token (https://momentjs.com/docs/#/displaying/format/) je možné použiť s {TOKEN}"
97 | },
98 | "backupPlugins": {
99 | "label": "Zálohovať doplnky",
100 | "description": "Zálohovať jpl súbory doplnku (Nie nastavenia doplnku!)"
101 | },
102 | "exportFormat": {
103 | "label": "Formát exportu",
104 | "description": "Formát exportu údajov aplikácie Joplin počas zálohovania"
105 | },
106 | "singleJex": {
107 | "label": "Jediný JEX",
108 | "description": "Vytvoriť iba jeden JEX súbor (Odporúča sa, aby sa predišlo strate interných odkazov na poznámky alebo štruktúry priečinkov počas obnovy!)"
109 | },
110 | "execFinishCmd": {
111 | "label": "Príkaz po dokončení zálohy",
112 | "description": "Spustiť príkaz po dokončení zálohy"
113 | }
114 | },
115 | "backupReadme": "# Joplin Backup\n\nTento priečinok obsahuje jednu alebo viac záloh údajov z aplikácie na zapisovanie poznámok Joplin.\n\nPozrite si [Dokumentáciu zálohovania](https://joplinapp.org/plugins/plugin/io.github.jackgruber.backup/#restore) pre informácie o tom, ako obnoviť túto zálohu.",
116 | "command": {
117 | "createBackup": "Vytvoriť zálohu"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/locales/zh_CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "msg": {
3 | "backup": {
4 | "completed": "备份完成"
5 | },
6 | "error": {
7 | "PluginUpgrade": "升级错误 %s: %s",
8 | "folderCreation": "创建文件夹时出错: %s",
9 | "ConfigureBackupPath": "请在Joplin > 工具 > 选项 > 备份 中配置备份路径",
10 | "PasswordMissMatch": "密码不正确!",
11 | "BackupPathDontExist": "备份路径 '%s' 不存在!",
12 | "BackupAlreadyRunning": "已经在进行备份!",
13 | "Backup": "备份为 %s 时出错: %s",
14 | "fileCopy": " %s 时复制文件/文件夹出错: %s",
15 | "deleteFile": " %s 时删除文件/文件夹出错: %s",
16 | "BackupSetNotSupportedChars": "“备份集名称”包含不允许的字符 ( %s )!"
17 | }
18 | },
19 | "settings": {
20 | "path": {
21 | "label": "备份路径"
22 | },
23 | "exportPath": {
24 | "label": "临时导出路径",
25 | "description": "用于导出的临时路径,笔记在被移动到“备份路径”前会存于此处"
26 | },
27 | "backupRetention": {
28 | "label": "保留 x 个备份",
29 | "description": "如果设置了多个备份, 则会根据“备份集名称”在“备份路径”下创建多个子文件夹"
30 | },
31 | "backupInterval": {
32 | "label": "备份间隔(以小时为单位)",
33 | "description": "0 = 停止自动备份"
34 | },
35 | "onlyOnChange": {
36 | "label": "仅在更改时备份",
37 | "description": "仅当笔记发生更改时,在指定的备份间隔内创建备份"
38 | },
39 | "usePassword": {
40 | "label": "使用密码保护备份",
41 | "description": "备份将通过加密的压缩文件进行保护"
42 | },
43 | "password": {
44 | "label": "密码",
45 | "description": "如果输入了密码,则备份将受到密码保护"
46 | },
47 | "passwordRepeat": {
48 | "label": "重复密码",
49 | "description": "请重复输入密码以确认"
50 | },
51 | "fileLogLevel": {
52 | "label": "日志级别"
53 | },
54 | "createSubfolder": {
55 | "label": "创建子文件夹",
56 | "description": "在配置的备份路径中创建一个子文件夹。请在“备份路径”中没有其他数据时才禁用!"
57 | },
58 | "zipArchive": {
59 | "label": "创建压缩文件",
60 | "description": "如果设置了“使用密码保护备份”,则总是会创建压缩文件"
61 | },
62 | "compressionLevel": {
63 | "label": "压缩等级",
64 | "description": "压缩文件的压缩等级"
65 | },
66 | "backupSetName": {
67 | "label": "备份集名称",
68 | "description": "如果要保留多个备份,请为备份集指定名称"
69 | },
70 | "backupPlugins": {
71 | "label": "备份插件",
72 | "description": "备份插件的JPL文件(不会备份插件设置!)"
73 | },
74 | "exportFormat": {
75 | "label": "导出格式",
76 | "description": "笔记的备份格式"
77 | },
78 | "singleJex": {
79 | "label": "生成单个JEX文件",
80 | "description": "为所有笔记本创建单个JEX文件(建议选中,以免丢失内部笔记链接和文件夹结构)"
81 | },
82 | "execFinishCmd": {
83 | "label": "备份后执行命令",
84 | "description": "备份完成后所执行的命令/程序"
85 | }
86 | },
87 | "command": {
88 | "createBackup": "创建备份"
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 1,
3 | "id": "io.github.jackgruber.backup",
4 | "app_min_version": "2.1.3",
5 | "version": "1.4.4",
6 | "name": "Backup",
7 | "description": "Plugin to create manual and automatic backups.",
8 | "author": "JackGruber",
9 | "homepage_url": "https://github.com/JackGruber/joplin-plugin-backup/blob/master/README.md",
10 | "repository_url": "https://github.com/JackGruber/joplin-plugin-backup",
11 | "keywords": [
12 | "backup",
13 | "jex",
14 | "export",
15 | "zip",
16 | "7zip",
17 | "encrypted",
18 | "archive"
19 | ],
20 | "categories": ["productivity", "files"],
21 | "screenshots": [
22 | {
23 | "src": "img/main.png",
24 | "label": "Screenshot: Showing the basic settings"
25 | },
26 | {
27 | "src": "img/showcase1.png",
28 | "label": "Screenshot: Showing the advanced settings"
29 | }
30 | ],
31 | "icons": {
32 | "256": "img/icon_256.png"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/settings.ts:
--------------------------------------------------------------------------------
1 | import joplin from "api";
2 | import { SettingItemType, SettingItemSubType } from "api/types";
3 | import { helper } from "./helper";
4 | import { i18n } from "./Backup";
5 |
6 | export namespace Settings {
7 | export async function register() {
8 | await joplin.settings.registerSection("backupSection", {
9 | label: i18n.__("settings.section.label"),
10 | iconName: "fas fa-archive",
11 | });
12 |
13 | const joplinVersionInfo = await helper.joplinVersionInfo();
14 | let pathSettings = null;
15 | pathSettings = {
16 | value: "",
17 | type: SettingItemType.String,
18 | section: "backupSection",
19 | public: true,
20 | label: i18n.__("settings.path.label"),
21 | description: i18n.__("settings.path.description"),
22 | };
23 |
24 | let exportPathSettings = null;
25 | exportPathSettings = {
26 | value: "",
27 | type: SettingItemType.String,
28 | section: "backupSection",
29 | public: true,
30 | advanced: true,
31 | label: i18n.__("settings.exportPath.label"),
32 | description: i18n.__("settings.exportPath.description"),
33 | };
34 |
35 | // Add DirectoryPath selector for newer Joplin versions
36 | if (
37 | joplinVersionInfo !== null &&
38 | (await helper.versionCompare(joplinVersionInfo.version, "2.10.4")) >= 0
39 | ) {
40 | pathSettings["subType"] = SettingItemSubType.DirectoryPath;
41 | exportPathSettings["subType"] = SettingItemSubType.DirectoryPath;
42 | }
43 |
44 | // Make export Format only onb Joplin > 2.9.12 public
45 | let exportFormatPublic = false;
46 | if (
47 | joplinVersionInfo !== null &&
48 | (await helper.versionCompare(joplinVersionInfo.version, "2.9.12")) >= 0
49 | ) {
50 | exportFormatPublic = true;
51 | }
52 |
53 | await joplin.settings.registerSettings({
54 | path: pathSettings,
55 | backupRetention: {
56 | value: 1,
57 | minimum: 1,
58 | maximum: 999,
59 | type: SettingItemType.Int,
60 | section: "backupSection",
61 | public: true,
62 | label: i18n.__("settings.backupRetention.label"),
63 | description: i18n.__("settings.backupRetention.description"),
64 | },
65 | backupInterval: {
66 | value: 24,
67 | minimum: 0,
68 | maximum: 999,
69 | type: SettingItemType.Int,
70 | section: "backupSection",
71 | public: true,
72 | label: i18n.__("settings.backupInterval.label"),
73 | description: i18n.__("settings.backupInterval.description"),
74 | },
75 | onlyOnChange: {
76 | value: false,
77 | type: SettingItemType.Bool,
78 | section: "backupSection",
79 | public: true,
80 | label: i18n.__("settings.onlyOnChange.label"),
81 | description: i18n.__("settings.onlyOnChange.description"),
82 | },
83 | usePassword: {
84 | value: false,
85 | type: SettingItemType.Bool,
86 | section: "backupSection",
87 | public: true,
88 | label: i18n.__("settings.usePassword.label"),
89 | description: i18n.__("settings.usePassword.description"),
90 | },
91 | password: {
92 | value: "password",
93 | type: SettingItemType.String,
94 | section: "backupSection",
95 | public: true,
96 | secure: true,
97 | label: i18n.__("settings.password.label"),
98 | description: i18n.__("settings.password.description"),
99 | },
100 | passwordRepeat: {
101 | value: "repeat12",
102 | type: SettingItemType.String,
103 | section: "backupSection",
104 | public: true,
105 | secure: true,
106 | label: i18n.__("settings.passwordRepeat.label"),
107 | description: i18n.__("settings.passwordRepeat.description"),
108 | },
109 | lastBackup: {
110 | value: 0,
111 | type: SettingItemType.Int,
112 | section: "backupSection",
113 | public: false,
114 | label: "last backup run",
115 | },
116 | fileLogLevel: {
117 | value: "error",
118 | type: SettingItemType.String,
119 | section: "backupSection",
120 | isEnum: true,
121 | public: true,
122 | label: i18n.__("settings.fileLogLevel.label"),
123 | description: i18n.__("settings.fileLogLevel.description"),
124 | options: {
125 | false: i18n.__("settings.fileLogLevel.value.false"),
126 | verbose: i18n.__("settings.fileLogLevel.value.verbose"),
127 | info: i18n.__("settings.fileLogLevel.value.info"),
128 | warn: i18n.__("settings.fileLogLevel.value.warn"),
129 | error: i18n.__("settings.fileLogLevel.value.error"),
130 | },
131 | },
132 | createSubfolder: {
133 | value: true,
134 | type: SettingItemType.Bool,
135 | section: "backupSection",
136 | public: true,
137 | advanced: true,
138 | label: i18n.__("settings.createSubfolder.label"),
139 | description: i18n.__("settings.createSubfolder.description", {
140 | backupPath: i18n.__("settings.path.label"),
141 | }),
142 | },
143 | createSubfolderPerProfile: {
144 | value: false,
145 | type: SettingItemType.Bool,
146 | section: "backupSection",
147 | public: true,
148 | advanced: true,
149 | label: i18n.__("settings.createSubfolderPerProfile.label"),
150 | description: i18n.__("settings.createSubfolderPerProfile.description"),
151 | },
152 | zipArchive: {
153 | value: "no",
154 | type: SettingItemType.String,
155 | section: "backupSection",
156 | isEnum: true,
157 | public: true,
158 | advanced: true,
159 | options: {
160 | no: i18n.__("settings.zipArchive.value.no"),
161 | yes: i18n.__("settings.zipArchive.value.yes"),
162 | yesone: i18n.__("settings.zipArchive.value.yesone"),
163 | },
164 | label: i18n.__("settings.zipArchive.label"),
165 | description: i18n.__("settings.zipArchive.description"),
166 | },
167 | compressionLevel: {
168 | value: 0,
169 | type: SettingItemType.Int,
170 | section: "backupSection",
171 | isEnum: true,
172 | public: true,
173 | advanced: true,
174 | options: {
175 | 0: i18n.__("settings.compressionLevel.value.copy"),
176 | 1: i18n.__("settings.compressionLevel.value.fastest"),
177 | 3: i18n.__("settings.compressionLevel.value.fast"),
178 | 5: i18n.__("settings.compressionLevel.value.normal"),
179 | 7: i18n.__("settings.compressionLevel.value.maximum"),
180 | 9: i18n.__("settings.compressionLevel.value.ultra"),
181 | },
182 | label: i18n.__("settings.compressionLevel.label"),
183 | description: i18n.__("settings.compressionLevel.description"),
184 | },
185 | exportPath: exportPathSettings,
186 | backupSetName: {
187 | value: "{YYYYMMDDHHmm}",
188 | type: SettingItemType.String,
189 | section: "backupSection",
190 | public: true,
191 | advanced: true,
192 | label: i18n.__("settings.backupSetName.label"),
193 | description: i18n.__("settings.backupSetName.description"),
194 | },
195 | backupPlugins: {
196 | value: true,
197 | type: SettingItemType.Bool,
198 | section: "backupSection",
199 | public: true,
200 | advanced: true,
201 | label: i18n.__("settings.backupPlugins.label"),
202 | description: i18n.__("settings.backupPlugins.description"),
203 | },
204 | exportFormat: {
205 | value: "jex",
206 | type: SettingItemType.String,
207 | section: "backupSection",
208 | isEnum: true,
209 | public: exportFormatPublic,
210 | advanced: true,
211 | options: {
212 | jex: "JEX",
213 | md_frontmatter: "MD Frontmatter",
214 | raw: "RAW",
215 | },
216 | label: i18n.__("settings.exportFormat.label"),
217 | description: i18n.__("settings.exportFormat.description"),
218 | },
219 | singleJexV2: {
220 | value: true,
221 | type: SettingItemType.Bool,
222 | section: "backupSection",
223 | public: true,
224 | advanced: true,
225 | label: i18n.__("settings.singleJex.label"),
226 | description: i18n.__("settings.singleJex.description"),
227 | },
228 | singleJex: {
229 | value: false,
230 | type: SettingItemType.Bool,
231 | section: "backupSection",
232 | public: false,
233 | advanced: true,
234 | label: "Single JEX",
235 | description: "Old setting, for compatibility and upgrade only.",
236 | },
237 | execFinishCmd: {
238 | value: "",
239 | type: SettingItemType.String,
240 | section: "backupSection",
241 | public: true,
242 | advanced: true,
243 | label: i18n.__("settings.execFinishCmd.label"),
244 | description: i18n.__("settings.execFinishCmd.description"),
245 | },
246 | backupVersion: {
247 | value: 0,
248 | type: SettingItemType.Int,
249 | section: "backupSection",
250 | public: false,
251 | label: "Backup Version",
252 | },
253 | backupInfo: {
254 | value: "[]",
255 | type: SettingItemType.String,
256 | section: "backupSection",
257 | public: false,
258 | label: "Backup info",
259 | },
260 | });
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/sevenZip.ts:
--------------------------------------------------------------------------------
1 | // https://sevenzip.osdn.jp/chm/cmdline/exit_codes.htm
2 | // https://sevenzip.osdn.jp/chm/cmdline/commands/index.htm
3 | import * as _7z from "node-7z";
4 | import * as sevenBin from "7zip-bin";
5 | import * as path from "path";
6 | import { exec } from "child_process";
7 | import joplin from "api";
8 |
9 | export let pathTo7zip = sevenBin.path7za;
10 |
11 | export namespace sevenZip {
12 | export async function updateBinPath() {
13 | pathTo7zip = path.join(
14 | await joplin.plugins.installationDir(),
15 | "7zip-bin",
16 | pathTo7zip
17 | );
18 | }
19 |
20 | export async function setExecutionFlag() {
21 | if (process.platform !== "win32") {
22 | exec(`chmod +x ${pathTo7zip}`, (error, stdout, stderr) => {
23 | if (error) {
24 | console.error(`exec error: ${error}`);
25 | return;
26 | }
27 | });
28 | }
29 | }
30 |
31 | async function addPassword(
32 | _7zOptions: any,
33 | password: string
34 | ): Promise