1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-02 20:46:21 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Laurent Cozic
f98c693adf CLI v1.0.112 2018-07-25 17:51:13 +02:00
Laurent Cozic
6da80f3291 Trying to get proxy to work 2018-07-25 17:47:35 +02:00
460 changed files with 24239 additions and 76524 deletions

4
.github/FUNDING.yml vendored
View File

@@ -1,4 +0,0 @@
# These are supported funding model platforms
patreon: joplin
custom: https://joplinapp.org/donate/

2
.gitignore vendored Normal file → Executable file
View File

@@ -39,7 +39,5 @@ node_modules
Tools/github_oauth_token.txt
_releases
ReactNativeClient/lib/csstojs/
ReactNativeClient/lib/rnInjectedJs/
ElectronClient/app/gui/note-viewer/fonts/
ElectronClient/app/gui/note-viewer/lib.js
Tools/commit_hook.txt

BIN
Assets/Adresse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

View File

@@ -1,8 +1,7 @@
[![Travis Build Status](https://travis-ci.org/laurent22/joplin.svg?branch=master)](https://travis-ci.org/laurent22/joplin) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/laurent22/joplin?branch=master&passingText=master%20-%20OK&svg=true)](https://ci.appveyor.com/project/laurent22/joplin)
# General information
- All the applications share the same library, which, for historical reasons, is in ReactNativeClient/lib. This library is copied to the relevant directories when building each app.
- The translations are built by running CliClient/build-translation.sh. You normally don't need to run this if you haven't updated the translation since the compiled files are on the repository.
## macOS dependencies
@@ -10,6 +9,8 @@
echo 'export PATH="/usr/local/opt/gettext/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`
## Linux and Windows (WSL) dependencies
- Install yarn - https://yarnpkg.com/lang/en/docs/install/
@@ -36,10 +37,6 @@ yarn dist
If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4`
If you get a node-gyp related error you might need to manually install it: `npm install -g node-gyp`.
If you get the error `libtool: unrecognized option '-static'`, follow the instructions [in this post](https://stackoverflow.com/a/38552393/561309) to use the correct libtool version.
That will create the executable file in the `dist` directory.
From `/ElectronClient` you can also run `run.sh` to run the app for testing.
@@ -59,10 +56,6 @@ If node-gyp does not works (MSBUILD: error MSB3428: Could not load the Visual C+
If `yarn dist` fails, it may need administrative rights.
If you get an `error MSB8020: The build tools for v140 cannot be found.` try to run with a different toolset version, eg `npm install --toolset=v141` (See [here](https://github.com/mapbox/node-sqlite3/issues/1124) for more info).
The [building\_win32\_tips on this page](./readme/building_win32_tips.md) might be helpful.
# Building the Mobile application
First you need to setup React Native to build projects with native code. For this, follow the instructions on the [Get Started](https://facebook.github.io/react-native/docs/getting-started.html) tutorial, in the "Building Projects with Native Code" tab.

View File

@@ -1,63 +1,19 @@
# User support
The [Joplin Forum](https://discourse.joplinapp.org/) is the community driven place for user support, general discussion about Joplin, problems with installation, new features and software development questions. It is possible to login with your GitHub account. Don't use the issue tracker for support questions.
For general discussion about Joplin, user support, software development questions, and to discuss new features, please go to the [Joplin Forum](https://discourse.joplin.cozic.net/). It is possible to login with your GitHub account.
# Reporting a bug
File bugs in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Please follow these guidelines:
Please check first that it [has not already been reported](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). Also consider [enabling debug mode](https://github.com/laurent22/joplin/blob/master/readme/debugging.md) before reporting the issue so that you can provide as much details as possible to help fix it.
- Search existing issues first, make sure yours hasn't already been reported.
- Consider [enabling debug mode](https://joplinapp.org/debugging/) so that you can provide as much details as possible when reporting the issue.
- Stay on topic, but describe the issue in detail so that others can reproduce it.
- **Provide a screenshot** if possible. A screenshot showing the problem is often more useful than a paragraph describing it.
- For web clipper bugs, **please provide the URL causing the issue**. Sometimes the clipper works in one page but not in another so it is important to know what URL has a problem.
If possible, **please provide a screenshot**. A screenshot showing the problem is often more useful than a paragraph describing it as it can make it immediately clear what the issue is.
# Feature requests
Please check that your request has not already been posted in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). If it has, **up-voting the issue** increases the chances it'll be noticed and implemented in the future. "+1" comments are not tracked.
Again, please check that it has not already been requested. If it has, simply **up-vote the issue** - the ones with the most up-votes are likely to be implemented. "+1" comments are not tracked.
As a general rule, suggestions to *improve Joplin* should be posted first in the [Joplin Forum](https://discourse.joplinapp.org/) for discussion.
# Adding new features
Avoid listing multiple requests in one report in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue). One issue per request makes it easier to track and discuss it.
Finally, when submitting a pull request, don't forget to [test your code](#unit-tests).
# Contribute to the project
## Contributing to Joplin's translation
Joplin is available in multiple languages thanks to the help of its users. You can help translate Joplin to your language or keep it up to date. Please read the documentation about [Localisation](https://joplinapp.org/#localisation).
## Contributing to Joplin's code
If you want to start contributing to the project's code, please follow these guidelines before creating a pull request:
- Bug fixes are always welcome. Start by reviewing the list of [essential issues](https://github.com/laurent22/joplin/issues?q=is%3Aissue+is%3Aopen+label%3Aessential)
- Before adding a new feature, ask about it in the [Github Issue Tracker](https://github.com/laurent22/joplin/issues?utf8=%E2%9C%93&q=is%3Aissue) or the [Joplin Forum](https://discourse.joplinapp.org/), or check if existing discussions exist to make sure the new functionality is desired.
- **Changes that will consist in more than 50 lines of code should be discussed the [Joplin Forum](https://discourse.joplinapp.org/)**, so that you don't spend too much time implementing something that might not be accepted.
If you want to add a new feature, consider asking about it before implementing it or checking existing discussions to make sure it is within the scope of the project. Of course you are free to create the pull request directly but it is not guaranteed it is going to be accepted.
Building the apps is relatively easy - please [see the build instructions](https://github.com/laurent22/joplin/blob/master/BUILD.md) for more details.
## Coding style
There are only two rules, but not following them means the pull request will not be accepted (it can be accepted once the issues are fixed):
- **Please use tabs, NOT spaces.**
- **Please do not add or remove optional characters, such as spaces or colons.** Please setup your editor so that it only changes what you are working on and is not making automated changes elsewhere. The reason for this is that small white space changes make diff hard to read and can cause needless conflicts.
## Unit tests
When submitting a pull request for a new feature or bug fix, please add unit tests for your code. Unit testing GUI changes is not always possible so it is not required, but any change in a file under /lib for example should be unit tested.
The tests are under CliClient/tests. To get them running, you first need to build the CLI app:
cd CliClient
npm i
Then to run all the test units:
./run_test.sh
To run just one particular file:
./run_test.sh markdownUtils # Don't add the .js extension

View File

@@ -13,7 +13,6 @@ tests/fuzzing.*
tests/fuzzing -*
tests/logs/*
tests/cli-integration/
tests/tmp/
*.mo
*.*~
tests/sync

View File

@@ -21,9 +21,8 @@ const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/loc
const os = require('os');
const fs = require('fs-extra');
const { cliUtils } = require('./cli-utils.js');
const EventEmitter = require('events');
const Cache = require('lib/Cache');
const WelcomeUtils = require('lib/WelcomeUtils');
const RevisionService = require('lib/services/RevisionService');
class Application extends BaseApplication {
@@ -395,12 +394,6 @@ class Application extends BaseApplication {
}
process.exit(1);
}
await Setting.saveAll();
// Need to call exit() explicitely, otherwise Node wait for any timeout to complete
// https://stackoverflow.com/questions/18050095
process.exit(0);
} else { // Otherwise open the GUI
this.initRedux();
@@ -421,8 +414,6 @@ class Application extends BaseApplication {
const tags = await Tag.allWithNotes();
ResourceService.runInBackground();
RevisionService.instance().runInBackground();
this.dispatch({
type: 'TAG_UPDATE_ALL',

View File

@@ -102,7 +102,7 @@ function getFooter() {
output.push('WEBSITE');
output.push('');
output.push(INDENT + 'https://joplinapp.org');
output.push(INDENT + 'https://joplin.cozic.net');
output.push('');

View File

@@ -1,299 +0,0 @@
const { BaseCommand } = require('./base-command.js');
const { _ } = require('lib/locale.js');
const { cliUtils } = require('./cli-utils.js');
const EncryptionService = require('lib/services/EncryptionService');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const MasterKey = require('lib/models/MasterKey');
const BaseItem = require('lib/models/BaseItem');
const BaseModel = require('lib/BaseModel');
const Setting = require('lib/models/Setting.js');
const { toTitleCase } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const markdownUtils = require('lib/markdownUtils');
const { Database } = require('lib/database.js');
class Command extends BaseCommand {
usage() {
return 'apidoc';
}
description() {
return 'Build the API doc';
}
createPropertiesTable(tableFields) {
const headers = [
{ name: 'name', label: 'Name' },
{ name: 'type', label: 'Type', filter: (value) => {
return Database.enumName('fieldType', value);
}},
{ name: 'description', label: 'Description' },
];
return markdownUtils.createMarkdownTable(headers, tableFields);
}
async action(args) {
const models = [
{
type: BaseModel.TYPE_NOTE,
},
{
type: BaseModel.TYPE_FOLDER,
},
{
type: BaseModel.TYPE_RESOURCE,
},
{
type: BaseModel.TYPE_TAG,
},
];
const lines = [];
lines.push('# Joplin API');
lines.push('');
lines.push('When the Web Clipper service is enabled, Joplin exposes a [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) which allows third-party applications to access Joplin\'s data and to create, modify or delete notes, notebooks, resources or tags.');
lines.push('');
lines.push('In order to use it, you\'ll first need to find on which port the service is running. To do so, open the Web Clipper Options in Joplin and if the service is running it should tell you on which port. Normally it runs on port **41184**. If you want to find it programmatically, you may follow this kind of algorithm:');
lines.push('');
lines.push('```javascript');
lines.push('let port = null;');
lines.push('for (let portToTest = 41184; portToTest <= 41194; portToTest++) {');
lines.push(' const result = pingPort(portToTest); // Call GET /ping');
lines.push(' if (result == \'JoplinClipperServer\') {');
lines.push(' port = portToTest; // Found the port');
lines.push(' break;');
lines.push(' }');
lines.push('}');
lines.push('```');
lines.push('');
lines.push('# Authorisation')
lines.push('');
lines.push('To prevent unauthorised applications from accessing the API, the calls must be authentified. To do so, you must provide a token as a query parameter for each API call. You can get this token from the Joplin desktop application, on the Web Clipper Options screen.');
lines.push('');
lines.push('This would be an example of valid cURL call using a token:');
lines.push('');
lines.push('\tcurl http://localhost:41184/notes?token=ABCD123ABCD123ABCD123ABCD123ABCD123');
lines.push('');
lines.push('In the documentation below, the token will not be specified every time however you will need to include it.');
lines.push('');
lines.push('# Using the API');
lines.push('');
lines.push('All the calls, unless noted otherwise, receives and send **JSON data**. For example to create a new note:');
lines.push('');
lines.push('\tcurl --data \'{ "title": "My note", "body": "Some note in **Markdown**"}\' http://localhost:41184/notes');
lines.push('');
lines.push('In the documentation below, the calls may include special parameters such as :id or :note_id. You would replace this with the item ID or note ID.');
lines.push('');
lines.push('For example, for the endpoint `DELETE /tags/:id/notes/:note_id`, to remove the tag with ID "ABCD1234" from the note with ID "EFGH789", you would run for example:');
lines.push('');
lines.push('\tcurl -X DELETE http://localhost:41184/tags/ABCD1234/notes/EFGH789');
lines.push('');
lines.push('The four verbs supported by the API are the following ones:');
lines.push('');
lines.push('* **GET**: To retrieve items (notes, notebooks, etc.).');
lines.push('* **POST**: To create new items. In general most item properties are optional. If you omit any, a default value will be used.');
lines.push('* **PUT**: To update an item. Note in a REST API, traditionally PUT is used to completely replace an item, however in this API it will only replace the properties that are provided. For example if you PUT {"title": "my new title"}, only the "title" property will be changed. The other properties will be left untouched (they won\'t be cleared nor changed).');
lines.push('* **DELETE**: To delete items.');
lines.push('');
lines.push('# Filtering data');
lines.push('');
lines.push('You can change the fields that will be returned by the API using the `fields=` query parameter, which takes a list of comma separated fields. For example, to get the longitude and latitude of a note, use this:');
lines.push('');
lines.push('\tcurl http://localhost:41184/notes/ABCD123?fields=longitude,latitude');
lines.push('');
lines.push('To get the IDs only of all the tags:');
lines.push('');
lines.push('\tcurl http://localhost:41184/tags?fields=id');
lines.push('');
lines.push('# Error handling');
lines.push('');
lines.push('In case of an error, an HTTP status code >= 400 will be returned along with a JSON object that provides more info about the error. The JSON object is in the format `{ "error": "description of error" }`.');
lines.push('');
lines.push('# About the property types');
lines.push('');
lines.push('* Text is UTF-8.');
lines.push('* All date/time are Unix timestamps in milliseconds.');
lines.push('* Booleans are integer values 0 or 1.');
lines.push('');
lines.push('# Testing if the service is available');
lines.push('');
lines.push('Call **GET /ping** to check if the service is available. It should return "JoplinClipperServer" if it works.');
lines.push('');
lines.push('# Searching');
lines.push('');
lines.push('Call **GET /search?query=YOUR_QUERY** to search for notes. This end-point supports the `field` parameter which is recommended to use so that you only get the data that you need. The query syntax is as described in the main documentation: https://joplinapp.org/#searching');
lines.push('');
for (let i = 0; i < models.length; i++) {
const model = models[i];
const ModelClass = BaseItem.getClassByItemType(model.type);
const tableName = ModelClass.tableName();
let tableFields = reg.db().tableFields(tableName, { includeDescription: true });
const singular = tableName.substr(0, tableName.length - 1);
if (model.type === BaseModel.TYPE_NOTE) {
tableFields = tableFields.slice();
tableFields.push({
name: 'body_html',
type: Database.enumId('fieldType', 'text'),
description: 'Note body, in HTML format',
});
tableFields.push({
name: 'base_url',
type: Database.enumId('fieldType', 'text'),
description: 'If `body_html` is provided and contains relative URLs, provide the `base_url` parameter too so that all the URLs can be converted to absolute ones. The base URL is basically where the HTML was fetched from, minus the query (everything after the \'?\'). For example if the original page was `https://stackoverflow.com/search?q=%5Bjava%5D+test`, the base URL is `https://stackoverflow.com/search`.',
});
tableFields.push({
name: 'image_data_url',
type: Database.enumId('fieldType', 'text'),
description: 'An image to attach to the note, in [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.',
});
tableFields.push({
name: 'crop_rect',
type: Database.enumId('fieldType', 'text'),
description: 'If an image is provided, you can also specify an optional rectangle that will be used to crop the image. In format `{ x: x, y: y, width: width, height: height }`',
});
// tableFields.push({
// name: 'tags',
// type: Database.enumId('fieldType', 'text'),
// description: 'Comma-separated list of tags. eg. `tag1,tag2`.',
// });
}
lines.push('# ' + toTitleCase(tableName));
lines.push('');
if (model.type === BaseModel.TYPE_FOLDER) {
lines.push('This is actually a notebook. Internally notebooks are called "folders".');
lines.push('');
}
lines.push('## Properties');
lines.push('');
lines.push(this.createPropertiesTable(tableFields));
lines.push('');
lines.push('## GET /' + tableName);
lines.push('');
lines.push('Gets all ' + tableName);
lines.push('');
if (model.type === BaseModel.TYPE_FOLDER) {
lines.push('The folders are returned as a tree. The sub-notebooks of a notebook, if any, are under the `children` key.');
lines.push('');
}
lines.push('## GET /' + tableName + '/:id');
lines.push('');
lines.push('Gets ' + singular + ' with ID :id');
lines.push('');
if (model.type === BaseModel.TYPE_TAG) {
lines.push('## GET /tags/:id/notes');
lines.push('');
lines.push('Gets all the notes with this tag.');
lines.push('');
}
if (model.type === BaseModel.TYPE_NOTE) {
lines.push('## GET /notes/:id/tags');
lines.push('');
lines.push('Gets all the tags attached to this note.');
lines.push('');
}
if (model.type === BaseModel.TYPE_FOLDER) {
lines.push('## GET /folders/:id/notes');
lines.push('');
lines.push('Gets all the notes inside this folder.');
lines.push('');
}
if (model.type === BaseModel.TYPE_RESOURCE) {
lines.push('## GET /resources/:id/file');
lines.push('');
lines.push('Gets the actual file associated with this resource.');
lines.push('');
}
lines.push('## POST /' + tableName);
lines.push('');
lines.push('Creates a new ' + singular);
lines.push('');
if (model.type === BaseModel.TYPE_RESOURCE) {
lines.push('Creating a new resource is special because you also need to upload the file. Unlike other API calls, this one must have the "multipart/form-data" Content-Type. The file data must be passed to the "data" form field, and the other properties to the "props" form field. An example of a valid call with cURL would be:');
lines.push('');
lines.push('\tcurl -F \'data=@/path/to/file.jpg\' -F \'props={"title":"my resource title"}\' http://localhost:41184/resources');
lines.push('');
lines.push('The "data" field is required, while the "props" one is not. If not specified, default values will be used.');
lines.push('');
}
if (model.type === BaseModel.TYPE_TAG) {
lines.push('## POST /tags/:id/notes');
lines.push('');
lines.push('Post a note to this endpoint to add the tag to the note. The note data must at least contain an ID property (all other properties will be ignored).');
lines.push('');
}
if (model.type === BaseModel.TYPE_NOTE) {
lines.push('You can either specify the note body as Markdown by setting the `body` parameter, or in HTML by setting the `body_html`.');
lines.push('');
lines.push('Examples:');
lines.push('');
lines.push('* Create a note from some Markdown text');
lines.push('');
lines.push(' curl --data \'{ "title": "My note", "body": "Some note in **Markdown**"}\' http://127.0.0.1:41184/notes');
lines.push('');
lines.push('* Create a note from some HTML');
lines.push('');
lines.push(' curl --data \'{ "title": "My note", "body_html": "Some note in <b>HTML</b>"}\' http://127.0.0.1:41184/notes');
lines.push('');
lines.push('* Create a note and attach an image to it:');
lines.push('');
lines.push(' curl --data \'{ "title": "Image test", "body": "Here is Joplin icon:", "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="}\' http://127.0.0.1:41184/notes');
lines.push('');
lines.push('### Creating a note with a specific ID');
lines.push('');
lines.push('When a new note is created, it is automatically assigned a new unique ID so **normally you do not need to set the ID**. However, if for some reason you want to set it, you can supply it as the `id` property. It needs to be a 32 characters long hexadecimal string. **Make sure it is unique**, for example by generating it using whatever GUID function is available in your programming language.');
lines.push('');
lines.push(' curl --data \'{ "id": "00a87474082744c1a8515da6aa5792d2", "title": "My note with custom ID"}\' http://127.0.0.1:41184/notes');
lines.push('');
}
lines.push('## PUT /' + tableName + '/:id');
lines.push('');
lines.push('Sets the properties of the ' + singular + ' with ID :id');
lines.push('');
lines.push('## DELETE /' + tableName + '/:id');
lines.push('');
lines.push('Deletes the ' + singular + ' with ID :id');
lines.push('');
if (model.type === BaseModel.TYPE_TAG) {
lines.push('## DELETE /tags/:id/notes/:note_id');
lines.push('');
lines.push('Remove the tag from the note.');
lines.push('');
}
}
this.stdout(lines.join('\n'));
}
}
module.exports = Command;

