You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
17 Commits
spellcheck
...
api_pagina
Author | SHA1 | Date | |
---|---|---|---|
|
f7bfd5dbe7 | ||
|
9a4b8685d2 | ||
|
f3376c779e | ||
|
76739eeff0 | ||
|
279998db2b | ||
|
18608f4c3a | ||
|
5059089479 | ||
|
6284c386fb | ||
|
3a63df6515 | ||
|
c08d225ae1 | ||
|
594720530e | ||
|
e7d6675f47 | ||
|
0d93f0c3c0 | ||
|
37b308d440 | ||
|
1f8f3f24ed | ||
|
3713c5ab2c | ||
|
3a94d2c49b |
@@ -60,6 +60,7 @@ Tools/PortableAppsLauncher
|
|||||||
Modules/TinyMCE/IconPack/postinstall.js
|
Modules/TinyMCE/IconPack/postinstall.js
|
||||||
Modules/TinyMCE/langs/
|
Modules/TinyMCE/langs/
|
||||||
CliClient/build/
|
CliClient/build/
|
||||||
|
plugin_types/
|
||||||
|
|
||||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||||
CliClient/app/LinkSelector.js
|
CliClient/app/LinkSelector.js
|
||||||
@@ -212,6 +213,7 @@ ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
|
|||||||
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||||
ReactNativeClient/lib/AsyncActionQueue.js
|
ReactNativeClient/lib/AsyncActionQueue.js
|
||||||
ReactNativeClient/lib/BaseApplication.js
|
ReactNativeClient/lib/BaseApplication.js
|
||||||
|
ReactNativeClient/lib/BaseModel.js
|
||||||
ReactNativeClient/lib/checkPermissions.js
|
ReactNativeClient/lib/checkPermissions.js
|
||||||
ReactNativeClient/lib/commands/historyBackward.js
|
ReactNativeClient/lib/commands/historyBackward.js
|
||||||
ReactNativeClient/lib/commands/historyForward.js
|
ReactNativeClient/lib/commands/historyForward.js
|
||||||
@@ -255,6 +257,9 @@ ReactNativeClient/lib/markdownUtils.js
|
|||||||
ReactNativeClient/lib/markupLanguageUtils.js
|
ReactNativeClient/lib/markupLanguageUtils.js
|
||||||
ReactNativeClient/lib/models/Alarm.js
|
ReactNativeClient/lib/models/Alarm.js
|
||||||
ReactNativeClient/lib/models/Setting.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/ntpDate.js
|
||||||
ReactNativeClient/lib/path-utils.js
|
ReactNativeClient/lib/path-utils.js
|
||||||
ReactNativeClient/lib/PoorManIntervals.js
|
ReactNativeClient/lib/PoorManIntervals.js
|
||||||
@@ -329,7 +334,22 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
|
|||||||
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
|
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
|
||||||
ReactNativeClient/lib/services/rest/actionApi.desktop.js
|
ReactNativeClient/lib/services/rest/actionApi.desktop.js
|
||||||
ReactNativeClient/lib/services/rest/Api.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/filterParser.js
|
||||||
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
||||||
ReactNativeClient/lib/services/SettingUtils.js
|
ReactNativeClient/lib/services/SettingUtils.js
|
||||||
|
21
.gitignore
vendored
21
.gitignore
vendored
@@ -206,6 +206,7 @@ ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
|
|||||||
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||||
ReactNativeClient/lib/AsyncActionQueue.js
|
ReactNativeClient/lib/AsyncActionQueue.js
|
||||||
ReactNativeClient/lib/BaseApplication.js
|
ReactNativeClient/lib/BaseApplication.js
|
||||||
|
ReactNativeClient/lib/BaseModel.js
|
||||||
ReactNativeClient/lib/checkPermissions.js
|
ReactNativeClient/lib/checkPermissions.js
|
||||||
ReactNativeClient/lib/commands/historyBackward.js
|
ReactNativeClient/lib/commands/historyBackward.js
|
||||||
ReactNativeClient/lib/commands/historyForward.js
|
ReactNativeClient/lib/commands/historyForward.js
|
||||||
@@ -249,6 +250,9 @@ ReactNativeClient/lib/markdownUtils.js
|
|||||||
ReactNativeClient/lib/markupLanguageUtils.js
|
ReactNativeClient/lib/markupLanguageUtils.js
|
||||||
ReactNativeClient/lib/models/Alarm.js
|
ReactNativeClient/lib/models/Alarm.js
|
||||||
ReactNativeClient/lib/models/Setting.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/ntpDate.js
|
||||||
ReactNativeClient/lib/path-utils.js
|
ReactNativeClient/lib/path-utils.js
|
||||||
ReactNativeClient/lib/PoorManIntervals.js
|
ReactNativeClient/lib/PoorManIntervals.js
|
||||||
@@ -323,7 +327,22 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
|
|||||||
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
|
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
|
||||||
ReactNativeClient/lib/services/rest/actionApi.desktop.js
|
ReactNativeClient/lib/services/rest/actionApi.desktop.js
|
||||||
ReactNativeClient/lib/services/rest/Api.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/filterParser.js
|
||||||
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
||||||
ReactNativeClient/lib/services/SettingUtils.js
|
ReactNativeClient/lib/services/SettingUtils.js
|
||||||
|
21
.ignore
21
.ignore
@@ -155,6 +155,7 @@ ElectronClient/services/plugins/UserWebviewDialogButtonBar.js
|
|||||||
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
ElectronClient/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||||
ReactNativeClient/lib/AsyncActionQueue.js
|
ReactNativeClient/lib/AsyncActionQueue.js
|
||||||
ReactNativeClient/lib/BaseApplication.js
|
ReactNativeClient/lib/BaseApplication.js
|
||||||
|
ReactNativeClient/lib/BaseModel.js
|
||||||
ReactNativeClient/lib/checkPermissions.js
|
ReactNativeClient/lib/checkPermissions.js
|
||||||
ReactNativeClient/lib/commands/historyBackward.js
|
ReactNativeClient/lib/commands/historyBackward.js
|
||||||
ReactNativeClient/lib/commands/historyForward.js
|
ReactNativeClient/lib/commands/historyForward.js
|
||||||
@@ -198,6 +199,9 @@ ReactNativeClient/lib/markdownUtils.js
|
|||||||
ReactNativeClient/lib/markupLanguageUtils.js
|
ReactNativeClient/lib/markupLanguageUtils.js
|
||||||
ReactNativeClient/lib/models/Alarm.js
|
ReactNativeClient/lib/models/Alarm.js
|
||||||
ReactNativeClient/lib/models/Setting.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/ntpDate.js
|
||||||
ReactNativeClient/lib/path-utils.js
|
ReactNativeClient/lib/path-utils.js
|
||||||
ReactNativeClient/lib/PoorManIntervals.js
|
ReactNativeClient/lib/PoorManIntervals.js
|
||||||
@@ -272,7 +276,22 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
|
|||||||
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
|
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
|
||||||
ReactNativeClient/lib/services/rest/actionApi.desktop.js
|
ReactNativeClient/lib/services/rest/actionApi.desktop.js
|
||||||
ReactNativeClient/lib/services/rest/Api.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/filterParser.js
|
||||||
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
ReactNativeClient/lib/services/searchengine/queryBuilder.js
|
||||||
ReactNativeClient/lib/services/SettingUtils.js
|
ReactNativeClient/lib/services/SettingUtils.js
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 48 KiB |
@@ -2,7 +2,7 @@ const Logger = require('lib/Logger').default;
|
|||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Tag = require('lib/models/Tag.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 Note = require('lib/models/Note.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const BaseApplication = require('lib/BaseApplication').default;
|
const BaseApplication = require('lib/BaseApplication').default;
|
||||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||||
const ResourceService = require('lib/services/ResourceService');
|
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 Folder = require('lib/models/Folder.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
@@ -5,7 +5,7 @@ const Logger = require('lib/Logger').default;
|
|||||||
const { dirname } = require('lib/path-utils');
|
const { dirname } = require('lib/path-utils');
|
||||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||||
const { JoplinDatabase } = require('lib/joplin-database.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 Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const BaseItem = require('lib/models/BaseItem');
|
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 { toTitleCase } = require('lib/string-utils.js');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const markdownUtils = require('lib/markdownUtils').default;
|
const markdownUtils = require('lib/markdownUtils').default;
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
usage() {
|
||||||
return 'apidoc';
|
return 'apidoc <file>';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
@@ -35,7 +36,7 @@ class Command extends BaseCommand {
|
|||||||
return markdownUtils.createMarkdownTable(headers, tableFields);
|
return markdownUtils.createMarkdownTable(headers, tableFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
async action() {
|
async action(args) {
|
||||||
const models = [
|
const models = [
|
||||||
{
|
{
|
||||||
type: BaseModel.TYPE_NOTE,
|
type: BaseModel.TYPE_NOTE,
|
||||||
@@ -112,6 +113,51 @@ class Command extends BaseCommand {
|
|||||||
lines.push('');
|
lines.push('');
|
||||||
lines.push('\tcurl http://localhost:41184/tags?fields=id');
|
lines.push('\tcurl http://localhost:41184/tags?fields=id');
|
||||||
lines.push('');
|
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('# Error handling');
|
||||||
lines.push('');
|
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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ const { app } = require('./app.js');
|
|||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
usage() {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const InteropService = require('lib/services/interop/InteropService').default;
|
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 { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const InteropService = require('lib/services/interop/InteropService').default;
|
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 { cliUtils } = require('./cli-utils.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
usage() {
|
||||||
|
@@ -2,7 +2,7 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
usage() {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const uuid = require('lib/uuid').default;
|
const uuid = require('lib/uuid').default;
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ const { BaseCommand } = require('./base-command.js');
|
|||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
const { BaseCommand } = require('./base-command.js');
|
const { BaseCommand } = require('./base-command.js');
|
||||||
const { app } = require('./app.js');
|
const { app } = require('./app.js');
|
||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
usage() {
|
usage() {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Tag = require('lib/models/Tag.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 ListWidget = require('tkwidgets/ListWidget.js');
|
||||||
const _ = require('lib/locale')._;
|
const _ = require('lib/locale')._;
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ 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 { 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 Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.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 shim = require('lib/shim').default;
|
||||||
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ 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 { 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 Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.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 shim = require('lib/shim').default;
|
||||||
const HtmlToHtml = require('lib/joplin-renderer/HtmlToHtml');
|
const HtmlToHtml = require('lib/joplin-renderer/HtmlToHtml');
|
||||||
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
||||||
|
@@ -8,7 +8,7 @@ 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 { 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 Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.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 shim = require('lib/shim').default;
|
||||||
const HtmlToMd = require('lib/HtmlToMd');
|
const HtmlToMd = require('lib/HtmlToMd');
|
||||||
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
const { enexXmlToMd } = require('lib/import-enex-md-gen.js');
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
|
import MdToHtml from 'lib/joplin-renderer/MdToHtml';
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const { filename } = require('lib/path-utils');
|
const { filename } = require('lib/path-utils');
|
||||||
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
|
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
const MdToHtml = require('lib/joplin-renderer/MdToHtml').default;
|
|
||||||
const { themeStyle } = require('lib/theme');
|
const { themeStyle } = require('lib/theme');
|
||||||
|
|
||||||
function newTestMdToHtml(options:any = null) {
|
function newTestMdToHtml(options:any = null) {
|
||||||
@@ -120,23 +120,54 @@ describe('MdToHtml', function() {
|
|||||||
it('should return the rendered body only', asyncTest(async () => {
|
it('should return the rendered body only', asyncTest(async () => {
|
||||||
const mdToHtml = newTestMdToHtml();
|
const mdToHtml = newTestMdToHtml();
|
||||||
|
|
||||||
// In this case, the HTML contains only the rendered markdown,
|
// In this case, the HTML contains only the rendered markdown, with
|
||||||
// with no wrapper and no style.
|
// no wrapper and no style. The style is instead in the cssStrings
|
||||||
// The style is instead in the cssStrings property.
|
// property.
|
||||||
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
|
{
|
||||||
expect(result.cssStrings.length).toBeGreaterThan(0);
|
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
|
||||||
expect(result.html.trim()).toBe('just <strong>testing</strong>');
|
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 () => {
|
it('should split HTML and CSS', asyncTest(async () => {
|
||||||
const mdToHtml = newTestMdToHtml();
|
const mdToHtml = newTestMdToHtml();
|
||||||
|
|
||||||
// It is similar to the bodyOnly option, excepts that
|
// It is similar to the bodyOnly option, excepts that the rendered
|
||||||
// the rendered Markdown is wrapped in a DIV
|
// Markdown is wrapped in a DIV
|
||||||
const result = await mdToHtml.render('just **testing**', null, { splitted: true });
|
const result = await mdToHtml.render('just **testing**', null, { splitted: true });
|
||||||
expect(result.cssStrings.length).toBeGreaterThan(0);
|
expect(result.cssStrings.length).toBeGreaterThan(0);
|
||||||
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
|
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);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -7,7 +7,7 @@ const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase,
|
|||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
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 ArrayUtils = require('lib/ArrayUtils.js');
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
require('app-module-path').addPath(__dirname);
|
require('app-module-path').addPath(__dirname);
|
||||||
const { asyncTest, id, ids, createNTestFolders, sortedIds, createNTestNotes, TestApp } = require('test-utils.js');
|
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 uuid = require('lib/uuid').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
|
@@ -8,7 +8,7 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Resource = require('lib/models/Resource.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;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
@@ -6,7 +6,7 @@ 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 { 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 Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.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 shim = require('lib/shim').default;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
@@ -7,7 +7,7 @@ const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase,
|
|||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
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 ArrayUtils = require('lib/ArrayUtils.js');
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ const { sortedIds, createNTestNotes, asyncTest, fileContentEqual, setupDatabase,
|
|||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
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 ArrayUtils = require('lib/ArrayUtils.js');
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer
|
|||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Resource = require('lib/models/Resource.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;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
@@ -9,7 +9,7 @@ const Note = require('lib/models/Note.js');
|
|||||||
const NoteTag = require('lib/models/NoteTag.js');
|
const NoteTag = require('lib/models/NoteTag.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const Revision = require('lib/models/Revision.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;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
@@ -8,7 +8,7 @@ const Folder = require('lib/models/Folder.js');
|
|||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const NoteTag = require('lib/models/NoteTag.js');
|
const NoteTag = require('lib/models/NoteTag.js');
|
||||||
const Tag = require('lib/models/Tag.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;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
@@ -10,7 +10,7 @@ const Tag = require('lib/models/Tag.js');
|
|||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
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 MasterKey = require('lib/models/MasterKey');
|
||||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||||
const EncryptionService = require('lib/services/EncryptionService.js');
|
const EncryptionService = require('lib/services/EncryptionService.js');
|
||||||
|
@@ -5,7 +5,7 @@ require('app-module-path').addPath(__dirname);
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
|
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
|
||||||
const InteropService_Exporter_Md = require('lib/services/interop/InteropService_Exporter_Md').default;
|
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 Folder = require('lib/models/Folder.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
@@ -11,7 +11,7 @@ const NoteTag = require('lib/models/NoteTag.js');
|
|||||||
const ItemChange = require('lib/models/ItemChange.js');
|
const ItemChange = require('lib/models/ItemChange.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const Revision = require('lib/models/Revision.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 RevisionService = require('lib/services/RevisionService.js');
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
|
@@ -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';
|
import shim from 'lib/shim';
|
||||||
|
|
||||||
const { asyncTest, setupDatabaseAndSynchronizer, switchClient, checkThrowAsync } = require('test-utils.js');
|
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 Tag = require('lib/models/Tag');
|
||||||
const NoteTag = require('lib/models/NoteTag');
|
const NoteTag = require('lib/models/NoteTag');
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
async function msleep(ms:number) {
|
||||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
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;
|
let api:Api = null;
|
||||||
|
|
||||||
@@ -24,24 +37,24 @@ describe('services_rest_Api', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should ping', asyncTest(async () => {
|
it('should ping', asyncTest(async () => {
|
||||||
const response = await api.route('GET', 'ping');
|
const response = await api.route(RequestMethod.GET, 'ping');
|
||||||
expect(response).toBe('JoplinClipperServer');
|
expect(response).toBe('JoplinClipperServer');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should handle Not Found errors', asyncTest(async () => {
|
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);
|
expect(hasThrown).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should get folders', asyncTest(async () => {
|
it('should get folders', asyncTest(async () => {
|
||||||
await Folder.save({ title: 'mon carnet' });
|
await Folder.save({ title: 'mon carnet' });
|
||||||
const response = await api.route('GET', 'folders');
|
const response = await api.route(RequestMethod.GET, 'folders');
|
||||||
expect(response.length).toBe(1);
|
expect(response.items.length).toBe(1);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should update folders', asyncTest(async () => {
|
it('should update folders', asyncTest(async () => {
|
||||||
const f1 = await Folder.save({ title: 'mon carnet' });
|
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é',
|
title: 'modifié',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -51,14 +64,14 @@ describe('services_rest_Api', function() {
|
|||||||
|
|
||||||
it('should delete folders', asyncTest(async () => {
|
it('should delete folders', asyncTest(async () => {
|
||||||
const f1 = await Folder.save({ title: 'mon carnet' });
|
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);
|
const f1b = await Folder.load(f1.id);
|
||||||
expect(!f1b).toBe(true);
|
expect(!f1b).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create folders', asyncTest(async () => {
|
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',
|
title: 'from api',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -71,26 +84,26 @@ describe('services_rest_Api', function() {
|
|||||||
|
|
||||||
it('should get one folder', asyncTest(async () => {
|
it('should get one folder', asyncTest(async () => {
|
||||||
const f1 = await Folder.save({ title: 'mon carnet' });
|
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);
|
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);
|
expect(hasThrown).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should get the folder notes', asyncTest(async () => {
|
it('should get the folder notes', asyncTest(async () => {
|
||||||
const f1 = await Folder.save({ title: 'mon carnet' });
|
const f1 = await Folder.save({ title: 'mon carnet' });
|
||||||
const response2 = await api.route('GET', `folders/${f1.id}/notes`);
|
const response2 = await api.route(RequestMethod.GET, `folders/${f1.id}/notes`);
|
||||||
expect(response2.length).toBe(0);
|
expect(response2.items.length).toBe(0);
|
||||||
|
|
||||||
await Note.save({ title: 'un', parent_id: f1.id });
|
await Note.save({ title: 'un', parent_id: f1.id });
|
||||||
await Note.save({ title: 'deux', parent_id: f1.id });
|
await Note.save({ title: 'deux', parent_id: f1.id });
|
||||||
const response = await api.route('GET', `folders/${f1.id}/notes`);
|
const response = await api.route(RequestMethod.GET, `folders/${f1.id}/notes`);
|
||||||
expect(response.length).toBe(2);
|
expect(response.items.length).toBe(2);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should fail on invalid paths', asyncTest(async () => {
|
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);
|
expect(hasThrown).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -102,13 +115,13 @@ describe('services_rest_Api', function() {
|
|||||||
await Note.save({ title: 'deux', parent_id: f1.id });
|
await Note.save({ title: 'deux', parent_id: f1.id });
|
||||||
const n3 = await Note.save({ title: 'trois', parent_id: f2.id });
|
const n3 = await Note.save({ title: 'trois', parent_id: f2.id });
|
||||||
|
|
||||||
response = await api.route('GET', 'notes');
|
response = await api.route(RequestMethod.GET, 'notes');
|
||||||
expect(response.length).toBe(3);
|
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);
|
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(Object.getOwnPropertyNames(response).length).toBe(3);
|
||||||
expect(response.id).toBe(n3.id);
|
expect(response.id).toBe(n3.id);
|
||||||
expect(response.title).toBe('trois');
|
expect(response.title).toBe('trois');
|
||||||
@@ -118,14 +131,14 @@ describe('services_rest_Api', function() {
|
|||||||
let response = null;
|
let response = null;
|
||||||
const f = await Folder.save({ title: 'mon carnet' });
|
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',
|
title: 'testing',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
}));
|
}));
|
||||||
expect(response.title).toBe('testing');
|
expect(response.title).toBe('testing');
|
||||||
expect(!!response.id).toBe(true);
|
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',
|
title: 'testing',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
}));
|
}));
|
||||||
@@ -137,7 +150,7 @@ describe('services_rest_Api', function() {
|
|||||||
let response:any = null;
|
let response:any = null;
|
||||||
const f = await Folder.save({ title: 'mon carnet' });
|
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',
|
title: 'testing',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
latitude: '48.732071',
|
latitude: '48.732071',
|
||||||
@@ -154,7 +167,7 @@ describe('services_rest_Api', function() {
|
|||||||
expect(note.altitude).toBe('21.0000');
|
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',
|
latitude: '49',
|
||||||
longitude: '-3',
|
longitude: '-3',
|
||||||
altitude: '22',
|
altitude: '22',
|
||||||
@@ -175,7 +188,7 @@ describe('services_rest_Api', function() {
|
|||||||
const updatedTime = Date.now() - 1000;
|
const updatedTime = Date.now() - 1000;
|
||||||
const createdTime = Date.now() - 10000;
|
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,
|
parent_id: f.id,
|
||||||
user_updated_time: updatedTime,
|
user_updated_time: updatedTime,
|
||||||
user_created_time: createdTime,
|
user_created_time: createdTime,
|
||||||
@@ -186,7 +199,7 @@ describe('services_rest_Api', function() {
|
|||||||
|
|
||||||
const timeBefore = Date.now();
|
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,
|
parent_id: f.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -201,7 +214,7 @@ describe('services_rest_Api', function() {
|
|||||||
const updatedTime = Date.now() - 1000;
|
const updatedTime = Date.now() - 1000;
|
||||||
const createdTime = Date.now() - 10000;
|
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,
|
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
|
// 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_updated_time: updatedTime,
|
||||||
user_created_time: createdTime,
|
user_created_time: createdTime,
|
||||||
title: 'mod',
|
title: 'mod',
|
||||||
@@ -227,7 +240,7 @@ describe('services_rest_Api', function() {
|
|||||||
|
|
||||||
const beforeTime = Date.now();
|
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',
|
title: 'mod2',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -242,7 +255,7 @@ describe('services_rest_Api', function() {
|
|||||||
let response = null;
|
let response = null;
|
||||||
const f = await Folder.save({ title: 'mon carnet' });
|
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',
|
id: '12345678123456781234567812345678',
|
||||||
title: 'testing',
|
title: 'testing',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
@@ -254,27 +267,27 @@ describe('services_rest_Api', function() {
|
|||||||
let response = null;
|
let response = null;
|
||||||
const f = await Folder.save({ title: 'stuff to do' });
|
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',
|
title: 'testing',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
is_todo: 1,
|
is_todo: 1,
|
||||||
}));
|
}));
|
||||||
expect(response.is_todo).toBe(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',
|
title: 'testing 2',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
is_todo: 0,
|
is_todo: 0,
|
||||||
}));
|
}));
|
||||||
expect(response.is_todo).toBe(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',
|
title: 'testing 3',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
}));
|
}));
|
||||||
expect(response.is_todo).toBeUndefined();
|
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',
|
title: 'testing 4',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
is_todo: '1',
|
is_todo: '1',
|
||||||
@@ -282,7 +295,7 @@ describe('services_rest_Api', function() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create folders with supplied ID', asyncTest(async () => {
|
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',
|
id: '12345678123456781234567812345678',
|
||||||
title: 'from api',
|
title: 'from api',
|
||||||
}));
|
}));
|
||||||
@@ -294,7 +307,7 @@ describe('services_rest_Api', function() {
|
|||||||
let response = null;
|
let response = null;
|
||||||
const f = await Folder.save({ title: 'mon carnet' });
|
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',
|
title: 'testing image',
|
||||||
parent_id: f.id,
|
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=',
|
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 () => {
|
it('should delete resources', asyncTest(async () => {
|
||||||
const f = await Folder.save({ title: 'mon carnet' });
|
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',
|
title: 'testing image',
|
||||||
parent_id: f.id,
|
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=',
|
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);
|
const filePath = Resource.fullPath(resource);
|
||||||
expect(await shim.fsDriver().exists(filePath)).toBe(true);
|
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 shim.fsDriver().exists(filePath)).toBe(false);
|
||||||
expect(!(await Resource.load(resource.id))).toBe(true);
|
expect(!(await Resource.load(resource.id))).toBe(true);
|
||||||
}));
|
}));
|
||||||
@@ -330,7 +343,7 @@ describe('services_rest_Api', function() {
|
|||||||
let response = null;
|
let response = null;
|
||||||
const f = await Folder.save({ title: 'mon carnet' });
|
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',
|
title: 'testing HTML',
|
||||||
parent_id: f.id,
|
parent_id: f.id,
|
||||||
body_html: '<b>Bold text</b>',
|
body_html: '<b>Bold text</b>',
|
||||||
@@ -339,32 +352,32 @@ describe('services_rest_Api', function() {
|
|||||||
expect(response.body).toBe('**Bold text**');
|
expect(response.body).toBe('**Bold text**');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should filter fields', asyncTest(async () => {
|
// it('should filter fields', asyncTest(async () => {
|
||||||
let f = api.fields_({ query: { fields: 'one,two' } }, []);
|
// let f = api.fields_({ query: { fields: 'one,two' } } as any, []);
|
||||||
expect(f.length).toBe(2);
|
// expect(f.length).toBe(2);
|
||||||
expect(f[0]).toBe('one');
|
// expect(f[0]).toBe('one');
|
||||||
expect(f[1]).toBe('two');
|
// expect(f[1]).toBe('two');
|
||||||
|
|
||||||
f = api.fields_({ query: { fields: 'one ,, two ' } }, []);
|
// f = api.fields_({ query: { fields: 'one ,, two ' } } as any, []);
|
||||||
expect(f.length).toBe(2);
|
// expect(f.length).toBe(2);
|
||||||
expect(f[0]).toBe('one');
|
// expect(f[0]).toBe('one');
|
||||||
expect(f[1]).toBe('two');
|
// expect(f[1]).toBe('two');
|
||||||
|
|
||||||
f = api.fields_({ query: { fields: ' ' } }, ['def']);
|
// f = api.fields_({ query: { fields: ' ' } } as any, ['def']);
|
||||||
expect(f.length).toBe(1);
|
// expect(f.length).toBe(1);
|
||||||
expect(f[0]).toBe('def');
|
// expect(f[0]).toBe('def');
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
it('should handle tokens', asyncTest(async () => {
|
it('should handle tokens', asyncTest(async () => {
|
||||||
api = new Api('mytoken');
|
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);
|
expect(hasThrown).toBe(true);
|
||||||
|
|
||||||
const response = await api.route('GET', 'notes', { token: 'mytoken' });
|
const response = await api.route(RequestMethod.GET, 'notes', { token: 'mytoken' });
|
||||||
expect(response.length).toBe(0);
|
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);
|
expect(hasThrown).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -372,7 +385,7 @@ describe('services_rest_Api', function() {
|
|||||||
const tag = await Tag.save({ title: 'mon étiquette' });
|
const tag = await Tag.save({ title: 'mon étiquette' });
|
||||||
const note = await Note.save({ title: 'ma note' });
|
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,
|
id: note.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -385,7 +398,7 @@ describe('services_rest_Api', function() {
|
|||||||
const note = await Note.save({ title: 'ma note' });
|
const note = await Note.save({ title: 'ma note' });
|
||||||
await Tag.addNote(tag.id, note.id);
|
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);
|
const noteIds = await Tag.noteIds(tag.id);
|
||||||
expect(noteIds.length).toBe(0);
|
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, note1.id);
|
||||||
await Tag.addNote(tag.id, note2.id);
|
await Tag.addNote(tag.id, note2.id);
|
||||||
|
|
||||||
const response = await api.route('GET', `tags/${tag.id}/notes`);
|
const response = await api.route(RequestMethod.GET, `tags/${tag.id}/notes`);
|
||||||
expect(response.length).toBe(2);
|
expect(response.items.length).toBe(2);
|
||||||
expect('id' in response[0]).toBe(true);
|
expect('id' in response.items[0]).toBe(true);
|
||||||
expect('title' in response[0]).toBe(true);
|
expect('title' in response.items[0]).toBe(true);
|
||||||
|
|
||||||
const response2 = await api.route('GET', `notes/${note1.id}/tags`);
|
const response2 = await api.route(RequestMethod.GET, `notes/${note1.id}/tags`);
|
||||||
expect(response2.length).toBe(1);
|
expect(response2.items.length).toBe(1);
|
||||||
await Tag.addNote(tag2.id, note1.id);
|
await Tag.addNote(tag2.id, note1.id);
|
||||||
const response3 = await api.route('GET', `notes/${note1.id}/tags`);
|
const response3 = await api.route(RequestMethod.GET, `notes/${note1.id}/tags`);
|
||||||
expect(response3.length).toBe(2);
|
expect(response3.items.length).toBe(2);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should update tags when updating notes', asyncTest(async () => {
|
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(tag1.id, note.id);
|
||||||
Tag.addNote(tag2.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}`,
|
tags: `${tag1.title},${tag3.title}`,
|
||||||
}));
|
}));
|
||||||
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
|
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
|
||||||
@@ -443,7 +456,7 @@ describe('services_rest_Api', function() {
|
|||||||
Tag.addNote(tag1.id, note.id);
|
Tag.addNote(tag1.id, note.id);
|
||||||
Tag.addNote(tag2.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}`,
|
tags: `${tag1.title},${newTagTitle}`,
|
||||||
}));
|
}));
|
||||||
const newTag = await Tag.loadByTitle(newTagTitle);
|
const newTag = await Tag.loadByTitle(newTagTitle);
|
||||||
@@ -464,7 +477,7 @@ describe('services_rest_Api', function() {
|
|||||||
Tag.addNote(tag1.id, note.id);
|
Tag.addNote(tag1.id, note.id);
|
||||||
Tag.addNote(tag2.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',
|
title: 'Some other title',
|
||||||
}));
|
}));
|
||||||
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
|
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
|
||||||
@@ -484,11 +497,160 @@ describe('services_rest_Api', function() {
|
|||||||
Tag.addNote(tag1.id, note.id);
|
Tag.addNote(tag1.id, note.id);
|
||||||
Tag.addNote(tag2.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: '',
|
tags: '',
|
||||||
}));
|
}));
|
||||||
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
|
const tagIds = await NoteTag.tagIdsByNoteId(note.id);
|
||||||
expect(response.tags === '').toBe(true);
|
expect(response.tags === '').toBe(true);
|
||||||
expect(tagIds.length === 0).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);
|
||||||
|
}
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
@@ -16,7 +16,7 @@ const Setting = require('lib/models/Setting').default;
|
|||||||
const MasterKey = require('lib/models/MasterKey');
|
const MasterKey = require('lib/models/MasterKey');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Revision = require('lib/models/Revision.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 SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
const WelcomeUtils = require('lib/WelcomeUtils');
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ const fs = require('fs-extra');
|
|||||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||||
const BaseApplication = require('lib/BaseApplication').default;
|
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 Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const ItemChange = require('lib/models/ItemChange.js');
|
const ItemChange = require('lib/models/ItemChange.js');
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { CommandRuntime, CommandDeclaration, CommandContext } from 'lib/services/CommandService';
|
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;
|
const uuid = require('lib/uuid').default;
|
||||||
|
|
||||||
export const declaration:CommandDeclaration = {
|
export const declaration:CommandDeclaration = {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import shim from 'lib/shim';
|
import shim from 'lib/shim';
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const Note = require('lib/models/Note.js');
|
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 Resource = require('lib/models/Resource.js');
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
const bridge = require('electron').remote.require('./bridge').default;
|
||||||
const ResourceFetcher = require('lib/services/ResourceFetcher.js');
|
const ResourceFetcher = require('lib/services/ResourceFetcher.js');
|
||||||
|
@@ -4,7 +4,7 @@ import contextMenu from './contextMenu';
|
|||||||
import ResourceEditWatcher from '../../../lib/services/ResourceEditWatcher/index';
|
import ResourceEditWatcher from '../../../lib/services/ResourceEditWatcher/index';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const BaseItem = require('lib/models/BaseItem');
|
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 Resource = require('lib/models/Resource.js');
|
||||||
const bridge = require('electron').remote.require('./bridge').default;
|
const bridge = require('electron').remote.require('./bridge').default;
|
||||||
const { urlDecode } = require('lib/string-utils');
|
const { urlDecode } = require('lib/string-utils');
|
||||||
|
@@ -7,7 +7,7 @@ const React = require('react');
|
|||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
const { themeStyle } = require('lib/theme');
|
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 bridge = require('electron').remote.require('./bridge').default;
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
|
@@ -4,7 +4,7 @@ const { themeStyle } = require('lib/theme');
|
|||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const NoteTextViewer = require('./NoteTextViewer').default;
|
const NoteTextViewer = require('./NoteTextViewer').default;
|
||||||
const HelpButton = require('./HelpButton.min');
|
const HelpButton = require('./HelpButton.min');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Revision = require('lib/models/Revision');
|
const Revision = require('lib/models/Revision');
|
||||||
const urlUtils = require('lib/urlUtils');
|
const urlUtils = require('lib/urlUtils');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
|
@@ -11,7 +11,7 @@ import { _ } from 'lib/locale';
|
|||||||
|
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const shared = require('lib/components/shared/side-menu-shared.js');
|
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 Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
|
@@ -7,7 +7,7 @@ import InteropServiceHelper from '../../InteropServiceHelper';
|
|||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
import { MenuItemLocation } from 'lib/services/plugins/api/types';
|
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 bridge = require('electron').remote.require('./bridge').default;
|
||||||
const Menu = bridge().Menu;
|
const Menu = bridge().Menu;
|
||||||
const MenuItem = bridge().MenuItem;
|
const MenuItem = bridge().MenuItem;
|
||||||
|
@@ -8,7 +8,7 @@ const { connect } = require('react-redux');
|
|||||||
const { _ } = require('lib/locale');
|
const { _ } = require('lib/locale');
|
||||||
const { themeStyle } = require('lib/theme');
|
const { themeStyle } = require('lib/theme');
|
||||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
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 Tag = require('lib/models/Tag');
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
|
@@ -11,7 +11,7 @@ const { defaultState, stateUtils } = require('lib/reducer');
|
|||||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||||
const { DatabaseDriverNode } = require('lib/database-driver-node.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 Folder = require('lib/models/Folder.js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
@@ -1,22 +1,89 @@
|
|||||||
|
import paginationToSql from './models/utils/paginationToSql';
|
||||||
|
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const uuid = require('lib/uuid').default;
|
const uuid = require('lib/uuid').default;
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
const Mutex = require('async-mutex').Mutex;
|
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 {
|
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');
|
throw new Error('Must be overriden');
|
||||||
}
|
}
|
||||||
|
|
||||||
static tableName() {
|
static tableName():string {
|
||||||
throw new Error('Must be overriden');
|
throw new Error('Must be overriden');
|
||||||
}
|
}
|
||||||
|
|
||||||
static setDb(db) {
|
static setDb(db:any) {
|
||||||
this.db_ = db;
|
this.db_ = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
static addModelMd(model) {
|
static addModelMd(model:any):any {
|
||||||
if (!model) return model;
|
if (!model) return model;
|
||||||
|
|
||||||
if (Array.isArray(model)) {
|
if (Array.isArray(model)) {
|
||||||
@@ -40,29 +107,29 @@ class BaseModel {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static byId(items, id) {
|
static byId(items:any[], id:string) {
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (items[i].id == id) return items[i];
|
if (items[i].id == id) return items[i];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultValues(fieldNames) {
|
static defaultValues(fieldNames:string[]) {
|
||||||
const output = {};
|
const output:any = {};
|
||||||
for (const n of fieldNames) {
|
for (const n of fieldNames) {
|
||||||
output[n] = this.db().fieldDefaultValue(this.tableName(), n);
|
output[n] = this.db().fieldDefaultValue(this.tableName(), n);
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static modelIndexById(items, id) {
|
static modelIndexById(items:any[], id:string) {
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (items[i].id == id) return i;
|
if (items[i].id == id) return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static modelsByIds(items, ids) {
|
static modelsByIds(items:any[], ids:string[]) {
|
||||||
const output = [];
|
const output = [];
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (ids.indexOf(items[i].id) >= 0) {
|
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
|
// 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.
|
// 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 true;
|
||||||
if (!id1 && !!id2) return false;
|
if (!id1 && !!id2) return false;
|
||||||
if (!!id1 && !id2) return false;
|
if (!!id1 && !id2) return false;
|
||||||
return id1 === id2;
|
return id1 === id2;
|
||||||
}
|
}
|
||||||
|
|
||||||
static modelTypeToName(type) {
|
static modelTypeToName(type:number) {
|
||||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||||
const e = BaseModel.typeEnum_[i];
|
const e = BaseModel.typeEnum_[i];
|
||||||
if (e[1] === type) return e[0].substr(5).toLowerCase();
|
if (e[1] === type) return e[0].substr(5).toLowerCase();
|
||||||
@@ -89,7 +156,7 @@ class BaseModel {
|
|||||||
throw new Error(`Unknown model type: ${type}`);
|
throw new Error(`Unknown model type: ${type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static modelNameToType(name) {
|
static modelNameToType(name:string) {
|
||||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||||
const e = BaseModel.typeEnum_[i];
|
const e = BaseModel.typeEnum_[i];
|
||||||
const eName = e[0].substr(5).toLowerCase();
|
const eName = e[0].substr(5).toLowerCase();
|
||||||
@@ -98,12 +165,12 @@ class BaseModel {
|
|||||||
throw new Error(`Unknown model name: ${name}`);
|
throw new Error(`Unknown model name: ${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
static hasField(name) {
|
static hasField(name:string) {
|
||||||
const fields = this.fieldNames();
|
const fields = this.fieldNames();
|
||||||
return fields.indexOf(name) >= 0;
|
return fields.indexOf(name) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fieldNames(withPrefix = false) {
|
static fieldNames(withPrefix:boolean = false) {
|
||||||
const output = this.db().tableFieldNames(this.tableName());
|
const output = this.db().tableFieldNames(this.tableName());
|
||||||
if (!withPrefix) return output;
|
if (!withPrefix) return output;
|
||||||
|
|
||||||
@@ -116,7 +183,7 @@ class BaseModel {
|
|||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fieldType(name, defaultValue = null) {
|
static fieldType(name:string, defaultValue:any = null) {
|
||||||
const fields = this.fields();
|
const fields = this.fields();
|
||||||
for (let i = 0; i < fields.length; i++) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
if (fields[i].name == name) return fields[i].type;
|
if (fields[i].name == name) return fields[i].type;
|
||||||
@@ -129,8 +196,8 @@ class BaseModel {
|
|||||||
return this.db().tableFields(this.tableName());
|
return this.db().tableFields(this.tableName());
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeUnknownFields(model) {
|
static removeUnknownFields(model:any) {
|
||||||
const newModel = {};
|
const newModel:any = {};
|
||||||
for (const n in model) {
|
for (const n in model) {
|
||||||
if (!model.hasOwnProperty(n)) continue;
|
if (!model.hasOwnProperty(n)) continue;
|
||||||
if (!this.hasField(n) && n !== 'type_') continue;
|
if (!this.hasField(n) && n !== 'type_') continue;
|
||||||
@@ -141,7 +208,7 @@ class BaseModel {
|
|||||||
|
|
||||||
static new() {
|
static new() {
|
||||||
const fields = this.fields();
|
const fields = this.fields();
|
||||||
const output = {};
|
const output:any = {};
|
||||||
for (let i = 0; i < fields.length; i++) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
const f = fields[i];
|
const f = fields[i];
|
||||||
output[f.name] = f.default;
|
output[f.name] = f.default;
|
||||||
@@ -149,7 +216,7 @@ class BaseModel {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static modOptions(options) {
|
static modOptions(options:any) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
} else {
|
} else {
|
||||||
@@ -161,42 +228,42 @@ class BaseModel {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
static count(options = null) {
|
static count(options:any = null) {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
let sql = `SELECT count(*) as total FROM \`${this.tableName()}\``;
|
let sql = `SELECT count(*) as total FROM \`${this.tableName()}\``;
|
||||||
if (options.where) sql += ` WHERE ${options.where}`;
|
if (options.where) sql += ` WHERE ${options.where}`;
|
||||||
return this.db()
|
return this.db()
|
||||||
.selectOne(sql)
|
.selectOne(sql)
|
||||||
.then(r => {
|
.then((r:any) => {
|
||||||
return r ? r['total'] : 0;
|
return r ? r['total'] : 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static load(id, options = null) {
|
static load(id:string, options:any = null) {
|
||||||
return this.loadByField('id', id, options);
|
return this.loadByField('id', id, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static shortId(id) {
|
static shortId(id:string) {
|
||||||
return id.substr(0, 5);
|
return id.substr(0, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByPartialId(partialId) {
|
static loadByPartialId(partialId:string) {
|
||||||
return this.modelSelectAll(`SELECT * FROM \`${this.tableName()}\` WHERE \`id\` LIKE ?`, [`${partialId}%`]);
|
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) options = {};
|
||||||
|
|
||||||
if (options.order && options.order.length) {
|
if (options.order && options.order.length) {
|
||||||
const items = [];
|
// const items = [];
|
||||||
for (let i = 0; i < options.order.length; i++) {
|
// for (let i = 0; i < options.order.length; i++) {
|
||||||
const o = options.order[i];
|
// const o = options.order[i];
|
||||||
let item = `\`${o.by}\``;
|
// let item = `\`${o.by}\``;
|
||||||
if (options.caseInsensitive === true) item += ' COLLATE NOCASE';
|
// if (options.caseInsensitive === true) item += ' COLLATE NOCASE';
|
||||||
if (o.dir) item += ` ${o.dir}`;
|
// if (o.dir) item += ` ${o.dir}`;
|
||||||
items.push(item);
|
// items.push(item);
|
||||||
}
|
// }
|
||||||
sql += ` ORDER BY ${items.join(', ')}`;
|
sql += ` ORDER BY ${paginationToSql(options)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.limit) sql += ` LIMIT ${options.limit}`;
|
if (options.limit) sql += ` LIMIT ${options.limit}`;
|
||||||
@@ -204,18 +271,18 @@ class BaseModel {
|
|||||||
return { sql: sql, params: params };
|
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 q = this.applySqlOptions(options, `SELECT id FROM \`${this.tableName()}\``);
|
||||||
const rows = await this.db().selectAll(q.sql, q.params);
|
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) options = {};
|
||||||
if (!options.fields) options.fields = '*';
|
if (!options.fields) options.fields = '*';
|
||||||
|
|
||||||
let sql = `SELECT ${this.db().escapeFields(options.fields)} FROM \`${this.tableName()}\``;
|
let sql = `SELECT ${this.db().escapeFields(options.fields)} FROM \`${this.tableName()}\``;
|
||||||
let params = [];
|
let params:any[] = [];
|
||||||
if (options.where) {
|
if (options.where) {
|
||||||
sql += ` WHERE ${options.where}`;
|
sql += ` WHERE ${options.where}`;
|
||||||
if (options.whereParams) params = params.concat(options.whereParams);
|
if (options.whereParams) params = params.concat(options.whereParams);
|
||||||
@@ -225,7 +292,7 @@ class BaseModel {
|
|||||||
return this.modelSelectAll(q.sql, q.params);
|
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 (!ids.length) return [];
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
if (!options.fields) options.fields = '*';
|
if (!options.fields) options.fields = '*';
|
||||||
@@ -236,7 +303,7 @@ class BaseModel {
|
|||||||
return this.modelSelectAll(q.sql);
|
return this.modelSelectAll(q.sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async search(options = null) {
|
static async search(options:any = null) {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
if (!options.fields) options.fields = '*';
|
if (!options.fields) options.fields = '*';
|
||||||
|
|
||||||
@@ -258,25 +325,25 @@ class BaseModel {
|
|||||||
return this.modelSelectAll(query.sql, query.params);
|
return this.modelSelectAll(query.sql, query.params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static modelSelectOne(sql, params = null) {
|
static modelSelectOne(sql:string, params:any[] = null) {
|
||||||
if (params === null) params = [];
|
if (params === null) params = [];
|
||||||
return this.db()
|
return this.db()
|
||||||
.selectOne(sql, params)
|
.selectOne(sql, params)
|
||||||
.then(model => {
|
.then((model:any) => {
|
||||||
return this.filter(this.addModelMd(model));
|
return this.filter(this.addModelMd(model));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static modelSelectAll(sql, params = null) {
|
static modelSelectAll(sql:string, params:any[] = null) {
|
||||||
if (params === null) params = [];
|
if (params === null) params = [];
|
||||||
return this.db()
|
return this.db()
|
||||||
.selectAll(sql, params)
|
.selectAll(sql, params)
|
||||||
.then(models => {
|
.then((models:any[]) => {
|
||||||
return this.filterArray(this.addModelMd(models));
|
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 (!options) options = {};
|
||||||
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
|
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
|
||||||
if (!options.fields) options.fields = '*';
|
if (!options.fields) options.fields = '*';
|
||||||
@@ -285,7 +352,7 @@ class BaseModel {
|
|||||||
return this.modelSelectOne(sql, [fieldValue]);
|
return this.modelSelectOne(sql, [fieldValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByFields(fields, options = null) {
|
static loadByFields(fields:string[], options:any = null) {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
|
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
|
||||||
if (!options.fields) options.fields = '*';
|
if (!options.fields) options.fields = '*';
|
||||||
@@ -300,12 +367,12 @@ class BaseModel {
|
|||||||
return this.modelSelectOne(sql, params);
|
return this.modelSelectOne(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByTitle(fieldValue) {
|
static loadByTitle(fieldValue:any) {
|
||||||
return this.modelSelectOne(`SELECT * FROM \`${this.tableName()}\` WHERE \`title\` = ?`, [fieldValue]);
|
return this.modelSelectOne(`SELECT * FROM \`${this.tableName()}\` WHERE \`title\` = ?`, [fieldValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static diffObjects(oldModel, newModel) {
|
static diffObjects(oldModel:any, newModel:any) {
|
||||||
const output = {};
|
const output:any = {};
|
||||||
const fields = this.diffObjectsFields(oldModel, newModel);
|
const fields = this.diffObjectsFields(oldModel, newModel);
|
||||||
for (let i = 0; i < fields.length; i++) {
|
for (let i = 0; i < fields.length; i++) {
|
||||||
output[fields[i]] = newModel[fields[i]];
|
output[fields[i]] = newModel[fields[i]];
|
||||||
@@ -314,7 +381,7 @@ class BaseModel {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static diffObjectsFields(oldModel, newModel) {
|
static diffObjectsFields(oldModel:any, newModel:any) {
|
||||||
const output = [];
|
const output = [];
|
||||||
for (const n in newModel) {
|
for (const n in newModel) {
|
||||||
if (!newModel.hasOwnProperty(n)) continue;
|
if (!newModel.hasOwnProperty(n)) continue;
|
||||||
@@ -326,15 +393,15 @@ class BaseModel {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static modelsAreSame(oldModel, newModel) {
|
static modelsAreSame(oldModel:any, newModel:any) {
|
||||||
const diff = this.diffObjects(oldModel, newModel);
|
const diff = this.diffObjects(oldModel, newModel);
|
||||||
delete diff.type_;
|
delete diff.type_;
|
||||||
return !Object.getOwnPropertyNames(diff).length;
|
return !Object.getOwnPropertyNames(diff).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static saveMutex(modelOrId) {
|
static saveMutex(modelOrId:any) {
|
||||||
const noLockMutex = {
|
const noLockMutex = {
|
||||||
acquire: function() {
|
acquire: function():any {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -353,7 +420,7 @@ class BaseModel {
|
|||||||
return mutex;
|
return mutex;
|
||||||
}
|
}
|
||||||
|
|
||||||
static releaseSaveMutex(modelOrId, release) {
|
static releaseSaveMutex(modelOrId:any, release:Function) {
|
||||||
if (!release) return;
|
if (!release) return;
|
||||||
if (!modelOrId) return release();
|
if (!modelOrId) return release();
|
||||||
|
|
||||||
@@ -368,8 +435,8 @@ class BaseModel {
|
|||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|
||||||
static saveQuery(o, options) {
|
static saveQuery(o:any, options:any) {
|
||||||
let temp = {};
|
let temp:any = {};
|
||||||
const fieldNames = this.fieldNames();
|
const fieldNames = this.fieldNames();
|
||||||
for (let i = 0; i < fieldNames.length; i++) {
|
for (let i = 0; i < fieldNames.length; i++) {
|
||||||
const n = fieldNames[i];
|
const n = fieldNames[i];
|
||||||
@@ -381,7 +448,7 @@ class BaseModel {
|
|||||||
// be part of the final list of fields if autoTimestamp is on.
|
// be part of the final list of fields if autoTimestamp is on.
|
||||||
// id also will stay.
|
// id also will stay.
|
||||||
if (!options.isNew && options.fields) {
|
if (!options.isNew && options.fields) {
|
||||||
const filtered = {};
|
const filtered:any = {};
|
||||||
for (const k in temp) {
|
for (const k in temp) {
|
||||||
if (!temp.hasOwnProperty(k)) continue;
|
if (!temp.hasOwnProperty(k)) continue;
|
||||||
if (k !== 'id' && options.fields.indexOf(k) < 0) continue;
|
if (k !== 'id' && options.fields.indexOf(k) < 0) continue;
|
||||||
@@ -393,7 +460,7 @@ class BaseModel {
|
|||||||
o = temp;
|
o = temp;
|
||||||
|
|
||||||
let modelId = temp.id;
|
let modelId = temp.id;
|
||||||
let query = {};
|
let query:any = {};
|
||||||
|
|
||||||
const timeNow = time.unixMs();
|
const timeNow = time.unixMs();
|
||||||
|
|
||||||
@@ -445,7 +512,7 @@ class BaseModel {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
static userSideValidation(o) {
|
static userSideValidation(o:any) {
|
||||||
if (o.id && !o.id.match(/^[a-f0-9]{32}$/)) {
|
if (o.id && !o.id.match(/^[a-f0-9]{32}$/)) {
|
||||||
throw new Error('Validation error: ID must a 32-characters lowercase hexadecimal string');
|
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
|
// 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).
|
// 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
|
// 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;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static isNew(object, options) {
|
static isNew(object:any, options:any) {
|
||||||
if (options && 'isNew' in options) {
|
if (options && 'isNew' in options) {
|
||||||
// options.isNew can be "auto" too
|
// options.isNew can be "auto" too
|
||||||
if (options.isNew === true) return true;
|
if (options.isNew === true) return true;
|
||||||
@@ -536,7 +603,7 @@ class BaseModel {
|
|||||||
return !object.id;
|
return !object.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static filterArray(models) {
|
static filterArray(models:any[]) {
|
||||||
const output = [];
|
const output = [];
|
||||||
for (let i = 0; i < models.length; i++) {
|
for (let i = 0; i < models.length; i++) {
|
||||||
output.push(this.filter(models[i]));
|
output.push(this.filter(models[i]));
|
||||||
@@ -544,7 +611,7 @@ class BaseModel {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static filter(model) {
|
static filter(model:any) {
|
||||||
if (!model) return model;
|
if (!model) return model;
|
||||||
|
|
||||||
const output = Object.assign({}, model);
|
const output = Object.assign({}, model);
|
||||||
@@ -567,12 +634,12 @@ class BaseModel {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
static delete(id) {
|
static delete(id:string) {
|
||||||
if (!id) throw new Error('Cannot delete object without an ID');
|
if (!id) throw new Error('Cannot delete object without an ID');
|
||||||
return this.db().exec(`DELETE FROM ${this.tableName()} WHERE id = ?`, [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;
|
if (!ids.length) return;
|
||||||
options = this.modOptions(options);
|
options = this.modOptions(options);
|
||||||
const idFieldName = options.idFieldName ? options.idFieldName : 'id';
|
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++) {
|
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||||
const e = BaseModel.typeEnum_[i];
|
const e = BaseModel.typeEnum_[i];
|
||||||
BaseModel[e[0]] = e[1];
|
(BaseModel as any)[e[0]] = e[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseModel.db_ = null;
|
export default BaseModel;
|
||||||
BaseModel.dispatch = function() {};
|
|
||||||
BaseModel.saveMutexes_ = {};
|
|
||||||
|
|
||||||
module.exports = BaseModel;
|
|
@@ -4,7 +4,7 @@ const Logger = require('lib/Logger').default;
|
|||||||
const { randomClipperPort, startPort } = require('lib/randomClipperPort');
|
const { randomClipperPort, startPort } = require('lib/randomClipperPort');
|
||||||
const enableServerDestroy = require('server-destroy');
|
const enableServerDestroy = require('server-destroy');
|
||||||
const Api = require('lib/services/rest/Api').default;
|
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');
|
const multiparty = require('multiparty');
|
||||||
|
|
||||||
class ClipperServer {
|
class ClipperServer {
|
||||||
@@ -29,7 +29,6 @@ class ClipperServer {
|
|||||||
|
|
||||||
setLogger(l) {
|
setLogger(l) {
|
||||||
this.logger_ = l;
|
this.logger_ = l;
|
||||||
this.api_.setLogger(l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger() {
|
logger() {
|
||||||
|
@@ -13,7 +13,7 @@ const Resource = require('lib/models/Resource.js');
|
|||||||
const ItemChange = require('lib/models/ItemChange.js');
|
const ItemChange = require('lib/models/ItemChange.js');
|
||||||
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
|
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
|
||||||
const MasterKey = require('lib/models/MasterKey.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 { sprintf } = require('sprintf-js');
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
const JoplinError = require('lib/JoplinError');
|
const JoplinError = require('lib/JoplinError');
|
||||||
|
@@ -20,7 +20,7 @@ const Clipboard = require('@react-native-community/clipboard').default;
|
|||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const { BackButtonService } = require('lib/services/back-button.js');
|
const { BackButtonService } = require('lib/services/back-button.js');
|
||||||
const NavService = require('lib/services/NavService.js');
|
const NavService = require('lib/services/NavService.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const { ActionButton } = require('lib/components/action-button.js');
|
const { ActionButton } = require('lib/components/action-button.js');
|
||||||
const { fileExtension, safeFileExtension } = require('lib/path-utils');
|
const { fileExtension, safeFileExtension } = require('lib/path-utils');
|
||||||
const mimeUtils = require('lib/mime-utils.js').mime;
|
const mimeUtils = require('lib/mime-utils.js').mime;
|
||||||
|
@@ -3,7 +3,7 @@ const React = require('react');
|
|||||||
const { View, TextInput, StyleSheet } = require('react-native');
|
const { View, TextInput, StyleSheet } = require('react-native');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const { ScreenHeader } = require('lib/components/screen-header.js');
|
const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||||
const { BaseScreenComponent } = require('lib/components/base-screen.js');
|
const { BaseScreenComponent } = require('lib/components/base-screen.js');
|
||||||
const { dialogs } = require('lib/dialogs.js');
|
const { dialogs } = require('lib/dialogs.js');
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const ResourceFetcher = require('lib/services/ResourceFetcher.js');
|
const ResourceFetcher = require('lib/services/ResourceFetcher.js');
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const Tag = require('lib/models/Tag');
|
const Tag = require('lib/models/Tag');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
const shared = {};
|
const shared = {};
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const uuid = require('lib/uuid').default;
|
const uuid = require('lib/uuid').default;
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Tag = require('lib/models/Tag.js');
|
const Tag = require('lib/models/Tag.js');
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
|
@@ -318,9 +318,14 @@ export default class MdToHtml {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The string we are looking for is: <p></p>\n
|
||||||
private removeMarkdownItWrappingParagraph_(html:string) {
|
private removeMarkdownItWrappingParagraph_(html:string) {
|
||||||
// <p></p>\n
|
|
||||||
if (html.length < 8) return html;
|
if (html.length < 8) return html;
|
||||||
|
|
||||||
|
// If there are multiple <p> tags, we keep them because it's multiple lines
|
||||||
|
// and removing the first and last tag will result in invalid HTML.
|
||||||
|
if ((html.match(/<\/p>/g) || []).length > 1) return html;
|
||||||
|
|
||||||
if (html.substr(0, 3) !== '<p>') return html;
|
if (html.substr(0, 3) !== '<p>') return html;
|
||||||
if (html.slice(-5) !== '</p>\n') return html;
|
if (html.slice(-5) !== '</p>\n') return html;
|
||||||
return html.substring(3, html.length - 5);
|
return html.substring(3, html.length - 5);
|
||||||
@@ -376,7 +381,7 @@ export default class MdToHtml {
|
|||||||
const markdownIt = new MarkdownIt({
|
const markdownIt = new MarkdownIt({
|
||||||
breaks: !this.pluginEnabled('softbreaks'),
|
breaks: !this.pluginEnabled('softbreaks'),
|
||||||
typographer: this.pluginEnabled('typographer'),
|
typographer: this.pluginEnabled('typographer'),
|
||||||
linkify: true,
|
// linkify: true,
|
||||||
html: true,
|
html: true,
|
||||||
highlight: (str:string, lang:string) => {
|
highlight: (str:string, lang:string) => {
|
||||||
let outputCodeHtml = '';
|
let outputCodeHtml = '';
|
||||||
|
@@ -72,11 +72,24 @@ function plugin(markdownIt:any, ruleOptions:RuleOptions) {
|
|||||||
|
|
||||||
if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place
|
if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place
|
||||||
|
|
||||||
|
const attrHtml = [];
|
||||||
|
attrHtml.push('data-from-md');
|
||||||
|
if (resourceIdAttr) attrHtml.push(resourceIdAttr);
|
||||||
|
if (title) attrHtml.push(`title='${htmlentities(title)}'`);
|
||||||
|
if (mime) attrHtml.push(`type='${htmlentities(mime)}'`);
|
||||||
|
|
||||||
if (ruleOptions.plainResourceRendering || ruleOptions.linkRenderingType === 2) {
|
if (ruleOptions.plainResourceRendering || ruleOptions.linkRenderingType === 2) {
|
||||||
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
|
icon = '';
|
||||||
|
attrHtml.push(`href='${htmlentities(href)}'`);
|
||||||
|
|
||||||
|
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
|
||||||
} else {
|
} else {
|
||||||
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
|
attrHtml.push(`href='${hrefAttr}'`);
|
||||||
|
if (js) attrHtml.push(js);
|
||||||
|
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return `<a ${attrHtml.join(' ')}>${icon}`;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const ItemChange = require('lib/models/ItemChange.js');
|
const ItemChange = require('lib/models/ItemChange.js');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Mutex = require('async-mutex').Mutex;
|
const Mutex = require('async-mutex').Mutex;
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
|
|
||||||
class MasterKey extends BaseItem {
|
class MasterKey extends BaseItem {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
const migrationScripts = {
|
const migrationScripts = {
|
||||||
20: require('lib/migrations/20.js'),
|
20: require('lib/migrations/20.js'),
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const ItemChange = require('lib/models/ItemChange.js');
|
const ItemChange = require('lib/models/ItemChange.js');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
// - If is_associated = 1, note_resources indicates which note_id is currently associated with the given resource_id
|
// - If is_associated = 1, note_resources indicates which note_id is currently associated with the given resource_id
|
||||||
// - If is_associated = 0, note_resources indicates which note_id *was* associated with the given resource_id
|
// - If is_associated = 0, note_resources indicates which note_id *was* associated with the given resource_id
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
class NoteTag extends BaseItem {
|
class NoteTag extends BaseItem {
|
||||||
static tableName() {
|
static tableName() {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const ItemChange = require('lib/models/ItemChange.js');
|
const ItemChange = require('lib/models/ItemChange.js');
|
||||||
const NoteResource = require('lib/models/NoteResource.js');
|
const NoteResource = require('lib/models/NoteResource.js');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
|
|
||||||
class ResourceLocalState extends BaseModel {
|
class ResourceLocalState extends BaseModel {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const DiffMatchPatch = require('diff-match-patch');
|
const DiffMatchPatch = require('diff-match-patch');
|
||||||
const ArrayUtils = require('lib/ArrayUtils.js');
|
const ArrayUtils = require('lib/ArrayUtils.js');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
class Search extends BaseModel {
|
class Search extends BaseModel {
|
||||||
static tableName() {
|
static tableName() {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import shim from 'lib/shim';
|
import shim from 'lib/shim';
|
||||||
import { _, supportedLocalesToLanguages, defaultLocale } from 'lib/locale';
|
import { _, supportedLocalesToLanguages, defaultLocale } from 'lib/locale';
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
||||||
const time = require('lib/time').default;
|
const time = require('lib/time').default;
|
||||||
@@ -565,11 +565,11 @@ class Setting extends BaseModel {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Deprecated - use markdown.plugin.*
|
// Deprecated - use markdown.plugin.*
|
||||||
'markdown.softbreaks': { value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
|
'markdown.softbreaks': { value: true, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
|
||||||
'markdown.typographer': { value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
|
'markdown.typographer': { value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
|
||||||
'markdown.plugin.softbreaks': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
|
'markdown.plugin.softbreaks': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
|
||||||
'markdown.plugin.typographer': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
|
'markdown.plugin.typographer': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
|
||||||
'markdown.plugin.katex': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
|
'markdown.plugin.katex': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
|
||||||
'markdown.plugin.fountain': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
|
'markdown.plugin.fountain': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
|
|
||||||
class SmartFilter extends BaseModel {
|
class SmartFilter extends BaseModel {
|
||||||
static tableName() {
|
static tableName() {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const NoteTag = require('lib/models/NoteTag.js');
|
const NoteTag = require('lib/models/NoteTag.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
|
120
ReactNativeClient/lib/models/utils/modelFeed.ts
Normal file
120
ReactNativeClient/lib/models/utils/modelFeed.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import paginationToSql from './paginationToSql';
|
||||||
|
import { Pagination, PaginationOrder } from './types';
|
||||||
|
const base64 = require('base-64');
|
||||||
|
|
||||||
|
interface Cursor {
|
||||||
|
lastRow: any,
|
||||||
|
pagination: Pagination,
|
||||||
|
fields: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelFeedPage {
|
||||||
|
items: any[],
|
||||||
|
cursor?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCursor(rows:any[], pagination:Pagination, fields:string[]):Cursor {
|
||||||
|
if (!rows.length) return null;
|
||||||
|
if (rows.length < pagination.limit) return null;
|
||||||
|
|
||||||
|
const orderFields = pagination.order.map((o:PaginationOrder) => o.by);
|
||||||
|
|
||||||
|
const lastRow:any = {};
|
||||||
|
const fullRow = rows[rows.length - 1];
|
||||||
|
for (const f of orderFields) {
|
||||||
|
const v = fullRow[f];
|
||||||
|
lastRow[f] = typeof v === 'string' ? v.substr(0, 256) : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lastRow,
|
||||||
|
pagination,
|
||||||
|
fields,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeCursor(cursor:Cursor):string {
|
||||||
|
return base64.encode(JSON.stringify(cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeCursor(cursor:string):Cursor {
|
||||||
|
if (!cursor) return null;
|
||||||
|
return JSON.parse(base64.decode(cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this method might return more fields than was requested as it will
|
||||||
|
// also return fields that are necessary for pagination.
|
||||||
|
export default async function(db:any, tableName:string, pagination:Pagination, encodedCursor:string, whereSql:string = '', fields:string[] = null):Promise<ModelFeedPage> {
|
||||||
|
fields = fields ? fields.slice() : ['id'];
|
||||||
|
const cursor = decodeCursor(encodedCursor);
|
||||||
|
|
||||||
|
const where = whereSql ? [whereSql] : [];
|
||||||
|
let sqlParams = [];
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
pagination = cursor.pagination;
|
||||||
|
fields = cursor.fields;
|
||||||
|
|
||||||
|
const paginationOrder = pagination.order[0].dir;
|
||||||
|
const orderFields = pagination.order.map((o:PaginationOrder) => o.by);
|
||||||
|
|
||||||
|
// Use row-value syntax for WHERE clause:
|
||||||
|
// https://use-the-index-luke.com/sql/partial-results/fetch-next-page
|
||||||
|
const rowValueWhere = [];
|
||||||
|
rowValueWhere.push(`(${orderFields.join(', ')})`);
|
||||||
|
rowValueWhere.push(paginationOrder === 'DESC' ? '<' : '>');
|
||||||
|
rowValueWhere.push(`(${orderFields.map((_f:any) => '?').join(', ')})`);
|
||||||
|
|
||||||
|
where.push(rowValueWhere.join(' '));
|
||||||
|
|
||||||
|
sqlParams = orderFields.map((f:string) => cursor.lastRow[f]);
|
||||||
|
} else {
|
||||||
|
if (!pagination.order.length) throw new Error('Pagination order must be provided');
|
||||||
|
if (pagination.order.length > 1) throw new Error('Only one pagination order field can be provided');
|
||||||
|
|
||||||
|
const paginationOrder = pagination.order[0].dir;
|
||||||
|
|
||||||
|
if (!pagination.order.find((o:PaginationOrder) => o.by === 'id')) {
|
||||||
|
pagination = {
|
||||||
|
...pagination,
|
||||||
|
order: pagination.order.concat([{
|
||||||
|
by: 'id',
|
||||||
|
dir: paginationOrder,
|
||||||
|
caseInsensitive: false,
|
||||||
|
}]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain fields are necessary for pagination, such as ID or
|
||||||
|
// updated_time, however they are not necessarily included in the
|
||||||
|
// selected fields. However in this case we need all the "order by"
|
||||||
|
// fields to be in the selected fields as they are later used in the
|
||||||
|
// above row-value clause.
|
||||||
|
const orderFields = pagination.order.map((o:PaginationOrder) => o.by);
|
||||||
|
for (const f of orderFields) {
|
||||||
|
if (!fields.includes(f)) fields.push(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderBySql = paginationToSql(pagination);
|
||||||
|
const fieldsSql = fields.length ? db.escapeFields(fields) : '*';
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
SELECT ${fieldsSql} FROM \`${tableName}\`
|
||||||
|
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
||||||
|
ORDER BY ${orderBySql}
|
||||||
|
LIMIT ${pagination.limit}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// console.info('SQL', sql, sqlParams);
|
||||||
|
|
||||||
|
const rows = await db.selectAll(sql, sqlParams);
|
||||||
|
|
||||||
|
const newCursor = makeCursor(rows, pagination, fields);
|
||||||
|
|
||||||
|
const output:ModelFeedPage = { items: rows };
|
||||||
|
|
||||||
|
if (newCursor) output.cursor = encodeCursor(newCursor);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
15
ReactNativeClient/lib/models/utils/paginationToSql.ts
Normal file
15
ReactNativeClient/lib/models/utils/paginationToSql.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Pagination } from './types';
|
||||||
|
|
||||||
|
export default function(pagination:Pagination):string {
|
||||||
|
const sql:string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < pagination.order.length; i++) {
|
||||||
|
const o = pagination.order[i];
|
||||||
|
let item = `\`${o.by}\``;
|
||||||
|
if (o.caseInsensitive === true) item += ' COLLATE NOCASE';
|
||||||
|
item += ` ${o.dir}`;
|
||||||
|
sql.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql.join(', ');
|
||||||
|
}
|
15
ReactNativeClient/lib/models/utils/types.ts
Normal file
15
ReactNativeClient/lib/models/utils/types.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export enum PaginationOrderDir {
|
||||||
|
ASC = 'ASC',
|
||||||
|
DESC = 'DESC',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginationOrder {
|
||||||
|
by: string,
|
||||||
|
dir: PaginationOrderDir,
|
||||||
|
caseInsensitive: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Pagination {
|
||||||
|
limit: number,
|
||||||
|
order: PaginationOrder[],
|
||||||
|
}
|
@@ -2,7 +2,7 @@ import produce, { Draft } from 'immer';
|
|||||||
import pluginServiceReducer, { stateRootKey as pluginServiceStateRootKey, defaultState as pluginServiceDefaultState, State as PluginServiceState } from 'lib/services/plugins/reducer';
|
import pluginServiceReducer, { stateRootKey as pluginServiceStateRootKey, defaultState as pluginServiceDefaultState, State as PluginServiceState } from 'lib/services/plugins/reducer';
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const ArrayUtils = require('lib/ArrayUtils.js');
|
const ArrayUtils = require('lib/ArrayUtils.js');
|
||||||
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
||||||
const { createSelectorCreator, defaultMemoize } = require('reselect');
|
const { createSelectorCreator, defaultMemoize } = require('reselect');
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
const BaseItem = require('lib/models/BaseItem');
|
const BaseItem = require('lib/models/BaseItem');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const MasterKey = require('lib/models/MasterKey');
|
const MasterKey = require('lib/models/MasterKey');
|
||||||
const Resource = require('lib/models/Resource');
|
const Resource = require('lib/models/Resource');
|
||||||
const ResourceService = require('lib/services/ResourceService');
|
const ResourceService = require('lib/services/ResourceService');
|
||||||
|
@@ -2,7 +2,7 @@ const ItemChange = require('lib/models/ItemChange');
|
|||||||
const NoteResource = require('lib/models/NoteResource');
|
const NoteResource = require('lib/models/NoteResource');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const Resource = require('lib/models/Resource');
|
const Resource = require('lib/models/Resource');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const BaseService = require('lib/services/BaseService').default;
|
const BaseService = require('lib/services/BaseService').default;
|
||||||
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
const SearchEngine = require('lib/services/searchengine/SearchEngine');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
|
@@ -3,7 +3,7 @@ const Note = require('lib/models/Note');
|
|||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
const Revision = require('lib/models/Revision');
|
const Revision = require('lib/models/Revision');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
|
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
const BaseService = require('lib/services/BaseService').default;
|
const BaseService = require('lib/services/BaseService').default;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { stateUtils } from 'lib/reducer';
|
import { stateUtils } from 'lib/reducer';
|
||||||
|
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const MarkupToHtml = require('lib/joplin-renderer/MarkupToHtml').default;
|
const MarkupToHtml = require('lib/joplin-renderer/MarkupToHtml').default;
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ import InteropService_Exporter_Custom from './InteropService_Exporter_Custom';
|
|||||||
import shim from 'lib/shim';
|
import shim from 'lib/shim';
|
||||||
import { _ } from 'lib/locale';
|
import { _ } from 'lib/locale';
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const NoteTag = require('lib/models/NoteTag.js');
|
const NoteTag = require('lib/models/NoteTag.js');
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const InteropService_Exporter_Base = require('lib/services/interop/InteropService_Exporter_Base').default;
|
const InteropService_Exporter_Base = require('lib/services/interop/InteropService_Exporter_Base').default;
|
||||||
const { basename, friendlySafeFilename, rtrimSlashes } = require('lib/path-utils');
|
const { basename, friendlySafeFilename, rtrimSlashes } = require('lib/path-utils');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const Setting = require('lib/models/Setting').default;
|
const Setting = require('lib/models/Setting').default;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
const InteropService_Exporter_Base = require('lib/services/interop/InteropService_Exporter_Base').default;
|
const InteropService_Exporter_Base = require('lib/services/interop/InteropService_Exporter_Base').default;
|
||||||
const { basename, dirname, friendlySafeFilename } = require('lib/path-utils');
|
const { basename, dirname, friendlySafeFilename } = require('lib/path-utils');
|
||||||
const BaseModel = require('lib/BaseModel');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Folder = require('lib/models/Folder');
|
const Folder = require('lib/models/Folder');
|
||||||
const Note = require('lib/models/Note');
|
const Note = require('lib/models/Note');
|
||||||
const shim = require('lib/shim').default;
|
const shim = require('lib/shim').default;
|
||||||
|
@@ -2,7 +2,7 @@ import { ImportExportResult } from './types';
|
|||||||
|
|
||||||
const InteropService_Importer_Base = require('lib/services/interop/InteropService_Importer_Base').default;
|
const InteropService_Importer_Base = require('lib/services/interop/InteropService_Importer_Base').default;
|
||||||
const BaseItem = require('lib/models/BaseItem.js');
|
const BaseItem = require('lib/models/BaseItem.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const Resource = require('lib/models/Resource.js');
|
const Resource = require('lib/models/Resource.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const NoteTag = require('lib/models/NoteTag.js');
|
const NoteTag = require('lib/models/NoteTag.js');
|
||||||
|
@@ -3,7 +3,7 @@ const BaseItem = require('lib/models/BaseItem.js');
|
|||||||
const Alarm = require('lib/models/Alarm').default;
|
const Alarm = require('lib/models/Alarm').default;
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel').default;
|
||||||
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
const DecryptionWorker = require('lib/services/DecryptionWorker');
|
||||||
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
const ResourceFetcher = require('lib/services/ResourceFetcher');
|
||||||
const Resource = require('lib/models/Resource');
|
const Resource = require('lib/models/Resource');
|
||||||
|
@@ -1,71 +1,112 @@
|
|||||||
import Setting from 'lib/models/Setting';
|
import { PaginationOrderDir } from 'lib/models/utils/types';
|
||||||
import Logger from 'lib/Logger';
|
import { ErrorMethodNotAllowed, ErrorForbidden, ErrorBadRequest, ErrorNotFound } from './utils/errors';
|
||||||
import shim from 'lib/shim';
|
|
||||||
import uuid from 'lib/uuid';
|
import route_folders from './routes/folders';
|
||||||
import markdownUtils from 'lib/markdownUtils';
|
import route_notes from './routes/notes';
|
||||||
|
import route_resources from './routes/resources';
|
||||||
|
import route_tags from './routes/tags';
|
||||||
|
import route_master_keys from './routes/master_keys';
|
||||||
|
import route_search from './routes/search';
|
||||||
|
import route_ping from './routes/ping';
|
||||||
|
|
||||||
const { ltrimSlashes } = require('lib/path-utils');
|
const { ltrimSlashes } = require('lib/path-utils');
|
||||||
const { Database } = require('lib/database.js');
|
|
||||||
const Folder = require('lib/models/Folder');
|
|
||||||
const Note = require('lib/models/Note');
|
|
||||||
const Tag = require('lib/models/Tag');
|
|
||||||
const BaseItem = require('lib/models/BaseItem');
|
|
||||||
const Resource = require('lib/models/Resource');
|
|
||||||
const BaseModel = require('lib/BaseModel');
|
|
||||||
const htmlUtils = require('lib/htmlUtils');
|
|
||||||
const markupLanguageUtils = require('lib/markupLanguageUtils').default;
|
|
||||||
const mimeUtils = require('lib/mime-utils.js').mime;
|
|
||||||
const md5 = require('md5');
|
const md5 = require('md5');
|
||||||
const HtmlToMd = require('lib/HtmlToMd');
|
|
||||||
const urlUtils = require('lib/urlUtils.js');
|
export enum RequestMethod {
|
||||||
const ArrayUtils = require('lib/ArrayUtils.js');
|
GET = 'GET',
|
||||||
const { netUtils } = require('lib/net-utils');
|
POST = 'POST',
|
||||||
const { fileExtension, safeFileExtension, safeFilename, filename } = require('lib/path-utils');
|
PUT = 'PUT',
|
||||||
const ApiResponse = require('lib/services/rest/ApiResponse');
|
DELETE = 'DELETE',
|
||||||
const SearchEngineUtils = require('lib/services/searchengine/SearchEngineUtils');
|
}
|
||||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
|
||||||
const uri2path = require('file-uri-to-path');
|
interface RequestFile {
|
||||||
const { MarkupToHtml } = require('lib/joplin-renderer');
|
path: string,
|
||||||
const { ErrorMethodNotAllowed, ErrorForbidden, ErrorBadRequest, ErrorNotFound } = require('./errors');
|
}
|
||||||
|
|
||||||
|
interface RequestQuery {
|
||||||
|
cursor?: string,
|
||||||
|
fields?: string[] | string,
|
||||||
|
token?: string,
|
||||||
|
nounce?: string,
|
||||||
|
|
||||||
|
// Search engine query
|
||||||
|
query?: string,
|
||||||
|
type?: string, // Model type as a string (eg. "note", "folder")
|
||||||
|
|
||||||
|
as_tree?: number,
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
limit?: number,
|
||||||
|
order_dir?: PaginationOrderDir,
|
||||||
|
order_by?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Request {
|
||||||
|
method: RequestMethod,
|
||||||
|
path: string,
|
||||||
|
query: RequestQuery,
|
||||||
|
body: any,
|
||||||
|
bodyJson_: any,
|
||||||
|
bodyJson: any,
|
||||||
|
files: RequestFile[],
|
||||||
|
params: any[],
|
||||||
|
action?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteFunction = (request:Request, id:string, link:string) => Promise<any | void>;
|
||||||
|
|
||||||
|
interface ResourceNameToRoute {
|
||||||
|
[key:string]: RouteFunction;
|
||||||
|
}
|
||||||
|
|
||||||
export default class Api {
|
export default class Api {
|
||||||
|
|
||||||
private token_:string | Function;
|
private token_:string | Function;
|
||||||
private knownNounces_:any = {};
|
private knownNounces_:any = {};
|
||||||
private logger_:Logger;
|
|
||||||
private actionApi_:any;
|
private actionApi_:any;
|
||||||
private htmlToMdParser_:any;
|
private resourceNameToRoute_:ResourceNameToRoute = {};
|
||||||
|
|
||||||
constructor(token:string = null, actionApi:any = null) {
|
public constructor(token:string = null, actionApi:any = null) {
|
||||||
this.token_ = token;
|
this.token_ = token;
|
||||||
this.logger_ = new Logger();
|
|
||||||
this.actionApi_ = actionApi;
|
this.actionApi_ = actionApi;
|
||||||
|
|
||||||
|
this.resourceNameToRoute_ = {
|
||||||
|
ping: route_ping,
|
||||||
|
notes: route_notes,
|
||||||
|
folders: route_folders,
|
||||||
|
tags: route_tags,
|
||||||
|
resources: route_resources,
|
||||||
|
master_keys: route_master_keys,
|
||||||
|
search: route_search,
|
||||||
|
services: this.action_services.bind(this),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get token() {
|
public get token() {
|
||||||
return typeof this.token_ === 'function' ? this.token_() : this.token_;
|
return typeof this.token_ === 'function' ? this.token_() : this.token_;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsePath(path:string) {
|
private parsePath(path:string) {
|
||||||
path = ltrimSlashes(path);
|
path = ltrimSlashes(path);
|
||||||
if (!path) return { callName: '', params: [] };
|
if (!path) return { fn: null, params: [] };
|
||||||
|
|
||||||
const pathParts = path.split('/');
|
const pathParts = path.split('/');
|
||||||
const callSuffix = pathParts.splice(0, 1)[0];
|
const callSuffix = pathParts.splice(0, 1)[0];
|
||||||
const callName = `action_${callSuffix}`;
|
const fn = this.resourceNameToRoute_[callSuffix];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callName: callName,
|
fn: fn,
|
||||||
params: pathParts,
|
params: pathParts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response can be any valid JSON object, so a string, and array or an object (key/value pairs).
|
// Response can be any valid JSON object, so a string, and array or an object (key/value pairs).
|
||||||
async route(method:string, path:string, query:any = null, body:any = null, files:string[] = null):Promise<any> {
|
public async route(method:RequestMethod, path:string, query:RequestQuery = null, body:any = null, files:RequestFile[] = null):Promise<any> {
|
||||||
if (!files) files = [];
|
if (!files) files = [];
|
||||||
if (!query) query = {};
|
if (!query) query = {};
|
||||||
|
|
||||||
const parsedPath = this.parsePath(path);
|
const parsedPath = this.parsePath(path);
|
||||||
if (!parsedPath.callName) throw new ErrorNotFound(); // Nothing at the root yet
|
if (!parsedPath.fn) throw new ErrorNotFound(); // Nothing at the root yet
|
||||||
|
|
||||||
if (query && query.nounce) {
|
if (query && query.nounce) {
|
||||||
const requestMd5 = md5(JSON.stringify([method, path, body, query, files.length]));
|
const requestMd5 = md5(JSON.stringify([method, path, body, query, files.length]));
|
||||||
@@ -75,11 +116,24 @@ export default class Api {
|
|||||||
this.knownNounces_[query.nounce] = requestMd5;
|
this.knownNounces_[query.nounce] = requestMd5;
|
||||||
}
|
}
|
||||||
|
|
||||||
const request:any = {
|
let id = null;
|
||||||
method: method,
|
let link = null;
|
||||||
|
const params = parsedPath.params;
|
||||||
|
|
||||||
|
if (params.length >= 1) {
|
||||||
|
id = params[0];
|
||||||
|
params.splice(0, 1);
|
||||||
|
if (params.length >= 1) {
|
||||||
|
link = params[0];
|
||||||
|
params.splice(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const request:Request = {
|
||||||
|
method,
|
||||||
path: ltrimSlashes(path),
|
path: ltrimSlashes(path),
|
||||||
query: query ? query : {},
|
query: query ? query : {},
|
||||||
body: body,
|
body,
|
||||||
bodyJson_: null,
|
bodyJson_: null,
|
||||||
bodyJson: function(disallowedProperties:string[] = null) {
|
bodyJson: function(disallowedProperties:string[] = null) {
|
||||||
if (!this.bodyJson_) this.bodyJson_ = JSON.parse(this.body);
|
if (!this.bodyJson_) this.bodyJson_ = JSON.parse(this.body);
|
||||||
@@ -95,60 +149,21 @@ export default class Api {
|
|||||||
|
|
||||||
return this.bodyJson_;
|
return this.bodyJson_;
|
||||||
},
|
},
|
||||||
files: files,
|
files,
|
||||||
|
params,
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = null;
|
this.checkToken_(request);
|
||||||
let link = null;
|
|
||||||
const params = parsedPath.params;
|
|
||||||
|
|
||||||
if (params.length >= 1) {
|
|
||||||
id = params[0];
|
|
||||||
params.splice(0, 1);
|
|
||||||
if (params.length >= 1) {
|
|
||||||
link = params[0];
|
|
||||||
params.splice(0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request.params = params;
|
|
||||||
|
|
||||||
if (!(this as any)[parsedPath.callName]) throw new ErrorNotFound();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await (this as any)[parsedPath.callName](request, id, link);
|
return await parsedPath.fn(request, id, link);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!error.httpCode) error.httpCode = 500;
|
if (!error.httpCode) error.httpCode = 500;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogger(l:Logger) {
|
private checkToken_(request:Request) {
|
||||||
this.logger_ = l;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger() {
|
|
||||||
return this.logger_;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonlyProperties(requestMethod:string) {
|
|
||||||
const output = ['created_time', 'updated_time', 'encryption_blob_encrypted', 'encryption_applied', 'encryption_cipher_text'];
|
|
||||||
if (requestMethod !== 'POST') output.splice(0, 0, 'id');
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
fields_(request:any, defaultFields:string[]) {
|
|
||||||
const query = request.query;
|
|
||||||
if (!query || !query.fields) return defaultFields;
|
|
||||||
if (Array.isArray(query.fields)) return query.fields.slice();
|
|
||||||
const fields = query.fields
|
|
||||||
.split(',')
|
|
||||||
.map((f:string) => f.trim())
|
|
||||||
.filter((f:string) => !!f);
|
|
||||||
return fields.length ? fields : defaultFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkToken_(request:any) {
|
|
||||||
// For now, whitelist some calls to allow the web clipper to work
|
// For now, whitelist some calls to allow the web clipper to work
|
||||||
// without an extra auth step
|
// without an extra auth step
|
||||||
const whiteList = [['GET', 'ping'], ['GET', 'tags'], ['GET', 'folders'], ['POST', 'notes']];
|
const whiteList = [['GET', 'ping'], ['GET', 'tags'], ['GET', 'folders'], ['POST', 'notes']];
|
||||||
@@ -162,202 +177,7 @@ export default class Api {
|
|||||||
if (request.query.token !== this.token) throw new ErrorForbidden('Invalid "token" parameter');
|
if (request.query.token !== this.token) throw new ErrorForbidden('Invalid "token" parameter');
|
||||||
}
|
}
|
||||||
|
|
||||||
async defaultAction_(modelType:number, request:any, id:string = null, link:string = null) {
|
private async execServiceActionFromRequest_(externalApi:any, request:Request) {
|
||||||
this.checkToken_(request);
|
|
||||||
|
|
||||||
if (link) throw new ErrorNotFound(); // Default action doesn't support links at all for now
|
|
||||||
|
|
||||||
const ModelClass = BaseItem.getClassByItemType(modelType);
|
|
||||||
|
|
||||||
const getOneModel = async () => {
|
|
||||||
const model = await ModelClass.load(id);
|
|
||||||
if (!model) throw new ErrorNotFound();
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (request.method === 'GET') {
|
|
||||||
if (id) {
|
|
||||||
return getOneModel();
|
|
||||||
} else {
|
|
||||||
const options:any = {};
|
|
||||||
const fields = this.fields_(request, []);
|
|
||||||
if (fields.length) options.fields = fields;
|
|
||||||
return await ModelClass.all(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'PUT' && id) {
|
|
||||||
const model = await getOneModel();
|
|
||||||
let newModel = Object.assign({}, model, request.bodyJson(this.readonlyProperties('PUT')));
|
|
||||||
newModel = await ModelClass.save(newModel, { userSideValidation: true });
|
|
||||||
return newModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'DELETE' && id) {
|
|
||||||
const model = await getOneModel();
|
|
||||||
await ModelClass.delete(model.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'POST') {
|
|
||||||
const props = this.readonlyProperties('POST');
|
|
||||||
const idIdx = props.indexOf('id');
|
|
||||||
if (idIdx >= 0) props.splice(idIdx, 1);
|
|
||||||
const model = request.bodyJson(props);
|
|
||||||
const result = await ModelClass.save(model, this.defaultSaveOptions_(model, 'POST'));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ErrorMethodNotAllowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
async action_ping(request:any) {
|
|
||||||
if (request.method === 'GET') {
|
|
||||||
return 'JoplinClipperServer';
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ErrorMethodNotAllowed();
|
|
||||||
}
|
|
||||||
|
|
||||||
async action_search(request:any) {
|
|
||||||
this.checkToken_(request);
|
|
||||||
|
|
||||||
if (request.method !== 'GET') throw new ErrorMethodNotAllowed();
|
|
||||||
|
|
||||||
const query = request.query.query;
|
|
||||||
if (!query) throw new ErrorBadRequest('Missing "query" parameter');
|
|
||||||
|
|
||||||
const queryType = request.query.type ? BaseModel.modelNameToType(request.query.type) : BaseModel.TYPE_NOTE;
|
|
||||||
|
|
||||||
if (queryType !== BaseItem.TYPE_NOTE) {
|
|
||||||
const ModelClass = BaseItem.getClassByItemType(queryType);
|
|
||||||
const options:any = {};
|
|
||||||
const fields = this.fields_(request, []);
|
|
||||||
if (fields.length) options.fields = fields;
|
|
||||||
const sqlQueryPart = query.replace(/\*/g, '%');
|
|
||||||
options.where = 'title LIKE ?';
|
|
||||||
options.whereParams = [sqlQueryPart];
|
|
||||||
options.caseInsensitive = true;
|
|
||||||
return await ModelClass.all(options);
|
|
||||||
} else {
|
|
||||||
return await SearchEngineUtils.notesForQuery(query, this.notePreviewsOptions_(request));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async action_folders(request:any, id:string = null, link:string = null) {
|
|
||||||
if (request.method === 'GET' && !id) {
|
|
||||||
const folders = await FoldersScreenUtils.allForDisplay({ fields: this.fields_(request, ['id', 'parent_id', 'title']) });
|
|
||||||
const output = await Folder.allAsTree(folders);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'GET' && id) {
|
|
||||||
if (link && link === 'notes') {
|
|
||||||
const options = this.notePreviewsOptions_(request);
|
|
||||||
return Note.previews(id, options);
|
|
||||||
} else if (link) {
|
|
||||||
throw new ErrorNotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.defaultAction_(BaseModel.TYPE_FOLDER, request, id, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
async action_tags(request:any, id:string = null, link:string = null) {
|
|
||||||
if (link === 'notes') {
|
|
||||||
const tag = await Tag.load(id);
|
|
||||||
if (!tag) throw new ErrorNotFound();
|
|
||||||
|
|
||||||
if (request.method === 'POST') {
|
|
||||||
const note = request.bodyJson();
|
|
||||||
if (!note || !note.id) throw new ErrorBadRequest('Missing note ID');
|
|
||||||
return await Tag.addNote(tag.id, note.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'DELETE') {
|
|
||||||
const noteId = request.params.length ? request.params[0] : null;
|
|
||||||
if (!noteId) throw new ErrorBadRequest('Missing note ID');
|
|
||||||
await Tag.removeNote(tag.id, noteId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'GET') {
|
|
||||||
// Ideally we should get all this in one SQL query but for now that will do
|
|
||||||
const noteIds = await Tag.noteIds(tag.id);
|
|
||||||
const output = [];
|
|
||||||
for (let i = 0; i < noteIds.length; i++) {
|
|
||||||
const n = await Note.preview(noteIds[i], this.notePreviewsOptions_(request));
|
|
||||||
if (!n) continue;
|
|
||||||
output.push(n);
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.defaultAction_(BaseModel.TYPE_TAG, request, id, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
async action_master_keys(request:any, id:string = null, link:string = null) {
|
|
||||||
return this.defaultAction_(BaseModel.TYPE_MASTER_KEY, request, id, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
async action_resources(request:any, id:string = null, link:string = null) {
|
|
||||||
// fieldName: "data"
|
|
||||||
// headers: Object
|
|
||||||
// originalFilename: "test.jpg"
|
|
||||||
// path: "C:\Users\Laurent\AppData\Local\Temp\BW77wkpP23iIGUstd0kDuXXC.jpg"
|
|
||||||
// size: 164394
|
|
||||||
|
|
||||||
if (request.method === 'GET') {
|
|
||||||
if (link === 'file') {
|
|
||||||
const resource = await Resource.load(id);
|
|
||||||
if (!resource) throw new ErrorNotFound();
|
|
||||||
|
|
||||||
const filePath = Resource.fullPath(resource);
|
|
||||||
const buffer = await shim.fsDriver().readFile(filePath, 'Buffer');
|
|
||||||
|
|
||||||
const response = new ApiResponse();
|
|
||||||
response.type = 'attachment';
|
|
||||||
response.body = buffer;
|
|
||||||
response.contentType = resource.mime;
|
|
||||||
response.attachmentFilename = Resource.friendlyFilename(resource);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (link) throw new ErrorNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'POST') {
|
|
||||||
if (!request.files.length) throw new ErrorBadRequest('Resource cannot be created without a file');
|
|
||||||
const filePath = request.files[0].path;
|
|
||||||
const defaultProps = request.bodyJson(this.readonlyProperties('POST'));
|
|
||||||
return shim.createResourceFromPath(filePath, defaultProps, { userSideValidation: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.defaultAction_(BaseModel.TYPE_RESOURCE, request, id, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
notePreviewsOptions_(request:any) {
|
|
||||||
const fields = this.fields_(request, []); // previews() already returns default fields
|
|
||||||
const options:any = {};
|
|
||||||
if (fields.length) options.fields = fields;
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultSaveOptions_(model:any, requestMethod:string) {
|
|
||||||
const options:any = { userSideValidation: true };
|
|
||||||
if (requestMethod === 'POST' && model.id) options.isNew = true;
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultLoadOptions_(request:any) {
|
|
||||||
const options:any = {};
|
|
||||||
const fields = this.fields_(request, []);
|
|
||||||
if (fields.length) options.fields = fields;
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
async execServiceActionFromRequest_(externalApi:any, request:any) {
|
|
||||||
const action = externalApi[request.action];
|
const action = externalApi[request.action];
|
||||||
if (!action) throw new ErrorNotFound(`Invalid action: ${request.action}`);
|
if (!action) throw new ErrorNotFound(`Invalid action: ${request.action}`);
|
||||||
const args = Object.assign({}, request);
|
const args = Object.assign({}, request);
|
||||||
@@ -365,10 +185,8 @@ export default class Api {
|
|||||||
return action(args);
|
return action(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async action_services(request:any, serviceName:string) {
|
private async action_services(request:Request, serviceName:string) {
|
||||||
this.checkToken_(request);
|
if (request.method !== RequestMethod.POST) throw new ErrorMethodNotAllowed();
|
||||||
|
|
||||||
if (request.method !== 'POST') throw new ErrorMethodNotAllowed();
|
|
||||||
if (!this.actionApi_) throw new ErrorNotFound('No action API has been setup!');
|
if (!this.actionApi_) throw new ErrorNotFound('No action API has been setup!');
|
||||||
if (!this.actionApi_[serviceName]) throw new ErrorNotFound(`No such service: ${serviceName}`);
|
if (!this.actionApi_[serviceName]) throw new ErrorNotFound(`No such service: ${serviceName}`);
|
||||||
|
|
||||||
@@ -376,385 +194,4 @@ export default class Api {
|
|||||||
return this.execServiceActionFromRequest_(externalApi, JSON.parse(request.body));
|
return this.execServiceActionFromRequest_(externalApi, JSON.parse(request.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
async action_notes(request:any, id:string = null, link:string = null) {
|
|
||||||
this.checkToken_(request);
|
|
||||||
|
|
||||||
if (request.method === 'GET') {
|
|
||||||
if (link && link === 'tags') {
|
|
||||||
return Tag.tagsByNoteId(id);
|
|
||||||
} else if (link && link === 'resources') {
|
|
||||||
const note = await Note.load(id);
|
|
||||||
if (!note) throw new ErrorNotFound();
|
|
||||||
const resourceIds = await Note.linkedResourceIds(note.body);
|
|
||||||
const output = [];
|
|
||||||
const loadOptions = this.defaultLoadOptions_(request);
|
|
||||||
for (const resourceId of resourceIds) {
|
|
||||||
output.push(await Resource.load(resourceId, loadOptions));
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
} else if (link) {
|
|
||||||
throw new ErrorNotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = this.notePreviewsOptions_(request);
|
|
||||||
if (id) {
|
|
||||||
return await Note.preview(id, options);
|
|
||||||
} else {
|
|
||||||
return await Note.previews(null, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'POST') {
|
|
||||||
const requestId = Date.now();
|
|
||||||
const requestNote = JSON.parse(request.body);
|
|
||||||
|
|
||||||
// const allowFileProtocolImages = urlUtils.urlProtocol(requestNote.base_url).toLowerCase() === 'file:';
|
|
||||||
|
|
||||||
const imageSizes = requestNote.image_sizes ? requestNote.image_sizes : {};
|
|
||||||
|
|
||||||
let note:any = await this.requestNoteToNote_(requestNote);
|
|
||||||
|
|
||||||
const imageUrls = ArrayUtils.unique(markupLanguageUtils.extractImageUrls(note.markup_language, note.body));
|
|
||||||
|
|
||||||
this.logger().info(`Request (${requestId}): Downloading images: ${imageUrls.length}`);
|
|
||||||
|
|
||||||
let result = await this.downloadImages_(imageUrls); // , allowFileProtocolImages);
|
|
||||||
|
|
||||||
this.logger().info(`Request (${requestId}): Creating resources from paths: ${Object.getOwnPropertyNames(result).length}`);
|
|
||||||
|
|
||||||
result = await this.createResourcesFromPaths_(result);
|
|
||||||
await this.removeTempFiles_(result);
|
|
||||||
note.body = this.replaceImageUrlsByResources_(note.markup_language, note.body, result, imageSizes);
|
|
||||||
|
|
||||||
this.logger().info(`Request (${requestId}): Saving note...`);
|
|
||||||
|
|
||||||
const saveOptions = this.defaultSaveOptions_(note, 'POST');
|
|
||||||
saveOptions.autoTimestamp = false; // No auto-timestamp because user may have provided them
|
|
||||||
const timestamp = Date.now();
|
|
||||||
note.updated_time = timestamp;
|
|
||||||
note.created_time = timestamp;
|
|
||||||
if (!('user_updated_time' in note)) note.user_updated_time = timestamp;
|
|
||||||
if (!('user_created_time' in note)) note.user_created_time = timestamp;
|
|
||||||
|
|
||||||
note = await Note.save(note, saveOptions);
|
|
||||||
|
|
||||||
if (requestNote.tags) {
|
|
||||||
const tagTitles = requestNote.tags.split(',');
|
|
||||||
await Tag.setNoteTagsByTitles(note.id, tagTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestNote.image_data_url) {
|
|
||||||
note = await this.attachImageFromDataUrl_(note, requestNote.image_data_url, requestNote.crop_rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger().info(`Request (${requestId}): Created note ${note.id}`);
|
|
||||||
|
|
||||||
return note;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method === 'PUT') {
|
|
||||||
const note = await Note.load(id);
|
|
||||||
|
|
||||||
if (!note) throw new ErrorNotFound();
|
|
||||||
|
|
||||||
const saveOptions = {
|
|
||||||
...this.defaultSaveOptions_(note, 'PUT'),
|
|
||||||
autoTimestamp: false, // No auto-timestamp because user may have provided them
|
|
||||||
userSideValidation: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const timestamp = Date.now();
|
|
||||||
|
|
||||||
const newProps = request.bodyJson(this.readonlyProperties('PUT'));
|
|
||||||
if (!('user_updated_time' in newProps)) newProps.user_updated_time = timestamp;
|
|
||||||
|
|
||||||
let newNote = {
|
|
||||||
...note,
|
|
||||||
...newProps,
|
|
||||||
updated_time: timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
newNote = await Note.save(newNote, saveOptions);
|
|
||||||
|
|
||||||
const requestNote = JSON.parse(request.body);
|
|
||||||
if (requestNote.tags || requestNote.tags === '') {
|
|
||||||
const tagTitles = requestNote.tags.split(',');
|
|
||||||
await Tag.setNoteTagsByTitles(id, tagTitles);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.defaultAction_(BaseModel.TYPE_NOTE, request, id, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================================================================================================
|
|
||||||
// UTILIY FUNCTIONS
|
|
||||||
// ========================================================================================================================
|
|
||||||
|
|
||||||
htmlToMdParser() {
|
|
||||||
if (this.htmlToMdParser_) return this.htmlToMdParser_;
|
|
||||||
this.htmlToMdParser_ = new HtmlToMd();
|
|
||||||
return this.htmlToMdParser_;
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestNoteToNote_(requestNote:any) {
|
|
||||||
const output:any = {
|
|
||||||
title: requestNote.title ? requestNote.title : '',
|
|
||||||
body: requestNote.body ? requestNote.body : '',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (requestNote.id) output.id = requestNote.id;
|
|
||||||
|
|
||||||
const baseUrl = requestNote.base_url ? requestNote.base_url : '';
|
|
||||||
|
|
||||||
if (requestNote.body_html) {
|
|
||||||
if (requestNote.convert_to === 'html') {
|
|
||||||
const style = await this.buildNoteStyleSheet_(requestNote.stylesheets);
|
|
||||||
const minify = require('html-minifier').minify;
|
|
||||||
|
|
||||||
const minifyOptions = {
|
|
||||||
// Remove all spaces and, especially, newlines from tag attributes, as that would
|
|
||||||
// break the rendering.
|
|
||||||
customAttrCollapse: /.*/,
|
|
||||||
// Need to remove all whitespaces because whitespace at a beginning of a line
|
|
||||||
// means a code block in Markdown.
|
|
||||||
collapseWhitespace: true,
|
|
||||||
minifyCSS: true,
|
|
||||||
maxLineLength: 300,
|
|
||||||
};
|
|
||||||
|
|
||||||
const uglifycss = require('uglifycss');
|
|
||||||
const styleString = uglifycss.processString(style.join('\n'), {
|
|
||||||
// Need to set a max length because Ace Editor takes forever
|
|
||||||
// to display notes with long lines.
|
|
||||||
maxLineLen: 200,
|
|
||||||
});
|
|
||||||
|
|
||||||
const styleTag = style.length ? `<style>${styleString}</style>` + '\n' : '';
|
|
||||||
let minifiedHtml = '';
|
|
||||||
try {
|
|
||||||
minifiedHtml = minify(requestNote.body_html, minifyOptions);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Could not minify HTML - using non-minified HTML instead', error);
|
|
||||||
minifiedHtml = requestNote.body_html;
|
|
||||||
}
|
|
||||||
output.body = styleTag + minifiedHtml;
|
|
||||||
output.body = htmlUtils.prependBaseUrl(output.body, baseUrl);
|
|
||||||
output.markup_language = MarkupToHtml.MARKUP_LANGUAGE_HTML;
|
|
||||||
} else {
|
|
||||||
// Convert to Markdown
|
|
||||||
// Parsing will not work if the HTML is not wrapped in a top level tag, which is not guaranteed
|
|
||||||
// when getting the content from elsewhere. So here wrap it - it won't change anything to the final
|
|
||||||
// rendering but it makes sure everything will be parsed.
|
|
||||||
output.body = await this.htmlToMdParser().parse(`<div>${requestNote.body_html}</div>`, {
|
|
||||||
baseUrl: baseUrl,
|
|
||||||
anchorNames: requestNote.anchor_names ? requestNote.anchor_names : [],
|
|
||||||
});
|
|
||||||
output.markup_language = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestNote.parent_id) {
|
|
||||||
output.parent_id = requestNote.parent_id;
|
|
||||||
} else {
|
|
||||||
const folder = await Folder.defaultFolder();
|
|
||||||
if (!folder) throw new Error('Cannot find folder for note');
|
|
||||||
output.parent_id = folder.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('source_url' in requestNote) output.source_url = requestNote.source_url;
|
|
||||||
if ('author' in requestNote) output.author = requestNote.author;
|
|
||||||
if ('user_updated_time' in requestNote) output.user_updated_time = Database.formatValue(Database.TYPE_INT, requestNote.user_updated_time);
|
|
||||||
if ('user_created_time' in requestNote) output.user_created_time = Database.formatValue(Database.TYPE_INT, requestNote.user_created_time);
|
|
||||||
if ('is_todo' in requestNote) output.is_todo = Database.formatValue(Database.TYPE_INT, requestNote.is_todo);
|
|
||||||
if ('markup_language' in requestNote) output.markup_language = Database.formatValue(Database.TYPE_INT, requestNote.markup_language);
|
|
||||||
if ('longitude' in requestNote) output.longitude = requestNote.longitude;
|
|
||||||
if ('latitude' in requestNote) output.latitude = requestNote.latitude;
|
|
||||||
if ('altitude' in requestNote) output.altitude = requestNote.altitude;
|
|
||||||
|
|
||||||
if (!output.markup_language) output.markup_language = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note must have been saved first
|
|
||||||
async attachImageFromDataUrl_(note:any, imageDataUrl:string, cropRect:any) {
|
|
||||||
const tempDir = Setting.value('tempDir');
|
|
||||||
const mime = mimeUtils.fromDataUrl(imageDataUrl);
|
|
||||||
let ext = mimeUtils.toFileExtension(mime) || '';
|
|
||||||
if (ext) ext = `.${ext}`;
|
|
||||||
const tempFilePath = `${tempDir}/${md5(`${Math.random()}_${Date.now()}`)}${ext}`;
|
|
||||||
const imageConvOptions:any = {};
|
|
||||||
if (cropRect) imageConvOptions.cropRect = cropRect;
|
|
||||||
await shim.imageFromDataUrl(imageDataUrl, tempFilePath, imageConvOptions);
|
|
||||||
return await shim.attachFileToNote(note, tempFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
async tryToGuessImageExtFromMimeType_(response:any, imagePath:string) {
|
|
||||||
const mimeType = netUtils.mimeTypeFromHeaders(response.headers);
|
|
||||||
if (!mimeType) return imagePath;
|
|
||||||
|
|
||||||
const newExt = mimeUtils.toFileExtension(mimeType);
|
|
||||||
if (!newExt) return imagePath;
|
|
||||||
|
|
||||||
const newImagePath = `${imagePath}.${newExt}`;
|
|
||||||
await shim.fsDriver().move(imagePath, newImagePath);
|
|
||||||
return newImagePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
async buildNoteStyleSheet_(stylesheets:any[]) {
|
|
||||||
if (!stylesheets) return [];
|
|
||||||
|
|
||||||
const output = [];
|
|
||||||
|
|
||||||
for (const stylesheet of stylesheets) {
|
|
||||||
if (stylesheet.type === 'text') {
|
|
||||||
output.push(stylesheet.value);
|
|
||||||
} else if (stylesheet.type === 'url') {
|
|
||||||
try {
|
|
||||||
const tempPath = `${Setting.value('tempDir')}/${md5(`${Math.random()}_${Date.now()}`)}.css`;
|
|
||||||
await shim.fetchBlob(stylesheet.value, { path: tempPath, maxRetry: 1 });
|
|
||||||
const text = await shim.fsDriver().readFile(tempPath);
|
|
||||||
output.push(text);
|
|
||||||
await shim.fsDriver().remove(tempPath);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger().warn(`Cannot download stylesheet at ${stylesheet.value}`, error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(`Invalid stylesheet type: ${stylesheet.type}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadImage_(url:string /* , allowFileProtocolImages */) {
|
|
||||||
const tempDir = Setting.value('tempDir');
|
|
||||||
|
|
||||||
// The URL we get to download have been extracted from the Markdown document
|
|
||||||
url = markdownUtils.unescapeLinkUrl(url);
|
|
||||||
|
|
||||||
const isDataUrl = url && url.toLowerCase().indexOf('data:') === 0;
|
|
||||||
|
|
||||||
const name = isDataUrl ? md5(`${Math.random()}_${Date.now()}`) : filename(url);
|
|
||||||
let fileExt = isDataUrl ? mimeUtils.toFileExtension(mimeUtils.fromDataUrl(url)) : safeFileExtension(fileExtension(url).toLowerCase());
|
|
||||||
if (!mimeUtils.fromFileExtension(fileExt)) fileExt = ''; // If the file extension is unknown - clear it.
|
|
||||||
if (fileExt) fileExt = `.${fileExt}`;
|
|
||||||
|
|
||||||
// Append a UUID because simply checking if the file exists is not enough since
|
|
||||||
// multiple resources can be downloaded at the same time (race condition).
|
|
||||||
let imagePath = `${tempDir}/${safeFilename(name)}_${uuid.create()}${fileExt}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (isDataUrl) {
|
|
||||||
await shim.imageFromDataUrl(url, imagePath);
|
|
||||||
} else if (urlUtils.urlProtocol(url).toLowerCase() === 'file:') {
|
|
||||||
// Can't think of any reason to disallow this at this point
|
|
||||||
// if (!allowFileProtocolImages) throw new Error('For security reasons, this URL with file:// protocol cannot be downloaded');
|
|
||||||
const localPath = uri2path(url);
|
|
||||||
await shim.fsDriver().copy(localPath, imagePath);
|
|
||||||
} else {
|
|
||||||
const response = await shim.fetchBlob(url, { path: imagePath, maxRetry: 1 });
|
|
||||||
|
|
||||||
// If we could not find the file extension from the URL, try to get it
|
|
||||||
// now based on the Content-Type header.
|
|
||||||
if (!fileExt) imagePath = await this.tryToGuessImageExtFromMimeType_(response, imagePath);
|
|
||||||
}
|
|
||||||
return imagePath;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger().warn(`Cannot download image at ${url}`, error);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async downloadImages_(urls:string[] /* , allowFileProtocolImages:boolean */) {
|
|
||||||
const PromisePool = require('es6-promise-pool');
|
|
||||||
|
|
||||||
const output:any = {};
|
|
||||||
|
|
||||||
const downloadOne = async (url:string) => {
|
|
||||||
const imagePath = await this.downloadImage_(url); // , allowFileProtocolImages);
|
|
||||||
if (imagePath) output[url] = { path: imagePath, originalUrl: url };
|
|
||||||
};
|
|
||||||
|
|
||||||
let urlIndex = 0;
|
|
||||||
const promiseProducer = () => {
|
|
||||||
if (urlIndex >= urls.length) return null;
|
|
||||||
|
|
||||||
const url = urls[urlIndex++];
|
|
||||||
return downloadOne(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
const concurrency = 10;
|
|
||||||
const pool = new PromisePool(promiseProducer, concurrency);
|
|
||||||
await pool.start();
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createResourcesFromPaths_(urls:string[]) {
|
|
||||||
for (const url in urls) {
|
|
||||||
if (!urls.hasOwnProperty(url)) continue;
|
|
||||||
const urlInfo:any = urls[url];
|
|
||||||
try {
|
|
||||||
const resource = await shim.createResourceFromPath(urlInfo.path);
|
|
||||||
urlInfo.resource = resource;
|
|
||||||
} catch (error) {
|
|
||||||
this.logger().warn(`Cannot create resource for ${url}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return urls;
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeTempFiles_(urls:string[]) {
|
|
||||||
for (const url in urls) {
|
|
||||||
if (!urls.hasOwnProperty(url)) continue;
|
|
||||||
const urlInfo:any = urls[url];
|
|
||||||
try {
|
|
||||||
await shim.fsDriver().remove(urlInfo.path);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger().warn(`Cannot remove ${urlInfo.path}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceImageUrlsByResources_(markupLanguage:number, md:string, urls:any, imageSizes:any) {
|
|
||||||
const imageSizesIndexes:any = {};
|
|
||||||
|
|
||||||
if (markupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML) {
|
|
||||||
return htmlUtils.replaceImageUrls(md, (imageUrl:string) => {
|
|
||||||
const urlInfo:any = urls[imageUrl];
|
|
||||||
if (!urlInfo || !urlInfo.resource) return imageUrl;
|
|
||||||
return Resource.internalUrl(urlInfo.resource);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-useless-escape
|
|
||||||
return md.replace(/(!\[.*?\]\()([^\s\)]+)(.*?\))/g, (_match:any, before:string, imageUrl:string, after:string) => {
|
|
||||||
const urlInfo = urls[imageUrl];
|
|
||||||
if (!urlInfo || !urlInfo.resource) return before + imageUrl + after;
|
|
||||||
if (!(urlInfo.originalUrl in imageSizesIndexes)) imageSizesIndexes[urlInfo.originalUrl] = 0;
|
|
||||||
|
|
||||||
const resourceUrl = Resource.internalUrl(urlInfo.resource);
|
|
||||||
const imageSizesCollection = imageSizes[urlInfo.originalUrl];
|
|
||||||
|
|
||||||
if (!imageSizesCollection) {
|
|
||||||
// In some cases, we won't find the image size information for that particular URL. Normally
|
|
||||||
// it will only happen when using the "Clip simplified page" feature, which can modify the
|
|
||||||
// image URLs (for example it will select a smaller size resolution). In that case, it's
|
|
||||||
// fine to return the image as-is because it has already good dimensions.
|
|
||||||
return before + resourceUrl + after;
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageSize = imageSizesCollection[imageSizesIndexes[urlInfo.originalUrl]];
|
|
||||||
imageSizesIndexes[urlInfo.originalUrl]++;
|
|
||||||
|
|
||||||
if (imageSize && (imageSize.naturalWidth !== imageSize.width || imageSize.naturalHeight !== imageSize.height)) {
|
|
||||||
return `<img width="${imageSize.width}" height="${imageSize.height}" src="${resourceUrl}"/>`;
|
|
||||||
} else {
|
|
||||||
return before + resourceUrl + after;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
class ApiResponse {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ApiResponse;
|
|
8
ReactNativeClient/lib/services/rest/ApiResponse.ts
Normal file
8
ReactNativeClient/lib/services/rest/ApiResponse.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default class ApiResponse {
|
||||||
|
|
||||||
|
public type:string;
|
||||||
|
public body:any;
|
||||||
|
public contentType:string;
|
||||||
|
public attachmentFilename:string;
|
||||||
|
|
||||||
|
}
|
37
ReactNativeClient/lib/services/rest/errors.js
Normal file
37
ReactNativeClient/lib/services/rest/errors.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
exports.ErrorBadRequest = exports.ErrorForbidden = exports.ErrorNotFound = exports.ErrorMethodNotAllowed = void 0;
|
||||||
|
class ApiError extends Error {
|
||||||
|
constructor(message, httpCode = 400) {
|
||||||
|
super(message);
|
||||||
|
this.httpCode_ = httpCode;
|
||||||
|
}
|
||||||
|
get httpCode() {
|
||||||
|
return this.httpCode_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ErrorMethodNotAllowed extends ApiError {
|
||||||
|
constructor(message = 'Method Not Allowed') {
|
||||||
|
super(message, 405);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ErrorMethodNotAllowed = ErrorMethodNotAllowed;
|
||||||
|
class ErrorNotFound extends ApiError {
|
||||||
|
constructor(message = 'Not Found') {
|
||||||
|
super(message, 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ErrorNotFound = ErrorNotFound;
|
||||||
|
class ErrorForbidden extends ApiError {
|
||||||
|
constructor(message = 'Forbidden') {
|
||||||
|
super(message, 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ErrorForbidden = ErrorForbidden;
|
||||||
|
class ErrorBadRequest extends ApiError {
|
||||||
|
constructor(message = 'Bad Request') {
|
||||||
|
super(message, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ErrorBadRequest = ErrorBadRequest;
|
||||||
|
// # sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZXJyb3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLE1BQU0sUUFBUyxTQUFRLEtBQUs7SUFHM0IsWUFBWSxPQUFjLEVBQUUsV0FBa0IsR0FBRztRQUNoRCxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixJQUFJLENBQUMsU0FBUyxHQUFHLFFBQVEsQ0FBQztJQUMzQixDQUFDO0lBRUQsSUFBSSxRQUFRO1FBQ1gsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQ3ZCLENBQUM7Q0FDRDtBQUVELE1BQWEscUJBQXNCLFNBQVEsUUFBUTtJQUNsRCxZQUFZLE9BQU8sR0FBRyxvQkFBb0I7UUFDekMsS0FBSyxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsQ0FBQztJQUNyQixDQUFDO0NBQ0Q7QUFKRCxzREFJQztBQUNELE1BQWEsYUFBYyxTQUFRLFFBQVE7SUFDMUMsWUFBWSxPQUFPLEdBQUcsV0FBVztRQUNoQyxLQUFLLENBQUMsT0FBTyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQ3JCLENBQUM7Q0FDRDtBQUpELHNDQUlDO0FBQ0QsTUFBYSxjQUFlLFNBQVEsUUFBUTtJQUMzQyxZQUFZLE9BQU8sR0FBRyxXQUFXO1FBQ2hDLEtBQUssQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDckIsQ0FBQztDQUNEO0FBSkQsd0NBSUM7QUFDRCxNQUFhLGVBQWdCLFNBQVEsUUFBUTtJQUM1QyxZQUFZLE9BQU8sR0FBRyxhQUFhO1FBQ2xDLEtBQUssQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDckIsQ0FBQztDQUNEO0FBSkQsMENBSUMifQ==
|
32
ReactNativeClient/lib/services/rest/routes/folders.ts
Normal file
32
ReactNativeClient/lib/services/rest/routes/folders.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
import { Request } from '../Api';
|
||||||
|
import defaultAction from '../utils/defaultAction';
|
||||||
|
import paginatedResults from '../utils/paginatedResults';
|
||||||
|
import BaseModel from 'lib/BaseModel';
|
||||||
|
import requestFields from '../utils/requestFields';
|
||||||
|
const Folder = require('lib/models/Folder');
|
||||||
|
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||||
|
const { ErrorNotFound } = require('../errors');
|
||||||
|
|
||||||
|
export default async function(request:Request, id:string = null, link:string = null) {
|
||||||
|
if (request.method === 'GET' && !id) {
|
||||||
|
if (request.query.as_tree) {
|
||||||
|
const folders = await FoldersScreenUtils.allForDisplay({ fields: requestFields(request, BaseModel.TYPE_FOLDER) });
|
||||||
|
const output = await Folder.allAsTree(folders);
|
||||||
|
return output;
|
||||||
|
} else {
|
||||||
|
return defaultAction(BaseModel.TYPE_FOLDER, request, id, link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.method === 'GET' && id) {
|
||||||
|
if (link && link === 'notes') {
|
||||||
|
const folder = await Folder.load(id);
|
||||||
|
return paginatedResults(BaseModel.TYPE_NOTE, request, `parent_id = "${folder.id}"`);
|
||||||
|
} else if (link) {
|
||||||
|
throw new ErrorNotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultAction(BaseModel.TYPE_FOLDER, request, id, link);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user