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

Compare commits

...

29 Commits

Author SHA1 Message Date
Laurent Cozic
f7bfd5dbe7 Merge branch 'release-1.4' into api_pagination 2020-10-31 23:57:52 +00:00
Laurent Cozic
9a4b8685d2 Merge branch 'release-1.4' into api_pagination 2020-10-31 23:54:09 +00:00
Laurent
f3376c779e Desktop: Resolves #275: Adds spell checker support for Rich Text editor (#3974) 2020-10-31 23:52:46 +00:00
Laurent
76739eeff0 All: Resolves #3839: Change Markdown link rendering and soft-break default to align with CommonMark spec (#3975) 2020-10-31 23:47:55 +00:00
Laurent Cozic
6d2bad01e6 Merge branch 'release-1.3' into dev 2020-10-31 16:49:50 +00:00
Laurent Cozic
3847831d80 Merge branch 'release-1.3' of github.com:laurent22/joplin into release-1.3 2020-10-31 16:45:17 +00:00
Robin
44462f4d71 All: Translation: Update nl_NL.po (#4012) 2020-10-31 12:38:54 -04:00
Laurent Cozic
4d20589773 Desktop: Remove from keymap editor commands that cannot be associated with a shortcut 2020-10-31 16:29:17 +00:00
Laurent Cozic
52140ec1a9 Merge branch 'release-1.3' into dev 2020-10-31 13:06:06 +00:00
Laurent Cozic
08c2a7ad64 Tools: Improved HTML escape in git-changelog 2020-10-31 13:05:46 +00:00
Laurent Cozic
d19796f14c Electron release v1.3.11 2020-10-31 13:02:14 +00:00
Laurent Cozic
e4f53a48d2 Desktop: Fixed handling of Option key for shortcuts in macOS 2020-10-31 13:01:40 +00:00
Laurent Cozic
154163bd6c Desktop: Fixes #4010: Add history backward and forward commands to keymap and menus 2020-10-31 12:46:55 +00:00
Laurent Cozic
4502414934 Desktop: Make sure all commands appear in keymap editor 2020-10-31 12:25:12 +00:00
Laurent Cozic
86c471afcd Cli: Fixes #4000: Display proper error message when decryption worker cannot be started 2020-10-29 23:37:19 +00:00
Laurent Cozic
279998db2b Merge branch 'dev' into api_pagination 2020-10-29 17:26:53 +00:00
Laurent Cozic
8cfe4b0f82 Mobile: Disable beta editor for now due to bugs that cannot be fixed
and crashes.

See https://discourse.joplinapp.org/t/11658/9?u=laurent
2020-10-29 16:42:47 +00:00
Laurent Cozic
18608f4c3a Fix 2020-10-27 00:37:50 +00:00
Laurent Cozic
5059089479 Upgraded clipper to support API pagination 2020-10-27 00:37:22 +00:00
Laurent Cozic
6284c386fb linting 2020-10-26 23:17:31 +00:00
Laurent Cozic
3a63df6515 Make it easier to build API doc 2020-10-26 23:15:15 +00:00
Laurent Cozic
c08d225ae1 Updated doc 2020-10-26 23:06:11 +00:00
Laurent Cozic
594720530e Moved functions under utils 2020-10-26 22:21:01 +00:00
Laurent Cozic
e7d6675f47 Finished moving routes outside 2020-10-26 22:10:13 +00:00
Laurent Cozic
0d93f0c3c0 Refactor resources 2020-10-26 17:17:19 +00:00
Laurent Cozic
37b308d440 More notes route out 2020-10-26 14:37:35 +00:00
Laurent Cozic
1f8f3f24ed Cleaning up and trimming down Api class 2020-10-26 14:12:14 +00:00
Laurent Cozic
3713c5ab2c Better handling of default fields 2020-10-26 12:43:42 +00:00
Laurent Cozic
3a94d2c49b Tests 2020-10-25 16:54:53 +00:00
189 changed files with 2772 additions and 1392 deletions

View File

@@ -60,6 +60,7 @@ Tools/PortableAppsLauncher
Modules/TinyMCE/IconPack/postinstall.js
Modules/TinyMCE/langs/
CliClient/build/
plugin_types/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
@@ -126,6 +127,7 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/menuCommandNames.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
@@ -148,8 +150,10 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/contextMenu.js
ElectronClient/gui/NoteEditor/utils/index.js
@@ -206,8 +210,10 @@ ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/BaseModel.js
ReactNativeClient/lib/checkPermissions.js
ReactNativeClient/lib/commands/historyBackward.js
ReactNativeClient/lib/commands/historyForward.js
@@ -251,6 +257,9 @@ ReactNativeClient/lib/markdownUtils.js
ReactNativeClient/lib/markupLanguageUtils.js
ReactNativeClient/lib/models/Alarm.js
ReactNativeClient/lib/models/Setting.js
ReactNativeClient/lib/models/utils/modelFeed.js
ReactNativeClient/lib/models/utils/paginationToSql.js
ReactNativeClient/lib/models/utils/types.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/path-utils.js
ReactNativeClient/lib/PoorManIntervals.js
@@ -325,10 +334,27 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/Api.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/rest/ApiResponse.js
ReactNativeClient/lib/services/rest/routes/folders.js
ReactNativeClient/lib/services/rest/routes/master_keys.js
ReactNativeClient/lib/services/rest/routes/notes.js
ReactNativeClient/lib/services/rest/routes/ping.js
ReactNativeClient/lib/services/rest/routes/resources.js
ReactNativeClient/lib/services/rest/routes/search.js
ReactNativeClient/lib/services/rest/routes/tags.js
ReactNativeClient/lib/services/rest/utils/defaultAction.js
ReactNativeClient/lib/services/rest/utils/defaultLoadOptions.js
ReactNativeClient/lib/services/rest/utils/defaultSaveOptions.js
ReactNativeClient/lib/services/rest/utils/errors.js
ReactNativeClient/lib/services/rest/utils/paginatedResults.js
ReactNativeClient/lib/services/rest/utils/readonlyProperties.js
ReactNativeClient/lib/services/rest/utils/requestFields.js
ReactNativeClient/lib/services/rest/utils/requestPaginationOptions.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
@@ -351,6 +377,7 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/time.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js

28
.gitignore vendored
View File

@@ -120,6 +120,7 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/menuCommandNames.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
@@ -142,8 +143,10 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/contextMenu.js
ElectronClient/gui/NoteEditor/utils/index.js
@@ -200,8 +203,10 @@ ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/BaseModel.js
ReactNativeClient/lib/checkPermissions.js
ReactNativeClient/lib/commands/historyBackward.js
ReactNativeClient/lib/commands/historyForward.js
@@ -245,6 +250,9 @@ ReactNativeClient/lib/markdownUtils.js
ReactNativeClient/lib/markupLanguageUtils.js
ReactNativeClient/lib/models/Alarm.js
ReactNativeClient/lib/models/Setting.js
ReactNativeClient/lib/models/utils/modelFeed.js
ReactNativeClient/lib/models/utils/paginationToSql.js
ReactNativeClient/lib/models/utils/types.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/path-utils.js
ReactNativeClient/lib/PoorManIntervals.js
@@ -319,10 +327,27 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/Api.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/rest/ApiResponse.js
ReactNativeClient/lib/services/rest/routes/folders.js
ReactNativeClient/lib/services/rest/routes/master_keys.js
ReactNativeClient/lib/services/rest/routes/notes.js
ReactNativeClient/lib/services/rest/routes/ping.js
ReactNativeClient/lib/services/rest/routes/resources.js
ReactNativeClient/lib/services/rest/routes/search.js
ReactNativeClient/lib/services/rest/routes/tags.js
ReactNativeClient/lib/services/rest/utils/defaultAction.js
ReactNativeClient/lib/services/rest/utils/defaultLoadOptions.js
ReactNativeClient/lib/services/rest/utils/defaultSaveOptions.js
ReactNativeClient/lib/services/rest/utils/errors.js
ReactNativeClient/lib/services/rest/utils/paginatedResults.js
ReactNativeClient/lib/services/rest/utils/readonlyProperties.js
ReactNativeClient/lib/services/rest/utils/requestFields.js
ReactNativeClient/lib/services/rest/utils/requestPaginationOptions.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
@@ -345,6 +370,7 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/time.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js

28
.ignore
View File

@@ -69,6 +69,7 @@ ElectronClient/gui/MainScreen/commands/toggleSideBar.js
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
ElectronClient/gui/MainScreen/MainScreen.js
ElectronClient/gui/MenuBar.js
ElectronClient/gui/menuCommandNames.js
ElectronClient/gui/MultiNoteActions.js
ElectronClient/gui/NoteContentPropertiesDialog.js
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
@@ -91,8 +92,10 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/setupContextMenu.js
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
ElectronClient/gui/NoteEditor/NoteEditor.js
ElectronClient/gui/NoteEditor/NoteTitle/NoteTitleBar.js
ElectronClient/gui/NoteEditor/styles/index.js
ElectronClient/gui/NoteEditor/utils/contextMenu.js
ElectronClient/gui/NoteEditor/utils/index.js
@@ -149,8 +152,10 @@ ElectronClient/services/plugins/PluginRunner.js
ElectronClient/services/plugins/UserWebview.js
ElectronClient/services/plugins/UserWebviewDialog.js
ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
ReactNativeClient/lib/AsyncActionQueue.js
ReactNativeClient/lib/BaseApplication.js
ReactNativeClient/lib/BaseModel.js
ReactNativeClient/lib/checkPermissions.js
ReactNativeClient/lib/commands/historyBackward.js
ReactNativeClient/lib/commands/historyForward.js
@@ -194,6 +199,9 @@ ReactNativeClient/lib/markdownUtils.js
ReactNativeClient/lib/markupLanguageUtils.js
ReactNativeClient/lib/models/Alarm.js
ReactNativeClient/lib/models/Setting.js
ReactNativeClient/lib/models/utils/modelFeed.js
ReactNativeClient/lib/models/utils/paginationToSql.js
ReactNativeClient/lib/models/utils/types.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/path-utils.js
ReactNativeClient/lib/PoorManIntervals.js
@@ -268,10 +276,27 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/Api.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/rest/ApiResponse.js
ReactNativeClient/lib/services/rest/routes/folders.js
ReactNativeClient/lib/services/rest/routes/master_keys.js
ReactNativeClient/lib/services/rest/routes/notes.js
ReactNativeClient/lib/services/rest/routes/ping.js
ReactNativeClient/lib/services/rest/routes/resources.js
ReactNativeClient/lib/services/rest/routes/search.js
ReactNativeClient/lib/services/rest/routes/tags.js
ReactNativeClient/lib/services/rest/utils/defaultAction.js
ReactNativeClient/lib/services/rest/utils/defaultLoadOptions.js
ReactNativeClient/lib/services/rest/utils/defaultSaveOptions.js
ReactNativeClient/lib/services/rest/utils/errors.js
ReactNativeClient/lib/services/rest/utils/paginatedResults.js
ReactNativeClient/lib/services/rest/utils/readonlyProperties.js
ReactNativeClient/lib/services/rest/utils/requestFields.js
ReactNativeClient/lib/services/rest/utils/requestPaginationOptions.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerService.js
ReactNativeClient/lib/services/spellChecker/SpellCheckerServiceDriverBase.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
ReactNativeClient/lib/services/synchronizer/MigrationHandler.js
@@ -294,6 +319,7 @@ ReactNativeClient/lib/themes/oledDark.js
ReactNativeClient/lib/themes/solarizedDark.js
ReactNativeClient/lib/themes/solarizedLight.js
ReactNativeClient/lib/themes/type.js
ReactNativeClient/lib/time.js
ReactNativeClient/lib/uuid.js
ReactNativeClient/lib/versionInfo.js
ReactNativeClient/PluginAssetsLoader.js

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -2,7 +2,7 @@ const Logger = require('lib/Logger').default;
const Folder = require('lib/models/Folder.js');
const BaseItem = require('lib/models/BaseItem.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const Setting = require('lib/models/Setting').default;

View File

@@ -1,7 +1,7 @@
const BaseApplication = require('lib/BaseApplication').default;
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const ResourceService = require('lib/services/ResourceService');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const BaseItem = require('lib/models/BaseItem.js');
const Note = require('lib/models/Note.js');

View File

@@ -5,7 +5,7 @@ const Logger = require('lib/Logger').default;
const { dirname } = require('lib/path-utils');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const { JoplinDatabase } = require('lib/joplin-database.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting').default;

View File

@@ -1,6 +1,6 @@
const yargParser = require('yargs-parser');
const { _ } = require('lib/locale');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const stringPadding = require('string-padding');
const Logger = require('lib/Logger').default;

View File

@@ -1,14 +1,15 @@
const { BaseCommand } = require('./base-command.js');
const BaseItem = require('lib/models/BaseItem');
const BaseModel = require('lib/BaseModel');
const BaseModel = require('lib/BaseModel').default;
const { toTitleCase } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const markdownUtils = require('lib/markdownUtils').default;
const { Database } = require('lib/database.js');
const shim = require('lib/shim').default;
class Command extends BaseCommand {
usage() {
return 'apidoc';
return 'apidoc <file>';
}
description() {
@@ -35,7 +36,7 @@ class Command extends BaseCommand {
return markdownUtils.createMarkdownTable(headers, tableFields);
}
async action() {
async action(args) {
const models = [
{
type: BaseModel.TYPE_NOTE,
@@ -112,6 +113,51 @@ class Command extends BaseCommand {
lines.push('');
lines.push('\tcurl http://localhost:41184/tags?fields=id');
lines.push('');
lines.push('By default API results will contain the following fields: **id**, **parent_id**, **title**');
lines.push('# Pagination');
lines.push('');
lines.push('All API calls that return multiple results will be paginated. The actual results will be under the `items` key, and if there are more results, there will also be a `cursor` key, which allows you to fetch the next results. If the `cursor` key is not present, it means you have reached the end of the data set.');
lines.push('');
lines.push('You can specify how the results should be sorted using the `order_by` and `order_dir` query parameters, and you can specify the number of items to be returned using the `limit` parameter (the maximum being 100 items).');
lines.push('');
lines.push('The following call for example will initiate a request to fetch all the notes, 10 at a time, and sorted by "updated_time" ascending:');
lines.push('');
lines.push('\tcurl http://localhost:41184/notes?order_by=updated_time&order_dir=ASC&limit=10');
lines.push('');
lines.push('This will return a result like this');
lines.push('');
lines.push('\t{ "items": [ /* 10 notes */ ], "cursor": "somecursor" }');
lines.push('');
lines.push('Then you will resume fetching the results using this query:');
lines.push('');
lines.push('\tcurl http://localhost:41184/notes?cursor=somecursor');
lines.push('');
lines.push('Note that you only need to pass the cursor to the next request, as it will continue the fetching process using the same parameters you initially provided.');
lines.push('');
lines.push('Eventually you will get some results that do not contain a "cursor" paramater, at which point you will have retrieved all the results');
lines.push('');
lines.push('As an example the pseudo-code below could be used to fetch all the notes:');
lines.push('');
lines.push('```javascript');
lines.push(`
async function fetchJson(url) {
return (await fetch(url)).json();
}
async function fetchAllNotes() {
let query = '';
const url = 'http://localhost:41184/notes';
do {
const response = await fetchJson(url + query);
console.info('Printing notes:');
console.info(response.items);
query = '?cursor' + response.cursor;
} while (response.cursor)
}`);
lines.push('```');
lines.push('');
lines.push('# Error handling');
lines.push('');
@@ -314,7 +360,9 @@ class Command extends BaseCommand {
}
}
this.stdout(lines.join('\n'));
const outFilePath = args['file'];
await shim.fsDriver().writeFile(outFilePath, lines.join('\n'), 'utf8');
}
}

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
class Command extends BaseCommand {

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const BaseItem = require('lib/models/BaseItem.js');
const Note = require('lib/models/Note.js');

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {

View File

@@ -1,9 +1,9 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Note = require('lib/models/Note.js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
class Command extends BaseCommand {
usage() {

View File

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

View File

@@ -6,7 +6,7 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
class Command extends BaseCommand {
usage() {

View File

@@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const InteropService = require('lib/services/interop/InteropService').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const { app } = require('./app.js');
const { _ } = require('lib/locale');

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {

View File

@@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const InteropService = require('lib/services/interop/InteropService').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const { cliUtils } = require('./cli-utils.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');

View File

@@ -1,12 +1,12 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const { sprintf } = require('sprintf-js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');

View File

@@ -2,7 +2,7 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const Folder = require('lib/models/Folder.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
class Command extends BaseCommand {
usage() {

View File

@@ -2,7 +2,7 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
class Command extends BaseCommand {
usage() {

View File

@@ -1,6 +1,6 @@
const { BaseCommand } = require('./base-command.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const uuid = require('lib/uuid').default;

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const { Database } = require('lib/database.js');
const Note = require('lib/models/Note.js');

View File

@@ -2,8 +2,8 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const { time } = require('lib/time-utils.js');
const BaseModel = require('lib/BaseModel').default;
const time = require('lib/time').default;
class Command extends BaseCommand {
usage() {

View File

@@ -1,9 +1,9 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Note = require('lib/models/Note.js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
class Command extends BaseCommand {
usage() {

View File

@@ -1,7 +1,7 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
class Command extends BaseCommand {
usage() {

View File

@@ -1,6 +1,6 @@
'use strict';
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const Logger = require('lib/Logger').default;
const Resource = require('lib/models/Resource.js');
const { dirname } = require('lib/path-utils');

View File

@@ -1,6 +1,6 @@
const Folder = require('lib/models/Folder.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const ListWidget = require('tkwidgets/ListWidget.js');
const _ = require('lib/locale')._;

View File

@@ -13,8 +13,10 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.3.1\n"
"X-Generator: Poedit 2.4.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: CliClient/app/command-cp.js:13
msgid ""
@@ -315,8 +317,9 @@ msgid "Sync to provided target (defaults to sync.target config value)"
msgstr "Synchroniseren naar opgegeven doel (standaard is dit sync.target)"
#: CliClient/app/command-sync.js:35
#, fuzzy
msgid "Upgrade the sync target to the latest version."
msgstr ""
msgstr "Sync target updaten naar de nieuwste versie."
#: CliClient/app/command-sync.js:81 CliClient/app/command-sync.js:95
#: ElectronClient/gui/OneDriveLoginScreen.min.js:40
@@ -818,6 +821,8 @@ msgstr "Annuleren"
msgid ""
"The app is now going to close. Please relaunch it to complete the process."
msgstr ""
"De applicatie zal nu afsluiten. Gelieve het weer op te starten om het proces "
"te voltooien."
#: ElectronClient/plugins/GotoAnything.min.js:459
msgid ""
@@ -835,15 +840,14 @@ msgid "Goto Anything..."
msgstr "Ga naar Alles..."
#: ElectronClient/plugins/GotoAnything.js:431
#, fuzzy
msgid ""
"Type a note title or part of its content to jump to it. Or type # followed "
"by a tag name, or @ followed by a notebook name. Or type : to search for "
"commands."
msgstr ""
"Typ de titel van een notitie of een deel van de inhoud om er naartoe te "
"springen. Of typ # gevolgd door de naam van een label, of @ gevolgd door de "
"naam van een notitieboek."
"springen. Typ # gevolgd door de naam van een label, @ gevolgd door de naam "
"van een notitieboek, of : om te zoeken op commando's."
#: ElectronClient/plugins/GotoAnything.js:463
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:28
@@ -884,9 +888,8 @@ msgid "New version: %s"
msgstr "Nieuwe versie: %s"
#: ElectronClient/checkForUpdates.js:154
#, fuzzy
msgid "Download"
msgstr "Gedownload"
msgstr "Downloaden"
#: ElectronClient/checkForUpdates.js:154
msgid "Full Release Notes"
@@ -1235,13 +1238,15 @@ msgstr "Bezig met creëren van nieuw(e) %s..."
#: ElectronClient/gui/NoteEditor/NoteEditor.js:379
msgid "The following attachments are being watched for changes:"
msgstr ""
msgstr "De volgende bijlagen worden gecontroleerd op wijzigingen:"
#: ElectronClient/gui/NoteEditor/NoteEditor.js:382
msgid ""
"The attachments will no longer be watched when you switch to a different "
"note."
msgstr ""
"De bijlagen worden niet meer gecontroleerd wanneer je naar een andere "
"notitie schakelt."
#: ElectronClient/gui/NoteEditor/NoteEditor.js:387
#: ElectronClient/gui/NoteText.min.js:1656
@@ -1358,9 +1363,9 @@ msgstr "Eigenschappen van notitie"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:63
#: ReactNativeClient/lib/services/KeymapService.js:144
#, fuzzy, javascript-format
#, javascript-format
msgid "Error: %s"
msgstr "Fout"
msgstr "Fout: %s"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:121
#: ElectronClient/gui/Root.min.js:91 ElectronClient/gui/MenuBar.js:391
@@ -1370,12 +1375,11 @@ msgstr "Importeren"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:126
msgid "Command"
msgstr ""
msgstr "Commando"
#: ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js:127
#, fuzzy
msgid "Keyboard Shortcut"
msgstr "Toetsenbordmodus"
msgstr "Sneltoets"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:14
#: ElectronClient/gui/MenuBar.js:178 ElectronClient/app.js:347
@@ -1398,9 +1402,8 @@ msgid "Website and documentation"
msgstr "Website en documentatie"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:24
#, fuzzy
msgid "Hide Joplin"
msgstr "Over Joplin"
msgstr "Joplin verbergen"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:26
#: ElectronClient/gui/MenuBar.js:428
@@ -1408,9 +1411,8 @@ msgid "Close Window"
msgstr "Venster afsluiten"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:30
#, fuzzy
msgid "Preferences"
msgstr "Voorkeuren..."
msgstr "Voorkeuren"
#: ElectronClient/gui/KeymapConfig/utils/getLabel.js:30
#: ElectronClient/gui/Root.min.js:92 ElectronClient/gui/MenuBar.js:304
@@ -1420,13 +1422,15 @@ msgstr "Opties"
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
msgid "Press the shortcut"
msgstr ""
msgstr "Druk op de sneltoetsen"
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:49
msgid ""
"Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the "
"shortcut."
msgstr ""
"Druk op de sneltoetsen en dan op ENTER. Of, druk op backspace om de "
"sneltoets te wissen."
#: ElectronClient/gui/KeymapConfig/ShortcutRecorder.js:50
#: ElectronClient/gui/EncryptionConfigScreen.min.js:96
@@ -1436,17 +1440,20 @@ msgstr "Opslaan"
#: ElectronClient/gui/MainScreen/MainScreen.js:418
#: ElectronClient/gui/MainScreen/MainScreen.min.js:301
#, fuzzy
msgid ""
"The sync target needs to be upgraded before Joplin can sync. The operation "
"may take a few minutes to complete and the app needs to be restarted. To "
"proceed please click on the link."
msgstr ""
"De sync target moet worden geüpdatet voordat Joplin kan synchroniseren. Het "
"kan een paar minuten duren en de app moet opnieuw opgestart worden. Om door "
"te gaan, klik op de link."
#: ElectronClient/gui/MainScreen/MainScreen.js:420
#: ElectronClient/gui/MainScreen/MainScreen.min.js:306
#, fuzzy
msgid "Restart and upgrade"
msgstr "Hoofdsleutels die moeten worden bijgewerkt"
msgstr "Opnieuw opstarten en bijwerken"
#: ElectronClient/gui/MainScreen/MainScreen.js:424
#: ElectronClient/gui/MainScreen/MainScreen.min.js:313
@@ -2268,18 +2275,16 @@ msgstr[0] "Kopieer Deelbare Link"
msgstr[1] "Kopieer Deelbare Links"
#: ElectronClient/commands/toggleExternalEditing.js:18
#, fuzzy
msgid "Toggle external editing"
msgstr "Klik om extern bewerken te stoppen"
msgstr "Extern bewerken in- of uitschakelen"
#: ElectronClient/commands/toggleExternalEditing.js:37
msgid "Stop"
msgstr ""
msgstr "Stop"
#: ElectronClient/commands/copyDevCommand.js:18
#, fuzzy
msgid "Copy dev mode command to clipboard"
msgstr "Pad kopiëren naar klembord"
msgstr "Ontwikkelaarsmodus commando kopiëren naar klembord"
#: ElectronClient/commands/stopExternalEditing.js:18
msgid "Stop external editing"
@@ -2291,9 +2296,8 @@ msgid "Error opening note in editor: %s"
msgstr "Fout bij openen van notitie in editor: %s"
#: ElectronClient/commands/openProfileDirectory.js:18
#, fuzzy
msgid "Open profile directory"
msgstr "Open sjabloon map"
msgstr "Open profiel map"
#: ElectronClient/app.js:345
#, javascript-format
@@ -2339,7 +2343,7 @@ msgstr ""
#: ReactNativeClient/lib/SyncTargetAmazonS3.js:28
msgid "AWS S3"
msgstr ""
msgstr "AWS S3"
#: ReactNativeClient/lib/SyncTargetDropbox.js:25
msgid "Dropbox"
@@ -2483,19 +2487,19 @@ msgstr "WebDAV-wachtwoord"
#: ReactNativeClient/lib/models/Setting.js:215
msgid "AWS S3 bucket"
msgstr ""
msgstr "AWS S3 bucket"
#: ReactNativeClient/lib/models/Setting.js:226
msgid "AWS S3 URL"
msgstr ""
msgstr "AWS S3 URL"
#: ReactNativeClient/lib/models/Setting.js:237
msgid "AWS key"
msgstr ""
msgstr "AWS key"
#: ReactNativeClient/lib/models/Setting.js:247
msgid "AWS secret"
msgstr ""
msgstr "AWS secret"
#: ReactNativeClient/lib/models/Setting.js:259
msgid "Attachment download behaviour"
@@ -2955,9 +2959,8 @@ msgid "Web Clipper"
msgstr "Webclipper"
#: ReactNativeClient/lib/models/Setting.js:1310
#, fuzzy
msgid "Keyboard Shortcuts"
msgstr "Toetsenbordmodus"
msgstr "Sneltoetsen"
#: ReactNativeClient/lib/models/Setting.js:1317
msgid ""
@@ -3074,9 +3077,8 @@ msgid "Save alarm"
msgstr "Alarm opslaan"
#: ReactNativeClient/lib/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:28
#, fuzzy
msgid "Open"
msgstr "Openen..."
msgstr "Openen"
#: ReactNativeClient/lib/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:29
#: ReactNativeClient/lib/components/screens/Note.js:794
@@ -3114,8 +3116,9 @@ msgstr ""
"Sommige items kunnen niet worden gesynchroniseerd. Klik voor meer informatie."
#: ReactNativeClient/lib/components/screen-header.js:453
#, fuzzy
msgid "The sync target needs to be upgraded. Press this banner to proceed."
msgstr ""
msgstr "De sync target moet worden geüpdatet. Druk hier om door te gaan."
#: ReactNativeClient/lib/components/side-menu-content.js:126
#, javascript-format
@@ -3239,8 +3242,9 @@ msgid "Refresh"
msgstr "Verversen"
#: ReactNativeClient/lib/components/screens/UpgradeSyncTargetScreen.js:42
#, fuzzy
msgid "Sync Target Upgrade"
msgstr ""
msgstr "Sync target update"
#: ReactNativeClient/lib/components/screens/NoteTagsDialog.js:163
msgid "New tags:"
@@ -3484,11 +3488,14 @@ msgid ""
"\n"
"You may turn off this option at any time in the Configuration screen."
msgstr ""
"Om een geolocatie aan de notitie te koppelen, heeft de app uw toestemming "
"nodig om toegang te krijgen tot uw locatie.\n"
"\n"
"U kunt deze optie op elk moment uitschakelen in het 'Configuratie'-scherm."
#: ReactNativeClient/lib/components/screens/Note.js:341
#, fuzzy
msgid "Permission needed"
msgstr "Toestemming om de camera te gebruiken"
msgstr "Toestemming vereist"
#: ReactNativeClient/lib/components/screens/Note.js:583
#, javascript-format
@@ -3737,23 +3744,23 @@ msgstr "Geef een importformaat op voor %s"
#: ReactNativeClient/lib/services/KeymapService.js:240
msgid "command"
msgstr ""
msgstr "commando"
#: ReactNativeClient/lib/services/KeymapService.js:240
#: ReactNativeClient/lib/services/KeymapService.js:245
#, javascript-format
msgid "\"%s\" is missing the required \"%s\" property."
msgstr ""
msgstr "\"%s\" mist de vereiste \"%s\" eigenschap."
#: ReactNativeClient/lib/services/KeymapService.js:245
#: ReactNativeClient/lib/services/KeymapService.js:252
msgid "accelerator"
msgstr ""
msgstr "versneller"
#: ReactNativeClient/lib/services/KeymapService.js:252
#, fuzzy, javascript-format
#, javascript-format
msgid "Invalid %s: %s."
msgstr "Ongeldig antwoord: %s"
msgstr "Ongeldig %s: %s."
#: ReactNativeClient/lib/services/KeymapService.js:270
#, javascript-format
@@ -3761,11 +3768,13 @@ msgid ""
"Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to "
"unexpected behaviour."
msgstr ""
"Versneller \"%s\" wordt gebruikt voor \"%s\" en \"%s\" commando's. Dit kan "
"tot onverwacht gedrag leiden."
#: ReactNativeClient/lib/services/KeymapService.js:295
#, javascript-format
msgid "Accelerator \"%s\" is not valid."
msgstr ""
msgstr "Versneller \"%s\" is niet geldig."
#: ReactNativeClient/lib/services/report.js:121
msgid "Items that cannot be synchronised"

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const ArrayUtils = require('lib/ArrayUtils.js');

View File

@@ -3,12 +3,12 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { filename } = require('lib/path-utils');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');

View File

@@ -3,12 +3,12 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { filename } = require('lib/path-utils');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
const HtmlToHtml = require('lib/joplin-renderer/HtmlToHtml');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');

View File

@@ -3,12 +3,12 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { filename } = require('lib/path-utils');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
const HtmlToMd = require('lib/HtmlToMd');
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');

View File

@@ -1,5 +1,5 @@
import InMemoryCache from 'lib/InMemoryCache';
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
describe('InMemoryCache', function() {

View File

@@ -1,8 +1,8 @@
import MdToHtml from 'lib/joplin-renderer/MdToHtml';
const os = require('os');
const { filename } = require('lib/path-utils');
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const shim = require('lib/shim').default;
const MdToHtml = require('lib/joplin-renderer/MdToHtml').default;
const { themeStyle } = require('lib/theme');
function newTestMdToHtml(options:any = null) {
@@ -120,23 +120,54 @@ describe('MdToHtml', function() {
it('should return the rendered body only', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();
// In this case, the HTML contains only the rendered markdown,
// with no wrapper and no style.
// The style is instead in the cssStrings property.
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('just <strong>testing</strong>');
// In this case, the HTML contains only the rendered markdown, with
// no wrapper and no style. The style is instead in the cssStrings
// property.
{
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('just <strong>testing</strong>');
}
// But it should not remove the wrapping <p> tags if there's more
// than one line
{
const result = await mdToHtml.render('one\n\ntwo', null, { bodyOnly: true });
expect(result.html.trim()).toBe('<p>one</p>\n<p>two</p>');
}
}));
it('should split HTML and CSS', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();
// It is similar to the bodyOnly option, excepts that
// the rendered Markdown is wrapped in a DIV
// It is similar to the bodyOnly option, excepts that the rendered
// Markdown is wrapped in a DIV
const result = await mdToHtml.render('just **testing**', null, { splitted: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
}));
it('should render links correctly', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();
const testCases = [
// None of these should result in a link
['https://example.com', 'https://example.com'],
['file://C:\\AUTOEXEC.BAT', 'file://C:\\AUTOEXEC.BAT'],
['example.com', 'example.com'],
['oo.ps', 'oo.ps'],
['test@example.com', 'test@example.com'],
// Those should be converted to links
['<https://example.com>', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>'],
['[ok](https://example.com)', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>'],
];
for (const testCase of testCases) {
const [input, expected] = testCase;
const actual = await mdToHtml.render(input, null, { bodyOnly: true, plainResourceRendering: true });
expect(actual.html).toBe(expected);
}
}));
});

View File

@@ -2,12 +2,12 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const ArrayUtils = require('lib/ArrayUtils.js');
const shim = require('lib/shim').default;

View File

@@ -1,6 +1,6 @@
require('app-module-path').addPath(__dirname);
const { asyncTest, id, ids, createNTestFolders, sortedIds, createNTestNotes, TestApp } = require('test-utils.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const uuid = require('lib/uuid').default;
const Note = require('lib/models/Note.js');
const Folder = require('lib/models/Folder.js');

View File

@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
let testApp = null;

View File

@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids.js');
//

View File

@@ -5,7 +5,7 @@ const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
let testApp = null;

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const uuid = require('lib/uuid').default;
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, sleep, fileApi, fileContentEqual, checkThrowAsync } = require('test-utils.js');
const shim = require('lib/shim').default;
const fs = require('fs-extra');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const mimeUtils = require('lib/mime-utils.js').mime;

View File

@@ -2,13 +2,13 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseItem = require('lib/models/BaseItem.js');
const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {

View File

@@ -2,11 +2,11 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, revisionService, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const ResourceService = require('lib/services/ResourceService');

View File

@@ -2,12 +2,12 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const ArrayUtils = require('lib/ArrayUtils.js');
const shim = require('lib/shim').default;

View File

@@ -2,12 +2,12 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const ArrayUtils = require('lib/ArrayUtils.js');
const shim = require('lib/shim').default;

View File

@@ -2,12 +2,12 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {

View File

@@ -2,14 +2,14 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const NoteTag = require('lib/models/NoteTag.js');
const Tag = require('lib/models/Tag.js');
const Revision = require('lib/models/Revision.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {

View File

@@ -2,13 +2,13 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const NoteTag = require('lib/models/NoteTag.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const shim = require('lib/shim').default;
process.on('unhandledRejection', (reason, p) => {

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
@@ -10,7 +10,7 @@ const Tag = require('lib/models/Tag.js');
const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting').default;
const BaseItem = require('lib/models/BaseItem.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const MasterKey = require('lib/models/MasterKey');
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const EncryptionService = require('lib/services/EncryptionService.js');

View File

@@ -5,7 +5,7 @@ require('app-module-path').addPath(__dirname);
const fs = require('fs-extra');
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const InteropService_Exporter_Md = require('lib/services/interop/InteropService_Exporter_Md').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const Resource = require('lib/models/Resource.js');
const Note = require('lib/models/Note.js');

View File

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

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, resourceService, decryptionWorker, encryptionService, loadEncryptionMasterKey, allSyncTargetItemsEncrypted, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const InteropService = require('lib/services/interop/InteropService').default;
const Folder = require('lib/models/Folder.js');

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const Folder = require('lib/models/Folder.js');
const Setting = require('lib/models/Setting').default;
@@ -11,7 +11,7 @@ const NoteTag = require('lib/models/NoteTag.js');
const ItemChange = require('lib/models/ItemChange.js');
const Tag = require('lib/models/Tag.js');
const Revision = require('lib/models/Revision.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const RevisionService = require('lib/services/RevisionService.js');
const shim = require('lib/shim').default;

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, mockDate, restoreDate } = require('test-utils.js');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');

View File

@@ -3,7 +3,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');

View File

@@ -3,7 +3,7 @@
// require('app-module-path').addPath(__dirname);
// const { time } = require('lib/time-utils.js');
// const time = require('lib/time').default;
// const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, createNTestNotes, switchClient, createNTestFolders } = require('test-utils.js');
// const SearchEngine = require('lib/services/searchengine/SearchEngine');
// const Note = require('lib/models/Note');

View File

@@ -1,4 +1,5 @@
import Api from 'lib/services/rest/Api';
import { PaginationOrderDir } from 'lib/models/utils/types';
import Api, { RequestMethod } from 'lib/services/rest/Api';
import shim from 'lib/shim';
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('test-utils.js');
@@ -8,9 +9,21 @@ const Note = require('lib/models/Note');
const Tag = require('lib/models/Tag');
const NoteTag = require('lib/models/NoteTag');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
async function msleep(ms:number) {
return new Promise((resolve) => {
shim.setTimeout(() => {
resolve();
}, ms);
});
}
const createFolderForPagination = async (num:number, time:number) => {
await Folder.save({
title: `folder${num}`,
updated_time: time,
created_time: time,
}, { autoTimestamp: false });
};
let api:Api = null;
@@ -24,24 +37,24 @@ describe('services_rest_Api', function() {
});
it('should ping', asyncTest(async () => {
const response = await api.route('GET', 'ping');
const response = await api.route(RequestMethod.GET, 'ping');
expect(response).toBe('JoplinClipperServer');
}));
it('should handle Not Found errors', asyncTest(async () => {
const hasThrown = await checkThrowAsync(async () => await api.route('GET', 'pong'));
const hasThrown = await checkThrowAsync(async () => await api.route(RequestMethod.GET, 'pong'));
expect(hasThrown).toBe(true);
}));
it('should get folders', asyncTest(async () => {
await Folder.save({ title: 'mon carnet' });
const response = await api.route('GET', 'folders');
expect(response.length).toBe(1);
const response = await api.route(RequestMethod.GET, 'folders');
expect(response.items.length).toBe(1);
}));
it('should update folders', asyncTest(async () => {
const f1 = await Folder.save({ title: 'mon carnet' });
await api.route('PUT', `folders/${f1.id}`, null, JSON.stringify({
await api.route(RequestMethod.PUT, `folders/${f1.id}`, null, JSON.stringify({
title: 'modifié',
}));
@@ -51,14 +64,14 @@ describe('services_rest_Api', function() {
it('should delete folders', asyncTest(async () => {
const f1 = await Folder.save({ title: 'mon carnet' });
await api.route('DELETE', `folders/${f1.id}`);
await api.route(RequestMethod.DELETE, `folders/${f1.id}`);
const f1b = await Folder.load(f1.id);
expect(!f1b).toBe(true);
}));
it('should create folders', asyncTest(async () => {
const response = await api.route('POST', 'folders', null, JSON.stringify({
const response = await api.route(RequestMethod.POST, 'folders', null, JSON.stringify({
title: 'from api',
}));
@@ -71,26 +84,26 @@ describe('services_rest_Api', function() {
it('should get one folder', asyncTest(async () => {
const f1 = await Folder.save({ title: 'mon carnet' });
const response = await api.route('GET', `folders/${f1.id}`);
const response = await api.route(RequestMethod.GET, `folders/${f1.id}`);
expect(response.id).toBe(f1.id);
const hasThrown = await checkThrowAsync(async () => await api.route('GET', 'folders/doesntexist'));
const hasThrown = await checkThrowAsync(async () => await api.route(RequestMethod.GET, 'folders/doesntexist'));
expect(hasThrown).toBe(true);
}));
it('should get the folder notes', asyncTest(async () => {
const f1 = await Folder.save({ title: 'mon carnet' });
const response2 = await api.route('GET', `folders/${f1.id}/notes`);
expect(response2.length).toBe(0);
const response2 = await api.route(RequestMethod.GET, `folders/${f1.id}/notes`);
expect(response2.items.length).toBe(0);
await Note.save({ title: 'un', parent_id: f1.id });
await Note.save({ title: 'deux', parent_id: f1.id });
const response = await api.route('GET', `folders/${f1.id}/notes`);
expect(response.length).toBe(2);
const response = await api.route(RequestMethod.GET, `folders/${f1.id}/notes`);
expect(response.items.length).toBe(2);
}));
it('should fail on invalid paths', asyncTest(async () => {
const hasThrown = await checkThrowAsync(async () => await api.route('GET', 'schtroumpf'));
const hasThrown = await checkThrowAsync(async () => await api.route(RequestMethod.GET, 'schtroumpf'));
expect(hasThrown).toBe(true);
}));
@@ -102,13 +115,13 @@ describe('services_rest_Api', function() {
await Note.save({ title: 'deux', parent_id: f1.id });
const n3 = await Note.save({ title: 'trois', parent_id: f2.id });
response = await api.route('GET', 'notes');
expect(response.length).toBe(3);
response = await api.route(RequestMethod.GET, 'notes');
expect(response.items.length).toBe(3);
response = await api.route('GET', `notes/${n1.id}`);
response = await api.route(RequestMethod.GET, `notes/${n1.id}`);
expect(response.id).toBe(n1.id);
response = await api.route('GET', `notes/${n3.id}`, { fields: 'id,title' });
response = await api.route(RequestMethod.GET, `notes/${n3.id}`, { fields: 'id,title' });
expect(Object.getOwnPropertyNames(response).length).toBe(3);
expect(response.id).toBe(n3.id);
expect(response.title).toBe('trois');
@@ -118,14 +131,14 @@ describe('services_rest_Api', function() {
let response = null;
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing',
parent_id: f.id,
}));
expect(response.title).toBe('testing');
expect(!!response.id).toBe(true);
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing',
parent_id: f.id,
}));
@@ -137,7 +150,7 @@ describe('services_rest_Api', function() {
let response:any = null;
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing',
parent_id: f.id,
latitude: '48.732071',
@@ -154,7 +167,7 @@ describe('services_rest_Api', function() {
expect(note.altitude).toBe('21.0000');
}
await api.route('PUT', `notes/${noteId}`, null, JSON.stringify({
await api.route(RequestMethod.PUT, `notes/${noteId}`, null, JSON.stringify({
latitude: '49',
longitude: '-3',
altitude: '22',
@@ -175,7 +188,7 @@ describe('services_rest_Api', function() {
const updatedTime = Date.now() - 1000;
const createdTime = Date.now() - 10000;
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
parent_id: f.id,
user_updated_time: updatedTime,
user_created_time: createdTime,
@@ -186,7 +199,7 @@ describe('services_rest_Api', function() {
const timeBefore = Date.now();
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
parent_id: f.id,
}));
@@ -201,7 +214,7 @@ describe('services_rest_Api', function() {
const updatedTime = Date.now() - 1000;
const createdTime = Date.now() - 10000;
const response = await api.route('POST', 'notes', null, JSON.stringify({
const response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
parent_id: folder.id,
}));
@@ -210,7 +223,7 @@ describe('services_rest_Api', function() {
{
// Check that if user timestamps are supplied, they are preserved by the API
await api.route('PUT', `notes/${noteId}`, null, JSON.stringify({
await api.route(RequestMethod.PUT, `notes/${noteId}`, null, JSON.stringify({
user_updated_time: updatedTime,
user_created_time: createdTime,
title: 'mod',
@@ -227,7 +240,7 @@ describe('services_rest_Api', function() {
const beforeTime = Date.now();
await api.route('PUT', `notes/${noteId}`, null, JSON.stringify({
await api.route(RequestMethod.PUT, `notes/${noteId}`, null, JSON.stringify({
title: 'mod2',
}));
@@ -242,7 +255,7 @@ describe('services_rest_Api', function() {
let response = null;
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
id: '12345678123456781234567812345678',
title: 'testing',
parent_id: f.id,
@@ -254,27 +267,27 @@ describe('services_rest_Api', function() {
let response = null;
const f = await Folder.save({ title: 'stuff to do' });
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing',
parent_id: f.id,
is_todo: 1,
}));
expect(response.is_todo).toBe(1);
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing 2',
parent_id: f.id,
is_todo: 0,
}));
expect(response.is_todo).toBe(0);
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing 3',
parent_id: f.id,
}));
expect(response.is_todo).toBeUndefined();
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing 4',
parent_id: f.id,
is_todo: '1',
@@ -282,7 +295,7 @@ describe('services_rest_Api', function() {
}));
it('should create folders with supplied ID', asyncTest(async () => {
const response = await api.route('POST', 'folders', null, JSON.stringify({
const response = await api.route(RequestMethod.POST, 'folders', null, JSON.stringify({
id: '12345678123456781234567812345678',
title: 'from api',
}));
@@ -294,7 +307,7 @@ describe('services_rest_Api', function() {
let response = null;
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing image',
parent_id: f.id,
image_data_url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII=',
@@ -310,7 +323,7 @@ describe('services_rest_Api', function() {
it('should delete resources', asyncTest(async () => {
const f = await Folder.save({ title: 'mon carnet' });
await api.route('POST', 'notes', null, JSON.stringify({
await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing image',
parent_id: f.id,
image_data_url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII=',
@@ -321,7 +334,7 @@ describe('services_rest_Api', function() {
const filePath = Resource.fullPath(resource);
expect(await shim.fsDriver().exists(filePath)).toBe(true);
await api.route('DELETE', `resources/${resource.id}`);
await api.route(RequestMethod.DELETE, `resources/${resource.id}`);
expect(await shim.fsDriver().exists(filePath)).toBe(false);
expect(!(await Resource.load(resource.id))).toBe(true);
}));
@@ -330,7 +343,7 @@ describe('services_rest_Api', function() {
let response = null;
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
response = await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({
title: 'testing HTML',
parent_id: f.id,
body_html: '<b>Bold text</b>',
@@ -339,32 +352,32 @@ describe('services_rest_Api', function() {
expect(response.body).toBe('**Bold text**');
}));
it('should filter fields', asyncTest(async () => {
let f = api.fields_({ query: { fields: 'one,two' } }, []);
expect(f.length).toBe(2);
expect(f[0]).toBe('one');
expect(f[1]).toBe('two');
// it('should filter fields', asyncTest(async () => {
// let f = api.fields_({ query: { fields: 'one,two' } } as any, []);
// expect(f.length).toBe(2);
// expect(f[0]).toBe('one');
// expect(f[1]).toBe('two');
f = api.fields_({ query: { fields: 'one ,, two ' } }, []);
expect(f.length).toBe(2);
expect(f[0]).toBe('one');
expect(f[1]).toBe('two');
// f = api.fields_({ query: { fields: 'one ,, two ' } } as any, []);
// expect(f.length).toBe(2);
// expect(f[0]).toBe('one');
// expect(f[1]).toBe('two');
f = api.fields_({ query: { fields: ' ' } }, ['def']);
expect(f.length).toBe(1);
expect(f[0]).toBe('def');
}));
// f = api.fields_({ query: { fields: ' ' } } as any, ['def']);
// expect(f.length).toBe(1);
// expect(f[0]).toBe('def');
// }));
it('should handle tokens', asyncTest(async () => {
api = new Api('mytoken');
let hasThrown = await checkThrowAsync(async () => await api.route('GET', 'notes'));
let hasThrown = await checkThrowAsync(async () => await api.route(RequestMethod.GET, 'notes'));
expect(hasThrown).toBe(true);
const response = await api.route('GET', 'notes', { token: 'mytoken' });
expect(response.length).toBe(0);
const response = await api.route(RequestMethod.GET, 'notes', { token: 'mytoken' });
expect(response.items.length).toBe(0);
hasThrown = await checkThrowAsync(async () => await api.route('POST', 'notes', null, JSON.stringify({ title: 'testing' })));
hasThrown = await checkThrowAsync(async () => await api.route(RequestMethod.POST, 'notes', null, JSON.stringify({ title: 'testing' })));
expect(hasThrown).toBe(true);
}));
@@ -372,7 +385,7 @@ describe('services_rest_Api', function() {
const tag = await Tag.save({ title: 'mon étiquette' });
const note = await Note.save({ title: 'ma note' });
await api.route('POST', `tags/${tag.id}/notes`, null, JSON.stringify({
await api.route(RequestMethod.POST, `tags/${tag.id}/notes`, null, JSON.stringify({
id: note.id,
}));
@@ -385,7 +398,7 @@ describe('services_rest_Api', function() {
const note = await Note.save({ title: 'ma note' });
await Tag.addNote(tag.id, note.id);
await api.route('DELETE', `tags/${tag.id}/notes/${note.id}`);
await api.route(RequestMethod.DELETE, `tags/${tag.id}/notes/${note.id}`);
const noteIds = await Tag.noteIds(tag.id);
expect(noteIds.length).toBe(0);
@@ -399,16 +412,16 @@ describe('services_rest_Api', function() {
await Tag.addNote(tag.id, note1.id);
await Tag.addNote(tag.id, note2.id);
const response = await api.route('GET', `tags/${tag.id}/notes`);
expect(response.length).toBe(2);
expect('id' in response[0]).toBe(true);
expect('title' in response[0]).toBe(true);
const response = await api.route(RequestMethod.GET, `tags/${tag.id}/notes`);
expect(response.items.length).toBe(2);
expect('id' in response.items[0]).toBe(true);
expect('title' in response.items[0]).toBe(true);
const response2 = await api.route('GET', `notes/${note1.id}/tags`);
expect(response2.length).toBe(1);
const response2 = await api.route(RequestMethod.GET, `notes/${note1.id}/tags`);
expect(response2.items.length).toBe(1);
await Tag.addNote(tag2.id, note1.id);
const response3 = await api.route('GET', `notes/${note1.id}/tags`);
expect(response3.length).toBe(2);
const response3 = await api.route(RequestMethod.GET, `notes/${note1.id}/tags`);
expect(response3.items.length).toBe(2);
}));
it('should update tags when updating notes', asyncTest(async () => {
@@ -422,7 +435,7 @@ describe('services_rest_Api', function() {
Tag.addNote(tag1.id, note.id);
Tag.addNote(tag2.id, note.id);
const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({
const response = await api.route(RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
tags: `${tag1.title},${tag3.title}`,
}));
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
@@ -443,7 +456,7 @@ describe('services_rest_Api', function() {
Tag.addNote(tag1.id, note.id);
Tag.addNote(tag2.id, note.id);
const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({
const response = await api.route(RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
tags: `${tag1.title},${newTagTitle}`,
}));
const newTag = await Tag.loadByTitle(newTagTitle);
@@ -464,7 +477,7 @@ describe('services_rest_Api', function() {
Tag.addNote(tag1.id, note.id);
Tag.addNote(tag2.id, note.id);
const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({
const response = await api.route(RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
title: 'Some other title',
}));
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
@@ -484,11 +497,160 @@ describe('services_rest_Api', function() {
Tag.addNote(tag1.id, note.id);
Tag.addNote(tag2.id, note.id);
const response = await api.route('PUT', `notes/${note.id}`, null, JSON.stringify({
const response = await api.route(RequestMethod.PUT, `notes/${note.id}`, null, JSON.stringify({
tags: '',
}));
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
expect(response.tags === '').toBe(true);
expect(tagIds.length === 0).toBe(true);
}));
it('should paginate results', asyncTest(async () => {
await createFolderForPagination(1, 1001);
await createFolderForPagination(2, 1002);
await createFolderForPagination(3, 1003);
await createFolderForPagination(4, 1004);
{
const r1 = await api.route(RequestMethod.GET, 'folders', {
fields: ['id', 'title', 'updated_time'],
limit: 2,
order_dir: PaginationOrderDir.ASC,
order_by: 'updated_time',
});
expect(r1.items.length).toBe(2);
expect(r1.items[0].title).toBe('folder1');
expect(r1.items[1].title).toBe('folder2');
const r2 = await api.route(RequestMethod.GET, 'folders', {
cursor: r1.cursor,
});
expect(r2.items.length).toBe(2);
expect(r2.items[0].title).toBe('folder3');
expect(r2.items[1].title).toBe('folder4');
const r3 = await api.route(RequestMethod.GET, 'folders', {
cursor: r2.cursor,
});
expect(r3.items.length).toBe(0);
expect(r3.cursor).toBe(undefined);
}
{
const r1 = await api.route(RequestMethod.GET, 'folders', {
fields: ['id', 'title', 'updated_time'],
limit: 3,
order_dir: PaginationOrderDir.ASC,
order_by: 'updated_time',
});
expect(r1.items.length).toBe(3);
expect(r1.items[0].title).toBe('folder1');
expect(r1.items[1].title).toBe('folder2');
expect(r1.items[2].title).toBe('folder3');
const r2 = await api.route(RequestMethod.GET, 'folders', {
cursor: r1.cursor,
});
expect(r2.items.length).toBe(1);
expect(r2.items[0].title).toBe('folder4');
expect(r2.cursor).toBe(undefined);
}
}));
it('should paginate results and handle duplicate cursor field value', asyncTest(async () => {
await createFolderForPagination(1, 1001);
await createFolderForPagination(2, 1002);
await createFolderForPagination(3, 1002);
await createFolderForPagination(4, 1003);
const r1 = await api.route(RequestMethod.GET, 'folders', {
fields: ['id', 'title', 'updated_time'],
limit: 2,
order_dir: PaginationOrderDir.ASC,
order_by: 'updated_time',
});
expect(r1.items.length).toBe(2);
expect(r1.items[0].title).toBe('folder1');
expect(['folder2', 'folder3'].includes(r1.items[1].title)).toBe(true);
const r2 = await api.route(RequestMethod.GET, 'folders', {
cursor: r1.cursor,
});
expect(r2.items.length).toBe(2);
expect(r2.items[0].title).toBe(r1.items[1].title === 'folder2' ? 'folder3' : 'folder2');
expect(r2.items[1].title).toBe('folder4');
}));
it('should paginate folder notes', asyncTest(async () => {
const folder = await Folder.save({});
const note1 = await Note.save({ parent_id: folder.id });
await msleep(1);
const note2 = await Note.save({ parent_id: folder.id });
await msleep(1);
const note3 = await Note.save({ parent_id: folder.id });
const r1 = await api.route(RequestMethod.GET, `folders/${folder.id}/notes`, {
limit: 2,
});
expect(r1.items.length).toBe(2);
expect(r1.items[0].id).toBe(note1.id);
expect(r1.items[1].id).toBe(note2.id);
const r2 = await api.route(RequestMethod.GET, `folders/${folder.id}/notes`, {
cursor: r1.cursor,
});
expect(r2.items.length).toBe(1);
expect(r2.items[0].id).toBe(note3.id);
}));
it('should return default fields', asyncTest(async () => {
const folder = await Folder.save({ title: 'folder' });
const note1 = await Note.save({ title: 'note1', parent_id: folder.id });
await Note.save({ title: 'note2', parent_id: folder.id });
const tag = await Tag.save({ title: 'tag' });
await Tag.addNote(tag.id, note1.id);
{
const r = await api.route(RequestMethod.GET, `folders/${folder.id}`);
expect('id' in r).toBe(true);
expect('title' in r).toBe(true);
expect('parent_id' in r).toBe(true);
}
{
const r = await api.route(RequestMethod.GET, `folders/${folder.id}/notes`);
expect('id' in r.items[0]).toBe(true);
expect('title' in r.items[0]).toBe(true);
expect('parent_id' in r.items[0]).toBe(true);
}
{
const r = await api.route(RequestMethod.GET, 'notes');
expect('id' in r.items[0]).toBe(true);
expect('title' in r.items[0]).toBe(true);
expect('parent_id' in r.items[0]).toBe(true);
}
{
const r = await api.route(RequestMethod.GET, `notes/${note1.id}/tags`);
expect('id' in r.items[0]).toBe(true);
expect('title' in r.items[0]).toBe(true);
}
{
const r = await api.route(RequestMethod.GET, `tags/${tag.id}`);
expect('id' in r).toBe(true);
expect('title' in r).toBe(true);
}
}));
});

View File

@@ -2,7 +2,7 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { setupDatabase, synchronizerStart, syncTargetName, allSyncTargetItemsEncrypted, tempFilePath, resourceFetcher, kvStore, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, checkThrowAsync, asyncTest } = require('test-utils.js');
const shim = require('lib/shim').default;
const fs = require('fs-extra');
@@ -16,7 +16,7 @@ const Setting = require('lib/models/Setting').default;
const MasterKey = require('lib/models/MasterKey');
const BaseItem = require('lib/models/BaseItem.js');
const Revision = require('lib/models/Revision.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const WelcomeUtils = require('lib/WelcomeUtils');

View File

@@ -4,7 +4,7 @@ const fs = require('fs-extra');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const BaseApplication = require('lib/BaseApplication').default;
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const ItemChange = require('lib/models/ItemChange.js');
@@ -25,7 +25,7 @@ const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js');
const { FileApiDriverAmazonS3 } = require('lib/file-api-driver-amazon-s3.js');
const BaseService = require('lib/services/BaseService').default;
const FsDriverNode = require('lib/fs-driver-node').default;
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { shimInit } = require('lib/shim-init-node.js');
const shim = require('lib/shim').default;
const uuid = require('lib/uuid').default;

View File

@@ -2,9 +2,9 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const timeUtils = require('../../ReactNativeClient/lib/time-utils');
const timeUtils = require('../../ReactNativeClient/lib/time');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@@ -86,6 +86,7 @@ export default class ElectronAppWrapper {
backgroundColor: '#fff', // required to enable sub pixel rendering, can't be in css
webPreferences: {
nodeIntegration: true,
spellcheck: true,
},
webviewTag: true,
// We start with a hidden window, which is then made visible depending on the showTrayIcon setting
@@ -97,14 +98,6 @@ export default class ElectronAppWrapper {
// Fix: https://github.com/electron-userland/electron-builder/issues/2269
if (shim.isLinux()) windowOptions.icon = path.join(__dirname, '..', 'build/icons/128x128.png');
require('electron-context-menu')({
shouldShowMenu: (_event:any, params:any) => {
// params.inputFieldType === 'none' when right-clicking the text editor. This is a bit of a hack to detect it because in this
// case we don't want to use the built-in context menu but a custom one.
return params.isEditable && params.inputFieldType !== 'none';
},
});
this.win_ = new BrowserWindow(windowOptions);
if (!screen.getDisplayMatching(this.win_.getBounds())) {

View File

@@ -8,7 +8,7 @@ const bridge = require('electron').remote.require('./bridge').default;
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const { friendlySafeFilename } = require('lib/path-utils');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const md5 = require('md5');
const url = require('url');

View File

@@ -14,6 +14,10 @@ import Setting from 'lib/models/Setting';
import actionApi from 'lib/services/rest/actionApi.desktop';
import BaseApplication from 'lib/BaseApplication';
import { _, setLocale } from 'lib/locale';
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
import SpellCheckerServiceDriverNative from './services/spellChecker/SpellCheckerServiceDriverNative';
import bridge from './services/bridge';
import menuCommandNames from './gui/menuCommandNames';
require('app-module-path').addPath(__dirname);
@@ -28,7 +32,6 @@ const DecryptionWorker = require('lib/services/DecryptionWorker');
const ResourceService = require('lib/services/ResourceService');
const ClipperServer = require('lib/ClipperServer');
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
const bridge = require('electron').remote.require('./bridge').default;
const { webFrame } = require('electron');
const Menu = bridge().Menu;
const PluginManager = require('lib/services/PluginManager');
@@ -451,6 +454,31 @@ class Application extends BaseApplication {
document.head.appendChild(styleTag);
}
setupContextMenu() {
// The context menu must be setup in renderer process because that's where
// the spell checker service lives.
require('electron-context-menu')({
shouldShowMenu: (_event:any, params:any) => {
// params.inputFieldType === 'none' when right-clicking the text editor. This is a bit of a hack to detect it because in this
// case we don't want to use the built-in context menu but a custom one.
return params.isEditable && params.inputFieldType !== 'none';
},
menu: (actions:any, props:any) => {
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(props.misspelledWord, props.dictionarySuggestions);
const output = [
actions.cut(),
actions.copy(),
actions.paste(),
...spellCheckerMenuItems,
];
return output;
},
});
}
async loadCustomCss(filePath:string) {
let cssString = '';
if (await fs.pathExists(filePath)) {
@@ -495,14 +523,6 @@ class Application extends BaseApplication {
const filename = Setting.custom_css_files.JOPLIN_APP;
await CssUtils.injectCustomStyles(`${dir}/${filename}`);
const keymapService = KeymapService.instance();
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (err) {
reg.logger().error(err.message);
}
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
@@ -533,9 +553,21 @@ class Application extends BaseApplication {
CommandService.instance().registerDeclaration(declaration);
}
// Since the settings need to be loaded before the store is created, it will never
// receive the SETTING_UPDATE_ALL even, which mean state.settings will not be
// initialised. So we manually call dispatchUpdateAll() to force an update.
const keymapService = KeymapService.instance();
// We only add the commands that appear in the menu because only
// those can have a shortcut associated with them.
keymapService.initialize(menuCommandNames());
try {
await keymapService.loadCustomKeymap(`${dir}/keymap-desktop.json`);
} catch (error) {
reg.logger().error(error);
}
// Since the settings need to be loaded before the store is
// created, it will never receive the SETTING_UPDATE_ALL even,
// which mean state.settings will not be initialised. So we
// manually call dispatchUpdateAll() to force an update.
Setting.dispatchUpdateAll();
await FoldersScreenUtils.refreshFolders();
@@ -684,6 +716,10 @@ class Application extends BaseApplication {
this.logger().error(`There was an error loading plugins from ${Setting.value('plugins.devPluginPaths')}:`, error);
}
this.setupContextMenu();
await SpellCheckerService.instance().initialize(new SpellCheckerServiceDriverNative());
// await populateDatabase(reg.db());
// setTimeout(() => {

View File

@@ -4,7 +4,7 @@ const Setting = require('lib/models/Setting').default;
const EncryptionService = require('lib/services/EncryptionService');
const { themeStyle } = require('lib/theme');
const { _ } = require('lib/locale');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const shim = require('lib/shim').default;
const dialogs = require('./dialogs');
const shared = require('lib/components/shared/encryption-config-shared.js');

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ const bridge = require('electron').remote.require('./bridge').default;
const PluginManager = require('lib/services/PluginManager');
const EncryptionService = require('lib/services/EncryptionService');
const ipcRenderer = require('electron').ipcRenderer;
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const styled = require('styled-components').default;
const StyledUserWebviewDialogContainer = styled.div`

View File

@@ -3,7 +3,7 @@ import eventManager from 'lib/eventManager';
import { _ } from 'lib/locale';
import { stateUtils } from 'lib/reducer';
const Note = require('lib/models/Note');
const { time } = require('lib/time-utils');
const time = require('lib/time').default;
export const declaration:CommandDeclaration = {
name: 'editAlarm',

View File

@@ -1,5 +1,5 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
const BaseModel = require('lib/BaseModel');
const BaseModel = require('lib/BaseModel').default;
const uuid = require('lib/uuid').default;
export const declaration:CommandDeclaration = {

View File

@@ -14,6 +14,8 @@ import InteropServiceHelper from '../InteropServiceHelper';
import { _ } from 'lib/locale';
import { MenuItem, MenuItemLocation } from 'lib/services/plugins/api/types';
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
import menuCommandNames from './menuCommandNames';
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
@@ -84,41 +86,11 @@ interface Props {
showCompletedTodos: boolean,
pluginMenuItems: any[],
pluginMenus: any[],
['spellChecker.enabled']: boolean,
['spellChecker.language']: string,
}
const commandNames:string[] = [
'focusElementSideBar',
'focusElementNoteList',
'focusElementNoteTitle',
'focusElementNoteBody',
'exportPdf',
'newNote',
'newTodo',
'newFolder',
'newSubFolder',
'print',
'synchronize',
'textCopy',
'textCut',
'textPaste',
'textSelectAll',
'textBold',
'textItalic',
'textLink',
'textCode',
'insertDateTime',
'attachFile',
'focusSearch',
'showLocalSearch',
'toggleSideBar',
'toggleNoteList',
'toggleVisiblePanes',
'toggleExternalEditing',
'setTags',
'showNoteContentProperties',
'copyDevCommand',
'openProfileDirectory',
];
const commandNames:string[] = menuCommandNames();
function menuItemSetChecked(id:string, checked:boolean) {
const menu = Menu.getApplicationMenu();
@@ -249,10 +221,8 @@ function useMenu(props:Props) {
menuItemDic.focusElementNoteBody,
];
let toolsItems:any[] = [];
const importItems = [];
const exportItems = [];
const toolsItemsFirst = [];
const templateItems:any[] = [];
const ioService = InteropService.instance();
const ioModules = ioService.modules();
@@ -299,16 +269,18 @@ function useMenu(props:Props) {
},
};
const separator = () => {
return {
type: 'separator',
};
};
const newNoteItem = menuItemDic.newNote;
const newTodoItem = menuItemDic.newTodo;
const newFolderItem = menuItemDic.newFolder;
const newSubFolderItem = menuItemDic.newSubFolder;
const printItem = menuItemDic.print;
toolsItemsFirst.push(syncStatusItem, {
type: 'separator',
});
templateItems.push({
label: _('Create note from template'),
click: () => {
@@ -342,18 +314,22 @@ function useMenu(props:Props) {
},
});
let toolsItems:any[] = [];
// we need this workaround, because on macOS the menu is different
const toolsItemsWindowsLinux:any[] = toolsItemsFirst.concat([{
label: _('Options'),
visible: !shim.isMac(),
accelerator: !shim.isMac() && keymapService.getAccelerator('config'),
click: () => {
props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
const toolsItemsWindowsLinux:any[] = [
{
label: _('Options'),
accelerator: keymapService.getAccelerator('config'),
click: () => {
props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
});
},
},
} as any]);
separator(),
];
// the following menu items will be available for all OS under Tools
const toolsItemsAll = [{
@@ -371,6 +347,8 @@ function useMenu(props:Props) {
}
toolsItems = toolsItems.concat(toolsItemsAll);
toolsItems.push(SpellCheckerService.instance().spellCheckerConfigMenuItem(props['spellChecker.language'], props['spellChecker.enabled']));
function _checkForUpdates() {
bridge().checkForUpdates(false, bridge().window(), `${Setting.value('profileDir')}/log-autoupdater.txt`, { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
@@ -451,9 +429,7 @@ function useMenu(props:Props) {
menuItemDic.synchronize,
shim.isMac() ? syncStatusItem : noItem, {
type: 'separator',
}, shim.isMac() ? noItem : printItem, {
shim.isMac() ? noItem : printItem, {
type: 'separator',
platforms: ['darwin'],
},
@@ -518,12 +494,6 @@ function useMenu(props:Props) {
});
}
const separator = () => {
return {
type: 'separator',
};
};
const rootMenus:any = {
edit: {
id: 'edit',
@@ -586,11 +556,6 @@ function useMenu(props:Props) {
},
},
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
separator(),
{
label: _('Actual Size'),
click: () => {
@@ -623,6 +588,18 @@ function useMenu(props:Props) {
accelerator: 'CommandOrControl+-',
}],
},
go: {
label: _('&Go'),
submenu: [
menuItemDic.historyBackward,
menuItemDic.historyForward,
separator(),
{
label: _('Focus'),
submenu: focusItems,
},
],
},
note: {
label: _('&Note'),
submenu: [
@@ -655,6 +632,8 @@ function useMenu(props:Props) {
click: () => _checkForUpdates(),
},
separator(),
syncStatusItem,
separator(),
{
id: 'help:toggleDevTools',
label: _('Toggle development tools'),
@@ -709,6 +688,7 @@ function useMenu(props:Props) {
const pluginMenuItems = PluginManager.instance().menuItems();
for (const item of pluginMenuItems) {
const itemParent = rootMenus[item.parent] ? rootMenus[item.parent] : 'tools';
itemParent.submenu.push(separator());
itemParent.submenu.push(item);
}
}
@@ -741,6 +721,7 @@ function useMenu(props:Props) {
rootMenus.file,
rootMenus.edit,
rootMenus.view,
rootMenus.go,
rootMenus.note,
rootMenus.tools,
rootMenus.help,
@@ -748,46 +729,6 @@ function useMenu(props:Props) {
if (shim.isMac()) template.splice(0, 0, rootMenus.macOsApp);
// TODO
// function isEmptyMenu(template:any[]) {
// for (let i = 0; i < template.length; i++) {
// const t = template[i];
// if (t.type !== 'separator') return false;
// }
// return true;
// }
// function removeUnwantedItems(template:any[], screen:string) {
// const platform = shim.platformName();
// let output = [];
// for (let i = 0; i < template.length; i++) {
// const t = Object.assign({}, template[i]);
// if (t.screens && t.screens.indexOf(screen) < 0) continue;
// if (t.platforms && t.platforms.indexOf(platform) < 0) continue;
// if (t.submenu) t.submenu = removeUnwantedItems(t.submenu, screen);
// if (('submenu' in t) && isEmptyMenu(t.submenu)) continue;
// output.push(t);
// }
// // Remove empty separator for now empty sections
// const temp = [];
// let previous = null;
// for (let i = 0; i < output.length; i++) {
// const t = Object.assign({}, output[i]);
// if (t.type === 'separator') {
// if (!previous) continue;
// if (previous.type === 'separator') continue;
// }
// temp.push(t);
// previous = t;
// }
// output = temp;
// return output;
// }
if (props.routeName !== 'Main') {
setMenu(Menu.buildFromTemplate([
{
@@ -807,7 +748,7 @@ function useMenu(props:Props) {
} else {
setMenu(Menu.buildFromTemplate(template));
}
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime]);
}, [props.routeName, props.pluginMenuItems, props.pluginMenus, keymapLastChangeTime, modulesLastChangeTime, props['spellChecker.language'], props['spellChecker.enabled']]);
useEffect(() => {
const whenClauseContext = CommandService.instance().currentWhenClauseContext();
@@ -902,6 +843,8 @@ const mapStateToProps = (state:AppState) => {
showCompletedTodos: state.settings.showCompletedTodos,
pluginMenuItems: stateUtils.selectArrayShallow({ array: pluginUtils.viewsByType(state.pluginService.plugins, 'menuItem') }, 'menuBar.pluginMenuItems'),
pluginMenus: stateUtils.selectArrayShallow({ array: pluginUtils.viewsByType(state.pluginService.plugins, 'menu') }, 'menuBar.pluginMenus'),
['spellChecker.language']: state.settings['spellChecker.language'],
['spellChecker.enabled']: state.settings['spellChecker.enabled'],
};
};

View File

@@ -4,7 +4,6 @@ import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps }
import { resourcesStatus, commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
import useScroll from './utils/useScroll';
import styles_ from './styles';
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../utils/contextMenu';
import CommandService from 'lib/services/CommandService';
import { ToolbarButtonInfo } from 'lib/services/commands/ToolbarButtonUtils';
import ToggleEditorsButton, { Value as ToggleEditorsButtonValue } from '../../../ToggleEditorsButton/ToggleEditorsButton';
@@ -12,13 +11,13 @@ import ToolbarButton from '../../../../gui/ToolbarButton/ToolbarButton';
import usePluginServiceRegistration from '../../utils/usePluginServiceRegistration';
import { utils as pluginUtils } from 'lib/services/plugins/reducer';
import { _, closestSupportedLocale } from 'lib/locale';
import setupContextMenu from './utils/setupContextMenu';
const { MarkupToHtml } = require('lib/joplin-renderer');
const taboverride = require('taboverride');
const { reg } = require('lib/registry.js');
const BaseItem = require('lib/models/BaseItem');
const shim = require('lib/shim').default;
const Resource = require('lib/models/Resource');
const { themeStyle } = require('lib/theme');
const { clipboard } = require('electron');
const supportedLocales = require('./supportedLocales');
@@ -143,8 +142,6 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
const props_onDrop = useRef(null);
props_onDrop.current = props.onDrop;
const contextMenuActionOptions = useRef<ContextMenuOptions>(null);
const markupToHtml = useRef(null);
markupToHtml.current = props.markupToHtml;
@@ -510,19 +507,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
loadedCssFiles_ = [];
loadedJsFiles_ = [];
function contextMenuItemNameWithNamespace(name:string) {
// For unknown reasons, TinyMCE converts all context menu names to
// lowercase when setting them in the init method, so we need to
// make them lowercase too, to make sure that the update() method
// addContextMenu is triggered.
return (`joplin${name}`).toLowerCase();
}
const loadEditor = async () => {
const contextMenuItems = menuItems();
const contextMenuItemNames = [];
for (const name in contextMenuItems) contextMenuItemNames.push(contextMenuItemNameWithNamespace(name));
const language = closestSupportedLocale(props.locale, true, supportedLocales);
const pluginCommandNames:string[] = [];
@@ -564,7 +549,8 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
language: ['en_US', 'en_GB'].includes(language) ? undefined : language,
toolbar: toolbar.join(' '),
localization_function: _,
contextmenu: contextMenuItemNames.join(' '),
contextmenu: false,
browser_spellcheck: true,
setup: (editor:any) => {
function openEditDialog(editable:any) {
@@ -680,51 +666,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
});
}
for (const itemName in contextMenuItems) {
const item = contextMenuItems[itemName];
const itemNameNS = contextMenuItemNameWithNamespace(itemName);
editor.ui.registry.addMenuItem(itemNameNS, {
text: item.label,
onAction: () => {
item.onAction(contextMenuActionOptions.current);
},
});
editor.ui.registry.addContextMenu(itemNameNS, {
update: function(element:any) {
let itemType:ContextMenuItemType = ContextMenuItemType.None;
let resourceId = '';
let linkToCopy = null;
if (element.nodeName === 'IMG') {
itemType = ContextMenuItemType.Image;
resourceId = Resource.pathToId(element.src);
} else if (element.nodeName === 'A') {
resourceId = Resource.pathToId(element.href);
itemType = resourceId ? ContextMenuItemType.Resource : ContextMenuItemType.Link;
linkToCopy = element.getAttribute('href') || '';
} else {
itemType = ContextMenuItemType.Text;
}
contextMenuActionOptions.current = {
itemType,
resourceId,
linkToCopy,
textToCopy: null,
htmlToCopy: editor.selection ? editor.selection.getContent() : '',
insertContent: (content:string) => {
editor.insertContent(content);
},
isReadOnly: false,
};
return item.isActive(itemType, contextMenuActionOptions.current) ? itemNameNS : '';
},
});
}
setupContextMenu(editor);
// TODO: remove event on unmount?
editor.on('DblClick', (event:any) => {

View File

@@ -0,0 +1,91 @@
import SpellCheckerService from 'lib/services/spellChecker/SpellCheckerService';
import bridge from '../../../../../services/bridge';
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../../utils/contextMenu';
const Resource = require('lib/models/Resource');
// x and y are the absolute coordinates, as returned by the context-menu event
// handler on the webContent. This function will return null if the point is
// not within the TinyMCE editor.
function contextMenuElement(editor:any, x:number, y:number) {
const iframes = document.getElementsByClassName('tox-edit-area__iframe');
if (!iframes.length) return null;
const iframeRect = iframes[0].getBoundingClientRect();
if (iframeRect.x < x && iframeRect.y < y && iframeRect.right > x && iframeRect.bottom > y) {
const relativeX = x - iframeRect.x;
const relativeY = y - iframeRect.y;
return editor.getDoc().elementFromPoint(relativeX, relativeY);
}
return null;
}
interface ContextMenuActionOptions {
current: ContextMenuOptions,
}
const contextMenuActionOptions:ContextMenuActionOptions = { current: null };
export default function(editor:any) {
const contextMenuItems = menuItems();
bridge().window().webContents.on('context-menu', (_event:any, params:any) => {
const element = contextMenuElement(editor, params.x, params.y);
if (!element) return;
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
let itemType:ContextMenuItemType = ContextMenuItemType.None;
let resourceId = '';
let linkToCopy = null;
if (element.nodeName === 'IMG') {
itemType = ContextMenuItemType.Image;
resourceId = Resource.pathToId(element.src);
} else if (element.nodeName === 'A') {
resourceId = Resource.pathToId(element.href);
itemType = resourceId ? ContextMenuItemType.Resource : ContextMenuItemType.Link;
linkToCopy = element.getAttribute('href') || '';
} else {
itemType = ContextMenuItemType.Text;
}
contextMenuActionOptions.current = {
itemType,
resourceId,
linkToCopy,
textToCopy: null,
htmlToCopy: editor.selection ? editor.selection.getContent() : '',
insertContent: (content:string) => {
editor.insertContent(content);
},
isReadOnly: false,
};
const menu = new Menu();
for (const itemName in contextMenuItems) {
const item = contextMenuItems[itemName];
if (!item.isActive(itemType, contextMenuActionOptions.current)) continue;
menu.append(new MenuItem({
label: item.label,
click: () => {
item.onAction(contextMenuActionOptions.current);
},
}));
}
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
for (const item of spellCheckerMenuItems) {
menu.append(item);
}
menu.popup();
});
}

View File

@@ -1,11 +1,9 @@
import * as React from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
// eslint-disable-next-line no-unused-vars
import TinyMCE from './NoteBody/TinyMCE/TinyMCE';
import CodeMirror from './NoteBody/CodeMirror/CodeMirror';
import { connect } from 'react-redux';
import MultiNoteActions from '../MultiNoteActions';
import NoteToolbar from '../NoteToolbar/NoteToolbar';
import { htmlToMarkdown, formNoteToNote } from './utils';
import useSearchMarkers from './utils/useSearchMarkers';
import useNoteSearchBar from './utils/useNoteSearchBar';
@@ -27,15 +25,15 @@ import ToolbarButtonUtils from 'lib/services/commands/ToolbarButtonUtils';
import { _ } from 'lib/locale';
import stateToWhenClauseContext from 'lib/services/commands/stateToWhenClauseContext';
import TagList from '../TagList';
import NoteTitleBar from './NoteTitle/NoteTitleBar';
import markupLanguageUtils from 'lib/markupLanguageUtils';
import usePrevious from 'lib/hooks/usePrevious';
import Setting from 'lib/models/Setting';
const { themeStyle } = require('lib/theme');
const { substrWithEllipsis } = require('lib/string-utils');
const NoteSearchBar = require('../NoteSearchBar.min.js');
const { reg } = require('lib/registry.js');
const { time } = require('lib/time-utils.js');
const markupLanguageUtils = require('lib/markupLanguageUtils').default;
const usePrevious = require('lib/hooks/usePrevious').default;
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const bridge = require('electron').remote.require('./bridge').default;
const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
@@ -45,10 +43,6 @@ const commands = [
require('./commands/showRevisions'),
];
const toolbarStyle = {
marginBottom: 0,
};
const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
function NoteEditor(props: NoteEditorProps) {
@@ -244,20 +238,20 @@ function NoteEditor(props: NoteEditorProps) {
const onTitleChange = useCallback((event: any) => onFieldChange('title', event.target.value), [onFieldChange]);
const onTitleKeydown = useCallback((event:any) => {
const keyCode = event.keyCode;
// const onTitleKeydown = useCallback((event:any) => {
// const keyCode = event.keyCode;
if (keyCode === 9) {
// TAB
event.preventDefault();
// if (keyCode === 9) {
// // TAB
// event.preventDefault();
if (event.shiftKey) {
CommandService.instance().execute('focusElement', 'noteList');
} else {
CommandService.instance().execute('focusElement', 'noteBody');
}
}
}, [props.dispatch]);
// if (event.shiftKey) {
// CommandService.instance().execute('focusElement', 'noteList');
// } else {
// CommandService.instance().execute('focusElement', 'noteBody');
// }
// }
// }, [props.dispatch]);
const onBodyWillChange = useCallback((event: any) => {
handleProvisionalFlag();
@@ -351,14 +345,6 @@ function NoteEditor(props: NoteEditorProps) {
return <div style={emptyDivStyle}></div>;
}
function renderNoteToolbar() {
return <NoteToolbar
themeId={props.themeId}
// note={formNote}
style={toolbarStyle}
/>;
}
function renderTagButton() {
return <ToolbarButton
themeId={props.themeId}
@@ -377,26 +363,6 @@ function NoteEditor(props: NoteEditorProps) {
);
}
function renderTitleBar() {
const theme = themeStyle(props.themeId);
const titleBarDate = <span style={styles.titleDate}>{time.formatMsToLocal(formNote.user_updated_time)}</span>;
return (
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', height: theme.topRowHeight }}>
<input
type="text"
ref={titleInputRef}
placeholder={props.isProvisional ? _('Creating new %s...', formNote.is_todo ? _('to-do') : _('note')) : ''}
style={styles.titleInput}
onChange={onTitleChange}
onKeyDown={onTitleKeydown}
value={formNote.title}
/>
{titleBarDate}
{renderNoteToolbar()}
</div>
);
}
const searchMarkers = useSearchMarkers(showLocalSearch, localSearchMarkerOptions, props.searches, props.selectedSearchId, props.highlightedWords);
const editorProps:NoteBodyEditorProps = {
@@ -546,7 +512,15 @@ function NoteEditor(props: NoteEditorProps) {
<div style={styles.root} onDrop={onDrop}>
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{renderResourceWatchingNotification()}
{renderTitleBar()}
<NoteTitleBar
titleInputRef={titleInputRef}
themeId={props.themeId}
isProvisional={props.isProvisional}
noteIsTodo={formNote.is_todo}
noteTitle={formNote.title}
noteUserUpdatedTime={formNote.user_updated_time}
onTitleChange={onTitleChange}
/>
{renderSearchInfo()}
<div style={{ display: 'flex', flex: 1 }}>
{editor}

View File

@@ -0,0 +1,98 @@
import * as React from 'react';
import { _ } from 'lib/locale';
import CommandService from 'lib/services/CommandService';
import { ChangeEvent, useCallback } from 'react';
import NoteToolbar from '../../NoteToolbar/NoteToolbar';
import { buildStyle } from 'lib/theme';
import time from 'lib/time';
interface Props {
themeId: number,
noteUserUpdatedTime: number,
noteTitle: string,
noteIsTodo: number,
isProvisional: boolean,
titleInputRef: any,
onTitleChange(event: ChangeEvent<HTMLInputElement>):void,
}
function styles_(props: Props) {
return buildStyle(['NoteEditorTitleBar'], props.themeId, (theme: any) => {
return {
root: {
display: 'flex', flexDirection: 'row', alignItems: 'center', height: theme.topRowHeight,
},
titleInput: {
flex: 1,
display: 'inline-block',
paddingTop: 5,
minHeight: 35,
boxSizing: 'border-box',
fontWeight: 'bold',
paddingBottom: 5,
paddingLeft: 0,
paddingRight: 8,
marginLeft: 5,
color: theme.textStyle.color,
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
backgroundColor: theme.backgroundColor,
border: 'none',
},
titleDate: {
...theme.textStyle,
color: theme.colorFaded,
paddingLeft: 10,
paddingRight: 10,
},
toolbarStyle: {
marginBottom: 0,
},
};
});
}
export default function NoteTitleBar(props:Props) {
const styles = styles_(props);
const onTitleKeydown = useCallback((event:any) => {
const keyCode = event.keyCode;
if (keyCode === 9) { // TAB
event.preventDefault();
if (event.shiftKey) {
CommandService.instance().execute('focusElement', 'noteList');
} else {
CommandService.instance().execute('focusElement', 'noteBody');
}
}
}, []);
function renderTitleBarDate() {
return <span style={styles.titleDate}>{time.formatMsToLocal(props.noteUserUpdatedTime)}</span>;
}
function renderNoteToolbar() {
return <NoteToolbar
themeId={props.themeId}
style={styles.toolbarStyle}
/>;
}
return (
<div style={styles.root}>
<input
type="text"
ref={props.titleInputRef}
placeholder={props.isProvisional ? _('Creating new %s...', props.noteIsTodo ? _('to-do') : _('note')) : ''}
style={styles.titleInput}
onChange={props.onTitleChange}
onKeyDown={onTitleKeydown}
value={props.noteTitle}
/>
{renderTitleBarDate()}
{renderNoteToolbar()}
</div>
);
}

View File

@@ -1,7 +1,7 @@
import shim from 'lib/shim';
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Resource = require('lib/models/Resource.js');
const bridge = require('electron').remote.require('./bridge').default;
const ResourceFetcher = require('lib/services/ResourceFetcher.js');

View File

@@ -4,7 +4,7 @@ import contextMenu from './contextMenu';
import ResourceEditWatcher from '../../../lib/services/ResourceEditWatcher/index';
import { _ } from 'lib/locale';
const BaseItem = require('lib/models/BaseItem');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Resource = require('lib/models/Resource.js');
const bridge = require('electron').remote.require('./bridge').default;
const { urlDecode } = require('lib/string-utils');

View File

@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { FormNote, ScrollOptionTypes } from './types';
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from 'lib/services/CommandService';
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { reg } = require('lib/registry.js');
const commandsWithDependencies = [

View File

@@ -5,9 +5,9 @@ import { _ } from 'lib/locale';
const { ItemList } = require('../ItemList.min.js');
const React = require('react');
const { connect } = require('react-redux');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { themeStyle } = require('lib/theme');
const BaseModel = require('lib/BaseModel');
const BaseModel = require('lib/BaseModel').default;
const bridge = require('electron').remote.require('./bridge').default;
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting').default;

View File

@@ -1,7 +1,7 @@
const React = require('react');
const { _ } = require('lib/locale');
const { themeStyle } = require('lib/theme');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const DialogButtonRow = require('./DialogButtonRow.min');
const Datetime = require('react-datetime');
const Note = require('lib/models/Note');

View File

@@ -4,14 +4,14 @@ const { themeStyle } = require('lib/theme');
const { _ } = require('lib/locale');
const NoteTextViewer = require('./NoteTextViewer').default;
const HelpButton = require('./HelpButton.min');
const BaseModel = require('lib/BaseModel');
const BaseModel = require('lib/BaseModel').default;
const Revision = require('lib/models/Revision');
const urlUtils = require('lib/urlUtils');
const Setting = require('lib/models/Setting').default;
const RevisionService = require('lib/services/RevisionService');
const shared = require('lib/components/shared/note-screen-shared.js');
const { MarkupToHtml } = require('lib/joplin-renderer');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const ReactTooltip = require('react-tooltip');
const { urlDecode, substrWithEllipsis } = require('lib/string-utils');
const bridge = require('electron').remote.require('./bridge').default;

View File

@@ -1,6 +1,6 @@
const React = require('react');
const { connect } = require('react-redux');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { themeStyle } = require('lib/theme');
class NoteStatusBarComponent extends React.Component {

View File

@@ -1,7 +1,7 @@
const React = require('react');
const { _ } = require('lib/locale');
const { themeStyle } = require('lib/theme');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const Datetime = require('react-datetime');
const CreatableSelect = require('react-select/lib/Creatable').default;
const Select = require('react-select').default;

View File

@@ -11,7 +11,7 @@ import { _ } from 'lib/locale';
const { connect } = require('react-redux');
const shared = require('lib/components/shared/side-menu-shared.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');

View File

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

View File

@@ -7,7 +7,7 @@ import InteropServiceHelper from '../../InteropServiceHelper';
import { _ } from 'lib/locale';
import { MenuItemLocation } from 'lib/services/plugins/api/types';
const BaseModel = require('lib/BaseModel');
const BaseModel = require('lib/BaseModel').default;
const bridge = require('electron').remote.require('./bridge').default;
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.10",
"version": "1.3.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "Joplin",
"version": "1.3.10",
"version": "1.3.11",
"description": "Joplin for Desktop",
"main": "main.js",
"scripts": {

View File

@@ -8,7 +8,7 @@ const { connect } = require('react-redux');
const { _ } = require('lib/locale');
const { themeStyle } = require('lib/theme');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const BaseModel = require('lib/BaseModel');
const BaseModel = require('lib/BaseModel').default;
const Tag = require('lib/models/Tag');
const Folder = require('lib/models/Folder');
const Note = require('lib/models/Note');
@@ -557,7 +557,7 @@ GotoAnything.manifest = {
menuItems: [
{
name: 'main',
parent: 'tools',
parent: 'go',
label: _('Goto Anything...'),
accelerator: () => KeymapService.instance().getAccelerator('gotoAnything'),
screens: ['Main'],

View File

@@ -0,0 +1,39 @@
// Provides spell checking feature via the native Electron built-in spell checker
import SpellCheckerServiceDriverBase from 'lib/services/spellChecker/SpellCheckerServiceDriverBase';
import bridge from '../bridge';
export default class SpellCheckerServiceDriverNative extends SpellCheckerServiceDriverBase {
private session():any {
return bridge().window().webContents.session;
}
public get availableLanguages():string[] {
return this.session().availableSpellCheckerLanguages;
}
// Language can be set to '' to disable spell-checking
public setLanguage(v:string) {
// If we pass an empty array, it disables spell checking
// https://github.com/electron/electron/issues/25228
this.session().setSpellCheckerLanguages(v ? [v] : []);
}
public get language():string {
const languages = this.session().getSpellCheckerLanguages();
return languages.length ? languages[0] : '';
}
public makeMenuItem(item:any):any {
const MenuItem = bridge().MenuItem;
return new MenuItem(item);
}
public addWordToSpellCheckerDictionary(_language:string, word:string) {
// Actually on Electron all languages share the same dictionary, or
// perhaps it's added to the currently active language.
this.session().addWordToSpellCheckerDictionary(word);
}
}

View File

@@ -11,14 +11,14 @@ const { defaultState, stateUtils } = require('lib/reducer');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const Folder = require('lib/models/Folder.js');
const BaseItem = require('lib/models/BaseItem.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { splitCommandString } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
const os = require('os');

View File

@@ -1,22 +1,89 @@
import paginationToSql from './models/utils/paginationToSql';
const { Database } = require('lib/database.js');
const uuid = require('lib/uuid').default;
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const Mutex = require('async-mutex').Mutex;
// New code should make use of this enum
export enum ModelType {
Note = 1,
Folder = 2,
Setting = 3,
Resource = 4,
Tag = 5,
NoteTag = 6,
Search = 7,
Alarm = 8,
MasterKey = 9,
ItemChange = 10,
NoteResource = 11,
ResourceLocalState = 12,
Revision = 13,
Migration = 14,
SmartFilter = 15,
Command = 16,
}
class BaseModel {
static modelType() {
// TODO: This ancient part of Joplin about model types is a bit of a
// mess and should be refactored properly.
public static typeEnum_:any[] = [
['TYPE_NOTE', ModelType.Note],
['TYPE_FOLDER', ModelType.Folder],
['TYPE_SETTING', ModelType.Setting],
['TYPE_RESOURCE', ModelType.Resource],
['TYPE_TAG', ModelType.Tag],
['TYPE_NOTE_TAG', ModelType.NoteTag],
['TYPE_SEARCH', ModelType.Search],
['TYPE_ALARM', ModelType.Alarm],
['TYPE_MASTER_KEY', ModelType.MasterKey],
['TYPE_ITEM_CHANGE', ModelType.ItemChange],
['TYPE_NOTE_RESOURCE', ModelType.NoteResource],
['TYPE_RESOURCE_LOCAL_STATE', ModelType.ResourceLocalState],
['TYPE_REVISION', ModelType.Revision],
['TYPE_MIGRATION', ModelType.Migration],
['TYPE_SMART_FILTER', ModelType.SmartFilter],
['TYPE_COMMAND', ModelType.Command],
]
public static TYPE_NOTE = ModelType.Note;
public static TYPE_FOLDER = ModelType.Folder;
public static TYPE_SETTING = ModelType.Setting;
public static TYPE_RESOURCE = ModelType.Resource;
public static TYPE_TAG = ModelType.Tag;
public static TYPE_NOTE_TAG = ModelType.NoteTag;
public static TYPE_SEARCH = ModelType.Search;
public static TYPE_ALARM = ModelType.Alarm;
public static TYPE_MASTER_KEY = ModelType.MasterKey;
public static TYPE_ITEM_CHANGE = ModelType.ItemChange;
public static TYPE_NOTE_RESOURCE = ModelType.NoteResource;
public static TYPE_RESOURCE_LOCAL_STATE = ModelType.ResourceLocalState;
public static TYPE_REVISION = ModelType.Revision;
public static TYPE_MIGRATION = ModelType.Migration;
public static TYPE_SMART_FILTER = ModelType.SmartFilter;
public static TYPE_COMMAND = ModelType.Command;
protected static dispatch:Function = function() {};
private static saveMutexes_:any = {};
private static db_:any;
static modelType():ModelType {
throw new Error('Must be overriden');
}
static tableName() {
static tableName():string {
throw new Error('Must be overriden');
}
static setDb(db) {
static setDb(db:any) {
this.db_ = db;
}
static addModelMd(model) {
static addModelMd(model:any):any {
if (!model) return model;
if (Array.isArray(model)) {
@@ -40,29 +107,29 @@ class BaseModel {
return false;
}
static byId(items, id) {
static byId(items:any[], id:string) {
for (let i = 0; i < items.length; i++) {
if (items[i].id == id) return items[i];
}
return null;
}
static defaultValues(fieldNames) {
const output = {};
static defaultValues(fieldNames:string[]) {
const output:any = {};
for (const n of fieldNames) {
output[n] = this.db().fieldDefaultValue(this.tableName(), n);
}
return output;
}
static modelIndexById(items, id) {
static modelIndexById(items:any[], id:string) {
for (let i = 0; i < items.length; i++) {
if (items[i].id == id) return i;
}
return -1;
}
static modelsByIds(items, ids) {
static modelsByIds(items:any[], ids:string[]) {
const output = [];
for (let i = 0; i < items.length; i++) {
if (ids.indexOf(items[i].id) >= 0) {
@@ -74,14 +141,14 @@ class BaseModel {
// Prefer the use of this function to compare IDs as it handles the case where
// one ID is null and the other is "", in which case they are actually considered to be the same.
static idsEqual(id1, id2) {
static idsEqual(id1:string, id2:string) {
if (!id1 && !id2) return true;
if (!id1 && !!id2) return false;
if (!!id1 && !id2) return false;
return id1 === id2;
}
static modelTypeToName(type) {
static modelTypeToName(type:number) {
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
const e = BaseModel.typeEnum_[i];
if (e[1] === type) return e[0].substr(5).toLowerCase();
@@ -89,7 +156,7 @@ class BaseModel {
throw new Error(`Unknown model type: ${type}`);
}
static modelNameToType(name) {
static modelNameToType(name:string) {
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
const e = BaseModel.typeEnum_[i];
const eName = e[0].substr(5).toLowerCase();
@@ -98,12 +165,12 @@ class BaseModel {
throw new Error(`Unknown model name: ${name}`);
}
static hasField(name) {
static hasField(name:string) {
const fields = this.fieldNames();
return fields.indexOf(name) >= 0;
}
static fieldNames(withPrefix = false) {
static fieldNames(withPrefix:boolean = false) {
const output = this.db().tableFieldNames(this.tableName());
if (!withPrefix) return output;
@@ -116,7 +183,7 @@ class BaseModel {
return temp;
}
static fieldType(name, defaultValue = null) {
static fieldType(name:string, defaultValue:any = null) {
const fields = this.fields();
for (let i = 0; i < fields.length; i++) {
if (fields[i].name == name) return fields[i].type;
@@ -129,8 +196,8 @@ class BaseModel {
return this.db().tableFields(this.tableName());
}
static removeUnknownFields(model) {
const newModel = {};
static removeUnknownFields(model:any) {
const newModel:any = {};
for (const n in model) {
if (!model.hasOwnProperty(n)) continue;
if (!this.hasField(n) && n !== 'type_') continue;
@@ -141,7 +208,7 @@ class BaseModel {
static new() {
const fields = this.fields();
const output = {};
const output:any = {};
for (let i = 0; i < fields.length; i++) {
const f = fields[i];
output[f.name] = f.default;
@@ -149,7 +216,7 @@ class BaseModel {
return output;
}
static modOptions(options) {
static modOptions(options:any) {
if (!options) {
options = {};
} else {
@@ -161,42 +228,42 @@ class BaseModel {
return options;
}
static count(options = null) {
static count(options:any = null) {
if (!options) options = {};
let sql = `SELECT count(*) as total FROM \`${this.tableName()}\``;
if (options.where) sql += ` WHERE ${options.where}`;
return this.db()
.selectOne(sql)
.then(r => {
.then((r:any) => {
return r ? r['total'] : 0;
});
}
static load(id, options = null) {
static load(id:string, options:any = null) {
return this.loadByField('id', id, options);
}
static shortId(id) {
static shortId(id:string) {
return id.substr(0, 5);
}
static loadByPartialId(partialId) {
static loadByPartialId(partialId:string) {
return this.modelSelectAll(`SELECT * FROM \`${this.tableName()}\` WHERE \`id\` LIKE ?`, [`${partialId}%`]);
}
static applySqlOptions(options, sql, params = null) {
static applySqlOptions(options:any, sql:string, params:any[] = null) {
if (!options) options = {};
if (options.order && options.order.length) {
const items = [];
for (let i = 0; i < options.order.length; i++) {
const o = options.order[i];
let item = `\`${o.by}\``;
if (options.caseInsensitive === true) item += ' COLLATE NOCASE';
if (o.dir) item += ` ${o.dir}`;
items.push(item);
}
sql += ` ORDER BY ${items.join(', ')}`;
// const items = [];
// for (let i = 0; i < options.order.length; i++) {
// const o = options.order[i];
// let item = `\`${o.by}\``;
// if (options.caseInsensitive === true) item += ' COLLATE NOCASE';
// if (o.dir) item += ` ${o.dir}`;
// items.push(item);
// }
sql += ` ORDER BY ${paginationToSql(options)}`;
}
if (options.limit) sql += ` LIMIT ${options.limit}`;
@@ -204,18 +271,18 @@ class BaseModel {
return { sql: sql, params: params };
}
static async allIds(options = null) {
static async allIds(options:any = null) {
const q = this.applySqlOptions(options, `SELECT id FROM \`${this.tableName()}\``);
const rows = await this.db().selectAll(q.sql, q.params);
return rows.map(r => r.id);
return rows.map((r:any) => r.id);
}
static async all(options = null) {
static async all(options:any = null) {
if (!options) options = {};
if (!options.fields) options.fields = '*';
let sql = `SELECT ${this.db().escapeFields(options.fields)} FROM \`${this.tableName()}\``;
let params = [];
let params:any[] = [];
if (options.where) {
sql += ` WHERE ${options.where}`;
if (options.whereParams) params = params.concat(options.whereParams);
@@ -225,7 +292,7 @@ class BaseModel {
return this.modelSelectAll(q.sql, q.params);
}
static async byIds(ids, options = null) {
static async byIds(ids:string[], options:any = null) {
if (!ids.length) return [];
if (!options) options = {};
if (!options.fields) options.fields = '*';
@@ -236,7 +303,7 @@ class BaseModel {
return this.modelSelectAll(q.sql);
}
static async search(options = null) {
static async search(options:any = null) {
if (!options) options = {};
if (!options.fields) options.fields = '*';
@@ -258,25 +325,25 @@ class BaseModel {
return this.modelSelectAll(query.sql, query.params);
}
static modelSelectOne(sql, params = null) {
static modelSelectOne(sql:string, params:any[] = null) {
if (params === null) params = [];
return this.db()
.selectOne(sql, params)
.then(model => {
.then((model:any) => {
return this.filter(this.addModelMd(model));
});
}
static modelSelectAll(sql, params = null) {
static modelSelectAll(sql:string, params:any[] = null) {
if (params === null) params = [];
return this.db()
.selectAll(sql, params)
.then(models => {
.then((models:any[]) => {
return this.filterArray(this.addModelMd(models));
});
}
static loadByField(fieldName, fieldValue, options = null) {
static loadByField(fieldName:string, fieldValue:any, options:any = null) {
if (!options) options = {};
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
if (!options.fields) options.fields = '*';
@@ -285,7 +352,7 @@ class BaseModel {
return this.modelSelectOne(sql, [fieldValue]);
}
static loadByFields(fields, options = null) {
static loadByFields(fields:string[], options:any = null) {
if (!options) options = {};
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
if (!options.fields) options.fields = '*';
@@ -300,12 +367,12 @@ class BaseModel {
return this.modelSelectOne(sql, params);
}
static loadByTitle(fieldValue) {
static loadByTitle(fieldValue:any) {
return this.modelSelectOne(`SELECT * FROM \`${this.tableName()}\` WHERE \`title\` = ?`, [fieldValue]);
}
static diffObjects(oldModel, newModel) {
const output = {};
static diffObjects(oldModel:any, newModel:any) {
const output:any = {};
const fields = this.diffObjectsFields(oldModel, newModel);
for (let i = 0; i < fields.length; i++) {
output[fields[i]] = newModel[fields[i]];
@@ -314,7 +381,7 @@ class BaseModel {
return output;
}
static diffObjectsFields(oldModel, newModel) {
static diffObjectsFields(oldModel:any, newModel:any) {
const output = [];
for (const n in newModel) {
if (!newModel.hasOwnProperty(n)) continue;
@@ -326,15 +393,15 @@ class BaseModel {
return output;
}
static modelsAreSame(oldModel, newModel) {
static modelsAreSame(oldModel:any, newModel:any) {
const diff = this.diffObjects(oldModel, newModel);
delete diff.type_;
return !Object.getOwnPropertyNames(diff).length;
}
static saveMutex(modelOrId) {
static saveMutex(modelOrId:any) {
const noLockMutex = {
acquire: function() {
acquire: function():any {
return null;
},
};
@@ -353,7 +420,7 @@ class BaseModel {
return mutex;
}
static releaseSaveMutex(modelOrId, release) {
static releaseSaveMutex(modelOrId:any, release:Function) {
if (!release) return;
if (!modelOrId) return release();
@@ -368,8 +435,8 @@ class BaseModel {
release();
}
static saveQuery(o, options) {
let temp = {};
static saveQuery(o:any, options:any) {
let temp:any = {};
const fieldNames = this.fieldNames();
for (let i = 0; i < fieldNames.length; i++) {
const n = fieldNames[i];
@@ -381,7 +448,7 @@ class BaseModel {
// be part of the final list of fields if autoTimestamp is on.
// id also will stay.
if (!options.isNew && options.fields) {
const filtered = {};
const filtered:any = {};
for (const k in temp) {
if (!temp.hasOwnProperty(k)) continue;
if (k !== 'id' && options.fields.indexOf(k) < 0) continue;
@@ -393,7 +460,7 @@ class BaseModel {
o = temp;
let modelId = temp.id;
let query = {};
let query:any = {};
const timeNow = time.unixMs();
@@ -445,7 +512,7 @@ class BaseModel {
return query;
}
static userSideValidation(o) {
static userSideValidation(o:any) {
if (o.id && !o.id.match(/^[a-f0-9]{32}$/)) {
throw new Error('Validation error: ID must a 32-characters lowercase hexadecimal string');
}
@@ -456,7 +523,7 @@ class BaseModel {
}
}
static async save(o, options = null) {
static async save(o:any, options:any = null) {
// When saving, there's a mutex per model ID. This is because the model returned from this function
// is basically its input `o` (instead of being read from the database, for performance reasons).
// This works well in general except if that model is saved simultaneously in two places. In that
@@ -526,7 +593,7 @@ class BaseModel {
return output;
}
static isNew(object, options) {
static isNew(object:any, options:any) {
if (options && 'isNew' in options) {
// options.isNew can be "auto" too
if (options.isNew === true) return true;
@@ -536,7 +603,7 @@ class BaseModel {
return !object.id;
}
static filterArray(models) {
static filterArray(models:any[]) {
const output = [];
for (let i = 0; i < models.length; i++) {
output.push(this.filter(models[i]));
@@ -544,7 +611,7 @@ class BaseModel {
return output;
}
static filter(model) {
static filter(model:any) {
if (!model) return model;
const output = Object.assign({}, model);
@@ -567,12 +634,12 @@ class BaseModel {
return output;
}
static delete(id) {
static delete(id:string) {
if (!id) throw new Error('Cannot delete object without an ID');
return this.db().exec(`DELETE FROM ${this.tableName()} WHERE id = ?`, [id]);
}
static batchDelete(ids, options = null) {
static batchDelete(ids:string[], options:any = null) {
if (!ids.length) return;
options = this.modOptions(options);
const idFieldName = options.idFieldName ? options.idFieldName : 'id';
@@ -590,32 +657,9 @@ class BaseModel {
}
}
BaseModel.typeEnum_ = [
['TYPE_NOTE', 1],
['TYPE_FOLDER', 2],
['TYPE_SETTING', 3],
['TYPE_RESOURCE', 4],
['TYPE_TAG', 5],
['TYPE_NOTE_TAG', 6],
['TYPE_SEARCH', 7],
['TYPE_ALARM', 8],
['TYPE_MASTER_KEY', 9],
['TYPE_ITEM_CHANGE', 10],
['TYPE_NOTE_RESOURCE', 11],
['TYPE_RESOURCE_LOCAL_STATE', 12],
['TYPE_REVISION', 13],
['TYPE_MIGRATION', 14],
['TYPE_SMART_FILTER', 15],
['TYPE_COMMAND', 16],
];
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
const e = BaseModel.typeEnum_[i];
BaseModel[e[0]] = e[1];
(BaseModel as any)[e[0]] = e[1];
}
BaseModel.db_ = null;
BaseModel.dispatch = function() {};
BaseModel.saveMutexes_ = {};
module.exports = BaseModel;
export default BaseModel;

View File

@@ -4,7 +4,7 @@ const Logger = require('lib/Logger').default;
const { randomClipperPort, startPort } = require('lib/randomClipperPort');
const enableServerDestroy = require('server-destroy');
const Api = require('lib/services/rest/Api').default;
const ApiResponse = require('lib/services/rest/ApiResponse');
const ApiResponse = require('lib/services/rest/ApiResponse').default;
const multiparty = require('multiparty');
class ClipperServer {
@@ -29,7 +29,6 @@ class ClipperServer {
setLogger(l) {
this.logger_ = l;
this.api_.setLogger(l);
}
logger() {

View File

@@ -1,7 +1,7 @@
const Logger = require('lib/Logger').default;
const shim = require('lib/shim').default;
const JoplinError = require('lib/JoplinError');
const { time } = require('lib/time-utils');
const time = require('lib/time').default;
const EventDispatcher = require('lib/EventDispatcher');
class DropboxApi {

View File

@@ -1,5 +1,5 @@
const moment = require('moment');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const { FsDriverDummy } = require('lib/fs-driver-dummy.js');
export enum TargetType {

View File

@@ -6,7 +6,7 @@
// whenever the update() function is called, and in mobile it's called for
// example on the Redux action middleware or when the app gets focus.
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
type IntervalId = number;

View File

@@ -13,9 +13,9 @@ const Resource = require('lib/models/Resource.js');
const ItemChange = require('lib/models/ItemChange.js');
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
const MasterKey = require('lib/models/MasterKey.js');
const BaseModel = require('lib/BaseModel.js');
const BaseModel = require('lib/BaseModel').default;
const { sprintf } = require('sprintf-js');
const { time } = require('lib/time-utils.js');
const time = require('lib/time').default;
const JoplinError = require('lib/JoplinError');
const TaskQueue = require('lib/TaskQueue');
const { Dirnames } = require('lib/services/synchronizer/utils/types');

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