View File

@@ -26,7 +26,7 @@ class Command extends BaseCommand {
const md = Setting.settingMetadata(name);
let value = Setting.value(name);
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
if (md.secure && value) value = '********';
if (md.secure) value = '********';
if (Setting.isEnum(name)) {
return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name));

View File

@@ -6,10 +6,6 @@ const DecryptionWorker = require('lib/services/DecryptionWorker');
const MasterKey = require('lib/models/MasterKey');
const BaseItem = require('lib/models/BaseItem');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim');
const pathUtils = require('lib/path-utils.js');
const imageType = require('image-type');
const readChunk = require('read-chunk');
class Command extends BaseCommand {
@@ -18,7 +14,7 @@ class Command extends BaseCommand {
}
description() {
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file` and `target-status`.');
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`.');
}
options() {
@@ -26,7 +22,6 @@ class Command extends BaseCommand {
// This is here mostly for testing - shouldn't be used
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
['-v, --verbose', 'More verbose output for the `target-status` command'],
['-o, --output <directory>', 'Output directory'],
];
}
@@ -35,18 +30,6 @@ class Command extends BaseCommand {
const options = args.options;
const askForMasterKey = async (error) => {
const masterKeyId = error.masterKeyId;
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
if (!password) {
this.stdout(_('Operation cancelled'));
return false;
}
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
await EncryptionService.instance().loadMasterKeysFromSettings();
return true;
}
if (args.command === 'enable') {
const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
if (!password) {
@@ -76,8 +59,14 @@ class Command extends BaseCommand {
break;
} catch (error) {
if (error.code === 'masterKeyNotLoaded') {
const ok = await askForMasterKey(error);
if (!ok) return;
const masterKeyId = error.masterKeyId;
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
if (!password) {
this.stdout(_('Operation cancelled'));
return;
}
Setting.setObjectKey('encryption.passwordCache', masterKeyId, password);
await EncryptionService.instance().loadMasterKeysFromSettings();
continue;
}
@@ -96,36 +85,6 @@ class Command extends BaseCommand {
return;
}
if (args.command === 'decrypt-file') {
while (true) {
try {
const outputDir = options.output ? options.output : require('os').tmpdir();
let outFile = outputDir + '/' + pathUtils.filename(args.path) + '.' + Date.now() + '.bin';
await EncryptionService.instance().decryptFile(args.path, outFile);
const buffer = await readChunk(outFile, 0, 64);
const detectedType = imageType(buffer);
if (detectedType) {
const newOutFile = outFile + '.' + detectedType.ext;
await shim.fsDriver().move(outFile, newOutFile);
outFile = newOutFile;
}
this.stdout(outFile);
break;
} catch (error) {
if (error.code === 'masterKeyNotLoaded') {
const ok = await askForMasterKey(error);
if (!ok) return;
continue;
}
throw error;
}
}
return;
}
if (args.command === 'target-status') {
const fs = require('fs-extra');
const pathUtils = require('lib/path-utils.js');

View File

@@ -37,7 +37,7 @@ class Command extends BaseCommand {
const stdoutWidth = app().commandStdoutMaxWidth();
if (args.command === 'shortcuts' || args.command === 'keymap') {
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplinapp.org/terminal/#shortcuts'));
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplin.cozic.net/terminal/#shortcuts'));
this.stdout('');
if (app().gui().isDummy()) {

View File

@@ -22,7 +22,7 @@ class Command extends BaseCommand {
enabled() {
return false;
}
options() {
return [
['-n, --limit <num>', _('Displays only the first top <num> notes.')],
@@ -93,7 +93,7 @@ class Command extends BaseCommand {
row.push(await Folder.noteCount(item.id));
}
row.push(time.formatMsToLocal(item.user_updated_time));
row.push(time.unixMsToLocalDateTime(item.user_updated_time));
}
let title = item.title;
@@ -123,4 +123,4 @@ class Command extends BaseCommand {
}
module.exports = Command;
module.exports = Command;

View File

@@ -49,6 +49,35 @@ class Command extends BaseCommand {
type: 'SEARCH_SELECT',
id: searchId,
});
// let fields = Note.previewFields();
// fields.push('body');
// const notes = await Note.previews(folder ? folder.id : null, {
// fields: fields,
// anywherePattern: '*' + pattern + '*',
// });
// const fragmentLength = 50;
// let parents = {};
// for (let i = 0; i < notes.length; i++) {
// const note = notes[i];
// const parent = parents[note.parent_id] ? parents[note.parent_id] : await Folder.load(note.parent_id);
// parents[note.parent_id] = parent;
// const idx = note.body.indexOf(pattern);
// let line = '';
// if (idx >= 0) {
// let fragment = note.body.substr(Math.max(0, idx - fragmentLength / 2), fragmentLength);
// fragment = fragment.replace(/\n/g, ' ');
// line = sprintf('%s: %s / %s: %s', BaseModel.shortId(note.id), parent.title, note.title, fragment);
// } else {
// line = sprintf('%s: %s / %s', BaseModel.shortId(note.id), parent.title, note.title);
// }
// this.stdout(line);
// }
}
}

View File

@@ -4,7 +4,6 @@ const { _ } = require('lib/locale.js');
const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js');
const Setting = require('lib/models/Setting.js');
const BaseItem = require('lib/models/BaseItem.js');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { Synchronizer } = require('lib/synchronizer.js');
const { reg } = require('lib/registry.js');
const { cliUtils } = require('./cli-utils.js');
@@ -117,6 +116,7 @@ class Command extends BaseCommand {
this.releaseLockFn_ = null;
// Lock is unique per profile/database
// TODO: use SQLite database to do lock?
const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41
if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock');
@@ -191,15 +191,6 @@ class Command extends BaseCommand {
}
}
// When using the tool in command line mode, the ResourceFetcher service is
// not going to be running in the background, so the resources need to be
// explicitely downloaded below.
if (!app().hasGui()) {
this.stdout(_('Downloading resources...'));
await ResourceFetcher.instance().fetchAll();
await ResourceFetcher.instance().waitForAllFinished();
}
await app().refreshCurrentFolder();
} catch (error) {
cleanUp();

View File

@@ -3,7 +3,6 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
@@ -12,19 +11,11 @@ class Command extends BaseCommand {
}
description() {
return _('<tag-command> can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags (use -l for long option).');
return _('<tag-command> can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags.');
}
options() {
return [
['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')],
];
}
async action(args) {
let tag = null;
let options = args.options;
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
let notes = [];
if (args.note) {
@@ -50,28 +41,7 @@ class Command extends BaseCommand {
} else if (command == 'list') {
if (tag) {
let notes = await Tag.notes(tag.id);
notes.map((note) => {
let line = '';
if (options.long) {
line += BaseModel.shortId(note.id);
line += ' ';
line += time.formatMsToLocal(note.user_updated_time);
line += ' ';
}
if (note.is_todo) {
line += '[';
if (note.todo_completed) {
line += 'X';
} else {
line += ' ';
}
line += '] ';
} else {
line += ' ';
}
line += note.title;
this.stdout(line);
});
notes.map((note) => { this.stdout(note.title); });
} else {
let tags = await Tag.all();
tags.map((tag) => { this.stdout(tag.title); });

View File

@@ -32,6 +32,8 @@ class FolderListWidget extends ListWidget {
output.push(_('Search:'));
output.push(item.title);
}
// if (item && item.id) output.push(item.id.substr(0, 5));
return output.join(' ');
};
@@ -83,6 +85,7 @@ class FolderListWidget extends ListWidget {
}
set notesParentType(v) {
//if (this.notesParentType_ === v) return;
this.notesParentType_ = v;
this.updateIndexFromSelectedItemId()
this.invalidate();
@@ -120,14 +123,6 @@ class FolderListWidget extends ListWidget {
this.updateIndexFromSelectedItemId()
this.invalidate();
}
folderHasChildren_(folders, folderId) {
for (let i = 0; i < folders.length; i++) {
let folder = folders[i];
if (folder.parent_id === folderId) return true;
}
return false;
}
render() {
if (this.updateItems_) {
@@ -135,19 +130,7 @@ class FolderListWidget extends ListWidget {
const wasSelectedItemId = this.selectedJoplinItemId;
const previousParentType = this.notesParentType;
let newItems = [];
const orderFolders = (parentId) => {
for (let i = 0; i < this.folders.length; i++) {
const f = this.folders[i];
const folderParentId = f.parent_id ? f.parent_id : '';
if (folderParentId === parentId) {
newItems.push(f);
if (this.folderHasChildren_(this.folders, f.id)) orderFolders(f.id);
}
}
}
orderFolders('');
let newItems = this.folders.slice();
if (this.tags.length) {
if (newItems.length) newItems.push('-');

View File

@@ -1,8 +1,5 @@
#!/usr/bin/env node
// Use njstrace to find out what Node.js might be spending time on
// var njstrace = require('njstrace').inject();
// Make it possible to require("/lib/...") without specifying full path
require('app-module-path').addPath(__dirname);
@@ -22,7 +19,6 @@ const Tag = require('lib/models/Tag.js');
const NoteTag = require('lib/models/NoteTag.js');
const MasterKey = require('lib/models/MasterKey');
const Setting = require('lib/models/Setting.js');
const Revision = require('lib/models/Revision.js');
const { Logger } = require('lib/logger.js');
const { FsDriverNode } = require('lib/fs-driver-node.js');
const { shimInit } = require('lib/shim-init-node.js');
@@ -44,7 +40,6 @@ BaseItem.loadClass('Resource', Resource);
BaseItem.loadClass('Tag', Tag);
BaseItem.loadClass('NoteTag', NoteTag);
BaseItem.loadClass('MasterKey', MasterKey);
BaseItem.loadClass('Revision', Revision);
Setting.setConstant('appId', 'net.cozic.joplin-cli');
Setting.setConstant('appType', 'cli');

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -119,7 +119,7 @@ msgstr ""
msgid ""
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
"`status`, `decrypt-file` and `target-status`."
"`status` and `target-status`."
msgstr ""
msgid "Enter master password:"
@@ -408,16 +408,13 @@ msgstr ""
msgid "Starting synchronisation..."
msgstr ""
msgid "Downloading resources..."
msgstr ""
msgid "Cancelling... Please wait."
msgstr ""
msgid ""
"<tag-command> can be \"add\", \"remove\" or \"list\" to assign or remove "
"[tag] from [note], or to list the notes associated with [tag]. The command "
"`tag list` can be used to list all the tags (use -l for long option)."
"`tag list` can be used to list all the tags."
msgstr ""
#, javascript-format
@@ -508,18 +505,6 @@ msgstr ""
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "Sidebar"
msgstr ""
msgid "Note list"
msgstr ""
msgid "Note title"
msgstr ""
msgid "Note body"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
@@ -527,7 +512,7 @@ msgstr ""
msgid "PDF File"
msgstr ""
msgid "Synchronisation status"
msgid "File"
msgstr ""
msgid "New note"
@@ -539,41 +524,13 @@ msgstr ""
msgid "New notebook"
msgstr ""
msgid "Print"
msgstr ""
msgid "General Options"
msgstr ""
msgid "Encryption options"
msgstr ""
msgid "Web clipper options"
msgstr ""
#, javascript-format
msgid "%s %s (%s, %s)"
msgstr ""
msgid "&File"
msgstr ""
msgid "About Joplin"
msgstr ""
msgid "Preferences..."
msgstr ""
msgid "Check for updates..."
msgstr ""
msgid "Import"
msgstr ""
msgid "Export"
msgstr ""
msgid "Synchronise"
msgid "Print"
msgstr ""
#, javascript-format
@@ -583,10 +540,7 @@ msgstr ""
msgid "Quit"
msgstr ""
msgid "Close Window"
msgstr ""
msgid "&Edit"
msgid "Edit"
msgstr ""
msgid "Copy"
@@ -598,37 +552,22 @@ msgstr ""
msgid "Paste"
msgstr ""
msgid "Select all"
msgstr ""
msgid "Bold"
msgstr ""
msgid "Italic"
msgstr ""
msgid "Link"
msgstr ""
msgid "Code"
msgstr ""
msgid "Insert Date Time"
msgstr ""
msgid "Edit in external editor"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Search in all the notes"
msgstr ""
msgid "Search in current note"
msgstr ""
msgid "&View"
msgid "View"
msgstr ""
msgid "Toggle sidebar"
@@ -637,13 +576,22 @@ msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Focus"
msgid "Tools"
msgstr ""
msgid "&Tools"
msgid "Synchronisation status"
msgstr ""
msgid "&Help"
msgid "Web clipper options"
msgstr ""
msgid "Encryption options"
msgstr ""
msgid "General Options"
msgstr ""
msgid "Help"
msgstr ""
msgid "Website and documentation"
@@ -652,7 +600,14 @@ msgstr ""
msgid "Make a donation"
msgstr ""
msgid "Toggle development tools"
msgid "Check for updates..."
msgstr ""
msgid "About Joplin"
msgstr ""
#, javascript-format
msgid "%s %s (%s, %s)"
msgstr ""
#, javascript-format
@@ -671,30 +626,15 @@ msgstr ""
msgid "Current version is up-to-date."
msgstr ""
#, javascript-format
msgid "%s (pre-release)"
msgstr ""
msgid "An update is available, do you want to download it now?"
msgstr ""
#, javascript-format
msgid "Your version: %s"
msgstr ""
#, javascript-format
msgid "New version: %s"
msgstr ""
msgid "Yes"
msgstr ""
msgid "No"
msgstr ""
msgid "Token has been copied to the clipboard!"
msgstr ""
msgid "The web clipper service is enabled and set to auto-start."
msgstr ""
@@ -738,30 +678,13 @@ msgstr ""
msgid "Download and install the relevant extension for your browser:"
msgstr ""
msgid "Advanced options"
msgstr ""
msgid "Authorisation token:"
msgstr ""
msgid "Copy token"
msgstr ""
msgid ""
"This authorisation token is only needed to allow third-party applications to "
"access Joplin."
msgid "Check synchronisation configuration"
msgstr ""
#, javascript-format
msgid "Notes and settings are stored in: %s"
msgstr ""
msgid "Check synchronisation configuration"
msgstr ""
msgid "Browse..."
msgstr ""
msgid "Apply"
msgstr ""
@@ -840,9 +763,6 @@ msgstr ""
msgid "Encryption is:"
msgstr ""
msgid "Usage"
msgstr ""
msgid "Back"
msgstr ""
@@ -863,6 +783,9 @@ msgstr ""
msgid "Add or remove tags:"
msgstr ""
msgid "Separate each tag by a comma."
msgstr ""
msgid "Rename notebook:"
msgstr ""
@@ -884,12 +807,34 @@ msgstr ""
msgid "View them now"
msgstr ""
msgid "One or more master keys need a password."
msgid "Some items cannot be decrypted."
msgstr ""
msgid "Set the password"
msgstr ""
msgid "Add or remove tags"
msgstr ""
msgid "Duplicate"
msgstr ""
#, javascript-format
msgid "%s - Copy"
msgstr ""
msgid "Switch between note and to-do type"
msgstr ""
msgid "Copy Markdown link"
msgstr ""
msgid "Delete"
msgstr ""
msgid "Delete notes?"
msgstr ""
msgid "No notes in here. Create one by clicking on \"New note\"."
msgstr ""
@@ -897,37 +842,6 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr ""
msgid "Location"
msgstr ""
msgid "URL"
msgstr ""
msgid "Note History"
msgstr ""
msgid "Previous versions of this note"
msgstr ""
msgid "Note properties"
msgstr ""
#, javascript-format
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
msgstr ""
msgid "This note has no history"
msgstr ""
msgid "Restore"
msgstr ""
#, javascript-format
msgid ""
"Click \"%s\" to restore the note. It will be copied in the notebook named "
"\"%s\". The current version of the note will not be replaced or modified."
msgstr ""
msgid "Open..."
msgstr ""
@@ -944,9 +858,6 @@ msgstr ""
msgid "Copy Link Address"
msgstr ""
msgid "This attachment is not downloaded or not decrypted yet."
msgstr ""
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr ""
@@ -957,9 +868,6 @@ msgid ""
"note."
msgstr ""
msgid "Only one note can be printed or exported to PDF at a time."
msgstr ""
msgid "strong text"
msgstr ""
@@ -975,6 +883,9 @@ msgstr ""
msgid "Attach file"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Set alarm"
msgstr ""
@@ -985,6 +896,9 @@ msgstr ""
msgid "Hyperlink"
msgstr ""
msgid "Code"
msgstr ""
msgid "Numbered List"
msgstr ""
@@ -1040,81 +954,24 @@ msgstr ""
msgid "Clipper Options"
msgstr ""
#, javascript-format
msgid ""
"Delete notebook \"%s\"?\n"
"\n"
"All notes and sub-notebooks within this notebook will also be deleted."
msgstr ""
#, javascript-format
msgid "Remove tag \"%s\" from all notes?"
msgid "Remove this tag from all the notes?"
msgstr ""
msgid "Remove this search from the sidebar?"
msgstr ""
msgid "Delete"
msgid "Rename"
msgstr ""
msgid "Rename"
msgid "Synchronise"
msgstr ""
msgid "Notebooks"
msgstr ""
#, javascript-format
msgid "Decrypting items: %d/%d"
msgstr ""
#, javascript-format
msgid "Fetching resources: %d/%d"
msgstr ""
msgid "Please select where the sync status should be exported to"
msgstr ""
msgid "Retry"
msgstr ""
msgid "Add or remove tags"
msgstr ""
msgid "Duplicate"
msgstr ""
#, javascript-format
msgid "%s - Copy"
msgstr ""
msgid "Switch between note and to-do type"
msgstr ""
msgid "Switch to note type"
msgstr ""
msgid "Switch to to-do type"
msgstr ""
msgid "Copy Markdown link"
msgstr ""
#, javascript-format
msgid "Delete note \"%s\"?"
msgstr ""
#, javascript-format
msgid "Delete these %d notes?"
msgstr ""
msgid ""
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
"followed by a notebook name."
msgstr ""
msgid "Goto Anything..."
msgstr ""
#, javascript-format
msgid "Usage: %s"
msgstr ""
@@ -1154,9 +1011,6 @@ msgid ""
"synchronisation again may fix the problem."
msgstr ""
msgid "Untitled"
msgstr ""
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1198,6 +1052,10 @@ msgstr ""
msgid "Fetched items: %d/%d."
msgstr ""
#, javascript-format
msgid "State: %s."
msgstr ""
msgid "Cancelling..."
msgstr ""
@@ -1219,35 +1077,38 @@ msgstr ""
msgid "Synchronisation is already in progress. State: %s"
msgstr ""
msgid ""
"Unknown item type downloaded - please upgrade Joplin to the latest version"
msgstr ""
msgid "Encrypted"
msgstr ""
msgid "Encrypted items cannot be modified"
msgstr ""
msgid "title"
msgstr ""
msgid "updated date"
msgstr ""
msgid "Conflicts"
msgstr ""
msgid "Cannot move notebook to this location"
msgstr ""
#, javascript-format
msgid "A notebook with this title already exists: \"%s\""
msgstr ""
#, javascript-format
msgid "Notebooks cannot be named \"%s\", which is a reserved title."
msgstr ""
msgid "title"
msgstr ""
msgid "updated date"
msgstr ""
msgid "created date"
msgstr ""
msgid "Untitled"
msgstr ""
msgid "This note does not have geolocation information."
msgstr ""
@@ -1259,63 +1120,6 @@ msgstr ""
msgid "Cannot move note to \"%s\" notebook"
msgstr ""
#, javascript-format
msgid ""
"Attention: If you change this location, make sure you copy all your content "
"to it before syncing, otherwise all files will be removed! See the FAQ for "
"more details: %s"
msgstr ""
msgid "Synchronisation target"
msgstr ""
msgid ""
"The target to synchonise to. Each sync target may have additional parameters "
"which are named as `sync.NUM.NAME` (all documented below)."
msgstr ""
msgid "Directory to synchronise with (absolute path)"
msgstr ""
msgid "Nextcloud WebDAV URL"
msgstr ""
msgid "Nextcloud username"
msgstr ""
msgid "Nextcloud password"
msgstr ""
msgid "WebDAV URL"
msgstr ""
msgid "WebDAV username"
msgstr ""
msgid "WebDAV password"
msgstr ""
msgid "Attachment download behaviour"
msgstr ""
msgid ""
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
"the attachments are downloaded whether you open the note or not."
msgstr ""
msgid "Always"
msgstr ""
msgid "Manual"
msgstr ""
msgid "Auto"
msgstr ""
msgid "Max concurrent connections"
msgstr ""
msgid "Language"
msgstr ""
@@ -1346,9 +1150,6 @@ msgstr ""
msgid "Reverse sort order"
msgstr ""
msgid "Sort notebooks by"
msgstr ""
msgid "Save geo-location with notes"
msgstr ""
@@ -1364,63 +1165,15 @@ msgstr ""
msgid "When creating a new note:"
msgstr ""
msgid "Enable soft breaks"
msgstr ""
msgid "Enable math expressions"
msgstr ""
msgid "Enable ==mark== syntax"
msgstr ""
msgid "Enable footnotes"
msgstr ""
msgid "Enable table of contents extension"
msgstr ""
msgid "Enable ~sub~ syntax"
msgstr ""
msgid "Enable ^sup^ syntax"
msgstr ""
msgid "Enable deflist syntax"
msgstr ""
msgid "Enable abbreviation syntax"
msgstr ""
msgid "Enable markdown emoji"
msgstr ""
msgid "Enable ++insert++ syntax"
msgstr ""
msgid "Enable multimarkdown table extension"
msgstr ""
msgid "Show tray icon"
msgstr ""
msgid "Note: Does not work in all desktop environments."
msgstr ""
msgid ""
"This will allow Joplin to run in the background. It is recommended to enable "
"this setting so that your notes are constantly being synchronised, thus "
"reducing the number of conflicts."
msgstr ""
msgid "Start application minimised in the tray icon"
msgstr ""
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font size"
msgstr ""
msgid "Editor font family"
msgstr ""
@@ -1432,13 +1185,6 @@ msgstr ""
msgid "Automatically update the application"
msgstr ""
msgid "Get pre-releases when checking for updates"
msgstr ""
#, javascript-format
msgid "See the pre-release page for more details: %s"
msgstr ""
msgid "Synchronisation interval"
msgstr ""
@@ -1465,6 +1211,40 @@ msgstr ""
msgid "Show advanced options"
msgstr ""
msgid "Synchronisation target"
msgstr ""
msgid ""
"The target to synchonise to. Each sync target may have additional parameters "
"which are named as `sync.NUM.NAME` (all documented below)."
msgstr ""
msgid "Directory to synchronise with (absolute path)"
msgstr ""
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
msgid "Nextcloud WebDAV URL"
msgstr ""
msgid "Nextcloud username"
msgstr ""
msgid "Nextcloud password"
msgstr ""
msgid "WebDAV URL"
msgstr ""
msgid "WebDAV username"
msgstr ""
msgid "WebDAV password"
msgstr ""
msgid "Custom TLS certificates"
msgstr ""
@@ -1478,45 +1258,10 @@ msgstr ""
msgid "Ignore TLS certificate errors"
msgstr ""
msgid "Enable note history"
msgstr ""
msgid "days"
msgstr ""
#, javascript-format
msgid "%d days"
msgstr ""
msgid "Keep note history for"
msgstr ""
#, javascript-format
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr ""
msgid "General"
msgstr ""
msgid "Synchronisation"
msgstr ""
msgid "Appearance"
msgstr ""
msgid "Note"
msgstr ""
msgid "Plugins"
msgstr ""
msgid "Application"
msgstr ""
#, javascript-format
msgid "The tag \"%s\" already exists. Please choose a different name."
msgstr ""
msgid "Joplin Export File"
msgstr ""
@@ -1529,12 +1274,6 @@ msgstr ""
msgid "Evernote Export File"
msgstr ""
msgid "Json Export Directory"
msgstr ""
msgid "File"
msgstr ""
msgid "Directory"
msgstr ""
@@ -1558,10 +1297,11 @@ msgstr ""
msgid "Please specify the notebook where the notes should be imported to."
msgstr ""
msgid "Restored Notes"
msgid "Items that cannot be synchronised"
msgstr ""
msgid "Items that cannot be synchronised"
#, javascript-format
msgid "%s (%s): %s"
msgstr ""
msgid ""
@@ -1570,23 +1310,6 @@ msgid ""
"(which is displayed in brackets above)."
msgstr ""
#, javascript-format
msgid "%s (%s) could not be uploaded: %s"
msgstr ""
#, javascript-format
msgid "Item \"%s\" could not be downloaded: %s"
msgstr ""
msgid "Items that cannot be decrypted"
msgstr ""
msgid ""
"Joplin failed to decrypt these items multiple times, possibly because they "
"are corrupted or too large. These items will remain on the device but Joplin "
"will no longer attempt to decrypt them."
msgstr ""
msgid "Sync status (synced items / total items)"
msgstr ""
@@ -1620,12 +1343,6 @@ msgstr ""
msgid "On %s: %s"
msgstr ""
msgid "Permission to use camera"
msgstr ""
msgid "Your permission to use your camera is required."
msgstr ""
msgid "There are currently no notes. Create one by clicking on the (+) button."
msgstr ""
@@ -1654,9 +1371,6 @@ msgstr ""
msgid "Press to set the decryption password."
msgstr ""
msgid "Clear alarm"
msgstr ""
msgid "Save alarm"
msgstr ""
@@ -1669,31 +1383,8 @@ msgstr ""
msgid "Cancel synchronisation"
msgstr ""
msgid "Checking... Please wait."
msgstr ""
msgid "Success! Synchronisation configuration appears to be correct."
msgstr ""
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
msgid "The application has been authorised!"
msgstr ""
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, javascript-format
msgid "Decrypted items: %s / %s"
msgid "Decrypting items: %d/%d"
msgstr ""
msgid "New tags:"
@@ -1702,9 +1393,6 @@ msgstr ""
msgid "Type new tags or select from list"
msgstr ""
msgid "More information"
msgstr ""
msgid ""
"To work correctly, the app needs the following permissions. Please enable "
"them in your phone settings, in Apps > Joplin > Permissions"
@@ -1724,20 +1412,9 @@ msgstr ""
msgid "Joplin website"
msgstr ""
#, javascript-format
msgid "Database v%s"
msgstr ""
#, javascript-format
msgid "FTS enabled: %d"
msgstr ""
msgid "Login with Dropbox"
msgstr ""
msgid "Enter code here"
msgstr ""
#, javascript-format
msgid "Master Key %s"
msgstr ""
@@ -1785,17 +1462,10 @@ msgstr ""
msgid "The Joplin mobile app does not currently support this type of link: %s"
msgstr ""
#, javascript-format
msgid "Links with protocol \"%s\" are not supported"
msgstr ""
#, javascript-format
msgid "Unsupported image type: %s"
msgstr ""
msgid "Take photo"
msgstr ""
msgid "Attach photo"
msgstr ""
@@ -1820,12 +1490,6 @@ msgstr ""
msgid "View on map"
msgstr ""
msgid "Go to source URL"
msgstr ""
msgid "Edit"
msgstr ""
msgid "Delete notebook"
msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -119,7 +119,7 @@ msgstr ""
msgid ""
"Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, "
"`status`, `decrypt-file` and `target-status`."
"`status` and `target-status`."
msgstr ""
msgid "Enter master password:"
@@ -408,16 +408,13 @@ msgstr ""
msgid "Starting synchronisation..."
msgstr ""
msgid "Downloading resources..."
msgstr ""
msgid "Cancelling... Please wait."
msgstr ""
msgid ""
"<tag-command> can be \"add\", \"remove\" or \"list\" to assign or remove "
"[tag] from [note], or to list the notes associated with [tag]. The command "
"`tag list` can be used to list all the tags (use -l for long option)."
"`tag list` can be used to list all the tags."
msgstr ""
#, javascript-format
@@ -508,18 +505,6 @@ msgstr ""
msgid "Exporting to \"%s\" as \"%s\" format. Please wait..."
msgstr ""
msgid "Sidebar"
msgstr ""
msgid "Note list"
msgstr ""
msgid "Note title"
msgstr ""
msgid "Note body"
msgstr ""
#, javascript-format
msgid "Importing from \"%s\" as \"%s\" format. Please wait..."
msgstr ""
@@ -527,7 +512,7 @@ msgstr ""
msgid "PDF File"
msgstr ""
msgid "Synchronisation status"
msgid "File"
msgstr ""
msgid "New note"
@@ -539,41 +524,13 @@ msgstr ""
msgid "New notebook"
msgstr ""
msgid "Print"
msgstr ""
msgid "General Options"
msgstr ""
msgid "Encryption options"
msgstr ""
msgid "Web clipper options"
msgstr ""
#, javascript-format
msgid "%s %s (%s, %s)"
msgstr ""
msgid "&File"
msgstr ""
msgid "About Joplin"
msgstr ""
msgid "Preferences..."
msgstr ""
msgid "Check for updates..."
msgstr ""
msgid "Import"
msgstr ""
msgid "Export"
msgstr ""
msgid "Synchronise"
msgid "Print"
msgstr ""
#, javascript-format
@@ -583,10 +540,7 @@ msgstr ""
msgid "Quit"
msgstr ""
msgid "Close Window"
msgstr ""
msgid "&Edit"
msgid "Edit"
msgstr ""
msgid "Copy"
@@ -598,37 +552,22 @@ msgstr ""
msgid "Paste"
msgstr ""
msgid "Select all"
msgstr ""
msgid "Bold"
msgstr ""
msgid "Italic"
msgstr ""
msgid "Link"
msgstr ""
msgid "Code"
msgstr ""
msgid "Insert Date Time"
msgstr ""
msgid "Edit in external editor"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Search in all the notes"
msgstr ""
msgid "Search in current note"
msgstr ""
msgid "&View"
msgid "View"
msgstr ""
msgid "Toggle sidebar"
@@ -637,13 +576,22 @@ msgstr ""
msgid "Toggle editor layout"
msgstr ""
msgid "Focus"
msgid "Tools"
msgstr ""
msgid "&Tools"
msgid "Synchronisation status"
msgstr ""
msgid "&Help"
msgid "Web clipper options"
msgstr ""
msgid "Encryption options"
msgstr ""
msgid "General Options"
msgstr ""
msgid "Help"
msgstr ""
msgid "Website and documentation"
@@ -652,7 +600,14 @@ msgstr ""
msgid "Make a donation"
msgstr ""
msgid "Toggle development tools"
msgid "Check for updates..."
msgstr ""
msgid "About Joplin"
msgstr ""
#, javascript-format
msgid "%s %s (%s, %s)"
msgstr ""
#, javascript-format
@@ -671,30 +626,15 @@ msgstr ""
msgid "Current version is up-to-date."
msgstr ""
#, javascript-format
msgid "%s (pre-release)"
msgstr ""
msgid "An update is available, do you want to download it now?"
msgstr ""
#, javascript-format
msgid "Your version: %s"
msgstr ""
#, javascript-format
msgid "New version: %s"
msgstr ""
msgid "Yes"
msgstr ""
msgid "No"
msgstr ""
msgid "Token has been copied to the clipboard!"
msgstr ""
msgid "The web clipper service is enabled and set to auto-start."
msgstr ""
@@ -738,30 +678,13 @@ msgstr ""
msgid "Download and install the relevant extension for your browser:"
msgstr ""
msgid "Advanced options"
msgstr ""
msgid "Authorisation token:"
msgstr ""
msgid "Copy token"
msgstr ""
msgid ""
"This authorisation token is only needed to allow third-party applications to "
"access Joplin."
msgid "Check synchronisation configuration"
msgstr ""
#, javascript-format
msgid "Notes and settings are stored in: %s"
msgstr ""
msgid "Check synchronisation configuration"
msgstr ""
msgid "Browse..."
msgstr ""
msgid "Apply"
msgstr ""
@@ -840,9 +763,6 @@ msgstr ""
msgid "Encryption is:"
msgstr ""
msgid "Usage"
msgstr ""
msgid "Back"
msgstr ""
@@ -863,6 +783,9 @@ msgstr ""
msgid "Add or remove tags:"
msgstr ""
msgid "Separate each tag by a comma."
msgstr ""
msgid "Rename notebook:"
msgstr ""
@@ -884,12 +807,34 @@ msgstr ""
msgid "View them now"
msgstr ""
msgid "One or more master keys need a password."
msgid "Some items cannot be decrypted."
msgstr ""
msgid "Set the password"
msgstr ""
msgid "Add or remove tags"
msgstr ""
msgid "Duplicate"
msgstr ""
#, javascript-format
msgid "%s - Copy"
msgstr ""
msgid "Switch between note and to-do type"
msgstr ""
msgid "Copy Markdown link"
msgstr ""
msgid "Delete"
msgstr ""
msgid "Delete notes?"
msgstr ""
msgid "No notes in here. Create one by clicking on \"New note\"."
msgstr ""
@@ -897,37 +842,6 @@ msgid ""
"There is currently no notebook. Create one by clicking on \"New notebook\"."
msgstr ""
msgid "Location"
msgstr ""
msgid "URL"
msgstr ""
msgid "Note History"
msgstr ""
msgid "Previous versions of this note"
msgstr ""
msgid "Note properties"
msgstr ""
#, javascript-format
msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"."
msgstr ""
msgid "This note has no history"
msgstr ""
msgid "Restore"
msgstr ""
#, javascript-format
msgid ""
"Click \"%s\" to restore the note. It will be copied in the notebook named "
"\"%s\". The current version of the note will not be replaced or modified."
msgstr ""
msgid "Open..."
msgstr ""
@@ -944,9 +858,6 @@ msgstr ""
msgid "Copy Link Address"
msgstr ""
msgid "This attachment is not downloaded or not decrypted yet."
msgstr ""
#, javascript-format
msgid "Unsupported link or message: %s"
msgstr ""
@@ -957,9 +868,6 @@ msgid ""
"note."
msgstr ""
msgid "Only one note can be printed or exported to PDF at a time."
msgstr ""
msgid "strong text"
msgstr ""
@@ -975,6 +883,9 @@ msgstr ""
msgid "Attach file"
msgstr ""
msgid "Tags"
msgstr ""
msgid "Set alarm"
msgstr ""
@@ -985,6 +896,9 @@ msgstr ""
msgid "Hyperlink"
msgstr ""
msgid "Code"
msgstr ""
msgid "Numbered List"
msgstr ""
@@ -1040,81 +954,24 @@ msgstr ""
msgid "Clipper Options"
msgstr ""
#, javascript-format
msgid ""
"Delete notebook \"%s\"?\n"
"\n"
"All notes and sub-notebooks within this notebook will also be deleted."
msgstr ""
#, javascript-format
msgid "Remove tag \"%s\" from all notes?"
msgid "Remove this tag from all the notes?"
msgstr ""
msgid "Remove this search from the sidebar?"
msgstr ""
msgid "Delete"
msgid "Rename"
msgstr ""
msgid "Rename"
msgid "Synchronise"
msgstr ""
msgid "Notebooks"
msgstr ""
#, javascript-format
msgid "Decrypting items: %d/%d"
msgstr ""
#, javascript-format
msgid "Fetching resources: %d/%d"
msgstr ""
msgid "Please select where the sync status should be exported to"
msgstr ""
msgid "Retry"
msgstr ""
msgid "Add or remove tags"
msgstr ""
msgid "Duplicate"
msgstr ""
#, javascript-format
msgid "%s - Copy"
msgstr ""
msgid "Switch between note and to-do type"
msgstr ""
msgid "Switch to note type"
msgstr ""
msgid "Switch to to-do type"
msgstr ""
msgid "Copy Markdown link"
msgstr ""
#, javascript-format
msgid "Delete note \"%s\"?"
msgstr ""
#, javascript-format
msgid "Delete these %d notes?"
msgstr ""
msgid ""
"Type a note title to jump to it. Or type # followed by a tag name, or @ "
"followed by a notebook name."
msgstr ""
msgid "Goto Anything..."
msgstr ""
#, javascript-format
msgid "Usage: %s"
msgstr ""
@@ -1154,9 +1011,6 @@ msgid ""
"synchronisation again may fix the problem."
msgstr ""
msgid "Untitled"
msgstr ""
msgid ""
"Could not synchronize with OneDrive.\n"
"\n"
@@ -1198,6 +1052,10 @@ msgstr ""
msgid "Fetched items: %d/%d."
msgstr ""
#, javascript-format
msgid "State: %s."
msgstr ""
msgid "Cancelling..."
msgstr ""
@@ -1219,35 +1077,38 @@ msgstr ""
msgid "Synchronisation is already in progress. State: %s"
msgstr ""
msgid ""
"Unknown item type downloaded - please upgrade Joplin to the latest version"
msgstr ""
msgid "Encrypted"
msgstr ""
msgid "Encrypted items cannot be modified"
msgstr ""
msgid "title"
msgstr ""
msgid "updated date"
msgstr ""
msgid "Conflicts"
msgstr ""
msgid "Cannot move notebook to this location"
msgstr ""
#, javascript-format
msgid "A notebook with this title already exists: \"%s\""
msgstr ""
#, javascript-format
msgid "Notebooks cannot be named \"%s\", which is a reserved title."
msgstr ""
msgid "title"
msgstr ""
msgid "updated date"
msgstr ""
msgid "created date"
msgstr ""
msgid "Untitled"
msgstr ""
msgid "This note does not have geolocation information."
msgstr ""
@@ -1259,63 +1120,6 @@ msgstr ""
msgid "Cannot move note to \"%s\" notebook"
msgstr ""
#, javascript-format
msgid ""
"Attention: If you change this location, make sure you copy all your content "
"to it before syncing, otherwise all files will be removed! See the FAQ for "
"more details: %s"
msgstr ""
msgid "Synchronisation target"
msgstr ""
msgid ""
"The target to synchonise to. Each sync target may have additional parameters "
"which are named as `sync.NUM.NAME` (all documented below)."
msgstr ""
msgid "Directory to synchronise with (absolute path)"
msgstr ""
msgid "Nextcloud WebDAV URL"
msgstr ""
msgid "Nextcloud username"
msgstr ""
msgid "Nextcloud password"
msgstr ""
msgid "WebDAV URL"
msgstr ""
msgid "WebDAV username"
msgstr ""
msgid "WebDAV password"
msgstr ""
msgid "Attachment download behaviour"
msgstr ""
msgid ""
"In \"Manual\" mode, attachments are downloaded only when you click on them. "
"In \"Auto\", they are downloaded when you open the note. In \"Always\", all "
"the attachments are downloaded whether you open the note or not."
msgstr ""
msgid "Always"
msgstr ""
msgid "Manual"
msgstr ""
msgid "Auto"
msgstr ""
msgid "Max concurrent connections"
msgstr ""
msgid "Language"
msgstr ""
@@ -1346,9 +1150,6 @@ msgstr ""
msgid "Reverse sort order"
msgstr ""
msgid "Sort notebooks by"
msgstr ""
msgid "Save geo-location with notes"
msgstr ""
@@ -1364,63 +1165,15 @@ msgstr ""
msgid "When creating a new note:"
msgstr ""
msgid "Enable soft breaks"
msgstr ""
msgid "Enable math expressions"
msgstr ""
msgid "Enable ==mark== syntax"
msgstr ""
msgid "Enable footnotes"
msgstr ""
msgid "Enable table of contents extension"
msgstr ""
msgid "Enable ~sub~ syntax"
msgstr ""
msgid "Enable ^sup^ syntax"
msgstr ""
msgid "Enable deflist syntax"
msgstr ""
msgid "Enable abbreviation syntax"
msgstr ""
msgid "Enable markdown emoji"
msgstr ""
msgid "Enable ++insert++ syntax"
msgstr ""
msgid "Enable multimarkdown table extension"
msgstr ""
msgid "Show tray icon"
msgstr ""
msgid "Note: Does not work in all desktop environments."
msgstr ""
msgid ""
"This will allow Joplin to run in the background. It is recommended to enable "
"this setting so that your notes are constantly being synchronised, thus "
"reducing the number of conflicts."
msgstr ""
msgid "Start application minimised in the tray icon"
msgstr ""
msgid "Global zoom percentage"
msgstr ""
msgid "Editor font size"
msgstr ""
msgid "Editor font family"
msgstr ""
@@ -1432,13 +1185,6 @@ msgstr ""
msgid "Automatically update the application"
msgstr ""
msgid "Get pre-releases when checking for updates"
msgstr ""
#, javascript-format
msgid "See the pre-release page for more details: %s"
msgstr ""
msgid "Synchronisation interval"
msgstr ""
@@ -1465,6 +1211,40 @@ msgstr ""
msgid "Show advanced options"
msgstr ""
msgid "Synchronisation target"
msgstr ""
msgid ""
"The target to synchonise to. Each sync target may have additional parameters "
"which are named as `sync.NUM.NAME` (all documented below)."
msgstr ""
msgid "Directory to synchronise with (absolute path)"
msgstr ""
msgid ""
"The path to synchronise with when file system synchronisation is enabled. "
"See `sync.target`."
msgstr ""
msgid "Nextcloud WebDAV URL"
msgstr ""
msgid "Nextcloud username"
msgstr ""
msgid "Nextcloud password"
msgstr ""
msgid "WebDAV URL"
msgstr ""
msgid "WebDAV username"
msgstr ""
msgid "WebDAV password"
msgstr ""
msgid "Custom TLS certificates"
msgstr ""
@@ -1478,45 +1258,10 @@ msgstr ""
msgid "Ignore TLS certificate errors"
msgstr ""
msgid "Enable note history"
msgstr ""
msgid "days"
msgstr ""
#, javascript-format
msgid "%d days"
msgstr ""
msgid "Keep note history for"
msgstr ""
#, javascript-format
msgid "Invalid option value: \"%s\". Possible values are: %s."
msgstr ""
msgid "General"
msgstr ""
msgid "Synchronisation"
msgstr ""
msgid "Appearance"
msgstr ""
msgid "Note"
msgstr ""
msgid "Plugins"
msgstr ""
msgid "Application"
msgstr ""
#, javascript-format
msgid "The tag \"%s\" already exists. Please choose a different name."
msgstr ""
msgid "Joplin Export File"
msgstr ""
@@ -1529,12 +1274,6 @@ msgstr ""
msgid "Evernote Export File"
msgstr ""
msgid "Json Export Directory"
msgstr ""
msgid "File"
msgstr ""
msgid "Directory"
msgstr ""
@@ -1558,10 +1297,11 @@ msgstr ""
msgid "Please specify the notebook where the notes should be imported to."
msgstr ""
msgid "Restored Notes"
msgid "Items that cannot be synchronised"
msgstr ""
msgid "Items that cannot be synchronised"
#, javascript-format
msgid "%s (%s): %s"
msgstr ""
msgid ""
@@ -1570,23 +1310,6 @@ msgid ""
"(which is displayed in brackets above)."
msgstr ""
#, javascript-format
msgid "%s (%s) could not be uploaded: %s"
msgstr ""
#, javascript-format
msgid "Item \"%s\" could not be downloaded: %s"
msgstr ""
msgid "Items that cannot be decrypted"
msgstr ""
msgid ""
"Joplin failed to decrypt these items multiple times, possibly because they "
"are corrupted or too large. These items will remain on the device but Joplin "
"will no longer attempt to decrypt them."
msgstr ""
msgid "Sync status (synced items / total items)"
msgstr ""
@@ -1620,12 +1343,6 @@ msgstr ""
msgid "On %s: %s"
msgstr ""
msgid "Permission to use camera"
msgstr ""
msgid "Your permission to use your camera is required."
msgstr ""
msgid "There are currently no notes. Create one by clicking on the (+) button."
msgstr ""
@@ -1654,9 +1371,6 @@ msgstr ""
msgid "Press to set the decryption password."
msgstr ""
msgid "Clear alarm"
msgstr ""
msgid "Save alarm"
msgstr ""
@@ -1669,31 +1383,8 @@ msgstr ""
msgid "Cancel synchronisation"
msgstr ""
msgid "Checking... Please wait."
msgstr ""
msgid "Success! Synchronisation configuration appears to be correct."
msgstr ""
msgid ""
"Error. Please check that URL, username, password, etc. are correct and that "
"the sync target is accessible. The reported error was:"
msgstr ""
msgid "The application has been authorised!"
msgstr ""
#, javascript-format
msgid ""
"Could not authorise application:\n"
"\n"
"%s\n"
"\n"
"Please try again."
msgstr ""
#, javascript-format
msgid "Decrypted items: %s / %s"
msgid "Decrypting items: %d/%d"
msgstr ""
msgid "New tags:"
@@ -1702,9 +1393,6 @@ msgstr ""
msgid "Type new tags or select from list"
msgstr ""
msgid "More information"
msgstr ""
msgid ""
"To work correctly, the app needs the following permissions. Please enable "
"them in your phone settings, in Apps > Joplin > Permissions"
@@ -1724,20 +1412,9 @@ msgstr ""
msgid "Joplin website"
msgstr ""
#, javascript-format
msgid "Database v%s"
msgstr ""
#, javascript-format
msgid "FTS enabled: %d"
msgstr ""
msgid "Login with Dropbox"
msgstr ""
msgid "Enter code here"
msgstr ""
#, javascript-format
msgid "Master Key %s"
msgstr ""
@@ -1785,17 +1462,10 @@ msgstr ""
msgid "The Joplin mobile app does not currently support this type of link: %s"
msgstr ""
#, javascript-format
msgid "Links with protocol \"%s\" are not supported"
msgstr ""
#, javascript-format
msgid "Unsupported image type: %s"
msgstr ""
msgid "Take photo"
msgstr ""
msgid "Attach photo"
msgstr ""
@@ -1820,12 +1490,6 @@ msgstr ""
msgid "View on map"
msgstr ""
msgid "Go to source URL"
msgstr ""
msgid "Edit"
msgstr ""
msgid "Delete notebook"
msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,12 +15,11 @@
"years": [
2016,
2017,
2018,
2019
2018
],
"owner": "Laurent Cozic"
},
"version": "1.0.140",
"version": "1.0.112",
"bin": {
"joplin": "./main.js"
},
@@ -32,23 +31,18 @@
"async-mutex": "^0.1.3",
"base-64": "^0.1.0",
"compare-version": "^0.1.2",
"diacritics": "^1.3.0",
"diff-match-patch": "^1.0.4",
"es6-promise-pool": "^2.5.0",
"file-uri-to-path": "^1.0.0",
"follow-redirects": "^1.2.4",
"form-data": "^2.1.4",
"fs-extra": "^5.0.0",
"html-entities": "^1.2.1",
"html-minifier": "^3.5.15",
"image-data-uri": "^2.0.0",
"image-type": "^3.0.0",
"joplin-turndown": "^4.0.15",
"joplin-turndown-plugin-gfm": "^1.0.8",
"joplin-turndown": "^4.0.8",
"joplin-turndown-plugin-gfm": "^1.0.7",
"jssha": "^2.3.0",
"levenshtein": "^1.0.5",
"lodash": "^4.17.4",
"markdown-it": "^8.4.2",
"md5": "^2.2.1",
"mime": "^2.0.3",
"moment": "^2.18.1",
@@ -62,9 +56,9 @@
"redux": "^3.7.2",
"sax": "^1.2.2",
"server-destroy": "^1.0.1",
"sharp": "^0.22.1",
"sharp": "^0.18.4",
"sprintf-js": "^1.1.1",
"sqlite3": "^4.0.7",
"sqlite3": "^4.0.1",
"string-padding": "^1.0.2",
"string-to-stream": "^1.1.0",
"strip-ansi": "^4.0.0",

View File

@@ -3,4 +3,5 @@ set -e
CLIENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
bash "$CLIENT_DIR/build.sh" && node "$CLIENT_DIR/build/main.js" --profile ~/Temp/TestNotes2 --stack-trace-enabled --log-level debug --env dev "$@"
# bash "$CLIENT_DIR/build.sh" && node "$CLIENT_DIR/build/main.js" --profile ~/.config/joplin --stack-trace-enabled --log-level debug --env dev "$@"
# bash $CLIENT_DIR/build.sh && NODE_PATH="$CLIENT_DIR/build/" node build/main.js --profile ~/.config/joplin --stack-trace-enabled --log-level debug "$@"

View File

@@ -26,22 +26,10 @@ npm test tests-build/encryption.js
npm test tests-build/EnexToMd.js
npm test tests-build/HtmlToMd.js
npm test tests-build/markdownUtils.js
npm test tests-build/models_BaseItem.js
npm test tests-build/models_Folder.js
npm test tests-build/models_ItemChange.js
npm test tests-build/models_Note.js
npm test tests-build/models_Resource.js
npm test tests-build/models_Revision.js
npm test tests-build/models_Setting.js
npm test tests-build/models_Tag.js
npm test tests-build/pathUtils.js
npm test tests-build/services_InteropService.js
npm test tests-build/services_KvStore.js
npm test tests-build/services_ResourceService.js
npm test tests-build/services_rest_Api.js
npm test tests-build/services_SearchEngine.js
npm test tests-build/services_Revision.js
npm test tests-build/StringUtils.js
npm test tests-build/TaskQueue.js
npm test tests-build/synchronizer.js
npm test tests-build/urlUtils.js

View File

@@ -1,6 +1,5 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const { time } = require('lib/time-utils.js');
const { filename } = require('lib/path-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
@@ -35,17 +34,12 @@ describe('EnexToMd', function() {
const htmlPath = basePath + '/' + htmlFilename;
const mdPath = basePath + '/' + filename(htmlFilename) + '.md';
// if (htmlFilename !== 'multiline_inner_text.html') continue;
// if (htmlFilename !== 'text2.html') continue;
const html = await shim.fsDriver().readFile(htmlPath);
let expectedMd = await shim.fsDriver().readFile(mdPath);
const expectedMd = await shim.fsDriver().readFile(mdPath);
let actualMd = await enexXmlToMd('<div>' + html + '</div>', []);
if (os.EOL === '\r\n') {
expectedMd = expectedMd.replace(/\r\n/g, '\n')
actualMd = actualMd.replace(/\r\n/g, '\n')
}
const actualMd = await enexXmlToMd('<div>' + html + '</div>', []);
if (actualMd !== expectedMd) {
console.info('');

View File

@@ -1,6 +1,5 @@
require('app-module-path').addPath(__dirname);
const os = require('os');
const { time } = require('lib/time-utils.js');
const { filename } = require('lib/path-utils.js');
const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
@@ -37,27 +36,12 @@ describe('HtmlToMd', function() {
const htmlPath = basePath + '/' + htmlFilename;
const mdPath = basePath + '/' + filename(htmlFilename) + '.md';
// if (htmlFilename !== 'anchor_local.html') continue;
const htmlToMdOptions = {}
if (htmlFilename === 'anchor_local.html') {
// Normally the list of anchor names in the document are retrieved from the HTML code
// This is straightforward when the document is still in DOM format, as with the clipper,
// but otherwise it would need to be somehow parsed out from the HTML. Here we just
// hard code the anchors that we know are in the file.
htmlToMdOptions.anchorNames = ['first', 'second']
}
// if (htmlFilename !== 'code_1.html') continue;
const html = await shim.fsDriver().readFile(htmlPath);
let expectedMd = await shim.fsDriver().readFile(mdPath);
const expectedMd = await shim.fsDriver().readFile(mdPath);
let actualMd = await htmlToMd.parse('<div>' + html + '</div>', htmlToMdOptions);
if (os.EOL === '\r\n') {
expectedMd = expectedMd.replace(/\r\n/g, '\n')
actualMd = actualMd.replace(/\r\n/g, '\n')
}
const actualMd = await htmlToMd.parse('<div>' + html + '</div>', []);
if (actualMd !== expectedMd) {
console.info('');

View File

@@ -1,45 +0,0 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const StringUtils = require('lib/string-utils');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('StringUtils', function() {
beforeEach(async (done) => {
done();
});
it('should surround keywords with strings', async (done) => {
const testCases = [
[[], 'test', 'a', 'b', 'test'],
[['test'], 'test', 'a', 'b', 'atestb'],
[['test'], 'Test', 'a', 'b', 'aTestb'],
[['te[]st'], 'Te[]st', 'a', 'b', 'aTe[]stb'],
// [['test1', 'test2'], 'bla test1 blabla test1 bla test2 not this one - test22', 'a', 'b', 'bla atest1b blabla atest1b bla atest2b not this one - test22'],
[['test1', 'test2'], 'bla test1 test1 bla test2', '<span class="highlighted-keyword">', '</span>', 'bla <span class="highlighted-keyword">test1</span> <span class="highlighted-keyword">test1</span> bla <span class="highlighted-keyword">test2</span>'],
// [[{ type:'regex', value:'test.*?'}], 'bla test1 test1 bla test2 test tttest', 'a', 'b', 'bla atest1b atest1b bla atest2b atestb tttest'],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
const keywords = t[0];
const input = t[1];
const prefix = t[2];
const suffix = t[3];
const expected = t[4];
const actual = StringUtils.surroundKeywords(keywords, input, prefix, suffix);
expect(actual).toBe(expected, 'Test case ' + i);
}
done();
});
});

View File

@@ -1,57 +0,0 @@
require('app-module-path').addPath(__dirname);
const { asyncTest, fileContentEqual, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const TaskQueue = require('lib/TaskQueue.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('TaskQueue', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should queue and execute tasks', asyncTest(async () => {
const queue = new TaskQueue();
queue.push(1, async () => { await sleep(0.5); return 'a'; });
queue.push(2, async () => { await sleep(0.5); return 'b'; });
queue.push(3, async () => { await sleep(0.5); return 'c'; });
const results = [];
results.push(await queue.waitForResult(1));
results.push(await queue.waitForResult(2));
results.push(await queue.waitForResult(3));
expect(results[0].id).toBe(1);
expect(results[0].result).toBe('a');
expect(results[1].id).toBe(2);
expect(results[1].result).toBe('b');
expect(results[2].id).toBe(3);
expect(results[2].result).toBe('c');
}));
it('should handle errors', asyncTest(async () => {
const queue = new TaskQueue();
queue.push(1, async () => { await sleep(0.5); return 'a'; });
queue.push(2, async () => { await sleep(0.5); throw new Error('e'); });
const results = [];
results.push(await queue.waitForResult(1));
results.push(await queue.waitForResult(2));
expect(results[0].id).toBe(1);
expect(results[0].result).toBe('a');
expect(results[1].id).toBe(2);
expect(!results[1].result).toBe(true);
expect(results[1].error.message).toBe('e');
}));
});

View File

@@ -25,7 +25,8 @@ describe('Encryption', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
//await setupDatabaseAndSynchronizer(2);
//await switchClient(1);
service = new EncryptionService();
BaseItem.encryptionService_ = service;
Setting.setValue('encryption.enabled', true);

View File

@@ -1,5 +0,0 @@
<ul>
<li><div>This note has an unordered list</div></li>
<li><div>List item</div></li>
<li><div>List item</div></li>
</ul>

View File

@@ -1,3 +0,0 @@
- This note has an unordered list
- List item
- List item

View File

@@ -1,16 +0,0 @@
<ul>
<li lang="en-US">
<div>Protocols</div>
</li>
<ul type="circle">
<li lang="en-US">
<div>two common network protocols used to send data packets over a network</div>
</li>
<li lang="en-US">
<div>TCP Transmission control protocol</div>
</li>
</ul>
<li lang="en-US">
<div>Network port - a network port is a process-specific or an application-specific software construct serving as a communication endpoint, which is used by the Transport Layer protocols of Internet Protocol suite, such as UDP and TCP</div>
</li>
</ul>

View File

@@ -1,7 +0,0 @@
- Protocols
- two common network protocols used to send data packets over a network
- TCP Transmission control protocol
- Network port - a network port is a process-specific or an application-specific software construct serving as a communication endpoint, which is used by the Transport Layer protocols of Internet Protocol suite, such as UDP and TCP

View File

@@ -1,5 +0,0 @@
<img src="https://joplinapp.org/images/Icon512.png" alt="multiple
lines
are
possible
I guess"/><img src="https://joplinapp.org/images/Icon512.png" alt="This should ] be escaped"/>

View File

@@ -1 +0,0 @@
![multiple lines are possible I guess](https://joplinapp.org/images/Icon512.png)![This should \] be escaped](https://joplinapp.org/images/Icon512.png)

View File

@@ -1,7 +0,0 @@
<div>Sometimes Evernote
wraps lines inside blocks</div>
<div>Sometimes it doesn't wrap them</div>
<pre>But
careful
with
pre tags</pre>

View File

@@ -1,6 +0,0 @@
Sometimes Evernote wraps lines inside blocks
Sometimes it doesn't wrap them
But
careful
with
pre tags

View File

@@ -1,6 +0,0 @@
<p><a href="#first">First</a></p>
<p><a href="#second">Second</a></p>
<p>Third</p>
<p><a name="first"></a>First</p>
<p><a id="second"></a>Second</p>
<p><a id="third"></a>Third</p>

View File

@@ -1,11 +0,0 @@
[First](#first)
[Second](#second)
Third
<a id="first"></a>First
<a id="second"></a>Second
Third

View File

@@ -1 +1 @@
<a href="https://joplinapp.org"><h1 id="joplin"><img class="title-icon" src="https://joplinapp.org/images/Icon512.png">oplin</h1></a>
<a href="https://joplin.cozic.net"><h1 id="joplin"><img class="title-icon" src="https://joplin.cozic.net/images/Icon512.png">oplin</h1></a>

View File

@@ -1 +1 @@
[# ![](https://joplinapp.org/images/Icon512.png)oplin](https://joplinapp.org)
[# ![](https://joplin.cozic.net/images/Icon512.png)oplin](https://joplin.cozic.net)

View File

@@ -1 +1 @@
Some text
[Some text]()

View File

@@ -1 +0,0 @@
<a href="http://example.com/That is not right"/>Testing</a>

View File

@@ -1 +0,0 @@
[Testing](http://example.com/That%20is%20not%20right)

View File

@@ -1,7 +1,5 @@
```
def ma_fonction():
"""
C'est une super fonction
"""
pass
```
def ma_fonction():
"""
C'est une super fonction
"""
pass

View File

@@ -1,2 +0,0 @@
<pre style="font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace;"><strong><font color="#008080">thatsCode();</font></strong></pre>
<pre>thatsJustPre(); // In that case we do not have enough info to know if it is a codeblock or not, so we leave it as plain text</pre>

View File

@@ -1,5 +0,0 @@
```
thatsCode();
```
thatsJustPre(); // In that case we do not have enough info to know if it is a codeblock or not, so we leave it as plain text

View File

@@ -1,47 +0,0 @@
<figure itemprop="associatedMedia image" itemscope="" itemtype="http://schema.org/ImageObject" data-component="image" data-media-id="75583fcfe2eb74f1e89ea320355ff4156f4ade7b" id="img-1">
<meta itemprop="representativeOfPage" content="true">
<meta itemprop="url" content="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=700&amp;quality=85&amp;auto=format&amp;fit=max&amp;s=2a6a7ba9738c6a6a79eab39ba46c34cd">
<meta itemprop="width" content="3904">
<meta itemprop="height" content="2342">
<a href="#img-1" data-link-name="Launch Article Lightbox" data-is-ajax="">
<div>
<picture>
<!--[if IE 9]><video style="display: none;"><![endif]-->
<source media="(min-width: 980px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: 980px) and (min-resolution: 120dpi)" sizes="620px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=620&amp;quality=45&amp;auto=format&amp;fit=max&amp;dpr=2&amp;s=bacff59339e5ba117f957c24218ef76b 1240w">
<source media="(min-width: 980px)" sizes="620px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=620&amp;quality=85&amp;auto=format&amp;fit=max&amp;s=f1427ce6689688d3d6e0087fe9cb5c18 620w">
<source media="(min-width: 740px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: 740px) and (min-resolution: 120dpi)" sizes="700px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=700&amp;quality=45&amp;auto=format&amp;fit=max&amp;dpr=2&amp;s=70accd3c6e7d2c36f5ccc7321eab097e 1400w">
<source media="(min-width: 740px)" sizes="700px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=700&amp;quality=85&amp;auto=format&amp;fit=max&amp;s=2a6a7ba9738c6a6a79eab39ba46c34cd 700w">
<source media="(min-width: 660px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: 660px) and (min-resolution: 120dpi)" sizes="620px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=620&amp;quality=45&amp;auto=format&amp;fit=max&amp;dpr=2&amp;s=bacff59339e5ba117f957c24218ef76b 1240w">
<source media="(min-width: 660px)" sizes="620px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=620&amp;quality=85&amp;auto=format&amp;fit=max&amp;s=f1427ce6689688d3d6e0087fe9cb5c18 620w">
<source media="(min-width: 480px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: 480px) and (min-resolution: 120dpi)" sizes="645px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=645&amp;quality=45&amp;auto=format&amp;fit=max&amp;dpr=2&amp;s=6751fcff1b880acc45ed5aab6511a2ca 1290w">
<source media="(min-width: 480px)" sizes="645px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=645&amp;quality=85&amp;auto=format&amp;fit=max&amp;s=d18702564383ce5d613d22b96ec6d726 645w">
<source media="(min-width: 0px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: 0px) and (min-resolution: 120dpi)" sizes="465px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=465&amp;quality=45&amp;auto=format&amp;fit=max&amp;dpr=2&amp;s=a7b87fb26b9813d197f3c236a5282ca4 930w">
<source media="(min-width: 0px)" sizes="465px" srcset="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=465&amp;quality=85&amp;auto=format&amp;fit=max&amp;s=821ae9e950ae92371b40a35e98a31116 465w">
<!--[if IE 9]></video><![endif]-->
<img itemprop="contentUrl" alt="A blood moon" src="https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=300&amp;quality=85&amp;auto=format&amp;fit=max&amp;s=1e9b643d2c109a1e271f50046eac1324">
</picture>
</div>
<span>
<svg width="22" height="22" viewBox="0 0 22 22">
<path d="M3.4 20.2L9 14.5 7.5 13l-5.7 5.6L1 14H0v7.5l.5.5H8v-1l-4.6-.8M18.7 1.9L13 7.6 14.4 9l5.7-5.7.5 4.7h1.2V.6l-.5-.5H14v1.2l4.7.6"></path>
</svg>
</span>
</a>
<label for="show-caption">
<span>
<svg width="6" height="14" viewBox="0 0 6 14">
<path d="M4.6 12l-.4 1.4c-.7.2-1.9.6-3 .6-.7 0-1.2-.2-1.2-.9 0-.2 0-.3.1-.5l2-6.7H.7l.4-1.5 4.2-.6h.2L3 12h1.6zm-.3-9.2c-.9 0-1.4-.5-1.4-1.3C2.9.5 3.7 0 4.6 0 5.4 0 6 .5 6 1.3c0 1-.8 1.5-1.7 1.5z"></path>
</svg>
</span>
</label>
<figcaption itemprop="description">
<span>
<svg width="11" height="10" viewBox="0 0 11 10">
<path fill-rule="evenodd" d="M5.5 0L11 10H0z"></path>
</svg>
</span>
A blood moon last occurred in July 2018, though clouds largely obscured the celestial phenomenon in the UK.
Photograph: JM F Almeida/Getty Images
</figcaption>
</figure>

View File

@@ -1,3 +0,0 @@
[![A blood moon](https://i.guim.co.uk/img/media/75583fcfe2eb74f1e89ea320355ff4156f4ade7b/0_49_3904_2342/master/3904.jpg?width=300&quality=85&auto=format&fit=max&s=1e9b643d2c109a1e271f50046eac1324)](#img-1)
A blood moon last occurred in July 2018, though clouds largely obscured the celestial phenomenon in the UK. Photograph: JM F Almeida/Getty Images

View File

@@ -1,29 +0,0 @@
Some pictures:
<picture>
<!--[if IE 9]><video style="display: none;"><![endif]-->
<source media="(min-width: 768px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w">
<source media="(min-width: 768px)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w">
<source media="(min-width: 481px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w">
<source media="(min-width: 481px)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w">
<source media="(min-width: 321px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="450px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop&amp;dpr=1.5 675w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop&amp;dpr=1.5 675w">
<source media="(min-width: 321px)" sizes="450px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop 450w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop 450w">
<source media="(min-width: 0px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="320px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop&amp;dpr=1.5 480w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop&amp;dpr=1.5 480w">
<source media="(min-width: 0px)" sizes="320px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop 320w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop 320w">
<!--[if IE 9]></video><![endif]-->
<img class=" lazyloaded" title="" alt="" id="img-id-0">
</picture>
<picture>
<!--[if IE 9]><video style="display: none;"><![endif]-->
<source media="(min-width: 768px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w">
<source media="(min-width: 768px)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w">
<source media="(min-width: 481px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop&amp;dpr=1.5 882w">
<source media="(min-width: 481px)" sizes="588px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=588&amp;h=900&amp;fit=crop 588w">
<source media="(min-width: 321px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="450px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop&amp;dpr=1.5 675w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop&amp;dpr=1.5 675w">
<source media="(min-width: 321px)" sizes="450px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop 450w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=450&amp;h=688&amp;fit=crop 450w">
<source media="(min-width: 0px) and (-webkit-min-device-pixel-ratio: 1.25), (min-width: px) and (min-resolution: 120dpi)" sizes="320px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop&amp;dpr=1.5 480w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop&amp;dpr=1.5 480w">
<source media="(min-width: 0px)" sizes="320px" data-srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop 320w" srcset="https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&amp;w=320&amp;h=489&amp;fit=crop 320w">
<!--[if IE 9]></video><![endif]-->
<img class=" lazyloaded" title="" alt="" id="img-id-0" src="http://example.com/test.gif">
</picture>

View File

@@ -1 +0,0 @@
Some pictures: ![](https://static2.cbrimages.com/wp-content/uploads/2018/09/Die-01-cvrA.jpg?q=35&w=588&h=900&fit=crop&dpr=1.5) ![](http://example.com/test.gif)

View File

@@ -1,42 +0,0 @@
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Official Things</strong></td>
<td></td>
</tr>
<tr>
<td><a href="https://nim-lang.org">Web Site</a></td>
<td>The project’s entry point</td>
</tr>
<tr>
<td><a href="https://github.com/nim-lang/nim">Source</a></td>
<td>The github project</td>
</tr>
<tr>
<td><a href="https://github.com/nim-lang/nimble">nimble</a></td>
<td>The nim package manager</td>
</tr>
<tr>
<td><a href="https://github.com/dom96/choosenim">choosenim</a></td>
<td>Toolchain installer</td>
</tr>
<tr>
<td>&nbsp;</td>
<td></td>
</tr>
<tr>
<td><strong>Community</strong></td>
<td></td>
</tr>
<tr>
<td><a href="https://forum.nim-lang.org">Forums</a></td>
<td>An async discussion board</td>
</tr>
</tbody>
</table>

View File

@@ -1,9 +0,0 @@
| | |
| --- | --- |
| **Official Things** | |
| [Web Site](https://nim-lang.org) | The project’s entry point |
| [Source](https://github.com/nim-lang/nim) | The github project |
| [nimble](https://github.com/nim-lang/nimble) | The nim package manager |
| [choosenim](https://github.com/dom96/choosenim) | Toolchain installer |
| **Community** | |
| [Forums](https://forum.nim-lang.org) | An async discussion board |

View File

@@ -6,6 +6,4 @@ Some text, not an image, so it should remain escaped:
But this is code so it can be unescaped:
```
<img src="http://test.com/image.png" />
```
<img src="http://test.com/image.png" />

View File

@@ -39,7 +39,6 @@ describe('markdownUtils', function() {
['![something](http://test.com/img.png)', ['http://test.com/img.png']],
['![something](http://test.com/img.png) ![something2](http://test.com/img2.png)', ['http://test.com/img.png', 'http://test.com/img2.png']],
['![something](http://test.com/img.png "Some description")', ['http://test.com/img.png']],
['![something](https://test.com/ohoh_(123).png)', ['https://test.com/ohoh_(123).png']],
];
for (let i = 0; i < testCases.length; i++) {

View File

@@ -1,66 +0,0 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-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 Note = require('lib/models/Note.js');
const BaseItem = require('lib/models/BaseItem.js');
const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
async function allItems() {
let folders = await Folder.all();
let notes = await Note.all();
return folders.concat(notes);
}
describe('models_BaseItem', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
// it('should be able to exclude keys when syncing', asyncTest(async () => {
// let folder1 = await Folder.save({ title: "folder1" });
// let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
// await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
// let resource1 = (await Resource.all())[0];
// console.info(await Resource.serializeForSync(resource1));
// }));
// This is to handle the case where a property is removed from a BaseItem table - in that case files in
// the sync target will still have the old property but we don't need it locally.
it('should ignore properties that are present in sync file but not in database when serialising', asyncTest(async () => {
let folder = await Folder.save({ title: "folder1" });
let serialized = await Folder.serialize(folder);
serialized += "\nignore_me: true"
let unserialized = await Folder.unserialize(serialized);
expect('ignore_me' in unserialized).toBe(false);
}));
it('should not modify title when unserializing', asyncTest(async () => {
let folder1 = await Folder.save({ title: "" });
let folder2 = await Folder.save({ title: "folder1" });
let serialized1 = await Folder.serialize(folder1);
let unserialized1 = await Folder.unserialize(serialized1);
expect(unserialized1.title).toBe(folder1.title);
let serialized2 = await Folder.serialize(folder2);
let unserialized2 = await Folder.unserialize(serialized2);
expect(unserialized2.title).toBe(folder2.title);
}));
});

View File

@@ -52,77 +52,4 @@ describe('models_Folder', function() {
expect(all.length).toBe(0);
}));
it('should sort by last modified, based on content', asyncTest(async () => {
let folders;
let f1 = await Folder.save({ title: "folder1" }); await sleep(0.1);
let f2 = await Folder.save({ title: "folder2" }); await sleep(0.1);
let f3 = await Folder.save({ title: "folder3" }); await sleep(0.1);
let n1 = await Note.save({ title: 'note1', parent_id: f2.id });
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
expect(folders.length).toBe(3);
expect(folders[0].id).toBe(f2.id);
expect(folders[1].id).toBe(f3.id);
expect(folders[2].id).toBe(f1.id);
let n2 = await Note.save({ title: 'note1', parent_id: f1.id });
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
expect(folders[0].id).toBe(f1.id);
expect(folders[1].id).toBe(f2.id);
expect(folders[2].id).toBe(f3.id);
await Note.save({ id: n1.id, title: 'note1 mod' });
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
expect(folders[0].id).toBe(f2.id);
expect(folders[1].id).toBe(f1.id);
expect(folders[2].id).toBe(f3.id);
folders = await Folder.orderByLastModified(await Folder.all(), 'asc');
expect(folders[0].id).toBe(f3.id);
expect(folders[1].id).toBe(f1.id);
expect(folders[2].id).toBe(f2.id);
}));
it('should sort by last modified, based on content (sub-folders too)', asyncTest(async () => {
let folders;
let f1 = await Folder.save({ title: "folder1" }); await sleep(0.1);
let f2 = await Folder.save({ title: "folder2" }); await sleep(0.1);
let f3 = await Folder.save({ title: "folder3", parent_id: f1.id }); await sleep(0.1);
let n1 = await Note.save({ title: 'note1', parent_id: f3.id });
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
expect(folders.length).toBe(3);
expect(folders[0].id).toBe(f1.id);
expect(folders[1].id).toBe(f3.id);
expect(folders[2].id).toBe(f2.id);
let n2 = await Note.save({ title: 'note2', parent_id: f2.id });
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
expect(folders[0].id).toBe(f2.id);
expect(folders[1].id).toBe(f1.id);
expect(folders[2].id).toBe(f3.id);
await Note.save({ id: n1.id, title: 'note1 MOD' });
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
expect(folders[0].id).toBe(f1.id);
expect(folders[1].id).toBe(f3.id);
expect(folders[2].id).toBe(f2.id);
let f4 = await Folder.save({ title: "folder4", parent_id: f1.id }); await sleep(0.1);
let n3 = await Note.save({ title: 'note3', parent_id: f4.id });
folders = await Folder.orderByLastModified(await Folder.all(), 'desc');
expect(folders.length).toBe(4);
expect(folders[0].id).toBe(f1.id);
expect(folders[1].id).toBe(f4.id);
expect(folders[2].id).toBe(f3.id);
expect(folders[3].id).toBe(f2.id);
}));
});

View File

@@ -1,51 +0,0 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, revisionService, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const SearchEngine = require('lib/services/SearchEngine');
const ResourceService = require('lib/services/ResourceService');
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting');
const ItemChange = require('lib/models/ItemChange');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
let searchEngine = null;
describe('models_ItemChange', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
searchEngine = new SearchEngine();
searchEngine.setDb(db());
done();
});
it('should delete old changes that have been processed', asyncTest(async () => {
const n1 = await Note.save({ title: "abcd efgh" }); // 3
await ItemChange.waitForAllSaved();
expect(await ItemChange.lastChangeId()).toBe(1);
const resourceService = new ResourceService();
await searchEngine.syncTables();
// If we run this now, it should not delete any change because
// the resource service has not yet processed the change
await ItemChangeUtils.deleteProcessedChanges();
expect(await ItemChange.lastChangeId()).toBe(1);
await resourceService.indexNoteResources();
await ItemChangeUtils.deleteProcessedChanges();
expect(await ItemChange.lastChangeId()).toBe(1);
await revisionService().collectRevisions();
await ItemChangeUtils.deleteProcessedChanges();
expect(await ItemChange.lastChangeId()).toBe(0);
}));
});

View File

@@ -5,7 +5,6 @@ const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const ArrayUtils = require('lib/ArrayUtils.js');
const { shim } = require('lib/shim');
process.on('unhandledRejection', (reason, p) => {
@@ -35,83 +34,6 @@ describe('models_Note', function() {
expect(items.length).toBe(2);
expect(items[0].type_).toBe(BaseModel.TYPE_NOTE);
expect(items[1].type_).toBe(BaseModel.TYPE_RESOURCE);
const resource2 = await shim.createResourceFromPath(__dirname + '/../tests/support/photo.jpg');
const resource3 = await shim.createResourceFromPath(__dirname + '/../tests/support/photo.jpg');
note2.body += '<img alt="bla" src=":/' + resource2.id + '"/>';
note2.body += '<img src=\':/' + resource3.id + '\' />';
items = await Note.linkedItems(note2.body);
expect(items.length).toBe(4);
}));
it('should find linked items', asyncTest(async () => {
const testCases = [
['[](:/06894e83b8f84d3d8cbe0f1587f9e226)', ['06894e83b8f84d3d8cbe0f1587f9e226']],
['[](:/06894e83b8f84d3d8cbe0f1587f9e226) [](:/06894e83b8f84d3d8cbe0f1587f9e226)', ['06894e83b8f84d3d8cbe0f1587f9e226']],
['[](:/06894e83b8f84d3d8cbe0f1587f9e226) [](:/06894e83b8f84d3d8cbe0f1587f9e227)', ['06894e83b8f84d3d8cbe0f1587f9e226', '06894e83b8f84d3d8cbe0f1587f9e227']],
['[](:/06894e83b8f84d3d8cbe0f1587f9e226 "some title")', ['06894e83b8f84d3d8cbe0f1587f9e226']],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
const input = t[0];
const expected = t[1];
const actual = Note.linkedItemIds(input);
const contentEquals = ArrayUtils.contentEquals(actual, expected);
// console.info(contentEquals, input, expected, actual);
expect(contentEquals).toBe(true);
}
}));
it('should change the type of notes', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await Note.load(note1.id);
let changedNote = Note.changeNoteType(note1, 'todo');
expect(changedNote === note1).toBe(false);
expect(!!changedNote.is_todo).toBe(true);
await Note.save(changedNote);
note1 = await Note.load(note1.id);
changedNote = Note.changeNoteType(note1, 'todo');
expect(changedNote === note1).toBe(true);
expect(!!changedNote.is_todo).toBe(true);
note1 = await Note.load(note1.id);
changedNote = Note.changeNoteType(note1, 'note');
expect(changedNote === note1).toBe(false);
expect(!!changedNote.is_todo).toBe(false);
}));
it('should serialize and unserialize without modifying data', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1"});
const testCases = [
[ {title: '', body:'Body and no title\nSecond line\nThird Line', parent_id: folder1.id},
'', 'Body and no title\nSecond line\nThird Line'],
[ {title: 'Note title', body:'Body and title', parent_id: folder1.id},
'Note title', 'Body and title'],
[ {title: 'Title and no body', body:'', parent_id: folder1.id},
'Title and no body', ''],
]
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
const input = t[0];
const expectedTitle = t[1];
const expectedBody = t[1];
let note1 = await Note.save(input);
let serialized = await Note.serialize(note1);
let unserialized = await Note.unserialize( serialized);
expect(unserialized.title).toBe(input.title);
expect(unserialized.body).toBe(input.body);
}
}));
});

View File

@@ -1,90 +0,0 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-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 Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
const testImagePath = __dirname + '/../tests/support/photo.jpg';
describe('models_Resource', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should have a "done" fetch_status when created locally', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, testImagePath);
let resource1 = (await Resource.all())[0];
let ls = await Resource.localState(resource1);
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE);
}));
it('should have a default local state', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, testImagePath);
let resource1 = (await Resource.all())[0];
let ls = await Resource.localState(resource1);
expect(!ls.id).toBe(true);
expect(ls.resource_id).toBe(resource1.id);
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_DONE);
}));
it('should save and delete local state', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, testImagePath);
let resource1 = (await Resource.all())[0];
await Resource.setLocalState(resource1, { fetch_status: Resource.FETCH_STATUS_IDLE });
let ls = await Resource.localState(resource1);
expect(!!ls.id).toBe(true);
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_IDLE);
await Resource.delete(resource1.id);
ls = await Resource.localState(resource1);
expect(!ls.id).toBe(true);
}));
it('should resize the resource if the image is below the required dimensions', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
const previousMax = Resource.IMAGE_MAX_DIMENSION;
Resource.IMAGE_MAX_DIMENSION = 5;
await shim.attachFileToNote(note1, testImagePath);
Resource.IMAGE_MAX_DIMENSION = previousMax;
let resource1 = (await Resource.all())[0];
const originalStat = await shim.fsDriver().stat(testImagePath);
const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1));
expect(newStat.size < originalStat.size).toBe(true);
}));
it('should not resize the resource if the image is below the required dimensions', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await shim.attachFileToNote(note1, testImagePath);
let resource1 = (await Resource.all())[0];
const originalStat = await shim.fsDriver().stat(testImagePath);
const newStat = await shim.fsDriver().stat(Resource.fullPath(resource1));
expect(originalStat.size).toBe(newStat.size);
}));
});

View File

@@ -1,104 +0,0 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-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 Note = require('lib/models/Note.js');
const NoteTag = require('lib/models/NoteTag.js');
const Tag = require('lib/models/Tag.js');
const Revision = require('lib/models/Revision.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('models_Revision', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should create patches of text and apply it', asyncTest(async () => {
const note1 = await Note.save({ body: 'my note\nsecond line' });
const patch = Revision.createTextPatch(note1.body, 'my new note\nsecond line');
const merged = Revision.applyTextPatch(note1.body, patch);
expect(merged).toBe('my new note\nsecond line');
}));
it('should create patches of objects and apply it', asyncTest(async () => {
const oldObject = {
one: '123',
two: '456',
three: '789',
};
const newObject = {
one: '123',
three: '999',
}
const patch = Revision.createObjectPatch(oldObject, newObject);
const merged = Revision.applyObjectPatch(oldObject, patch);
expect(JSON.stringify(merged)).toBe(JSON.stringify(newObject));
}));
it('should move target revision to the top', asyncTest(async () => {
const revs = [
{ id: '123' },
{ id: '456' },
{ id: '789' },
];
let newRevs;
newRevs = Revision.moveRevisionToTop({ id: '456' }, revs);
expect(newRevs[0].id).toBe('123');
expect(newRevs[1].id).toBe('789');
expect(newRevs[2].id).toBe('456');
newRevs = Revision.moveRevisionToTop({ id: '789' }, revs);
expect(newRevs[0].id).toBe('123');
expect(newRevs[1].id).toBe('456');
expect(newRevs[2].id).toBe('789');
}));
it('should create patch stats', asyncTest(async () => {
const tests = [
{
patch: `@@ -625,16 +625,48 @@
rrupted download
+%0A- %5B %5D Fix mobile screen options`,
expected: [-0, +32],
},
{
patch: `@@ -564,17 +564,17 @@
ages%0A- %5B
-
+x
%5D Check `,
expected: [-1, +1],
},
{
patch: `@@ -1022,56 +1022,415 @@
.%0A%0A#
- How to view a note history%0A%0AWhile all the apps
+%C2%A0How does it work?%0A%0AAll the apps save a version of the modified notes every 10 minutes.
%0A%0A# `,
expected: [-(19+27+2), 17+67+4],
},
];
for (const test of tests) {
const stats = Revision.patchStats(test.patch);
expect(stats.removed).toBe(-test.expected[0]);
expect(stats.added).toBe(test.expected[1]);
}
}));
});

View File

@@ -1,60 +0,0 @@
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-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 Note = require('lib/models/Note.js');
const NoteTag = require('lib/models/NoteTag.js');
const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('models_Tag', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
done();
});
it('should add tags by title', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await Tag.setNoteTagsByTitles(note1.id, ['un', 'deux']);
const noteTags = await Tag.tagsByNoteId(note1.id);
expect(noteTags.length).toBe(2);
}));
it('should not allow renaming tag to existing tag names', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await Tag.setNoteTagsByTitles(note1.id, ['un', 'deux']);
const tagUn = await Tag.loadByTitle('un');
const hasThrown = await checkThrowAsync(async () => await Tag.save({ id: tagUn.id, title: 'deux' }, { userSideValidation: true }));
expect(hasThrown).toBe(true);
}));
it('should not return tags without notes', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await Tag.setNoteTagsByTitles(note1.id, ['un']);
let tags = await Tag.allWithNotes();
expect(tags.length).toBe(1);
await Note.delete(note1.id);
tags = await Tag.allWithNotes();
expect(tags.length).toBe(0);
}));
});

View File

@@ -1,76 +0,0 @@
require('app-module-path').addPath(__dirname);
const { extractExecutablePath, quotePath, unquotePath, friendlySafeFilename } = require('lib/path-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
describe('pathUtils', function() {
beforeEach(async (done) => {
done();
});
it('should create friendly safe filename', async (done) => {
const testCases = [
['生活', '生活'],
['not/good', 'not_good'],
['really/not/good', 'really_not_good'],
['con', '___'],
['no space at the end ', 'no space at the end'],
['nor dots...', 'nor dots'],
[' no space before either', 'no space before either'],
['thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong', 'thatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylongthatsreallylong'],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
expect(friendlySafeFilename(t[0])).toBe(t[1]);
}
expect(!!friendlySafeFilename('')).toBe(true);
expect(!!friendlySafeFilename('...')).toBe(true);
done();
});
it('should quote and unquote paths', async (done) => {
const testCases = [
['', ''],
['/my/path', '/my/path'],
['/my/path with spaces', '"/my/path with spaces"'],
['/my/weird"path', '"/my/weird\\"path"'],
['c:\\Windows\\test.dll', 'c:\\Windows\\test.dll'],
['c:\\Windows\\test test.dll', '"c:\\Windows\\test test.dll"'],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
expect(quotePath(t[0])).toBe(t[1]);
expect(unquotePath(quotePath(t[0]))).toBe(t[0]);
}
done();
});
it('should extract executable path from command', async (done) => {
const testCases = [
['', ''],
['/my/cmd -some -args', '/my/cmd'],
['"/my/cmd" -some -args', '"/my/cmd"'],
['"/my/cmd"', '"/my/cmd"'],
['"/my/cmd and space" -some -flags', '"/my/cmd and space"'],
['"" -some -flags', '""'],
];
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
expect(extractExecutablePath(t[0])).toBe(t[1]);
}
done();
});
});

View File

@@ -310,51 +310,4 @@ describe('services_InteropService', function() {
expect(note2_2.body.indexOf(note1_2.id) >= 0).toBe(true);
}));
it('should export into json format', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await Note.load(note1.id);
const filePath = exportDir();
await service.export({ path: filePath, format: 'json' });
// verify that the json files exist and can be parsed
const items = [folder1, note1];
for (let i = 0; i < items.length; i++) {
const jsonFile = filePath + '/' + items[i].id + '.json';
let json = await fs.readFile(jsonFile, 'utf-8');
let obj = JSON.parse(json);
expect(obj.id).toBe(items[i].id);
expect(obj.type_).toBe(items[i].type_);
expect(obj.title).toBe(items[i].title);
expect(obj.body).toBe(items[i].body);
}
}));
it('should export MD with unicode filenames', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: 'folder1' });
let folder2 = await Folder.save({ title: 'ジョプリン' });
let note1 = await Note.save({ title: '生活', parent_id: folder1.id });
let note2 = await Note.save({ title: '生活', parent_id: folder1.id });
let note2b = await Note.save({ title: '生活', parent_id: folder1.id });
let note3 = await Note.save({ title: '', parent_id: folder1.id });
let note4 = await Note.save({ title: '', parent_id: folder1.id });
let note5 = await Note.save({ title: 'salut, ça roule ?', parent_id: folder1.id });
let note6 = await Note.save({ title: 'ジョプリン', parent_id: folder2.id });
const outDir = exportDir();
await service.export({ path: outDir, format: 'md' });
expect(await shim.fsDriver().exists(outDir + '/folder1/生活.md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/生活 (1).md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/生活 (2).md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/Untitled.md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/Untitled (1).md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/folder1/salut, ça roule _.md')).toBe(true);
expect(await shim.fsDriver().exists(outDir + '/ジョプリン/ジョプリン.md')).toBe(true);
}));
});

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