1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Linter update (#1777)

* Update eslint config

* Applied linter to lib

* Applied eslint config to CliClient/app

* Removed prettier due to https://github.com/prettier/prettier/pull/4765

* First pass on test units

* Applied linter config to test units

* Applied eslint config to clipper

* Applied to plugin dir

* Applied to root of ElectronClient

* Applied on RN root

* Applied on CLI root

* Applied on Clipper root

* Applied config to tools

* test hook

* test hook

* test hook

* Added pre-commit hook

* Applied rule no-trailing-spaces

* Make sure root packages are installed when installing sub-dir

* Added doc
This commit is contained in:
Laurent Cozic 2019-07-30 09:35:42 +02:00 committed by GitHub
parent b8fbaa2029
commit 71efff6827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
208 changed files with 7715 additions and 5019 deletions

View File

@ -1,3 +1,40 @@
*.min.js
.git/
.github/
_mydocs/
_releases/
Assets/
CliClient/build
CliClient/locales
CliClient/node_modules
CliClient/tests-build
CliClient/tests/enex_to_md
CliClient/tests/html_to_md
CliClient/tests/logs
CliClient/tests/support
CliClient/tests/sync
CliClient/tests/tmp
Clipper/joplin-webclipper/content_scripts/JSDOMParser.js
Clipper/joplin-webclipper/content_scripts/Readability-readerable.js
Clipper/joplin-webclipper/content_scripts/Readability.js
Clipper/joplin-webclipper/dist
Clipper/joplin-webclipper/icons
Clipper/joplin-webclipper/popup/build
Clipper/joplin-webclipper/popup/node_modules
docs/
ElectronClient/app/dist
ElectronClient/app/lib
ElectronClient/app/lib/vendor/sjcl-rn.js
ElectronClient/app/lib/vendor/sjcl.js
ElectronClient/app/locales
ElectronClient/app/node_modules
highlight.pack.js
ReactNativeClient/lib/vendor/
node_modules/
ReactNativeClient/android
ReactNativeClient/ios
ReactNativeClient/lib/vendor/
ReactNativeClient/locales
ReactNativeClient/node_modules
readme/
Tools/node_modules
Tools/PortableAppsLauncher

View File

@ -4,30 +4,50 @@ module.exports = {
'es6': true,
'node': true,
},
'extends': ['eslint:recommended', 'prettier'],
'extends': ['eslint:recommended'],
'globals': {
'Atomics': 'readonly',
'SharedArrayBuffer': 'readonly'
'SharedArrayBuffer': 'readonly',
// Jasmine variables
'expect': 'readonly',
'describe': 'readonly',
'it': 'readonly',
'beforeEach': 'readonly',
'jasmine': 'readonly',
// React Native variables
'__DEV__': 'readonly',
// Clipper variables
'browserSupportsPromises_': true,
'chrome': 'readonly',
'browser': 'readonly',
},
'parserOptions': {
'ecmaVersion': 2018,
"ecmaFeatures": {
"jsx": true,
},
"sourceType": "module",
},
'rules': {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-unused-vars": ["error", { "argsIgnorePattern": "event|reject|resolve|prevState|snapshot|prevProps" }],
// Ignore all unused function arguments, because in some
// case they are kept to indicate the function signature.
"no-unused-vars": ["error", { "argsIgnorePattern": ".*" }],
"no-constant-condition": 0,
"no-prototype-builtins": 0,
"prettier/prettier": "error",
// Uncomment this to automatically remove unused requires:
// "autofix/no-unused-vars": "error",
"space-in-parens": ["error", "never"],
"semi": ["error", "always"],
"eol-last": ["error", "always"],
"quotes": ["error", "single"],
"indent": ["error", "tab"],
"comma-dangle": ["error", "always-multiline"],
"no-trailing-spaces": "error",
},
"plugins": [
"react",
"prettier",
"autofix",
],
};

View File

@ -1,3 +0,0 @@
*.min.js
highlight.pack.js
ReactNativeClient/lib/vendor/

View File

@ -1,8 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"printWidth": 1000,
"semi": true,
"trailingComma": "es5",
"endOfLine": "auto"
}

View File

@ -40,10 +40,7 @@ Building the apps is relatively easy - please [see the build instructions](https
## 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.
Coding style is enforced by a pre-commit hook that runs eslint. This hook is installed whenever running `npm install` on any of the application directory. If for some reason the pre-commit hook didn't get installed, you can manually install it by running `npm install` at the root of the repository.
## Unit tests

View File

@ -1,14 +1,11 @@
const { _ } = require('lib/locale.js');
const { Logger } = require('lib/logger.js');
const Resource = require('lib/models/Resource.js');
const { netUtils } = require('lib/net-utils.js');
const http = require("http");
const urlParser = require("url");
const http = require('http');
const urlParser = require('url');
const enableServerDestroy = require('server-destroy');
class ResourceServer {
constructor() {
this.server_ = null;
this.logger_ = new Logger();
@ -40,7 +37,7 @@ class ResourceServer {
async start() {
this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]);
if (!this.port_) {
if (!this.port_) {
this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin');
return;
}
@ -48,11 +45,10 @@ class ResourceServer {
this.server_ = http.createServer();
this.server_.on('request', async (request, response) => {
const writeResponse = (message) => {
const writeResponse = message => {
response.write(message);
response.end();
}
};
const url = urlParser.parse(request.url, true);
let resourceId = url.pathname.split('/');
@ -69,6 +65,7 @@ class ResourceServer {
if (!done) throw new Error('Unhandled resource: ' + resourceId);
} catch (error) {
response.setHeader('Content-Type', 'text/plain');
// eslint-disable-next-line require-atomic-updates
response.statusCode = 400;
response.write(error.message);
}
@ -76,7 +73,7 @@ class ResourceServer {
response.end();
});
this.server_.on('error', (error) => {
this.server_.on('error', error => {
this.logger().error('Resource server:', error);
});
@ -91,7 +88,6 @@ class ResourceServer {
if (this.server_) this.server_.destroy();
this.server_ = null;
}
}
module.exports = ResourceServer;
module.exports = ResourceServer;

View File

@ -5,13 +5,12 @@ const Tag = require('lib/models/Tag.js');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const { cliUtils } = require('./cli-utils.js');
const { reducer, defaultState } = require('lib/reducer.js');
const { splitCommandString } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const { _ } = require('lib/locale.js');
const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = (new Entities()).encode;
const htmlentities = new Entities().encode;
const chalk = require('chalk');
const tk = require('terminal-kit');
@ -20,12 +19,10 @@ const Renderer = require('tkwidgets/framework/Renderer.js');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseWidget = require('tkwidgets/BaseWidget.js');
const ListWidget = require('tkwidgets/ListWidget.js');
const TextWidget = require('tkwidgets/TextWidget.js');
const HLayoutWidget = require('tkwidgets/HLayoutWidget.js');
const VLayoutWidget = require('tkwidgets/VLayoutWidget.js');
const ReduxRootWidget = require('tkwidgets/ReduxRootWidget.js');
const RootWidget = require('tkwidgets/RootWidget.js');
const WindowWidget = require('tkwidgets/WindowWidget.js');
const NoteWidget = require('./gui/NoteWidget.js');
@ -37,7 +34,6 @@ const StatusBarWidget = require('./gui/StatusBarWidget.js');
const ConsoleWidget = require('./gui/ConsoleWidget.js');
class AppGui {
constructor(app, store, keymap) {
try {
this.app_ = app;
@ -50,12 +46,12 @@ class AppGui {
// Some keys are directly handled by the tkwidget framework
// so they need to be remapped in a different way.
this.tkWidgetKeys_ = {
'focus_next': 'TAB',
'focus_previous': 'SHIFT_TAB',
'move_up': 'UP',
'move_down': 'DOWN',
'page_down': 'PAGE_DOWN',
'page_up': 'PAGE_UP',
focus_next: 'TAB',
focus_previous: 'SHIFT_TAB',
move_up: 'UP',
move_down: 'DOWN',
page_down: 'PAGE_DOWN',
page_up: 'PAGE_UP',
};
this.renderer_ = null;
@ -64,7 +60,7 @@ class AppGui {
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
this.app_.on('modelAction', async (event) => {
this.app_.on('modelAction', async event => {
await this.handleModelAction(event.action);
});
@ -134,7 +130,7 @@ class AppGui {
};
folderList.name = 'folderList';
folderList.vStretch = true;
folderList.on('currentItemChange', async (event) => {
folderList.on('currentItemChange', async event => {
const item = folderList.currentItem;
if (item === '-') {
@ -169,7 +165,7 @@ class AppGui {
});
}
});
this.rootWidget_.connect(folderList, (state) => {
this.rootWidget_.connect(folderList, state => {
return {
selectedFolderId: state.selectedFolderId,
selectedTagId: state.selectedTagId,
@ -196,7 +192,7 @@ class AppGui {
id: note ? note.id : null,
});
});
this.rootWidget_.connect(noteList, (state) => {
this.rootWidget_.connect(noteList, state => {
return {
selectedNoteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
items: state.notes,
@ -210,7 +206,7 @@ class AppGui {
borderBottomWidth: 1,
borderLeftWidth: 1,
};
this.rootWidget_.connect(noteText, (state) => {
this.rootWidget_.connect(noteText, state => {
return {
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
notes: state.notes,
@ -225,7 +221,7 @@ class AppGui {
borderLeftWidth: 1,
borderRightWidth: 1,
};
this.rootWidget_.connect(noteMetadata, (state) => {
this.rootWidget_.connect(noteMetadata, state => {
return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null };
});
noteMetadata.hide();
@ -292,7 +288,7 @@ class AppGui {
if (!cmd) return;
const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0;
if (isConfigPassword) return;
this.stdout(chalk.cyan.bold('> ' + cmd));
this.stdout(chalk.cyan.bold('> ' + cmd));
}
setupKeymap(keymap) {
@ -408,7 +404,7 @@ class AppGui {
activeListItem() {
const widget = this.widget('mainWindow').focusedWidget;
if (!widget) return null;
if (widget.name == 'noteList' || widget.name == 'folderList') {
return widget.currentItem;
}
@ -430,18 +426,14 @@ class AppGui {
}
async processFunctionCommand(cmd) {
if (cmd === 'activate') {
const w = this.widget('mainWindow').focusedWidget;
if (w.name === 'folderList') {
this.widget('noteList').focus();
} else if (w.name === 'noteList' || w.name === 'noteText') {
this.processPromptCommand('edit $n');
}
} else if (cmd === 'delete') {
if (this.widget('folderList').hasFocus) {
const item = this.widget('folderList').selectedJoplinItem;
@ -462,9 +454,7 @@ class AppGui {
} else {
this.stdout(_('Please select the note or notebook to be deleted first.'));
}
} else if (cmd === 'toggle_console') {
if (!this.consoleIsShown()) {
this.showConsole();
this.minimizeConsole();
@ -475,22 +465,15 @@ class AppGui {
this.maximizeConsole();
}
}
} else if (cmd === 'toggle_metadata') {
this.toggleNoteMetadata();
} else if (cmd === 'enter_command_line_mode') {
const cmd = await this.widget('statusBar').prompt();
if (!cmd) return;
this.addCommandToConsole(cmd);
await this.processPromptCommand(cmd);
await this.processPromptCommand(cmd);
} else {
throw new Error('Unknown command: ' + cmd);
}
}
@ -501,7 +484,7 @@ class AppGui {
// this.logger().debug('Got command: ' + cmd);
try {
try {
let note = this.widget('noteList').currentItem;
let folder = this.widget('folderList').currentItem;
let args = splitCommandString(cmd);
@ -511,7 +494,7 @@ class AppGui {
args[i] = note ? note.id : '';
} else if (args[i] == '$b') {
args[i] = folder ? folder.id : '';
} else if (args[i] == '$c') {
} else if (args[i] == '$c') {
const item = this.activeListItem();
args[i] = item ? item.id : '';
}
@ -523,7 +506,7 @@ class AppGui {
}
this.widget('console').scrollBottom();
// Invalidate so that the screen is redrawn in case inputting a command has moved
// the GUI up (in particular due to autocompletion), it's moved back to the right position.
this.widget('root').invalidate();
@ -603,7 +586,7 @@ class AppGui {
async setupResourceServer() {
const linkStyle = chalk.blue.underline;
const noteTextWidget = this.widget('noteText');
const resourceIdRegex = /^:\/[a-f0-9]+$/i
const resourceIdRegex = /^:\/[a-f0-9]+$/i;
const noteLinks = {};
const hasProtocol = function(s, protocols) {
@ -613,7 +596,7 @@ class AppGui {
if (s.indexOf(protocols[i] + '://') === 0) return true;
}
return false;
}
};
// By default, before the server is started, only the regular
// URLs appear in blue.
@ -637,7 +620,7 @@ class AppGui {
const link = noteLinks[path];
if (link.type === 'url') {
response.writeHead(302, { 'Location': link.url });
response.writeHead(302, { Location: link.url });
return true;
}
@ -650,11 +633,13 @@ class AppGui {
if (item.mime) response.setHeader('Content-Type', item.mime);
response.write(await Resource.content(item));
} else if (item.type_ === BaseModel.TYPE_NOTE) {
const html = [`
const html = [
`
<!DOCTYPE html>
<html class="client-nojs" lang="en" dir="ltr">
<head><meta charset="UTF-8"/></head><body>
`];
`,
];
html.push('<pre>' + htmlentities(item.title) + '\n\n' + htmlentities(item.body) + '</pre>');
html.push('</body></html>');
response.write(html.join(''));
@ -679,7 +664,7 @@ class AppGui {
noteLinks[index] = {
type: 'item',
id: url.substr(2),
};
};
} else if (hasProtocol(url, ['http', 'https', 'file', 'ftp'])) {
noteLinks[index] = {
type: 'url',
@ -711,7 +696,6 @@ class AppGui {
term.grabInput();
term.on('key', async (name, matches, data) => {
// -------------------------------------------------------------------------
// Handle special shortcuts
// -------------------------------------------------------------------------
@ -729,13 +713,13 @@ class AppGui {
return;
}
if (name === 'CTRL_C' ) {
if (name === 'CTRL_C') {
const cmd = this.app().currentCommand();
if (!cmd || !cmd.cancellable() || this.commandCancelCalled_) {
this.stdout(_('Press Ctrl+D or type "exit" to exit the application'));
} else {
this.commandCancelCalled_ = true;
await cmd.cancel()
await cmd.cancel();
this.commandCancelCalled_ = false;
}
return;
@ -744,8 +728,8 @@ class AppGui {
// -------------------------------------------------------------------------
// Build up current shortcut
// -------------------------------------------------------------------------
const now = (new Date()).getTime();
const now = new Date().getTime();
if (now - this.lastShortcutKeyTime_ > 800 || this.isSpecialKey(name)) {
this.currentShortcutKeys_ = [name];
@ -813,7 +797,6 @@ class AppGui {
process.exit(1);
});
}
}
AppGui.INPUT_MODE_NORMAL = 1;

View File

@ -1,10 +1,5 @@
const { BaseApplication } = require('lib/BaseApplication');
const { createStore, applyMiddleware } = require('redux');
const { reducer, defaultState } = require('lib/reducer.js');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { Database } = require('lib/database.js');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const ResourceService = require('lib/services/ResourceService');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
@ -12,21 +7,15 @@ const BaseItem = require('lib/models/BaseItem.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const Setting = require('lib/models/Setting.js');
const { Logger } = require('lib/logger.js');
const { sprintf } = require('sprintf-js');
const { reg } = require('lib/registry.js');
const { fileExtension } = require('lib/path-utils.js');
const { shim } = require('lib/shim.js');
const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/locale.js');
const os = require('os');
const { _ } = require('lib/locale.js');
const fs = require('fs-extra');
const { cliUtils } = require('./cli-utils.js');
const Cache = require('lib/Cache');
const WelcomeUtils = require('lib/WelcomeUtils');
const RevisionService = require('lib/services/RevisionService');
class Application extends BaseApplication {
constructor() {
super();
@ -75,7 +64,7 @@ class Application extends BaseApplication {
// const response = await cliUtils.promptMcq(msg, answers);
// if (!response) return null;
return output[response - 1];
// return output[response - 1];
} else {
return output.length ? output[0] : null;
}
@ -97,10 +86,12 @@ class Application extends BaseApplication {
const parent = options.parent ? options.parent : app().currentFolder();
const ItemClass = BaseItem.itemClass(type);
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { // Handle it as pattern
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) {
// Handle it as pattern
if (!parent) throw new Error(_('No notebook selected.'));
return await Note.previews(parent.id, { titlePattern: pattern });
} else { // Single item
} else {
// Single item
let item = null;
if (type == BaseModel.TYPE_NOTE) {
if (!parent) throw new Error(_('No notebook has been specified.'));
@ -126,15 +117,15 @@ class Application extends BaseApplication {
}
setupCommand(cmd) {
cmd.setStdout((text) => {
cmd.setStdout(text => {
return this.stdout(text);
});
cmd.setDispatcher((action) => {
cmd.setDispatcher(action => {
if (this.store()) {
return this.store().dispatch(action);
} else {
return (action) => {};
return action => {};
}
});
@ -185,9 +176,9 @@ class Application extends BaseApplication {
commands(uiType = null) {
if (!this.allCommandsLoaded_) {
fs.readdirSync(__dirname).forEach((path) => {
fs.readdirSync(__dirname).forEach(path => {
if (path.indexOf('command-') !== 0) return;
const ext = fileExtension(path)
const ext = fileExtension(path);
if (ext != 'js') return;
let CommandClass = require('./' + path);
@ -276,19 +267,27 @@ class Application extends BaseApplication {
dummyGui() {
return {
isDummy: () => { return true; },
prompt: (initialText = '', promptString = '', options = null) => { return cliUtils.prompt(initialText, promptString, options); },
isDummy: () => {
return true;
},
prompt: (initialText = '', promptString = '', options = null) => {
return cliUtils.prompt(initialText, promptString, options);
},
showConsole: () => {},
maximizeConsole: () => {},
stdout: (text) => { console.info(text); },
fullScreen: (b=true) => {},
stdout: text => {
console.info(text);
},
fullScreen: (b = true) => {},
exit: () => {},
showModalOverlay: (text) => {},
showModalOverlay: text => {},
hideModalOverlay: () => {},
stdoutMaxWidth: () => { return 100; },
stdoutMaxWidth: () => {
return 100;
},
forceRender: () => {},
termSaveState: () => {},
termRestoreState: (state) => {},
termRestoreState: state => {},
};
}
@ -300,7 +299,7 @@ class Application extends BaseApplication {
let outException = null;
try {
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name()));
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
await this.activeCommand_.action(cmdArgs);
} catch (error) {
@ -316,24 +315,24 @@ class Application extends BaseApplication {
async loadKeymaps() {
const defaultKeyMap = [
{ "keys": [":"], "type": "function", "command": "enter_command_line_mode" },
{ "keys": ["TAB"], "type": "function", "command": "focus_next" },
{ "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" },
{ "keys": ["UP"], "type": "function", "command": "move_up" },
{ "keys": ["DOWN"], "type": "function", "command": "move_down" },
{ "keys": ["PAGE_UP"], "type": "function", "command": "page_up" },
{ "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" },
{ "keys": ["ENTER"], "type": "function", "command": "activate" },
{ "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
{ "keys": [" "], "command": "todo toggle $n" },
{ "keys": ["tc"], "type": "function", "command": "toggle_console" },
{ "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
{ "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
{ "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 },
{ "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 },
{ "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 },
{ "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 },
{ "keys": ["dn"], "type": "prompt", "command": "mv $n \"\"", "cursorPosition": -2 }
{ keys: [':'], type: 'function', command: 'enter_command_line_mode' },
{ keys: ['TAB'], type: 'function', command: 'focus_next' },
{ keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' },
{ keys: ['UP'], type: 'function', command: 'move_up' },
{ keys: ['DOWN'], type: 'function', command: 'move_down' },
{ keys: ['PAGE_UP'], type: 'function', command: 'page_up' },
{ keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' },
{ keys: ['ENTER'], type: 'function', command: 'activate' },
{ keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' },
{ keys: [' '], command: 'todo toggle $n' },
{ keys: ['tc'], type: 'function', command: 'toggle_console' },
{ keys: ['tm'], type: 'function', command: 'toggle_metadata' },
{ keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 },
{ keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 },
{ keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 },
{ keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 },
{ keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 },
{ keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 },
];
// Filter the keymap item by command so that items in keymap.json can override
@ -341,7 +340,7 @@ class Application extends BaseApplication {
const itemsByCommand = {};
for (let i = 0; i < defaultKeyMap.length; i++) {
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]
itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i];
}
const filePath = Setting.value('profileDir') + '/keymap.json';
@ -374,7 +373,7 @@ class Application extends BaseApplication {
async start(argv) {
argv = await super.start(argv);
cliUtils.setStdout((object) => {
cliUtils.setStdout(object => {
return this.stdout(object);
});
@ -401,7 +400,8 @@ class Application extends BaseApplication {
// 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
} else {
// Otherwise open the GUI
this.initRedux();
const keymap = await this.loadKeymaps();
@ -421,7 +421,7 @@ class Application extends BaseApplication {
const tags = await Tag.allWithNotes();
ResourceService.runInBackground();
RevisionService.instance().runInBackground();
this.dispatch({
@ -435,7 +435,6 @@ class Application extends BaseApplication {
});
}
}
}
let application_ = null;
@ -446,4 +445,4 @@ function app() {
return application_;
}
module.exports = { app };
module.exports = { app };

View File

@ -14,11 +14,11 @@ async function handleAutocompletionPromise(line) {
//should look for commmands it could be
if (words.length == 1) {
if (names.indexOf(words[0]) === -1) {
let x = names.filter((n) => n.indexOf(words[0]) === 0);
let x = names.filter(n => n.indexOf(words[0]) === 0);
if (x.length === 1) {
return x[0] + ' ';
}
return x.length > 0 ? x.map((a) => a + ' ') : line;
return x.length > 0 ? x.map(a => a + ' ') : line;
} else {
return line;
}
@ -34,9 +34,9 @@ async function handleAutocompletionPromise(line) {
let next = words.length > 1 ? words[words.length - 1] : '';
let l = [];
if (next[0] === '-') {
for (let i = 0; i<metadata.options.length; i++) {
for (let i = 0; i < metadata.options.length; i++) {
const options = metadata.options[i][0].split(' ');
//if there are multiple options then they will be separated by comma and
//if there are multiple options then they will be separated by comma and
//space. The comma should be removed
if (options[0][options[0].length - 1] === ',') {
options[0] = options[0].slice(0, -1);
@ -55,20 +55,19 @@ async function handleAutocompletionPromise(line) {
if (l.length === 0) {
return line;
}
let ret = l.map(a=>toCommandLine(a));
let ret = l.map(a => toCommandLine(a));
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
return ret;
}
//Complete an argument
//Determine the number of positional arguments by counting the number of
//words that don't start with a - less one for the command name
const positionalArgs = words.filter((a)=>a.indexOf('-') !== 0).length - 1;
const positionalArgs = words.filter(a => a.indexOf('-') !== 0).length - 1;
let cmdUsage = yargParser(metadata.usage)['_'];
cmdUsage.splice(0, 1);
if (cmdUsage.length >= positionalArgs) {
let argName = cmdUsage[positionalArgs - 1];
argName = cliUtils.parseCommandArg(argName).name;
@ -76,23 +75,23 @@ async function handleAutocompletionPromise(line) {
if (argName == 'note' || argName == 'note-pattern') {
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
l.push(...notes.map((n) => n.title));
l.push(...notes.map(n => n.title));
}
if (argName == 'notebook') {
const folders = await Folder.search({ titlePattern: next + '*' });
l.push(...folders.map((n) => n.title));
l.push(...folders.map(n => n.title));
}
if (argName == 'item') {
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : [];
const folders = await Folder.search({ titlePattern: next + '*' });
l.push(...notes.map((n) => n.title), folders.map((n) => n.title));
l.push(...notes.map(n => n.title), folders.map(n => n.title));
}
if (argName == 'tag') {
let tags = await Tag.search({ titlePattern: next + '*' });
l.push(...tags.map((n) => n.title));
l.push(...tags.map(n => n.title));
}
if (argName == 'file') {
@ -113,12 +112,11 @@ async function handleAutocompletionPromise(line) {
if (l.length === 1) {
return toCommandLine([...words.slice(0, -1), l[0]]);
} else if (l.length > 1) {
let ret = l.map(a=>toCommandLine(a));
let ret = l.map(a => toCommandLine(a));
ret.prefix = toCommandLine(words.slice(0, -1)) + ' ';
return ret;
}
return line;
}
function handleAutocompletion(str, callback) {
handleAutocompletionPromise(str).then(function(res) {
@ -127,19 +125,21 @@ function handleAutocompletion(str, callback) {
}
function toCommandLine(args) {
if (Array.isArray(args)) {
return args.map(function(a) {
if(a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
return "'" + a + "'";
} else if (a.indexOf("'") !== -1) {
return '"' + a + '"';
} else {
return a;
}
}).join(' ');
return args
.map(function(a) {
if (a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
return '\'' + a + '\'';
} else if (a.indexOf('\'') !== -1) {
return '"' + a + '"';
} else {
return a;
}
})
.join(' ');
} else {
if(args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
return "'" + args + "' ";
} else if (args.indexOf("'") !== -1) {
if (args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
return '\'' + args + '\' ';
} else if (args.indexOf('\'') !== -1) {
return '"' + args + '" ';
} else {
return args + ' ';
@ -151,9 +151,9 @@ function getArguments(line) {
let inDoubleQuotes = false;
let currentWord = '';
let parsed = [];
for(let i = 0; i<line.length; i++) {
if(line[i] === '"') {
if(inDoubleQuotes) {
for (let i = 0; i < line.length; i++) {
if (line[i] === '"') {
if (inDoubleQuotes) {
inDoubleQuotes = false;
//maybe push word to parsed?
//currentWord += '"';
@ -161,8 +161,8 @@ function getArguments(line) {
inDoubleQuotes = true;
//currentWord += '"';
}
} else if(line[i] === "'") {
if(inSingleQuotes) {
} else if (line[i] === '\'') {
if (inSingleQuotes) {
inSingleQuotes = false;
//maybe push word to parsed?
//currentWord += "'";
@ -170,8 +170,7 @@ function getArguments(line) {
inSingleQuotes = true;
//currentWord += "'";
}
} else if (/\s/.test(line[i]) &&
!(inDoubleQuotes || inSingleQuotes)) {
} else if (/\s/.test(line[i]) && !(inDoubleQuotes || inSingleQuotes)) {
if (currentWord !== '') {
parsed.push(currentWord);
currentWord = '';

View File

@ -2,7 +2,6 @@ const { _ } = require('lib/locale.js');
const { reg } = require('lib/registry.js');
class BaseCommand {
constructor() {
this.stdout_ = null;
this.prompt_ = null;
@ -93,7 +92,6 @@ class BaseCommand {
logger() {
return reg.logger();
}
}
module.exports = { BaseCommand };
module.exports = { BaseCommand };

View File

@ -1,7 +1,7 @@
const fs = require('fs-extra');
const { fileExtension, basename, dirname } = require('lib/path-utils.js');
const { fileExtension, dirname } = require('lib/path-utils.js');
const wrap_ = require('word-wrap');
const { _, setLocale, languageCode } = require('lib/locale.js');
const { languageCode } = require('lib/locale.js');
const rootDir = dirname(dirname(__dirname));
const MAX_WIDTH = 78;
@ -22,14 +22,14 @@ function renderOptions(options) {
let option = options[i];
const flag = option[0];
const indent = INDENT + INDENT + ' '.repeat(optionColWidth + 2);
let r = wrap(option[1], indent);
r = r.substr(flag.length + (INDENT + INDENT).length);
r = INDENT + INDENT + flag + r;
output.push(r);
}
return output.join("\n");
return output.join('\n');
}
function renderCommand(cmd) {
@ -44,14 +44,14 @@ function renderCommand(cmd) {
output.push('');
output.push(optionString);
}
return output.join("\n");
return output.join('\n');
}
function getCommands() {
let output = [];
fs.readdirSync(__dirname).forEach((path) => {
fs.readdirSync(__dirname).forEach(path => {
if (path.indexOf('command-') !== 0) return;
const ext = fileExtension(path)
const ext = fileExtension(path);
if (ext != 'js') return;
let CommandClass = require('./' + path);
@ -87,14 +87,14 @@ function getHeader() {
let description = [];
description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.');
description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.');
description.push("\n\n");
description.push('\n\n');
description.push('The notes can be synchronised with various target including the file system (for example with a network directory) or with Microsoft OneDrive.');
description.push("\n\n");
description.push('\n\n');
description.push('Notes exported from Evenotes via .enex files can be imported into Joplin, including the formatted content, resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).');
output.push(wrap(description.join(''), INDENT));
return output.join("\n");
return output.join('\n');
}
function getFooter() {
@ -113,7 +113,7 @@ function getFooter() {
const licenseText = fs.readFileSync(filePath, 'utf8');
output.push(wrap(licenseText, INDENT));
return output.join("\n");
return output.join('\n');
}
async function main() {
@ -128,12 +128,12 @@ async function main() {
}
const headerText = getHeader();
const commandsText = commandBlocks.join("\n\n");
const commandsText = commandBlocks.join('\n\n');
const footerText = getFooter();
console.info(headerText + "\n\n" + 'USAGE' + "\n\n" + commandsText + "\n\n" + footerText);
console.info(headerText + '\n\n' + 'USAGE' + '\n\n' + commandsText + '\n\n' + footerText);
}
main().catch((error) => {
main().catch(error => {
console.error(error);
});
});

View File

@ -1,4 +1,4 @@
"use strict"
'use strict';
const fs = require('fs-extra');
const { Logger } = require('lib/logger.js');
@ -10,7 +10,7 @@ const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const { sprintf } = require('sprintf-js');
const exec = require('child_process').exec
const exec = require('child_process').exec;
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled promise rejection', p, 'reason:', reason);
@ -32,8 +32,8 @@ db.setLogger(dbLogger);
function createClient(id) {
return {
'id': id,
'profileDir': baseDir + '/client' + id,
id: id,
profileDir: baseDir + '/client' + id,
};
}
@ -72,14 +72,7 @@ function assertEquals(expected, real) {
}
async function clearDatabase() {
await db.transactionExecBatch([
'DELETE FROM folders',
'DELETE FROM notes',
'DELETE FROM tags',
'DELETE FROM note_tags',
'DELETE FROM resources',
'DELETE FROM deleted_items',
]);
await db.transactionExecBatch(['DELETE FROM folders', 'DELETE FROM notes', 'DELETE FROM tags', 'DELETE FROM note_tags', 'DELETE FROM resources', 'DELETE FROM deleted_items']);
}
const testUnits = {};
@ -101,7 +94,7 @@ testUnits.testFolders = async () => {
folders = await Folder.all();
assertEquals(0, folders.length);
}
};
testUnits.testNotes = async () => {
await execCommand(client, 'mkbook nb1');
@ -121,16 +114,16 @@ testUnits.testNotes = async () => {
notes = await Note.all();
assertEquals(2, notes.length);
await execCommand(client, "rm -f 'blabla*'");
await execCommand(client, 'rm -f \'blabla*\'');
notes = await Note.all();
assertEquals(2, notes.length);
await execCommand(client, "rm -f 'n*'");
await execCommand(client, 'rm -f \'n*\'');
notes = await Note.all();
assertEquals(0, notes.length);
}
};
testUnits.testCat = async () => {
await execCommand(client, 'mkbook nb1');
@ -145,7 +138,7 @@ testUnits.testCat = async () => {
r = await execCommand(client, 'cat -v mynote');
assertTrue(r.indexOf(note.id) >= 0);
}
};
testUnits.testConfig = async () => {
await execCommand(client, 'config editor vim');
@ -159,7 +152,7 @@ testUnits.testConfig = async () => {
let r = await execCommand(client, 'config');
assertTrue(r.indexOf('editor') >= 0);
assertTrue(r.indexOf('subl') >= 0);
}
};
testUnits.testCp = async () => {
await execCommand(client, 'mkbook nb2');
@ -180,7 +173,7 @@ testUnits.testCp = async () => {
notes = await Note.previews(f2.id);
assertEquals(1, notes.length);
assertEquals(notesF1[0].title, notes[0].title);
}
};
testUnits.testLs = async () => {
await execCommand(client, 'mkbook nb1');
@ -190,7 +183,7 @@ testUnits.testLs = async () => {
assertTrue(r.indexOf('note1') >= 0);
assertTrue(r.indexOf('note2') >= 0);
}
};
testUnits.testMv = async () => {
await execCommand(client, 'mkbook nb2');
@ -210,14 +203,14 @@ testUnits.testMv = async () => {
await execCommand(client, 'mknote note2');
await execCommand(client, 'mknote note3');
await execCommand(client, 'mknote blabla');
await execCommand(client, "mv 'note*' nb2");
await execCommand(client, 'mv \'note*\' nb2');
notes1 = await Note.previews(f1.id);
notes2 = await Note.previews(f2.id);
assertEquals(1, notes1.length);
assertEquals(4, notes2.length);
}
};
async function main(argv) {
await fs.remove(baseDir);
@ -243,7 +236,7 @@ async function main(argv) {
}
}
main(process.argv).catch((error) => {
main(process.argv).catch(error => {
console.info('');
logger.error(error);
});
});

View File

@ -16,7 +16,7 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
for (let j = 0; j < row.length; j++) {
let item = row[j];
let width = item ? item.toString().length : 0;
@ -26,7 +26,6 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
}
}
let lines = [];
for (let row = 0; row < rows.length; row++) {
let line = [];
for (let col = 0; col < colWidths.length; col++) {
@ -37,7 +36,7 @@ cliUtils.printArray = function(logFunction, rows, headers = null) {
}
logFunction(line.join(' '));
}
}
};
cliUtils.parseFlags = function(flags) {
let output = {};
@ -56,7 +55,7 @@ cliUtils.parseFlags = function(flags) {
}
}
return output;
}
};
cliUtils.parseCommandArg = function(arg) {
if (arg.length <= 2) throw new Error('Invalid command arg: ' + arg);
@ -72,7 +71,7 @@ cliUtils.parseCommandArg = function(arg) {
} else {
throw new Error('Invalid command arg: ' + arg);
}
}
};
cliUtils.makeCommandArgs = function(cmd, argv) {
let cmdUsage = cmd.usage();
@ -85,7 +84,6 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
for (let i = 0; i < options.length; i++) {
if (options[i].length != 2) throw new Error('Invalid options: ' + options[i]);
let flags = options[i][0];
let text = options[i][1];
flags = cliUtils.parseFlags(flags);
@ -125,27 +123,27 @@ cliUtils.makeCommandArgs = function(cmd, argv) {
output.options = argOptions;
return output;
}
};
cliUtils.promptMcq = function(message, answers) {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
});
message += "\n\n";
message += '\n\n';
for (let n in answers) {
if (!answers.hasOwnProperty(n)) continue;
message += _('%s: %s', n, answers[n]) + "\n";
message += _('%s: %s', n, answers[n]) + '\n';
}
message += "\n";
message += '\n';
message += _('Your choice: ');
return new Promise((resolve, reject) => {
rl.question(message, (answer) => {
rl.question(message, answer => {
rl.close();
if (!(answer in answers)) {
@ -156,7 +154,7 @@ cliUtils.promptMcq = function(message, answers) {
resolve(answer);
});
});
}
};
cliUtils.promptConfirm = function(message, answers = null) {
if (!answers) answers = [_('Y'), _('n')];
@ -164,19 +162,19 @@ cliUtils.promptConfirm = function(message, answers = null) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
});
message += ' (' + answers.join('/') + ')';
return new Promise((resolve, reject) => {
rl.question(message + ' ', (answer) => {
rl.question(message + ' ', answer => {
const ok = !answer || answer.toLowerCase() == answers[0].toLowerCase();
rl.close();
resolve(ok);
});
});
}
};
// Note: initialText is there to have the same signature as statusBar.prompt() so that
// it can be a drop-in replacement, however initialText is not used (and cannot be
@ -189,10 +187,9 @@ cliUtils.prompt = function(initialText = '', promptString = ':', options = null)
const mutableStdout = new Writable({
write: function(chunk, encoding, callback) {
if (!this.muted)
process.stdout.write(chunk, encoding);
if (!this.muted) process.stdout.write(chunk, encoding);
callback();
}
},
});
const rl = readline.createInterface({
@ -204,15 +201,15 @@ cliUtils.prompt = function(initialText = '', promptString = ':', options = null)
return new Promise((resolve, reject) => {
mutableStdout.muted = false;
rl.question(promptString, (answer) => {
rl.question(promptString, answer => {
rl.close();
if (!!options.secure) this.stdout_('');
if (options.secure) this.stdout_('');
resolve(answer);
});
mutableStdout.muted = !!options.secure;
});
}
};
let redrawStarted_ = false;
let redrawLastLog_ = null;
@ -220,7 +217,7 @@ let redrawLastUpdateTime_ = 0;
cliUtils.setStdout = function(v) {
this.stdout_ = v;
}
};
cliUtils.redraw = function(s) {
const now = time.unixMs();
@ -233,8 +230,8 @@ cliUtils.redraw = function(s) {
redrawLastLog_ = s;
}
redrawStarted_ = true;
}
redrawStarted_ = true;
};
cliUtils.redrawDone = function() {
if (!redrawStarted_) return;
@ -245,6 +242,6 @@ cliUtils.redrawDone = function() {
redrawLastLog_ = null;
redrawStarted_ = false;
}
};
module.exports = { cliUtils };
module.exports = { cliUtils };

View File

@ -1,19 +1,12 @@
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';
}
@ -23,15 +16,19 @@ class Command extends BaseCommand {
}
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);
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) {
@ -70,8 +67,8 @@ class Command extends BaseCommand {
lines.push('}');
lines.push('```');
lines.push('');
lines.push('# Authorisation')
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('');
@ -293,7 +290,6 @@ class Command extends BaseCommand {
this.stdout(lines.join('\n'));
}
}
module.exports = Command;

View File

@ -3,10 +3,8 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const { shim } = require('lib/shim.js');
const fs = require('fs-extra');
class Command extends BaseCommand {
usage() {
return 'attach <note> <file>';
}
@ -26,7 +24,6 @@ class Command extends BaseCommand {
await shim.attachFileToNote(note, localFilePath);
}
}
module.exports = Command;
module.exports = Command;

View File

@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {
usage() {
return 'cat <note>';
}
@ -16,9 +14,7 @@ class Command extends BaseCommand {
}
options() {
return [
['-v, --verbose', _('Displays the complete information about note.')],
];
return [['-v, --verbose', _('Displays the complete information about note.')]];
}
async action(args) {
@ -30,10 +26,13 @@ class Command extends BaseCommand {
const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item);
this.stdout(content);
app().gui().showConsole();
app().gui().maximizeConsole();
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
}
}
module.exports = Command;
module.exports = Command;

View File

@ -4,25 +4,22 @@ const { app } = require('./app.js');
const Setting = require('lib/models/Setting.js');
class Command extends BaseCommand {
usage() {
return 'config [name] [value]';
}
description() {
return _("Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.");
return _('Gets or sets a config value. If [value] is not provided, it will show the value of [name]. If neither [name] nor [value] is provided, it will list the current configuration.');
}
options() {
return [
['-v, --verbose', _('Also displays unset and hidden config variables.')],
];
return [['-v, --verbose', _('Also displays unset and hidden config variables.')]];
}
async action(args) {
const verbose = args.options.verbose;
const renderKeyValue = (name) => {
const renderKeyValue = name => {
const md = Setting.settingMetadata(name);
let value = Setting.value(name);
if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value);
@ -33,7 +30,7 @@ class Command extends BaseCommand {
} else {
return _('%s = %s', name, value);
}
}
};
if (!args.name && !args.value) {
let keys = Setting.keys(!verbose, 'cli');
@ -43,15 +40,23 @@ class Command extends BaseCommand {
if (!verbose && !value) continue;
this.stdout(renderKeyValue(keys[i]));
}
app().gui().showConsole();
app().gui().maximizeConsole();
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
return;
}
if (args.name && !args.value) {
this.stdout(renderKeyValue(args.name));
app().gui().showConsole();
app().gui().maximizeConsole();
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
return;
}
@ -64,7 +69,6 @@ class Command extends BaseCommand {
await Setting.saveAll();
}
}
module.exports = Command;
module.exports = Command;

View File

@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {
usage() {
return 'cp <note> [notebook]';
}
@ -33,7 +31,6 @@ class Command extends BaseCommand {
Note.updateGeolocation(newNote.id);
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -2,12 +2,10 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {
return 'done <note>';
}
@ -35,7 +33,6 @@ class Command extends BaseCommand {
async action(args) {
await Command.handleAction(this, args, true);
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,12 +1,9 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
class Command extends BaseCommand {
usage() {
return 'dump';
}
@ -35,10 +32,9 @@ class Command extends BaseCommand {
}
items = items.concat(tags);
this.stdout(JSON.stringify(items));
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,9 +1,7 @@
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 Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim');
@ -12,7 +10,6 @@ const imageType = require('image-type');
const readChunk = require('read-chunk');
class Command extends BaseCommand {
usage() {
return 'e2ee <command> [path]';
}
@ -35,7 +32,7 @@ class Command extends BaseCommand {
const options = args.options;
const askForMasterKey = async (error) => {
const askForMasterKey = async error => {
const masterKeyId = error.masterKeyId;
const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true });
if (!password) {
@ -45,7 +42,7 @@ class Command extends BaseCommand {
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 });
@ -100,7 +97,7 @@ class Command extends BaseCommand {
while (true) {
try {
const outputDir = options.output ? options.output : require('os').tmpdir();
let outFile = outputDir + '/' + pathUtils.filename(args.path) + '.' + Date.now() + '.bin';
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);
@ -128,19 +125,17 @@ class Command extends BaseCommand {
if (args.command === 'target-status') {
const fs = require('fs-extra');
const pathUtils = require('lib/path-utils.js');
const fsDriver = new (require('lib/fs-driver-node.js').FsDriverNode)();
const targetPath = args.path;
if (!targetPath) throw new Error('Please specify the sync target path.');
const dirPaths = function(targetPath) {
let paths = [];
fs.readdirSync(targetPath).forEach((path) => {
fs.readdirSync(targetPath).forEach(path => {
paths.push(path);
});
return paths;
}
};
let itemCount = 0;
let resourceCount = 0;
@ -224,7 +219,6 @@ class Command extends BaseCommand {
return;
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -3,15 +3,11 @@ const { BaseCommand } = require('./base-command.js');
const { uuid } = require('lib/uuid.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const BaseModel = require('lib/BaseModel.js');
const { cliUtils } = require('./cli-utils.js');
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {
return 'edit <note>';
}
@ -21,20 +17,19 @@ class Command extends BaseCommand {
}
async action(args) {
let watcher = null;
let tempFilePath = null;
const onFinishedEditing = async () => {
if (tempFilePath) fs.removeSync(tempFilePath);
}
};
const textEditorPath = () => {
if (Setting.value('editor')) return Setting.value('editor');
if (process.env.EDITOR) return process.env.EDITOR;
throw new Error(_('No text editor is defined. Please set it using `config editor <editor-path>`'));
}
};
try {
try {
// -------------------------------------------------------------------------
// Load note or create it if it doesn't exist
// -------------------------------------------------------------------------
@ -76,18 +71,30 @@ class Command extends BaseCommand {
this.logger().info('Disabling fullscreen...');
app().gui().showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
await app().gui().forceRender();
const termState = app().gui().termSaveState();
app()
.gui()
.showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.'));
await app()
.gui()
.forceRender();
const termState = app()
.gui()
.termSaveState();
const spawnSync = require('child_process').spawnSync;
const spawnSync = require('child_process').spawnSync;
const result = spawnSync(editorPath, editorArgs, { stdio: 'inherit' });
if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message));
app().gui().termRestoreState(termState);
app().gui().hideModalOverlay();
app().gui().forceRender();
app()
.gui()
.termRestoreState(termState);
app()
.gui()
.hideModalOverlay();
app()
.gui()
.forceRender();
// -------------------------------------------------------------------------
// Save the note and clean up
@ -107,13 +114,11 @@ class Command extends BaseCommand {
});
await onFinishedEditing();
} catch(error) {
} catch (error) {
await onFinishedEditing();
throw error;
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -3,7 +3,6 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
class Command extends BaseCommand {
usage() {
return 'exit';
}
@ -19,7 +18,6 @@ class Command extends BaseCommand {
async action(args) {
await app().exit();
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,13 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { Database } = require('lib/database.js');
const { app } = require('./app.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale.js');
const { ReportService } = require('lib/services/report.js');
const fs = require('fs-extra');
class Command extends BaseCommand {
usage() {
return 'export-sync-status';
}
@ -23,14 +20,17 @@ class Command extends BaseCommand {
async action(args) {
const service = new ReportService();
const csv = await service.basicItemList({ format: 'csv' });
const filePath = Setting.value('profileDir') + '/syncReport-' + (new Date()).getTime() + '.csv';
const filePath = Setting.value('profileDir') + '/syncReport-' + new Date().getTime() + '.csv';
await fs.writeFileSync(filePath, csv);
this.stdout('Sync status exported to ' + filePath);
app().gui().showConsole();
app().gui().maximizeConsole();
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,14 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const InteropService = require('lib/services/InteropService.js');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const { reg } = require('lib/registry.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const fs = require('fs-extra');
class Command extends BaseCommand {
usage() {
return 'export <path>';
}
@ -19,17 +15,14 @@ class Command extends BaseCommand {
options() {
const service = new InteropService();
const formats = service.modules()
const formats = service
.modules()
.filter(m => m.type === 'exporter')
.map(m => m.format + (m.description ? ' (' + m.description + ')' : ''));
return [
['--format <format>', _('Destination format: %s', formats.join(', '))],
['--note <note>', _('Exports only the given note.')],
['--notebook <notebook>', _('Exports only the given notebook.')],
];
return [['--format <format>', _('Destination format: %s', formats.join(', '))], ['--note <note>', _('Exports only the given note.')], ['--notebook <notebook>', _('Exports only the given notebook.')]];
}
async action(args) {
let exportOptions = {};
exportOptions.path = args.path;
@ -37,25 +30,20 @@ class Command extends BaseCommand {
exportOptions.format = args.options.format ? args.options.format : 'jex';
if (args.options.note) {
const notes = await app().loadItems(BaseModel.TYPE_NOTE, args.options.note, { parent: app().currentFolder() });
if (!notes.length) throw new Error(_('Cannot find "%s".', args.options.note));
exportOptions.sourceNoteIds = notes.map((n) => n.id);
exportOptions.sourceNoteIds = notes.map(n => n.id);
} else if (args.options.notebook) {
const folders = await app().loadItems(BaseModel.TYPE_FOLDER, args.options.notebook);
if (!folders.length) throw new Error(_('Cannot find "%s".', args.options.notebook));
exportOptions.sourceFolderIds = folders.map((n) => n.id);
exportOptions.sourceFolderIds = folders.map(n => n.id);
}
const service = new InteropService();
const result = await service.export(exportOptions);
result.warnings.map((w) => this.stdout(w));
result.warnings.map(w => this.stdout(w));
}
}
module.exports = Command;
module.exports = Command;

View File

@ -2,11 +2,9 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {
usage() {
return 'geoloc <note>';
}
@ -23,9 +21,10 @@ class Command extends BaseCommand {
const url = Note.geolocationUrl(item);
this.stdout(url);
app().gui().showConsole();
app()
.gui()
.showConsole();
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,14 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { renderCommandHelp } = require('./help-utils.js');
const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting.js');
const { wrap } = require('lib/string-utils.js');
const { _ } = require('lib/locale.js');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {
usage() {
return 'help [command]';
}
@ -28,7 +24,7 @@ class Command extends BaseCommand {
output.push(command);
}
output.sort((a, b) => a.name() < b.name() ? -1 : +1);
output.sort((a, b) => (a.name() < b.name() ? -1 : +1));
return output;
}
@ -40,31 +36,37 @@ class Command extends BaseCommand {
this.stdout(_('For information on how to customise the shortcuts please visit %s', 'https://joplinapp.org/terminal/#shortcuts'));
this.stdout('');
if (app().gui().isDummy()) {
if (
app()
.gui()
.isDummy()
) {
throw new Error(_('Shortcuts are not available in CLI mode.'));
}
const keymap = app().gui().keymap();
const keymap = app()
.gui()
.keymap();
let rows = [];
for (let i = 0; i < keymap.length; i++) {
const item = keymap[i];
const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k);
const keys = item.keys.map(k => (k === ' ' ? '(SPACE)' : k));
rows.push([keys.join(', '), item.command]);
}
cliUtils.printArray(this.stdout.bind(this), rows);
} else if (args.command === 'all') {
const commands = this.allCommands();
const output = commands.map((c) => renderCommandHelp(c));
const output = commands.map(c => renderCommandHelp(c));
this.stdout(output.join('\n\n'));
} else if (args.command) {
const command = app().findCommandByName(args['command']);
if (!command) throw new Error(_('Cannot find "%s".', args.command));
this.stdout(renderCommandHelp(command, stdoutWidth));
} else {
const commandNames = this.allCommands().map((a) => a.name());
const commandNames = this.allCommands().map(a => a.name());
this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.'));
this.stdout('');
@ -82,10 +84,13 @@ class Command extends BaseCommand {
this.stdout(_('For the list of keyboard shortcuts and config options, type `help keymap`'));
}
app().gui().showConsole();
app().gui().maximizeConsole();
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,17 +1,11 @@
const { BaseCommand } = require('./base-command.js');
const InteropService = require('lib/services/InteropService.js');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const { filename, basename, fileExtension } = require('lib/path-utils.js');
const { importEnex } = require('lib/import-enex');
const { cliUtils } = require('./cli-utils.js');
const { reg } = require('lib/registry.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const fs = require('fs-extra');
class Command extends BaseCommand {
usage() {
return 'import <path> [notebook]';
}
@ -22,14 +16,14 @@ class Command extends BaseCommand {
options() {
const service = new InteropService();
const formats = service.modules().filter(m => m.type === 'importer').map(m => m.format);
const formats = service
.modules()
.filter(m => m.type === 'importer')
.map(m => m.format);
return [
['--format <format>', _('Source format: %s', (['auto'].concat(formats)).join(', '))],
['-f, --force', _('Do not ask for confirmation.')],
];
return [['--format <format>', _('Source format: %s', ['auto'].concat(formats).join(', '))], ['-f, --force', _('Do not ask for confirmation.')]];
}
async action(args) {
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook);
@ -44,7 +38,7 @@ class Command extends BaseCommand {
// onProgress/onError supported by Enex import only
importOptions.onProgress = (progressState) => {
importOptions.onProgress = progressState => {
let line = [];
line.push(_('Found: %d.', progressState.loaded));
line.push(_('Created: %d.', progressState.created));
@ -56,20 +50,21 @@ class Command extends BaseCommand {
cliUtils.redraw(lastProgress);
};
importOptions.onError = (error) => {
importOptions.onError = error => {
let s = error.trace ? error.trace : error.toString();
this.stdout(s);
};
app().gui().showConsole();
app()
.gui()
.showConsole();
this.stdout(_('Importing notes...'));
const service = new InteropService();
const result = await service.import(importOptions);
result.warnings.map((w) => this.stdout(w));
result.warnings.map(w => this.stdout(w));
cliUtils.redrawDone();
if (lastProgress) this.stdout(_('The notes have been imported: %s', lastProgress));
}
}
module.exports = Command;
module.exports = Command;

View File

@ -10,7 +10,6 @@ const { time } = require('lib/time-utils.js');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {
usage() {
return 'ls [note-pattern]';
}
@ -24,14 +23,7 @@ class Command extends BaseCommand {
}
options() {
return [
['-n, --limit <num>', _('Displays only the first top <num> notes.')],
['-s, --sort <field>', _('Sorts the item by <field> (eg. title, updated_time, created_time).')],
['-r, --reverse', _('Reverses the sorting order.')],
['-t, --type <type>', _('Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos.')],
['-f, --format <format>', _('Either "text" or "json"')],
['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')],
];
return [['-n, --limit <num>', _('Displays only the first top <num> notes.')], ['-s, --sort <field>', _('Sorts the item by <field> (eg. title, updated_time, created_time).')], ['-r, --reverse', _('Reverses the sorting order.')], ['-t, --type <type>', _('Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos.')], ['-f, --format <format>', _('Either "text" or "json"')], ['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')]];
}
async action(args) {
@ -105,7 +97,7 @@ class Command extends BaseCommand {
if (hasTodos) {
if (item.is_todo) {
row.push(sprintf('[%s]', !!item.todo_completed ? 'X' : ' '));
row.push(sprintf('[%s]', item.todo_completed ? 'X' : ' '));
} else {
row.push(' ');
}
@ -118,9 +110,7 @@ class Command extends BaseCommand {
cliUtils.printArray(this.stdout.bind(this), rows);
}
}
}
module.exports = Command;

View File

@ -2,10 +2,8 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const Folder = require('lib/models/Folder.js');
const { reg } = require('lib/registry.js');
class Command extends BaseCommand {
usage() {
return 'mkbook <new-notebook>';
}
@ -15,10 +13,9 @@ class Command extends BaseCommand {
}
async action(args) {
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
app().switchCurrentFolder(folder);
}
}
module.exports = Command;
module.exports = Command;

View File

@ -4,7 +4,6 @@ const { _ } = require('lib/locale.js');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {
usage() {
return 'mknote <new-note>';
}
@ -26,7 +25,6 @@ class Command extends BaseCommand {
app().switchCurrentFolder(app().currentFolder());
}
}
module.exports = Command;
module.exports = Command;

View File

@ -4,7 +4,6 @@ const { _ } = require('lib/locale.js');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {
usage() {
return 'mktodo <new-todo>';
}
@ -27,7 +26,6 @@ class Command extends BaseCommand {
app().switchCurrentFolder(app().currentFolder());
}
}
module.exports = Command;
module.exports = Command;

View File

@ -6,7 +6,6 @@ const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {
usage() {
return 'mv <note> [notebook]';
}
@ -18,7 +17,7 @@ class Command extends BaseCommand {
async action(args) {
const pattern = args['note'];
const destination = args['notebook'];
const folder = await Folder.loadByField('title', destination);
if (!folder) throw new Error(_('Cannot find "%s".', destination));
@ -29,7 +28,6 @@ class Command extends BaseCommand {
await Note.moveToFolder(notes[i].id, folder.id);
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -6,7 +6,6 @@ const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
class Command extends BaseCommand {
usage() {
return 'ren <item> <name>';
}
@ -35,7 +34,6 @@ class Command extends BaseCommand {
await Note.save(newItem);
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,14 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseItem = require('lib/models/BaseItem.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {
usage() {
return 'rmbook <notebook>';
}
@ -18,9 +14,7 @@ class Command extends BaseCommand {
}
options() {
return [
['-f, --force', _('Deletes the notebook without asking for confirmation.')],
];
return [['-f, --force', _('Deletes the notebook without asking for confirmation.')]];
}
async action(args) {
@ -34,7 +28,6 @@ class Command extends BaseCommand {
await Folder.delete(folder.id);
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,14 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseItem = require('lib/models/BaseItem.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseModel = require('lib/BaseModel.js');
const { cliUtils } = require('./cli-utils.js');
class Command extends BaseCommand {
usage() {
return 'rmnote <note-pattern>';
}
@ -18,9 +14,7 @@ class Command extends BaseCommand {
}
options() {
return [
['-f, --force', _('Deletes the notes without asking for confirmation.')],
];
return [['-f, --force', _('Deletes the notes without asking for confirmation.')]];
}
async action(args) {
@ -32,10 +26,9 @@ class Command extends BaseCommand {
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' });
if (!ok) return;
let ids = notes.map((n) => n.id);
let ids = notes.map(n => n.id);
await Note.batchDelete(ids);
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,15 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const { sprintf } = require('sprintf-js');
const { time } = require('lib/time-utils.js');
const { uuid } = require('lib/uuid.js');
class Command extends BaseCommand {
usage() {
return 'search <pattern> [notebook]';
}
@ -50,7 +45,6 @@ class Command extends BaseCommand {
id: searchId,
});
}
}
module.exports = Command;
module.exports = Command;

View File

@ -3,12 +3,9 @@ const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const { Database } = require('lib/database.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const BaseItem = require('lib/models/BaseItem.js');
class Command extends BaseCommand {
usage() {
return 'set <note> <name> [value]';
}
@ -45,7 +42,6 @@ class Command extends BaseCommand {
await Note.save(newNote);
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,12 +1,10 @@
const { BaseCommand } = require('./base-command.js');
const { Database } = require('lib/database.js');
const { app } = require('./app.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale.js');
const { ReportService } = require('lib/services/report.js');
class Command extends BaseCommand {
usage() {
return 'status';
}
@ -34,10 +32,13 @@ class Command extends BaseCommand {
}
}
app().gui().showConsole();
app().gui().maximizeConsole();
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
}
}
module.exports = Command;
module.exports = Command;

View File

@ -3,7 +3,6 @@ const { app } = require('./app.js');
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');
@ -14,7 +13,6 @@ const fs = require('fs-extra');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
class Command extends BaseCommand {
constructor() {
super();
this.syncTargetId_ = null;
@ -31,9 +29,7 @@ class Command extends BaseCommand {
}
options() {
return [
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
];
return [['--target <target>', _('Sync to provided target (defaults to sync.target config value)')]];
}
static lockFile(filePath) {
@ -66,13 +62,16 @@ class Command extends BaseCommand {
const syncTarget = reg.syncTarget(this.syncTargetId_);
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { // OneDrive
if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) {
// OneDrive
this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api());
const auth = await this.oneDriveApiUtils_.oauthDance({
log: (...s) => { return this.stdout(...s); }
log: (...s) => {
return this.stdout(...s);
},
});
this.oneDriveApiUtils_ = null;
Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null);
if (!auth) {
this.stdout(_('Authentication was not completed (did not receive an authentication token).'));
@ -80,7 +79,8 @@ class Command extends BaseCommand {
}
return true;
} else if (syncTargetMd.name === 'dropbox') { // Dropbox
} else if (syncTargetMd.name === 'dropbox') {
// Dropbox
const api = await syncTarget.api();
const loginUrl = api.loginUrl();
this.stdout(_('To allow Joplin to synchronise with Dropbox, please follow the steps below:'));
@ -118,7 +118,7 @@ class Command extends BaseCommand {
// Lock is unique per profile/database
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');
if (!(await fs.pathExists(lockFilePath))) await fs.writeFile(lockFilePath, 'synclock');
try {
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
@ -147,22 +147,26 @@ class Command extends BaseCommand {
const syncTarget = reg.syncTarget(this.syncTargetId_);
if (!await syncTarget.isAuthenticated()) {
app().gui().showConsole();
app().gui().maximizeConsole();
if (!(await syncTarget.isAuthenticated())) {
app()
.gui()
.showConsole();
app()
.gui()
.maximizeConsole();
const authDone = await this.doAuth();
if (!authDone) return cleanUp();
}
const sync = await syncTarget.synchronizer();
let options = {
onProgress: (report) => {
onProgress: report => {
let lines = Synchronizer.reportToLines(report);
if (lines.length) cliUtils.redraw(lines.join(' '));
},
onMessage: (msg) => {
onMessage: msg => {
cliUtils.redrawDone();
this.stdout(msg);
},
@ -237,7 +241,6 @@ class Command extends BaseCommand {
cancellable() {
return true;
}
}
module.exports = Command;
module.exports = Command;

View File

@ -6,7 +6,6 @@ const BaseModel = require('lib/BaseModel.js');
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {
return 'tag <tag-command> [tag] [note]';
}
@ -16,9 +15,7 @@ class Command extends BaseCommand {
}
options() {
return [
['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')],
];
return [['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')]];
}
async action(args) {
@ -50,7 +47,7 @@ class Command extends BaseCommand {
} else if (command == 'list') {
if (tag) {
let notes = await Tag.notes(tag.id);
notes.map((note) => {
notes.map(note => {
let line = '';
if (options.long) {
line += BaseModel.shortId(note.id);
@ -61,7 +58,7 @@ class Command extends BaseCommand {
if (note.is_todo) {
line += '[';
if (note.todo_completed) {
line += 'X';
line += 'X';
} else {
line += ' ';
}
@ -74,13 +71,14 @@ class Command extends BaseCommand {
});
} else {
let tags = await Tag.all();
tags.map((tag) => { this.stdout(tag.title); });
tags.map(tag => {
this.stdout(tag.title);
});
}
} else {
throw new Error(_('Invalid command: "%s"', command));
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -2,12 +2,10 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const { time } = require('lib/time-utils.js');
class Command extends BaseCommand {
usage() {
return 'todo <todo-command> <note-pattern>';
}
@ -39,12 +37,11 @@ class Command extends BaseCommand {
}
} else if (action == 'clear') {
toSave.is_todo = 0;
}
}
await Note.save(toSave);
}
}
}
module.exports = Command;
module.exports = Command;

View File

@ -1,15 +1,9 @@
const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const { time } = require('lib/time-utils.js');
const CommandDone = require('./command-done.js');
class Command extends BaseCommand {
usage() {
return 'undone <note>';
}
@ -21,7 +15,6 @@ class Command extends BaseCommand {
async action(args) {
await CommandDone.handleAction(this, args, false);
}
}
module.exports = Command;
module.exports = Command;

View File

@ -2,10 +2,8 @@ const { BaseCommand } = require('./base-command.js');
const { app } = require('./app.js');
const { _ } = require('lib/locale.js');
const BaseModel = require('lib/BaseModel.js');
const Folder = require('lib/models/Folder.js');
class Command extends BaseCommand {
usage() {
return 'use <notebook>';
}
@ -14,10 +12,6 @@ class Command extends BaseCommand {
return _('Switches to [notebook] - all further operations will happen within this notebook.');
}
autocomplete() {
return { data: autocompleteFolders };
}
compatibleUis() {
return ['cli'];
}
@ -27,7 +21,6 @@ class Command extends BaseCommand {
if (!folder) throw new Error(_('Cannot find "%s".', args['notebook']));
app().switchCurrentFolder(folder);
}
}
module.exports = Command;
module.exports = Command;

View File

@ -3,7 +3,6 @@ const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale.js');
class Command extends BaseCommand {
usage() {
return 'version';
}
@ -16,7 +15,6 @@ class Command extends BaseCommand {
const p = require('./package.json');
this.stdout(_('%s %s (%s)', p.name, p.version, Setting.value('env')));
}
}
module.exports = Command;
module.exports = Command;

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,6 @@
const TextWidget = require('tkwidgets/TextWidget.js');
class ConsoleWidget extends TextWidget {
constructor() {
super();
this.lines_ = [];
@ -16,7 +15,7 @@ class ConsoleWidget extends TextWidget {
}
get lastLine() {
return this.lines_.length ? this.lines_[this.lines_.length-1] : '';
return this.lines_.length ? this.lines_[this.lines_.length - 1] : '';
}
addLine(line) {
@ -40,13 +39,12 @@ class ConsoleWidget extends TextWidget {
if (this.lines_.length > this.maxLines_) {
this.lines_.splice(0, this.lines_.length - this.maxLines_);
}
this.text = this.lines_.join("\n");
this.text = this.lines_.join('\n');
this.updateText_ = false;
}
super.render();
}
}
module.exports = ConsoleWidget;
module.exports = ConsoleWidget;

View File

@ -5,7 +5,6 @@ const ListWidget = require('tkwidgets/ListWidget.js');
const _ = require('lib/locale.js')._;
class FolderListWidget extends ListWidget {
constructor() {
super();
@ -20,7 +19,7 @@ class FolderListWidget extends ListWidget {
this.updateItems_ = false;
this.trimItemTitle = false;
this.itemRenderer = (item) => {
this.itemRenderer = item => {
let output = [];
if (item === '-') {
output.push('-'.repeat(this.innerWidth));
@ -32,7 +31,7 @@ class FolderListWidget extends ListWidget {
output.push(_('Search:'));
output.push(item.title);
}
return output.join(' ');
};
}
@ -45,7 +44,6 @@ class FolderListWidget extends ListWidget {
output++;
folderId = folder.parent_id;
}
throw new Error('unreachable');
}
get selectedFolderId() {
@ -54,7 +52,7 @@ class FolderListWidget extends ListWidget {
set selectedFolderId(v) {
this.selectedFolderId_ = v;
this.updateIndexFromSelectedItemId()
this.updateIndexFromSelectedItemId();
this.invalidate();
}
@ -64,7 +62,7 @@ class FolderListWidget extends ListWidget {
set selectedSearchId(v) {
this.selectedSearchId_ = v;
this.updateIndexFromSelectedItemId()
this.updateIndexFromSelectedItemId();
this.invalidate();
}
@ -74,7 +72,7 @@ class FolderListWidget extends ListWidget {
set selectedTagId(v) {
this.selectedTagId_ = v;
this.updateIndexFromSelectedItemId()
this.updateIndexFromSelectedItemId();
this.invalidate();
}
@ -84,7 +82,7 @@ class FolderListWidget extends ListWidget {
set notesParentType(v) {
this.notesParentType_ = v;
this.updateIndexFromSelectedItemId()
this.updateIndexFromSelectedItemId();
this.invalidate();
}
@ -95,7 +93,7 @@ class FolderListWidget extends ListWidget {
set searches(v) {
this.searches_ = v;
this.updateItems_ = true;
this.updateIndexFromSelectedItemId()
this.updateIndexFromSelectedItemId();
this.invalidate();
}
@ -106,7 +104,7 @@ class FolderListWidget extends ListWidget {
set tags(v) {
this.tags_ = v;
this.updateItems_ = true;
this.updateIndexFromSelectedItemId()
this.updateIndexFromSelectedItemId();
this.invalidate();
}
@ -117,7 +115,7 @@ class FolderListWidget extends ListWidget {
set folders(v) {
this.folders_ = v;
this.updateItems_ = true;
this.updateIndexFromSelectedItemId()
this.updateIndexFromSelectedItemId();
this.invalidate();
}
@ -128,7 +126,7 @@ class FolderListWidget extends ListWidget {
}
return false;
}
render() {
if (this.updateItems_) {
this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId);
@ -136,7 +134,7 @@ class FolderListWidget extends ListWidget {
const previousParentType = this.notesParentType;
let newItems = [];
const orderFolders = (parentId) => {
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 : '';
@ -145,7 +143,7 @@ class FolderListWidget extends ListWidget {
if (this.folderHasChildren_(this.folders, f.id)) orderFolders(f.id);
}
}
}
};
orderFolders('');
@ -162,7 +160,7 @@ class FolderListWidget extends ListWidget {
this.items = newItems;
this.notesParentType = previousParentType;
this.updateIndexFromSelectedItemId(wasSelectedItemId)
this.updateIndexFromSelectedItemId(wasSelectedItemId);
this.updateItems_ = false;
}
@ -188,7 +186,6 @@ class FolderListWidget extends ListWidget {
const index = this.itemIndexByKey('id', itemId);
this.currentIndex = index >= 0 ? index : 0;
}
}
module.exports = FolderListWidget;
module.exports = FolderListWidget;

View File

@ -2,14 +2,13 @@ const Note = require('lib/models/Note.js');
const ListWidget = require('tkwidgets/ListWidget.js');
class NoteListWidget extends ListWidget {
constructor() {
super();
this.selectedNoteId_ = 0;
this.updateIndexFromSelectedNoteId_ = false;
this.itemRenderer = (note) => {
this.itemRenderer = note => {
let label = Note.displayTitle(note); // + ' ' + note.id;
if (note.is_todo) {
label = '[' + (note.todo_completed ? 'X' : ' ') + '] ' + label;
@ -32,7 +31,6 @@ class NoteListWidget extends ListWidget {
super.render();
}
}
module.exports = NoteListWidget;
module.exports = NoteListWidget;

View File

@ -2,7 +2,6 @@ const Note = require('lib/models/Note.js');
const TextWidget = require('tkwidgets/TextWidget.js');
class NoteMetadataWidget extends TextWidget {
constructor() {
super();
this.noteId_ = 0;
@ -30,7 +29,6 @@ class NoteMetadataWidget extends TextWidget {
this.text = this.note_ ? await Note.minimalSerializeForDisplay(this.note_) : '';
}
}
}
module.exports = NoteMetadataWidget;
module.exports = NoteMetadataWidget;

View File

@ -3,7 +3,6 @@ const TextWidget = require('tkwidgets/TextWidget.js');
const { _ } = require('lib/locale.js');
class NoteWidget extends TextWidget {
constructor() {
super();
this.noteId_ = 0;
@ -44,11 +43,11 @@ class NoteWidget extends TextWidget {
} else if (this.noteId_) {
this.doAsync('loadNote', async () => {
this.note_ = await Note.load(this.noteId_);
if (this.note_ && this.note_.encryption_applied) {
this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.');
} else {
this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : '';
this.text = this.note_ ? this.note_.title + '\n\n' + this.note_.body : '';
}
if (this.lastLoadedNoteId_ !== this.noteId_) this.scrollTop = 0;
@ -59,7 +58,6 @@ class NoteWidget extends TextWidget {
this.scrollTop = 0;
}
}
}
module.exports = NoteWidget;
module.exports = NoteWidget;

View File

@ -5,7 +5,6 @@ const stripAnsi = require('strip-ansi');
const { handleAutocompletion } = require('../autocompletion.js');
class StatusBarWidget extends BaseWidget {
constructor() {
super();
@ -75,7 +74,7 @@ class StatusBarWidget extends BaseWidget {
super.render();
const doSaveCursor = !this.promptActive;
if (doSaveCursor) this.term.saveCursor();
this.innerClear();
@ -87,14 +86,13 @@ class StatusBarWidget extends BaseWidget {
//const textStyle = this.promptActive ? (s) => s : chalk.bgBlueBright.white;
//const textStyle = (s) => s;
const textStyle = this.promptActive ? (s) => s : chalk.gray;
const textStyle = this.promptActive ? s => s : chalk.gray;
this.term.drawHLine(this.absoluteInnerX, this.absoluteInnerY, this.innerWidth, textStyle(' '));
this.term.moveTo(this.absoluteInnerX, this.absoluteInnerY);
if (this.promptActive) {
this.term.write(textStyle(this.promptState_.promptString));
if (this.inputEventEmitter_) {
@ -113,8 +111,8 @@ class StatusBarWidget extends BaseWidget {
history: this.history,
default: this.promptState_.initialText,
autoComplete: handleAutocompletion,
autoCompleteHint : true,
autoCompleteMenu : true,
autoCompleteHint: true,
autoCompleteMenu: true,
};
if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition;
@ -153,19 +151,15 @@ class StatusBarWidget extends BaseWidget {
// Only callback once everything has been cleaned up and reset
resolveFn(resolveResult);
});
} else {
for (let i = 0; i < this.items_.length; i++) {
const s = this.items_[i].substr(0, this.innerWidth - 1);
this.term.write(textStyle(s));
}
}
if (doSaveCursor) this.term.restoreCursor();
}
}
module.exports = StatusBarWidget;

View File

@ -1,10 +1,7 @@
const fs = require('fs-extra');
const { wrap } = require('lib/string-utils.js');
const Setting = require('lib/models/Setting.js');
const { fileExtension, basename, dirname } = require('lib/path-utils.js');
const { _, setLocale, languageCode } = require('lib/locale.js');
const { _ } = require('lib/locale.js');
const rootDir = dirname(dirname(__dirname));
const MAX_WIDTH = 78;
const INDENT = ' ';
@ -16,14 +13,14 @@ function renderTwoColumnData(options, baseIndent, width) {
let option = options[i];
const flag = option[0];
const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2);
let r = wrap(option[1], indent, width);
r = r.substr(flag.length + (baseIndent + INDENT).length);
r = baseIndent + INDENT + flag + r;
output.push(r);
}
return output.join("\n");
return output.join('\n');
}
function renderCommandHelp(cmd, width = null) {
@ -44,7 +41,7 @@ function renderCommandHelp(cmd, width = null) {
}
if (cmd.name() === 'config') {
const renderMetadata = (md) => {
const renderMetadata = md => {
let desc = [];
if (md.label) {
@ -67,13 +64,13 @@ function renderCommandHelp(cmd, width = null) {
} else if (md.type === Setting.TYPE_INT) {
defaultString = (md.value ? md.value : 0).toString();
} else if (md.type === Setting.TYPE_BOOL) {
defaultString = (md.value === true ? 'true' : 'false');
defaultString = md.value === true ? 'true' : 'false';
}
}
if (defaultString !== null) desc.push(_('Default: %s', defaultString));
return [md.key, desc.join("\n")];
return [md.key, desc.join('\n')];
};
output.push('');
@ -83,7 +80,7 @@ function renderCommandHelp(cmd, width = null) {
let keysValues = [];
const keys = Setting.keys(true, 'cli');
for (let i = 0; i < keys.length; i++) {
if (keysValues.length) keysValues.push(['','']);
if (keysValues.length) keysValues.push(['', '']);
const md = Setting.settingMetadata(keys[i]);
if (!md.label) continue;
keysValues.push(renderMetadata(md));
@ -91,8 +88,8 @@ function renderCommandHelp(cmd, width = null) {
output.push(renderTwoColumnData(keysValues, baseIndent, width));
}
return output.join("\n");
return output.join('\n');
}
function getOptionColWidth(options) {
@ -104,4 +101,4 @@ function getOptionColWidth(options) {
return output;
}
module.exports = { renderCommandHelp };
module.exports = { renderCommandHelp };

View File

@ -53,25 +53,25 @@ shimInit();
const application = app();
if (process.platform === "win32") {
var rl = require("readline").createInterface({
if (process.platform === 'win32') {
var rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout
output: process.stdout,
});
rl.on("SIGINT", function () {
process.emit("SIGINT");
rl.on('SIGINT', function() {
process.emit('SIGINT');
});
}
process.stdout.on('error', function( err ) {
process.stdout.on('error', function(err) {
// https://stackoverflow.com/questions/12329816/error-write-epipe-when-piping-node-output-to-head#15884508
if (err.code == "EPIPE") {
if (err.code == 'EPIPE') {
process.exit(0);
}
});
application.start(process.argv).catch((error) => {
application.start(process.argv).catch(error => {
if (error.code == 'flagError') {
console.error(error.message);
console.error(_('Type `joplin help` for usage information.'));
@ -81,4 +81,4 @@ application.start(process.argv).catch((error) => {
}
process.exit(1);
});
});

View File

@ -1,13 +1,11 @@
const { _ } = require('lib/locale.js');
const { netUtils } = require('lib/net-utils.js');
const http = require("http");
const urlParser = require("url");
const FormData = require('form-data');
const http = require('http');
const urlParser = require('url');
const enableServerDestroy = require('server-destroy');
class OneDriveApiNodeUtils {
constructor(api) {
this.api_ = api;
this.oauthServer_ = null;
@ -48,7 +46,7 @@ class OneDriveApiNodeUtils {
let authCodeUrl = this.api().authCodeUrl('http://localhost:' + port);
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
this.oauthServer_ = http.createServer();
let errorMessage = null;
@ -56,7 +54,7 @@ class OneDriveApiNodeUtils {
const url = urlParser.parse(request.url, true);
if (url.pathname === '/auth') {
response.writeHead(302, { 'Location': authCodeUrl });
response.writeHead(302, { Location: authCodeUrl });
response.end();
return;
}
@ -64,10 +62,10 @@ class OneDriveApiNodeUtils {
const query = url.query;
const writeResponse = (code, message) => {
response.writeHead(code, {"Content-Type": "text/html"});
response.writeHead(code, { 'Content-Type': 'text/html' });
response.write(this.makePage(message));
response.end();
}
};
// After the response has been received, don't destroy the server right
// away or the browser might display a connection reset error (even
@ -77,21 +75,24 @@ class OneDriveApiNodeUtils {
this.oauthServer_.destroy();
this.oauthServer_ = null;
}, 1000);
}
};
if (!query.code) return writeResponse(400, '"code" query parameter is missing');
this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then(() => {
writeResponse(200, _('The application has been authorised - you may now close this browser tab.'));
targetConsole.log('');
targetConsole.log(_('The application has been successfully authorised.'));
waitAndDestroy();
}).catch((error) => {
writeResponse(400, error.message);
targetConsole.log('');
targetConsole.log(error.message);
waitAndDestroy();
});
this.api()
.execTokenRequest(query.code, 'http://localhost:' + port.toString())
.then(() => {
writeResponse(200, _('The application has been authorised - you may now close this browser tab.'));
targetConsole.log('');
targetConsole.log(_('The application has been successfully authorised.'));
waitAndDestroy();
})
.catch(error => {
writeResponse(400, error.message);
targetConsole.log('');
targetConsole.log(error.message);
waitAndDestroy();
});
});
this.oauthServer_.on('close', () => {
@ -116,7 +117,6 @@ class OneDriveApiNodeUtils {
targetConsole.log('http://127.0.0.1:' + port + '/auth');
});
}
}
module.exports = { OneDriveApiNodeUtils };
module.exports = { OneDriveApiNodeUtils };

View File

@ -82,6 +82,7 @@
"jasmine": "^2.6.0"
},
"scripts": {
"test": "jasmine"
"test": "jasmine",
"postinstall": "cd .. && npm i"
}
}

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -53,4 +55,4 @@ describe('ArrayUtils', function() {
done();
});
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const os = require('os');
@ -27,7 +29,7 @@ describe('EnexToMd', function() {
it('should convert from Enex to Markdown', asyncTest(async () => {
const basePath = __dirname + '/enex_to_md';
const files = await shim.fsDriver().readDirStats(basePath);
for (let i = 0; i < files.length; i++) {
const htmlFilename = files[i].path;
if (htmlFilename.indexOf('.html') < 0) continue;
@ -41,10 +43,10 @@ describe('EnexToMd', function() {
let 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')
expectedMd = expectedMd.replace(/\r\n/g, '\n');
actualMd = actualMd.replace(/\r\n/g, '\n');
}
if (actualMd !== expectedMd) {
@ -60,9 +62,9 @@ describe('EnexToMd', function() {
expect(false).toBe(true);
// return;
} else {
expect(true).toBe(true)
expect(true).toBe(true);
}
}
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const os = require('os');
@ -29,7 +31,7 @@ describe('HtmlToMd', function() {
const basePath = __dirname + '/html_to_md';
const files = await shim.fsDriver().readDirStats(basePath);
const htmlToMd = new HtmlToMd();
for (let i = 0; i < files.length; i++) {
const htmlFilename = files[i].path;
if (htmlFilename.indexOf('.html') < 0) continue;
@ -39,14 +41,14 @@ describe('HtmlToMd', function() {
// if (htmlFilename !== 'mathjax_block.html') continue;
const htmlToMdOptions = {}
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']
htmlToMdOptions.anchorNames = ['first', 'second'];
}
const html = await shim.fsDriver().readFile(htmlPath);
@ -55,8 +57,8 @@ describe('HtmlToMd', function() {
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')
expectedMd = expectedMd.replace(/\r\n/g, '\n');
actualMd = actualMd.replace(/\r\n/g, '\n');
}
if (actualMd !== expectedMd) {
@ -74,9 +76,9 @@ describe('HtmlToMd', function() {
expect(false).toBe(true);
// return;
} else {
expect(true).toBe(true)
expect(true).toBe(true);
}
}
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -42,4 +44,4 @@ describe('StringUtils', function() {
done();
});
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
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');
@ -54,4 +56,4 @@ describe('TaskQueue', function() {
expect(results[1].error.message).toBe('e');
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -116,7 +118,7 @@ describe('Encryption', function() {
await service.loadMasterKey(masterKey, '123456', true);
let cipherText = await service.encryptString('some secret');
cipherText += "ABCDEFGHIJ";
cipherText += 'ABCDEFGHIJ';
let hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText));
@ -147,8 +149,8 @@ describe('Encryption', function() {
// Check that encrypted data is there
expect(!!deserialized.encryption_cipher_text).toBe(true);
encryptedNote = await Note.save(deserialized);
decryptedNote = await Note.decrypt(encryptedNote);
const encryptedNote = await Note.save(deserialized);
const decryptedNote = await Note.decrypt(encryptedNote);
expect(decryptedNote.title).toBe(note.title);
expect(decryptedNote.body).toBe(note.body);
@ -176,4 +178,4 @@ describe('Encryption', function() {
done();
});
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -46,8 +48,8 @@ describe('htmlUtils', function() {
return function(src) {
i++;
return urls[i];
}
}
};
};
for (let i = 0; i < testCases.length; i++) {
const md = testCases[i][0];
@ -102,4 +104,4 @@ describe('htmlUtils', function() {
done();
});
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -52,4 +54,4 @@ describe('markdownUtils', function() {
done();
});
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -38,29 +40,29 @@ describe('models_BaseItem', function() {
// 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 folder = await Folder.save({ title: 'folder1' });
let serialized = await Folder.serialize(folder);
serialized += "\nignore_me: true"
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);
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

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -26,10 +28,10 @@ describe('models_Folder', function() {
});
it('should tell if a notebook can be nested under another one', asyncTest(async () => {
let f1 = await Folder.save({ title: "folder1" });
let f2 = await Folder.save({ title: "folder2", parent_id: f1.id });
let f3 = await Folder.save({ title: "folder3", parent_id: f2.id });
let f4 = await Folder.save({ title: "folder4" });
let f1 = await Folder.save({ title: 'folder1' });
let f2 = await Folder.save({ title: 'folder2', parent_id: f1.id });
let f3 = await Folder.save({ title: 'folder3', parent_id: f2.id });
let f4 = await Folder.save({ title: 'folder4' });
expect(await Folder.canNestUnder(f1.id, f2.id)).toBe(false);
expect(await Folder.canNestUnder(f2.id, f2.id)).toBe(false);
@ -42,8 +44,8 @@ describe('models_Folder', function() {
}));
it('should recursively delete notes and sub-notebooks', asyncTest(async () => {
let f1 = await Folder.save({ title: "folder1" });
let f2 = await Folder.save({ title: "folder2", parent_id: f1.id });
let f1 = await Folder.save({ title: 'folder1' });
let f2 = await Folder.save({ title: 'folder2', parent_id: f1.id });
let n1 = await Note.save({ title: 'note1', parent_id: f2.id });
await Folder.delete(f1.id);
@ -55,9 +57,9 @@ describe('models_Folder', function() {
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 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');
@ -89,9 +91,9 @@ describe('models_Folder', function() {
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 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');
@ -100,7 +102,7 @@ describe('models_Folder', function() {
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 });
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);
@ -108,13 +110,13 @@ describe('models_Folder', function() {
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 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');
@ -125,4 +127,4 @@ describe('models_Folder', function() {
expect(folders[3].id).toBe(f2.id);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -25,7 +27,7 @@ describe('models_ItemChange', function() {
});
it('should delete old changes that have been processed', asyncTest(async () => {
const n1 = await Note.save({ title: "abcd efgh" }); // 3
const n1 = await Note.save({ title: 'abcd efgh' }); // 3
await ItemChange.waitForAllSaved();
@ -48,4 +50,4 @@ describe('models_ItemChange', function() {
expect(await ItemChange.lastChangeId()).toBe(0);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -21,7 +23,7 @@ describe('models_Note', function() {
});
it('should find resource and note IDs', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
let note2 = await Note.save({ title: 'ma deuxième note', body: 'Lien vers première note : ' + Note.markdownTag(note1), parent_id: folder1.id });
@ -67,7 +69,7 @@ describe('models_Note', function() {
}));
it('should change the type of notes', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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);
@ -86,9 +88,9 @@ describe('models_Note', function() {
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"});
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'],
@ -96,22 +98,22 @@ describe('models_Note', function() {
'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);
let unserialized = await Note.unserialize(serialized);
expect(unserialized.title).toBe(input.title);
expect(unserialized.body).toBe(input.body);
}
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars, require-atomic-updates */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -25,7 +27,7 @@ describe('models_Resource', function() {
});
it('should have a "done" fetch_status when created locally', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -34,7 +36,7 @@ describe('models_Resource', function() {
}));
it('should have a default local state', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -45,7 +47,7 @@ describe('models_Resource', function() {
}));
it('should save and delete local state', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -61,7 +63,7 @@ describe('models_Resource', function() {
}));
it('should resize the resource if the image is below the required dimensions', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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;
@ -76,7 +78,7 @@ describe('models_Resource', function() {
}));
it('should not resize the resource if the image is below the required dimensions', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -87,4 +89,4 @@ describe('models_Resource', function() {
expect(originalStat.size).toBe(newStat.size);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -41,7 +43,7 @@ describe('models_Revision', function() {
const newObject = {
one: '123',
three: '999',
}
};
const patch = Revision.createObjectPatch(oldObject, newObject);
const merged = Revision.applyObjectPatch(oldObject, patch);
@ -82,16 +84,16 @@ describe('models_Revision', function() {
-
+x
%5D Check `,
expected: [-1, +1],
},
{
patch: `@@ -1022,56 +1022,415 @@
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],
},
expected: [-(19+27+2), 17+67+4],
},
];
for (const test of tests) {
@ -101,4 +103,4 @@ describe('models_Revision', function() {
}
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -18,7 +20,7 @@ describe('models_Setting', function() {
const settings = {
'sync.5.path': 'http://example.com',
'sync.5.username': 'testing',
}
};
let output = Setting.subValues('sync.5', settings);
expect(output['path']).toBe('http://example.com');
@ -29,4 +31,4 @@ describe('models_Setting', function() {
expect('username' in output).toBe(false);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -22,7 +24,7 @@ describe('models_Tag', function() {
});
it('should add tags by title', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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']);
@ -32,7 +34,7 @@ describe('models_Tag', function() {
}));
it('should not allow renaming tag to existing tag names', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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']);
@ -44,7 +46,7 @@ describe('models_Tag', function() {
}));
it('should not return tags without notes', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
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']);
@ -57,4 +59,4 @@ describe('models_Tag', function() {
expect(tags.length).toBe(0);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { extractExecutablePath, quotePath, unquotePath, friendlySafeFilename, toFileProtocolPath} = require('lib/path-utils.js');
@ -50,7 +52,7 @@ describe('pathUtils', function() {
const t = testCases[i];
expect(quotePath(t[0])).toBe(t[1]);
expect(unquotePath(quotePath(t[0]))).toBe(t[0]);
}
}
done();
});
@ -68,7 +70,7 @@ describe('pathUtils', function() {
for (let i = 0; i < testCases.length; i++) {
const t = testCases[i];
expect(extractExecutablePath(t[0])).toBe(t[1]);
}
}
done();
});
@ -77,7 +79,7 @@ describe('pathUtils', function() {
const testCases_win32 = [
['C:\\handle\\space test', 'file:///C:/handle/space+test'],
['C:\\escapeplus\\+', 'file:///C:/escapeplus/%2B'],
['C:\\handle\\single quote\'', 'file:///C:/handle/single+quote%27'],
['C:\\handle\\single quote\'', 'file:///C:/handle/single+quote%27'],
];
const testCases_unixlike = [
['/handle/space test', 'file:///handle/space+test'],
@ -88,13 +90,13 @@ describe('pathUtils', function() {
for (let i = 0; i < testCases_win32.length; i++) {
const t = testCases_win32[i];
expect(toFileProtocolPath(t[0], 'win32')).toBe(t[1]);
}
}
for (let i = 0; i < testCases_unixlike.length; i++) {
const t = testCases_unixlike[i];
expect(toFileProtocolPath(t[0], 'linux')).toBe(t[1]);
}
}
done();
});
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -44,7 +46,7 @@ describe('services_InteropService', function() {
it('should export and import folders', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
folder1 = await Folder.load(folder1.id);
const filePath = exportDir() + '/test.jex';
@ -79,7 +81,7 @@ describe('services_InteropService', function() {
it('should export and import folders and notes', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: "folder1" });
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() + '/test.jex';
@ -118,7 +120,7 @@ describe('services_InteropService', function() {
it('should export and import notes to specific folder', asyncTest(async () => {
const service = new InteropService();
let folder1 = await Folder.save({ title: "folder1" });
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() + '/test.jex';
@ -138,7 +140,7 @@ describe('services_InteropService', function() {
it('should export and import tags', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
let tag1 = await Tag.save({ title: 'mon tag' });
tag1 = await Tag.load(tag1.id);
@ -178,7 +180,7 @@ describe('services_InteropService', function() {
it('should export and import resources', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
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');
note1 = await Note.load(note1.id);
@ -186,7 +188,7 @@ describe('services_InteropService', function() {
let resource1 = await Resource.load(resourceIds[0]);
await service.export({ path: filePath });
await Note.delete(note1.id);
await service.import({ path: filePath });
@ -214,11 +216,11 @@ describe('services_InteropService', function() {
it('should export and import single notes', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await service.export({ path: filePath, sourceNoteIds: [note1.id] });
await Note.delete(note1.id);
await Folder.delete(folder1.id);
@ -234,11 +236,11 @@ describe('services_InteropService', function() {
it('should export and import single folders', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
await service.export({ path: filePath, sourceFolderIds: [folder1.id] });
await Note.delete(note1.id);
await Folder.delete(folder1.id);
@ -252,17 +254,17 @@ describe('services_InteropService', function() {
}));
it('should export and import folder and its sub-folders', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let folder2 = await Folder.save({ title: "folder2", parent_id: folder1.id });
let folder3 = await Folder.save({ title: "folder3", parent_id: folder2.id });
let folder4 = await Folder.save({ title: "folder4", parent_id: folder2.id });
let folder1 = await Folder.save({ title: 'folder1' });
let folder2 = await Folder.save({ title: 'folder2', parent_id: folder1.id });
let folder3 = await Folder.save({ title: 'folder3', parent_id: folder2.id });
let folder4 = await Folder.save({ title: 'folder4', parent_id: folder2.id });
let note1 = await Note.save({ title: 'ma note', parent_id: folder4.id });
await service.export({ path: filePath, sourceFolderIds: [folder1.id] });
await Note.delete(note1.id);
await Folder.delete(folder1.id);
await Folder.delete(folder2.id);
@ -273,7 +275,7 @@ describe('services_InteropService', function() {
expect(await Note.count()).toBe(1);
expect(await Folder.count()).toBe(4);
let folder1_2 = await Folder.loadByTitle('folder1');
let folder2_2 = await Folder.loadByTitle('folder2');
let folder3_2 = await Folder.loadByTitle('folder3');
@ -289,12 +291,12 @@ describe('services_InteropService', function() {
it('should export and import links to notes', asyncTest(async () => {
const service = new InteropService();
const filePath = exportDir() + '/test.jex';
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
let note2 = await Note.save({ title: 'ma deuxième note', body: 'Lien vers première note : ' + Note.markdownTag(note1), parent_id: folder1.id });
await service.export({ path: filePath, sourceFolderIds: [folder1.id] });
await Note.delete(note1.id);
await Note.delete(note2.id);
await Folder.delete(folder1.id);
@ -322,7 +324,7 @@ describe('services_InteropService', function() {
// 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';
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);
@ -357,4 +359,4 @@ describe('services_InteropService', function() {
expect(await shim.fsDriver().exists(outDir + '/ジョプリン/ジョプリン.md')).toBe(true);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
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');
@ -104,4 +106,4 @@ describe('services_KvStore', function() {
expect(numbers[1]).toBe(2);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -46,7 +48,7 @@ describe('services_ResourceService', function() {
it('should delete orphaned resources', asyncTest(async () => {
const service = new ResourceService();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
@ -77,7 +79,7 @@ describe('services_ResourceService', function() {
it('should not delete resource if still associated with at least one note', asyncTest(async () => {
const service = new ResourceService();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
let note2 = await Note.save({ title: 'ma deuxième note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
@ -86,9 +88,9 @@ describe('services_ResourceService', function() {
await service.indexNoteResources();
await Note.delete(note1.id);
await service.indexNoteResources();
await Note.save({ id: note2.id, body: Resource.markdownTag(resource1) });
await service.indexNoteResources();
@ -111,7 +113,7 @@ describe('services_ResourceService', function() {
it('should not delete resource if it is used in an IMG tag', asyncTest(async () => {
const service = new ResourceService();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
@ -119,9 +121,9 @@ describe('services_ResourceService', function() {
await service.indexNoteResources();
await Note.save({ id: note1.id, body: 'This is HTML: <img src=":/' + resource1.id + '"/>' });
await service.indexNoteResources();
await service.deleteOrphanResources(0);
expect(!!(await Resource.load(resource1.id))).toBe(true);
@ -130,7 +132,7 @@ describe('services_ResourceService', function() {
it('should not process twice the same change', asyncTest(async () => {
const service = new ResourceService();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
let resource1 = (await Resource.all())[0];
@ -161,13 +163,13 @@ describe('services_ResourceService', function() {
// - Client 1 syncs
// - Client 1 runs resource indexer - but because N1 hasn't been decrypted yet, it found that R1 is no longer associated with any note
// - Client 1 decrypts notes, but too late
//
//
// Eventually R1 is deleted because service thinks that it was at some point associated with a note, but no longer.
const masterKey = await loadEncryptionMasterKey();
await encryptionService().enableEncryption(masterKey, '123456');
await encryptionService().loadMasterKeysFromSettings();
let folder1 = await Folder.save({ title: "folder1" });
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'); // R1
await resourceService().indexNoteResources();
@ -197,7 +199,7 @@ describe('services_ResourceService', function() {
it('should double-check if the resource is still linked before deleting it', asyncTest(async () => {
SearchEngine.instance().setDb(db()); // /!\ Note that we use the global search engine here, which we shouldn't but will work for now
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id });
note1 = await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
await resourceService().indexNoteResources();
@ -213,4 +215,4 @@ describe('services_ResourceService', function() {
expect(!!nr.is_associated).toBe(true); // And it should have fixed the situation by re-indexing the note content
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -22,7 +24,7 @@ describe('services_Revision', function() {
beforeEach(async (done) => {
await setupDatabaseAndSynchronizer(1);
await switchClient(1);
Setting.setValue('revisionService.intervalBetweenRevisions', 0)
Setting.setValue('revisionService.intervalBetweenRevisions', 0);
done();
});
@ -377,7 +379,7 @@ describe('services_Revision', function() {
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello' });
await revisionService().collectRevisions(); // Note has not changed (except its timestamp) so don't create a revision
expect((await Revision.all()).length).toBe(1);
expect((await Revision.all()).length).toBe(1);
}));
it('should preserve user update time', asyncTest(async () => {
@ -392,7 +394,7 @@ describe('services_Revision', function() {
const userUpdatedTime = Date.now() - 1000 * 60 * 60;
const n1_v2 = await Note.save({ id: n1_v0.id, title: 'hello', updated_time: Date.now(), user_updated_time: userUpdatedTime }, { autoTimestamp: false });
await revisionService().collectRevisions(); // Only the user timestamp has changed, but that needs to be saved
const revisions = await Revision.all();
expect(revisions.length).toBe(2);
@ -417,4 +419,4 @@ describe('services_Revision', function() {
expect((await Revision.all()).length).toBe(2);
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -21,15 +23,15 @@ describe('services_SearchEngine', function() {
engine = new SearchEngine();
engine.setDb(db());
done();
});
it('should keep the content and FTS table in sync', asyncTest(async () => {
let rows, n1, n2, n3;
n1 = await Note.save({ title: "a" });
n2 = await Note.save({ title: "b" });
n1 = await Note.save({ title: 'a' });
n2 = await Note.save({ title: 'b' });
await engine.syncTables();
rows = await engine.search('a');
expect(rows.length).toBe(1);
@ -61,8 +63,8 @@ describe('services_SearchEngine', function() {
}));
it('should, after initial indexing, save the last change ID', asyncTest(async () => {
const n1 = await Note.save({ title: "abcd efgh" }); // 3
const n2 = await Note.save({ title: "abcd aaaaa abcd abcd" }); // 1
const n1 = await Note.save({ title: 'abcd efgh' }); // 3
const n2 = await Note.save({ title: 'abcd aaaaa abcd abcd' }); // 1
expect(Setting.value('searchEngine.initialIndexingDone')).toBe(false);
@ -77,9 +79,9 @@ describe('services_SearchEngine', function() {
it('should order search results by relevance (1)', asyncTest(async () => {
const n1 = await Note.save({ title: "abcd efgh" }); // 3
const n2 = await Note.save({ title: "abcd aaaaa abcd abcd" }); // 1
const n3 = await Note.save({ title: "abcd aaaaa bbbb eeee abcd" }); // 2
const n1 = await Note.save({ title: 'abcd efgh' }); // 3
const n2 = await Note.save({ title: 'abcd aaaaa abcd abcd' }); // 1
const n3 = await Note.save({ title: 'abcd aaaaa bbbb eeee abcd' }); // 2
await engine.syncTables();
const rows = await engine.search('abcd');
@ -91,15 +93,15 @@ describe('services_SearchEngine', function() {
it('should order search results by relevance (2)', asyncTest(async () => {
// 1
const n1 = await Note.save({ title: "abcd efgh", body: "XX abcd XX efgh" });
const n1 = await Note.save({ title: 'abcd efgh', body: 'XX abcd XX efgh' });
// 4
const n2 = await Note.save({ title: "abcd aaaaa bbbb eeee efgh" });
const n2 = await Note.save({ title: 'abcd aaaaa bbbb eeee efgh' });
// 3
const n3 = await Note.save({ title: "abcd aaaaa efgh" });
const n3 = await Note.save({ title: 'abcd aaaaa efgh' });
// 2
const n4 = await Note.save({ title: "blablablabla blabla bla abcd X efgh" });
const n4 = await Note.save({ title: 'blablablabla blabla bla abcd X efgh' });
// 5
const n5 = await Note.save({ title: "occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh" });
const n5 = await Note.save({ title: 'occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh occurence many times but very abcd spread appart spread appart spread appart spread appart spread appart efgh' });
await engine.syncTables();
const rows = await engine.search('abcd efgh');
@ -114,11 +116,11 @@ describe('services_SearchEngine', function() {
it('should order search results by relevance (last updated first)', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "abcd" });
const n1 = await Note.save({ title: 'abcd' });
await sleep(0.1);
const n2 = await Note.save({ title: "abcd" });
const n2 = await Note.save({ title: 'abcd' });
await sleep(0.1);
const n3 = await Note.save({ title: "abcd" });
const n3 = await Note.save({ title: 'abcd' });
await sleep(0.1);
await engine.syncTables();
@ -128,7 +130,7 @@ describe('services_SearchEngine', function() {
expect(rows[1].id).toBe(n2.id);
expect(rows[2].id).toBe(n1.id);
await Note.save({ id: n1.id, title: "abcd" });
await Note.save({ id: n1.id, title: 'abcd' });
await engine.syncTables();
rows = await engine.search('abcd');
@ -140,11 +142,11 @@ describe('services_SearchEngine', function() {
it('should order search results by relevance (completed to-dos last)', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "abcd", is_todo: 1 });
const n1 = await Note.save({ title: 'abcd', is_todo: 1 });
await sleep(0.1);
const n2 = await Note.save({ title: "abcd", is_todo: 1 });
const n2 = await Note.save({ title: 'abcd', is_todo: 1 });
await sleep(0.1);
const n3 = await Note.save({ title: "abcd", is_todo: 1 });
const n3 = await Note.save({ title: 'abcd', is_todo: 1 });
await sleep(0.1);
await engine.syncTables();
@ -166,11 +168,11 @@ describe('services_SearchEngine', function() {
it('should supports various query types', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "abcd efgh ijkl", body: "aaaa bbbb" });
const n2 = await Note.save({ title: "iiii efgh bbbb", body: "aaaa bbbb" });
const n3 = await Note.save({ title: "Агентство Рейтер" });
const n4 = await Note.save({ title: "Dog" });
const n5 = await Note.save({ title: "СООБЩИЛО" });
const n1 = await Note.save({ title: 'abcd efgh ijkl', body: 'aaaa bbbb' });
const n2 = await Note.save({ title: 'iiii efgh bbbb', body: 'aaaa bbbb' });
const n3 = await Note.save({ title: 'Агентство Рейтер' });
const n4 = await Note.save({ title: 'Dog' });
const n5 = await Note.save({ title: 'СООБЩИЛО' });
await engine.syncTables();
@ -216,7 +218,7 @@ describe('services_SearchEngine', function() {
it('should support queries with or without accents', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "père noël" });
const n1 = await Note.save({ title: 'père noël' });
await engine.syncTables();
@ -228,7 +230,7 @@ describe('services_SearchEngine', function() {
it('should support queries with Chinese characters', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "我是法国人" });
const n1 = await Note.save({ title: '我是法国人' });
await engine.syncTables();
@ -238,7 +240,7 @@ describe('services_SearchEngine', function() {
it('should support queries with Japanese characters', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "私は日本語を話すことができません" });
const n1 = await Note.save({ title: '私は日本語を話すことができません' });
await engine.syncTables();
@ -248,7 +250,7 @@ describe('services_SearchEngine', function() {
it('should support queries with Korean characters', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "이것은 한국말이다" });
const n1 = await Note.save({ title: '이것은 한국말이다' });
await engine.syncTables();
@ -258,7 +260,7 @@ describe('services_SearchEngine', function() {
it('should support field restricted queries with Chinese characters', asyncTest(async () => {
let rows;
const n1 = await Note.save({ title: "你好", body: "我是法国人" });
const n1 = await Note.save({ title: '你好', body: '我是法国人' });
await engine.syncTables();
@ -323,4 +325,4 @@ describe('services_SearchEngine', function() {
}
}));
});
});

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -40,7 +42,7 @@ describe('services_rest_Api', function() {
});
it('should get folders', async (done) => {
let f1 = await Folder.save({ title: "mon carnet" });
let f1 = await Folder.save({ title: 'mon carnet' });
const response = await api.route('GET', 'folders');
expect(response.length).toBe(1);
expect(response[0].title).toBe('mon carnet');
@ -48,7 +50,7 @@ describe('services_rest_Api', function() {
});
it('should update folders', async (done) => {
let f1 = await Folder.save({ title: "mon carnet" });
let f1 = await Folder.save({ title: 'mon carnet' });
const response = await api.route('PUT', 'folders/' + f1.id, null, JSON.stringify({
title: 'modifié',
}));
@ -60,12 +62,12 @@ describe('services_rest_Api', function() {
});
it('should delete folders', async (done) => {
let f1 = await Folder.save({ title: "mon carnet" });
let f1 = await Folder.save({ title: 'mon carnet' });
await api.route('DELETE', 'folders/' + f1.id);
let f1b = await Folder.load(f1.id);
expect(!f1b).toBe(true);
done();
});
@ -79,12 +81,12 @@ describe('services_rest_Api', function() {
let f = await Folder.all();
expect(f.length).toBe(1);
expect(f[0].title).toBe('from api');
done();
});
it('should get one folder', async (done) => {
let f1 = await Folder.save({ title: "mon carnet" });
let f1 = await Folder.save({ title: 'mon carnet' });
const response = await api.route('GET', 'folders/' + f1.id);
expect(response.id).toBe(f1.id);
@ -95,7 +97,7 @@ describe('services_rest_Api', function() {
});
it('should get the folder notes', async (done) => {
let f1 = await Folder.save({ title: "mon carnet" });
let f1 = await Folder.save({ title: 'mon carnet' });
const response2 = await api.route('GET', 'folders/' + f1.id + '/notes');
expect(response2.length).toBe(0);
@ -116,12 +118,12 @@ describe('services_rest_Api', function() {
it('should get notes', async (done) => {
let response = null;
const f1 = await Folder.save({ title: "mon carnet" });
const f2 = await Folder.save({ title: "mon deuxième carnet" });
const f1 = await Folder.save({ title: 'mon carnet' });
const f2 = await Folder.save({ title: 'mon deuxième carnet' });
const n1 = await Note.save({ title: 'un', parent_id: f1.id });
const n2 = await Note.save({ title: 'deux', parent_id: f1.id });
const n3 = await Note.save({ title: 'trois', parent_id: f2.id });
response = await api.route('GET', 'notes');
expect(response.length).toBe(3);
@ -138,8 +140,8 @@ describe('services_rest_Api', function() {
it('should create notes', async (done) => {
let response = null;
const f = await Folder.save({ title: "mon carnet" });
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
title: 'testing',
parent_id: f.id,
@ -159,11 +161,11 @@ describe('services_rest_Api', function() {
it('should preserve user timestamps when creating notes', async (done) => {
let response = null;
const f = await Folder.save({ title: "mon carnet" });
const f = await Folder.save({ title: 'mon carnet' });
const updatedTime = Date.now() - 1000;
const createdTime = Date.now() - 10000;
response = await api.route('POST', 'notes', null, JSON.stringify({
parent_id: f.id,
user_updated_time: updatedTime,
@ -178,8 +180,8 @@ describe('services_rest_Api', function() {
it('should create notes with supplied ID', async (done) => {
let response = null;
const f = await Folder.save({ title: "mon carnet" });
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
id: '12345678123456781234567812345678',
title: 'testing',
@ -192,19 +194,19 @@ describe('services_rest_Api', function() {
it('should create todos', async (done) => {
let response = null;
const f = await Folder.save({ title: "stuff to do" });
const f = await Folder.save({ title: 'stuff to do' });
response = await api.route('POST', 'notes', null, JSON.stringify({
title: 'testing',
parent_id: f.id,
is_todo: 1
is_todo: 1,
}));
expect(response.is_todo).toBe(1);
response = await api.route('POST', 'notes', null, JSON.stringify({
title: 'testing 2',
parent_id: f.id,
is_todo: 0
is_todo: 0,
}));
expect(response.is_todo).toBe(0);
@ -217,7 +219,7 @@ describe('services_rest_Api', function() {
response = await api.route('POST', 'notes', null, JSON.stringify({
title: 'testing 4',
parent_id: f.id,
is_todo: '1'
is_todo: '1',
}));
expect(response.is_todo).toBe(1);
done();
@ -230,18 +232,18 @@ describe('services_rest_Api', function() {
}));
expect(response.id).toBe('12345678123456781234567812345678');
done();
});
it('should create notes with images', async (done) => {
let response = null;
const f = await Folder.save({ title: "mon carnet" });
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
title: 'testing image',
parent_id: f.id,
image_data_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII="
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=',
}));
const resources = await Resource.all();
@ -255,12 +257,12 @@ describe('services_rest_Api', function() {
it('should delete resources', async (done) => {
let response = null;
const f = await Folder.save({ title: "mon carnet" });
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
title: 'testing image',
parent_id: f.id,
image_data_url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANZJREFUeNoAyAA3/wFwtO3K6gUB/vz2+Prw9fj/+/r+/wBZKAAExOgF4/MC9ff+MRH6Ui4E+/0Bqc/zutj6AgT+/Pz7+vv7++nu82c4DlMqCvLs8goA/gL8/fz09fb59vXa6vzZ6vjT5fbn6voD/fwC8vX4UiT9Zi//APHyAP8ACgUBAPv5APz7BPj2+DIaC2o3E+3o6ywaC5fT6gD6/QD9/QEVf9kD+/dcLQgJA/7v8vqfwOf18wA1IAIEVycAyt//v9XvAPv7APz8LhoIAPz9Ri4OAgwARgx4W/6fVeEAAAAASUVORK5CYII="
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=',
}));
const resource = (await Resource.all())[0];
@ -271,14 +273,14 @@ describe('services_rest_Api', function() {
await api.route('DELETE', 'resources/' + resource.id);
expect(await shim.fsDriver().exists(filePath)).toBe(false);
expect(!(await Resource.load(resource.id))).toBe(true);
done();
});
it('should create notes from HTML', async (done) => {
let response = null;
const f = await Folder.save({ title: "mon carnet" });
const f = await Folder.save({ title: 'mon carnet' });
response = await api.route('POST', 'notes', null, JSON.stringify({
title: 'testing HTML',
parent_id: f.id,
@ -314,7 +316,7 @@ describe('services_rest_Api', function() {
let hasThrown = await checkThrowAsync(async () => await api.route('GET', 'notes'));
expect(hasThrown).toBe(true);
const response = await api.route('GET', 'notes', { token: 'mytoken' })
const response = await api.route('GET', 'notes', { token: 'mytoken' });
expect(response.length).toBe(0);
hasThrown = await checkThrowAsync(async () => await api.route('POST', 'notes', null, JSON.stringify({title:'testing'})));
@ -324,37 +326,37 @@ describe('services_rest_Api', function() {
});
it('should add tags to notes', async (done) => {
const tag = await Tag.save({ title: "mon étiquette" });
const note = await Note.save({ title: "ma note" });
const tag = await Tag.save({ title: 'mon étiquette' });
const note = await Note.save({ title: 'ma note' });
const response = await api.route('POST', 'tags/' + tag.id + '/notes', null, JSON.stringify({
id: note.id,
}));
const noteIds = await Tag.noteIds(tag.id);
const noteIds = await Tag.noteIds(tag.id);
expect(noteIds[0]).toBe(note.id);
done();
});
it('should remove tags from notes', async (done) => {
const tag = await Tag.save({ title: "mon étiquette" });
const note = await Note.save({ title: "ma note" });
const tag = await Tag.save({ title: 'mon étiquette' });
const note = await Note.save({ title: 'ma note' });
await Tag.addNote(tag.id, note.id);
const response = await api.route('DELETE', 'tags/' + tag.id + '/notes/' + note.id);
const noteIds = await Tag.noteIds(tag.id);
const noteIds = await Tag.noteIds(tag.id);
expect(noteIds.length).toBe(0);
done();
});
it('should list all tag notes', async (done) => {
const tag = await Tag.save({ title: "mon étiquette" });
const tag2 = await Tag.save({ title: "mon étiquette 2" });
const note1 = await Note.save({ title: "ma note un" });
const note2 = await Note.save({ title: "ma note deux" });
const tag = await Tag.save({ title: 'mon étiquette' });
const tag2 = await Tag.save({ title: 'mon étiquette 2' });
const note1 = await Note.save({ title: 'ma note un' });
const note2 = await Note.save({ title: 'ma note deux' });
await Tag.addNote(tag.id, note1.id);
await Tag.addNote(tag.id, note2.id);

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
require('app-module-path').addPath(__dirname);
const { time } = require('lib/time-utils.js');
@ -99,8 +101,8 @@ describe('Synchronizer', function() {
});
it('should create remote items', asyncTest(async () => {
let folder = await Folder.save({ title: "folder1" });
await Note.save({ title: "un", parent_id: folder.id });
let folder = await Folder.save({ title: 'folder1' });
await Note.save({ title: 'un', parent_id: folder.id });
let all = await allNotesFolders();
@ -110,11 +112,11 @@ describe('Synchronizer', function() {
}));
it('should update remote items', asyncTest(async () => {
let folder = await Folder.save({ title: "folder1" });
let note = await Note.save({ title: "un", parent_id: folder.id });
let folder = await Folder.save({ title: 'folder1' });
let note = await Note.save({ title: 'un', parent_id: folder.id });
await synchronizer().start();
await Note.save({ title: "un UPDATE", id: note.id });
await Note.save({ title: 'un UPDATE', id: note.id });
let all = await allNotesFolders();
await synchronizer().start();
@ -123,8 +125,8 @@ describe('Synchronizer', function() {
}));
it('should create local items', asyncTest(async () => {
let folder = await Folder.save({ title: "folder1" });
await Note.save({ title: "un", parent_id: folder.id });
let folder = await Folder.save({ title: 'folder1' });
await Note.save({ title: 'un', parent_id: folder.id });
await synchronizer().start();
await switchClient(2);
@ -137,8 +139,8 @@ describe('Synchronizer', function() {
}));
it('should update local items', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
@ -148,7 +150,7 @@ describe('Synchronizer', function() {
await sleep(0.1);
let note2 = await Note.load(note1.id);
note2.title = "Updated on client 2";
note2.title = 'Updated on client 2';
await Note.save(note2);
note2 = await Note.load(note2.id);
@ -164,15 +166,15 @@ describe('Synchronizer', function() {
}));
it('should resolve note conflicts', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
let note2 = await Note.load(note1.id);
note2.title = "Updated on client 2";
note2.title = 'Updated on client 2';
await Note.save(note2);
note2 = await Note.load(note2.id);
await synchronizer().start();
@ -180,7 +182,7 @@ describe('Synchronizer', function() {
await switchClient(1);
let note2conf = await Note.load(note1.id);
note2conf.title = "Updated on client 1";
note2conf.title = 'Updated on client 1';
await Note.save(note2conf);
note2conf = await Note.load(note1.id);
await synchronizer().start();
@ -205,8 +207,8 @@ describe('Synchronizer', function() {
}));
it('should resolve folders conflicts', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
await synchronizer().start();
await switchClient(2); // ----------------------------------
@ -216,7 +218,7 @@ describe('Synchronizer', function() {
await sleep(0.1);
let folder1_modRemote = await Folder.load(folder1.id);
folder1_modRemote.title = "folder1 UPDATE CLIENT 2";
folder1_modRemote.title = 'folder1 UPDATE CLIENT 2';
await Folder.save(folder1_modRemote);
folder1_modRemote = await Folder.load(folder1_modRemote.id);
@ -227,7 +229,7 @@ describe('Synchronizer', function() {
await sleep(0.1);
let folder1_modLocal = await Folder.load(folder1.id);
folder1_modLocal.title = "folder1 UPDATE CLIENT 1";
folder1_modLocal.title = 'folder1 UPDATE CLIENT 1';
await Folder.save(folder1_modLocal);
folder1_modLocal = await Folder.load(folder1.id);
@ -238,8 +240,8 @@ describe('Synchronizer', function() {
}));
it('should delete remote notes', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
@ -261,8 +263,8 @@ describe('Synchronizer', function() {
}));
it('should not created deleted_items entries for items deleted via sync', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
@ -283,9 +285,9 @@ describe('Synchronizer', function() {
// property of the basicDelta() function is cleared properly at the end of a sync operation. If it is not cleared
// it means items will no longer be deleted locally via sync.
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let note2 = await Note.save({ title: "deux", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
let note2 = await Note.save({ title: 'deux', parent_id: folder1.id });
let context1 = await synchronizer().start();
await switchClient(2);
@ -306,8 +308,8 @@ describe('Synchronizer', function() {
}));
it('should delete remote folder', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let folder2 = await Folder.save({ title: "folder2" });
let folder1 = await Folder.save({ title: 'folder1' });
let folder2 = await Folder.save({ title: 'folder2' });
await synchronizer().start();
await switchClient(2);
@ -325,8 +327,8 @@ describe('Synchronizer', function() {
}));
it('should delete local folder', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let folder2 = await Folder.save({ title: "folder2" });
let folder1 = await Folder.save({ title: 'folder1' });
let folder2 = await Folder.save({ title: 'folder2' });
let context1 = await synchronizer().start();
await switchClient(2);
@ -343,7 +345,7 @@ describe('Synchronizer', function() {
}));
it('should resolve conflict if remote folder has been deleted, but note has been added to folder locally', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
await synchronizer().start();
await switchClient(2);
@ -354,17 +356,17 @@ describe('Synchronizer', function() {
await switchClient(1);
let note = await Note.save({ title: "note1", parent_id: folder1.id });
let note = await Note.save({ title: 'note1', parent_id: folder1.id });
await synchronizer().start();
let items = await allNotesFolders();
let items = await allNotesFolders();
expect(items.length).toBe(1);
expect(items[0].title).toBe('note1');
expect(items[0].is_conflict).toBe(1);
}));
it('should resolve conflict if note has been deleted remotely and locally', asyncTest(async () => {
let folder = await Folder.save({ title: "folder" });
let note = await Note.save({ title: "note", parent_id: folder.title });
let folder = await Folder.save({ title: 'folder' });
let note = await Note.save({ title: 'note', parent_id: folder.title });
await synchronizer().start();
await switchClient(2);
@ -389,8 +391,8 @@ describe('Synchronizer', function() {
// If client1 and 2 have two folders, client 1 deletes item 1 and client
// 2 deletes item 2, they should both end up with no items after sync.
let folder1 = await Folder.save({ title: "folder1" });
let folder2 = await Folder.save({ title: "folder2" });
let folder1 = await Folder.save({ title: 'folder1' });
let folder2 = await Folder.save({ title: 'folder2' });
await synchronizer().start();
await switchClient(2);
@ -424,8 +426,8 @@ describe('Synchronizer', function() {
}));
it('should handle conflict when remote note is deleted then local note is modified', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
@ -456,9 +458,9 @@ describe('Synchronizer', function() {
}));
it('should handle conflict when remote folder is deleted then local folder is renamed', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let folder2 = await Folder.save({ title: "folder2" });
let note1 = await Note.save({ title: "un", parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let folder2 = await Folder.save({ title: 'folder2' });
let note1 = await Note.save({ title: 'un', parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
@ -486,11 +488,11 @@ describe('Synchronizer', function() {
}));
it('should allow duplicate folder titles', asyncTest(async () => {
let localF1 = await Folder.save({ title: "folder" });
let localF1 = await Folder.save({ title: 'folder' });
await switchClient(2);
let remoteF2 = await Folder.save({ title: "folder" });
let remoteF2 = await Folder.save({ title: 'folder' });
await synchronizer().start();
await switchClient(1);
@ -526,9 +528,9 @@ describe('Synchronizer', function() {
masterKey = await loadEncryptionMasterKey();
}
let f1 = await Folder.save({ title: "folder" });
let n1 = await Note.save({ title: "mynote" });
let n2 = await Note.save({ title: "mynote2" });
let f1 = await Folder.save({ title: 'folder' });
let n1 = await Note.save({ title: 'mynote' });
let n2 = await Note.save({ title: 'mynote2' });
let tag = await Tag.save({ title: 'mytag' });
let context1 = await synchronizer().start();
@ -577,22 +579,22 @@ describe('Synchronizer', function() {
}));
it('should not sync notes with conflicts', asyncTest(async () => {
let f1 = await Folder.save({ title: "folder" });
let n1 = await Note.save({ title: "mynote", parent_id: f1.id, is_conflict: 1 });
let f1 = await Folder.save({ title: 'folder' });
let n1 = await Note.save({ title: 'mynote', parent_id: f1.id, is_conflict: 1 });
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
let notes = await Note.all();
let folders = await Folder.all()
let folders = await Folder.all();
expect(notes.length).toBe(0);
expect(folders.length).toBe(1);
}));
it('should not try to delete on remote conflicted notes that have been deleted', asyncTest(async () => {
let f1 = await Folder.save({ title: "folder" });
let n1 = await Note.save({ title: "mynote", parent_id: f1.id });
let f1 = await Folder.save({ title: 'folder' });
let n1 = await Note.save({ title: 'mynote', parent_id: f1.id });
await synchronizer().start();
await switchClient(2);
@ -604,15 +606,15 @@ describe('Synchronizer', function() {
expect(deletedItems.length).toBe(0);
}));
async function ignorableNoteConflictTest(withEncryption) {
if (withEncryption) {
Setting.setValue('encryption.enabled', true);
await loadEncryptionMasterKey();
}
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
@ -650,7 +652,7 @@ describe('Synchronizer', function() {
let notes = await Note.all();
expect(notes.length).toBe(1);
expect(notes[0].id).toBe(note1.id);
expect(notes[0].todo_completed).toBe(note2.todo_completed);
expect(notes[0].todo_completed).toBe(note2.todo_completed);
} else {
// If the notes are encrypted however it's not possible to do this kind of
// smart conflict resolving since we don't know the content, so in that
@ -673,13 +675,13 @@ describe('Synchronizer', function() {
}));
it('items should be downloaded again when user cancels in the middle of delta operation', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id });
await synchronizer().start();
await switchClient(2);
synchronizer().testingHooks_ = ['cancelDeltaLoop2'];
synchronizer().testingHooks_ = ['cancelDeltaLoop2'];
let context = await synchronizer().start();
let notes = await Note.all();
expect(notes.length).toBe(0);
@ -691,13 +693,13 @@ describe('Synchronizer', function() {
}));
it('should skip items that cannot be synced', asyncTest(async () => {
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', is_todo: 1, parent_id: folder1.id });
const noteId = note1.id;
await synchronizer().start();
let disabledItems = await BaseItem.syncDisabledItems(syncTargetId());
expect(disabledItems.length).toBe(0);
await Note.save({ id: noteId, title: "un mod", });
await Note.save({ id: noteId, title: 'un mod' });
synchronizer().testingHooks_ = ['notesRejectedByTarget'];
await synchronizer().start();
synchronizer().testingHooks_ = [];
@ -719,8 +721,8 @@ describe('Synchronizer', function() {
it('notes and folders should get encrypted when encryption is enabled', asyncTest(async () => {
Setting.setValue('encryption.enabled', true);
const masterKey = await loadEncryptionMasterKey();
let folder1 = await Folder.save({ title: "folder1" });
let note1 = await Note.save({ title: "un", body: 'to be encrypted', parent_id: folder1.id });
let folder1 = await Folder.save({ title: 'folder1' });
let note1 = await Note.save({ title: 'un', body: 'to be encrypted', parent_id: folder1.id });
await synchronizer().start();
// After synchronisation, remote items should be encrypted but local ones remain plain text
note1 = await Note.load(note1.id);
@ -762,7 +764,7 @@ describe('Synchronizer', function() {
// Enable encryption on client 1 and sync an item
Setting.setValue('encryption.enabled', true);
await loadEncryptionMasterKey();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
await synchronizer().start();
await switchClient(2);
@ -802,7 +804,7 @@ describe('Synchronizer', function() {
// Decrypt all the data. Now change the title and sync again - this time the changes should be transmitted
await decryptionWorker().start();
folder1_2 = await Folder.save({ id: folder1.id, title: "change test" });
await Folder.save({ id: folder1.id, title: 'change test' });
// If we sync now, this time client 1 should get the changes we did earlier
await synchronizer().start();
@ -818,11 +820,11 @@ describe('Synchronizer', function() {
it('should encrypt existing notes too when enabling E2EE', asyncTest(async () => {
// First create a folder, without encryption enabled, and sync it
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
await synchronizer().start();
let files = await fileApi().list()
let files = await fileApi().list();
let content = await fileApi().get(files.items[0].path);
expect(content.indexOf('folder1') >= 0).toBe(true)
expect(content.indexOf('folder1') >= 0).toBe(true);
// Then enable encryption and sync again
let masterKey = await encryptionService().generateMasterKey('123456');
@ -830,10 +832,10 @@ describe('Synchronizer', function() {
await encryptionService().enableEncryption(masterKey, '123456');
await encryptionService().loadMasterKeysFromSettings();
await synchronizer().start();
// Even though the folder has not been changed it should have been synced again so that
// an encrypted version of it replaces the decrypted version.
files = await fileApi().list()
files = await fileApi().list();
expect(files.items.length).toBe(2);
// By checking that the folder title is not present, we can confirm that the item has indeed been encrypted
// One of the two items is the master key
@ -846,12 +848,12 @@ describe('Synchronizer', function() {
it('should sync resources', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
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];
let resourcePath1 = Resource.fullPath(resource1);
await synchronizer().start();
await synchronizer().start();
expect((await remoteNotesFoldersResources()).length).toBe(3);
await switchClient(2);
@ -864,7 +866,7 @@ describe('Synchronizer', function() {
expect(resource1_2.id).toBe(resource1.id);
expect(ls.fetch_status).toBe(Resource.FETCH_STATUS_IDLE);
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
const fetcher = new ResourceFetcher(() => { return synchronizer().api(); });
fetcher.queueDownload_(resource1_2.id);
await fetcher.waitForAllFinished();
@ -879,7 +881,7 @@ describe('Synchronizer', function() {
it('should handle resource download errors', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -892,8 +894,8 @@ describe('Synchronizer', function() {
const fetcher = new ResourceFetcher(() => { return {
// Simulate a failed download
get: () => { return new Promise((resolve, reject) => { reject(new Error('did not work')) }); }
} });
get: () => { return new Promise((resolve, reject) => { reject(new Error('did not work')); }); },
}; });
fetcher.queueDownload_(resource1.id);
await fetcher.waitForAllFinished();
@ -906,10 +908,10 @@ describe('Synchronizer', function() {
it('should set the resource file size if it is missing', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
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');
await synchronizer().start();
await synchronizer().start();
await switchClient(2);
@ -919,7 +921,7 @@ describe('Synchronizer', function() {
r1 = await Resource.load(r1.id);
expect(r1.size).toBe(-1);
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
const fetcher = new ResourceFetcher(() => { return synchronizer().api(); });
fetcher.queueDownload_(r1.id);
await fetcher.waitForAllFinished();
r1 = await Resource.load(r1.id);
@ -929,7 +931,7 @@ describe('Synchronizer', function() {
it('should delete resources', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(500);
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -963,7 +965,7 @@ describe('Synchronizer', function() {
Setting.setValue('encryption.enabled', true);
const masterKey = await loadEncryptionMasterKey();
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -976,10 +978,10 @@ describe('Synchronizer', function() {
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
await encryptionService().loadMasterKeysFromSettings();
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
const fetcher = new ResourceFetcher(() => { return synchronizer().api(); });
fetcher.queueDownload_(resource1.id);
await fetcher.waitForAllFinished();
let resource1_2 = (await Resource.all())[0];
resource1_2 = await Resource.decrypt(resource1_2);
let resourcePath1_2 = Resource.fullPath(resource1_2);
@ -991,7 +993,7 @@ describe('Synchronizer', function() {
Setting.setValue('encryption.enabled', true);
const masterKey = await loadEncryptionMasterKey();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
await synchronizer().start();
let allEncrypted = await allSyncTargetItemsEncrypted();
@ -1012,7 +1014,7 @@ describe('Synchronizer', function() {
Setting.setValue('encryption.enabled', true);
const masterKey = await loadEncryptionMasterKey();
let folder1 = await Folder.save({ title: "folder1" });
let folder1 = await Folder.save({ title: 'folder1' });
await synchronizer().start();
await switchClient(2);
@ -1028,16 +1030,16 @@ describe('Synchronizer', function() {
// Now supply the password, and decrypt the items
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
await encryptionService().loadMasterKeysFromSettings();
await encryptionService().loadMasterKeysFromSettings();
await decryptionWorker().start();
// Try to disable encryption again
hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption());
const hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption());
expect(hasThrown).toBe(false);
// If we sync now the target should receive the decrypted items
await synchronizer().start();
allEncrypted = await allSyncTargetItemsEncrypted();
const allEncrypted = await allSyncTargetItemsEncrypted();
expect(allEncrypted).toBe(false);
}));
@ -1045,7 +1047,7 @@ describe('Synchronizer', function() {
Setting.setValue('encryption.enabled', true);
const masterKey = await loadEncryptionMasterKey();
let folder1 = await Folder.save({ title: "folder1" });
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];
@ -1059,7 +1061,7 @@ describe('Synchronizer', function() {
Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456');
await encryptionService().loadMasterKeysFromSettings();
const fetcher = new ResourceFetcher(() => { return synchronizer().api() });
const fetcher = new ResourceFetcher(() => { return synchronizer().api(); });
fetcher.queueDownload_(resource1.id);
await fetcher.waitForAllFinished();
await decryptionWorker().start();
@ -1071,10 +1073,9 @@ describe('Synchronizer', function() {
it('should encrypt remote resources after encryption has been enabled', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(100);
let folder1 = await Folder.save({ title: "folder1" });
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];
await synchronizer().start();
expect(await allSyncTargetItemsEncrypted()).toBe(false);
@ -1091,7 +1092,7 @@ describe('Synchronizer', function() {
it('should upload encrypted resource, but it should not mark the blob as encrypted locally', asyncTest(async () => {
while (insideBeforeEach) await time.msleep(100);
let folder1 = await Folder.save({ title: "folder1" });
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');
const masterKey = await loadEncryptionMasterKey();
@ -1104,8 +1105,8 @@ describe('Synchronizer', function() {
}));
it('should create remote items with UTF-8 content', asyncTest(async () => {
let folder = await Folder.save({ title: "Fahrräder" });
await Note.save({ title: "Fahrräder", body: "Fahrräder", parent_id: folder.id });
let folder = await Folder.save({ title: 'Fahrräder' });
await Note.save({ title: 'Fahrräder', body: 'Fahrräder', parent_id: folder.id });
let all = await allNotesFolders();
await synchronizer().start();
@ -1113,21 +1114,21 @@ describe('Synchronizer', function() {
await localNotesFoldersSameAsRemote(all, expect);
}));
it("should update remote items but not pull remote changes", asyncTest(async () => {
let folder = await Folder.save({ title: "folder1" });
let note = await Note.save({ title: "un", parent_id: folder.id });
it('should update remote items but not pull remote changes', asyncTest(async () => {
let folder = await Folder.save({ title: 'folder1' });
let note = await Note.save({ title: 'un', parent_id: folder.id });
await synchronizer().start();
await switchClient(2);
await synchronizer().start();
await Note.save({ title: "deux", parent_id: folder.id });
await Note.save({ title: 'deux', parent_id: folder.id });
await synchronizer().start();
await switchClient(1);
await Note.save({ title: "un UPDATE", id: note.id });
await synchronizer().start({ syncSteps: ["update_remote"] });
await Note.save({ title: 'un UPDATE', id: note.id });
await synchronizer().start({ syncSteps: ['update_remote'] });
let all = await allNotesFolders();
expect(all.length).toBe(2);
@ -1135,10 +1136,10 @@ describe('Synchronizer', function() {
await synchronizer().start();
let note2 = await Note.load(note.id);
expect(note2.title).toBe("un UPDATE");
expect(note2.title).toBe('un UPDATE');
}));
it("should create a new Welcome notebook on each client", asyncTest(async () => {
it('should create a new Welcome notebook on each client', asyncTest(async () => {
// Create the Welcome items on two separate clients
await WelcomeUtils.createWelcomeItems();
@ -1151,7 +1152,7 @@ describe('Synchronizer', function() {
const beforeNoteCount = (await Note.all()).length;
expect(beforeFolderCount === 1).toBe(true);
expect(beforeNoteCount > 1).toBe(true);
await synchronizer().start();
const afterFolderCount = (await Folder.all()).length;
@ -1175,7 +1176,7 @@ describe('Synchronizer', function() {
expect(f1_1.title).toBe('Welcome MOD');
}));
it("should not save revisions when updating a note via sync", asyncTest(async () => {
it('should not save revisions when updating a note via sync', asyncTest(async () => {
// When a note is updated, a revision of the original is created.
// Here, on client 1, the note is updated for the first time, however since it is
// via sync, we don't create a revision - that revision has already been created on client
@ -1201,7 +1202,7 @@ describe('Synchronizer', function() {
expect(allRevs2[0].id).toBe(allRevs1[0].id);
}));
it("should not save revisions when deleting a note via sync", asyncTest(async () => {
it('should not save revisions when deleting a note via sync', asyncTest(async () => {
const n1 = await Note.save({ title: 'testing' });
await synchronizer().start();
@ -1228,7 +1229,7 @@ describe('Synchronizer', function() {
expect(notes.length).toBe(0);
}));
it("should not save revisions when an item_change has been generated as a result of a sync", asyncTest(async () => {
it('should not save revisions when an item_change has been generated as a result of a sync', asyncTest(async () => {
// When a note is modified an item_change object is going to be created. This
// is used for example to tell the search engine, when note should be indexed. It is
// also used by the revision service to tell what note should get a new revision.
@ -1266,7 +1267,7 @@ describe('Synchronizer', function() {
}
}));
it("should handle case when new rev is created on client, then older rev arrives later via sync", asyncTest(async () => {
it('should handle case when new rev is created on client, then older rev arrives later via sync', asyncTest(async () => {
// - C1 creates note 1
// - C1 modifies note 1 - REV1 created
// - C1 sync
@ -1304,7 +1305,7 @@ describe('Synchronizer', function() {
expect((await revisionService().revisionNote(revisions, 1)).title).toBe('note REV2');
}));
it("should not download resources over the limit", asyncTest(async () => {
it('should not download resources over the limit', asyncTest(async () => {
const note1 = await Note.save({ title: 'note' });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
await synchronizer().start();
@ -1322,7 +1323,7 @@ describe('Synchronizer', function() {
expect(syncItems[1].sync_disabled).toBe(1);
}));
it("should not upload a resource if it has not been fetched yet", asyncTest(async () => {
it('should not upload a resource if it has not been fetched yet', asyncTest(async () => {
// In some rare cases, the synchronizer might try to upload a resource even though it
// doesn't have the resource file. It can happen in this situation:
// - C1 create resource
@ -1351,7 +1352,7 @@ describe('Synchronizer', function() {
it('should decrypt the resource metadata, but not try to decrypt the file, if it is not present', asyncTest(async () => {
const note1 = await Note.save({ title: 'note' });
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg');
const masterKey = await loadEncryptionMasterKey();
await encryptionService().enableEncryption(masterKey, '123456');
await encryptionService().loadMasterKeysFromSettings();
@ -1370,7 +1371,7 @@ describe('Synchronizer', function() {
expect(!!resource.encryption_applied).toBe(false);
expect(!!resource.encryption_blob_encrypted).toBe(true);
const resourceFetcher = new ResourceFetcher(() => { return synchronizer().api() });
const resourceFetcher = new ResourceFetcher(() => { return synchronizer().api(); });
await resourceFetcher.start();
await resourceFetcher.waitForAllFinished();
@ -1400,7 +1401,7 @@ describe('Synchronizer', function() {
const dateInPast = revisionService().oldNoteCutOffDate_() - 1000;
const note1 = await Note.save({ title: 'ma note', updated_time: dateInPast, created_time: dateInPast }, { autoTimestamp: false });
await Note.save({ title: 'ma note', updated_time: dateInPast, created_time: dateInPast }, { autoTimestamp: false });
const masterKey = await loadEncryptionMasterKey();
await encryptionService().enableEncryption(masterKey, '123456');
await encryptionService().loadMasterKeysFromSettings();

View File

@ -1,3 +1,5 @@
/* eslint-disable require-atomic-updates */
const fs = require('fs-extra');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
@ -13,7 +15,6 @@ const { Logger } = require('lib/logger.js');
const Setting = require('lib/models/Setting.js');
const MasterKey = require('lib/models/MasterKey');
const BaseItem = require('lib/models/BaseItem.js');
const { Synchronizer } = require('lib/synchronizer.js');
const { FileApi } = require('lib/file-api.js');
const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
@ -67,7 +68,7 @@ SyncTargetRegistry.addClass(SyncTargetNextcloud);
SyncTargetRegistry.addClass(SyncTargetDropbox);
// const syncTargetId_ = SyncTargetRegistry.nameToId("nextcloud");
const syncTargetId_ = SyncTargetRegistry.nameToId("memory");
const syncTargetId_ = SyncTargetRegistry.nameToId('memory');
//const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem');
// const syncTargetId_ = SyncTargetRegistry.nameToId('dropbox');
const syncDir = __dirname + '/../tests/sync';
@ -151,7 +152,7 @@ async function clearDatabase(id = null) {
'master_keys',
'item_changes',
'note_resources',
'settings',
'settings',
'deleted_items',
'sync_items',
'notes_normalized',
@ -186,7 +187,7 @@ async function setupDatabase(id = null) {
await fs.unlink(filePath);
} catch (error) {
// Don't care if the file doesn't exist
};
}
databases_[id] = new JoplinDatabase(new DatabaseDriverNode());
databases_[id].setLogger(dbLogger);
@ -279,9 +280,9 @@ async function loadEncryptionMasterKey(id = null, useExisting = false) {
masterKey = await service.generateMasterKey('123456');
masterKey = await MasterKey.save(masterKey);
} else { // Use the one already available
materKey = await MasterKey.all();
if (!materKey.length) throw new Error('No mater key available');
masterKey = materKey[0];
const masterKeys = await MasterKey.all();
if (!masterKeys.length) throw new Error('No mater key available');
masterKey = masterKeys[0];
}
await service.loadMasterKey(masterKey, '123456', true);
@ -293,7 +294,7 @@ function fileApi() {
if (fileApi_) return fileApi_;
if (syncTargetId_ == SyncTargetRegistry.nameToId('filesystem')) {
fs.removeSync(syncDir)
fs.removeSync(syncDir);
fs.mkdirpSync(syncDir, 0o755);
fileApi_ = new FileApi(syncDir, new FileApiDriverLocal());
} else if (syncTargetId_ == SyncTargetRegistry.nameToId('memory')) {
@ -359,7 +360,7 @@ function asyncTest(callback) {
} finally {
done();
}
}
};
}
async function allSyncTargetItemsEncrypted() {
@ -381,10 +382,10 @@ async function allSyncTargetItemsEncrypted() {
if (remoteContent.type_ === BaseModel.TYPE_RESOURCE) {
const content = await fileApi().get('.resource/' + remoteContent.id);
totalCount++;
if (content.substr(0, 5) === 'JED01') output = encryptedCount++;
if (content.substr(0, 5) === 'JED01') encryptedCount++;
}
if (!!remoteContent.encryption_applied) encryptedCount++;
if (remoteContent.encryption_applied) encryptedCount++;
}
if (!totalCount) throw new Error('No encryptable item on sync target');

View File

@ -1,7 +1,5 @@
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 urlUtils = require('lib/urlUtils.js');
process.on('unhandledRejection', (reason, p) => {
@ -35,4 +33,4 @@ describe('urlUtils', function() {
done();
});
});
});

View File

@ -1,13 +1,10 @@
let browser_ = null;
let browserName_ = null;
if (typeof browser !== 'undefined') {
browser_ = browser;
browserSupportsPromises_ = true;
browserName_ = 'firefox';
} else if (typeof chrome !== 'undefined') {
browser_ = chrome;
browserSupportsPromises_ = false;
browserName_ = 'chrome';
}
let env_ = null;
@ -21,7 +18,7 @@ window.joplinEnv = function() {
const manifest = browser_.runtime.getManifest();
env_ = manifest.name.indexOf('[DEV]') >= 0 ? 'dev' : 'prod';
return env_;
}
};
async function browserCaptureVisibleTabs(windowId) {
const options = { format: 'jpeg' };
@ -58,7 +55,7 @@ browser_.runtime.onMessage.addListener(async (command) => {
const zoom = await browserGetZoom();
const imageDataUrl = await browserCaptureVisibleTabs(null);
content = Object.assign({}, command.content);
const content = Object.assign({}, command.content);
content.image_data_url = imageDataUrl;
const newArea = Object.assign({}, command.content.crop_rect);
@ -68,13 +65,13 @@ browser_.runtime.onMessage.addListener(async (command) => {
newArea.height *= zoom;
content.crop_rect = newArea;
fetch(command.api_base_url + "/notes", {
method: "POST",
fetch(command.api_base_url + '/notes', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
body: JSON.stringify(content)
body: JSON.stringify(content),
});
}
});

File diff suppressed because it is too large Load Diff

View File

@ -24,15 +24,15 @@
*/
var REGEXPS = {
// NOTE: These two regular expressions are duplicated in
// Readability.js. Please keep both copies in sync.
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
// NOTE: These two regular expressions are duplicated in
// Readability.js. Please keep both copies in sync.
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
};
function isNodeVisible(node) {
// Have to null-check node.style to deal with SVG and MathML nodes.
return (!node.style || node.style.display != "none") && !node.hasAttribute("hidden");
// Have to null-check node.style to deal with SVG and MathML nodes.
return (!node.style || node.style.display != 'none') && !node.hasAttribute('hidden');
}
/**
@ -41,59 +41,59 @@ function isNodeVisible(node) {
* @return boolean Whether or not we suspect Readability.parse() will suceeed at returning an article object.
*/
function isProbablyReaderable(doc, isVisible) {
if (!isVisible) {
isVisible = isNodeVisible;
}
if (!isVisible) {
isVisible = isNodeVisible;
}
var nodes = doc.querySelectorAll("p, pre");
var nodes = doc.querySelectorAll('p, pre');
// Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
// Some articles' DOM structures might look like
// <div>
// Sentences<br>
// <br>
// Sentences<br>
// </div>
var brNodes = doc.querySelectorAll("div > br");
if (brNodes.length) {
var set = new Set(nodes);
[].forEach.call(brNodes, function(node) {
set.add(node.parentNode);
});
nodes = Array.from(set);
}
// Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
// Some articles' DOM structures might look like
// <div>
// Sentences<br>
// <br>
// Sentences<br>
// </div>
var brNodes = doc.querySelectorAll('div > br');
if (brNodes.length) {
var set = new Set(nodes);
[].forEach.call(brNodes, function(node) {
set.add(node.parentNode);
});
nodes = Array.from(set);
}
var score = 0;
// This is a little cheeky, we use the accumulator 'score' to decide what to return from
// this callback:
return [].some.call(nodes, function(node) {
if (!isVisible(node))
return false;
var score = 0;
// This is a little cheeky, we use the accumulator 'score' to decide what to return from
// this callback:
return [].some.call(nodes, function(node) {
if (!isVisible(node))
return false;
var matchString = node.className + " " + node.id;
if (REGEXPS.unlikelyCandidates.test(matchString) &&
var matchString = node.className + ' ' + node.id;
if (REGEXPS.unlikelyCandidates.test(matchString) &&
!REGEXPS.okMaybeItsACandidate.test(matchString)) {
return false;
}
return false;
}
if (node.matches("li p")) {
return false;
}
if (node.matches('li p')) {
return false;
}
var textContentLength = node.textContent.trim().length;
if (textContentLength < 140) {
return false;
}
var textContentLength = node.textContent.trim().length;
if (textContentLength < 140) {
return false;
}
score += Math.sqrt(textContentLength - 140);
score += Math.sqrt(textContentLength - 140);
if (score > 20) {
return true;
}
return false;
});
if (score > 20) {
return true;
}
return false;
});
}
if (typeof exports === "object") {
exports.isProbablyReaderable = isProbablyReaderable;
if (typeof exports === 'object') {
exports.isProbablyReaderable = isProbablyReaderable;
}

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,14 @@
let browser_ = null;
if (typeof browser !== 'undefined') {
// eslint-disable-next-line no-undef
browser_ = browser;
// eslint-disable-next-line no-undef
browserSupportsPromises_ = true;
} else if (typeof chrome !== 'undefined') {
// eslint-disable-next-line no-undef
browser_ = chrome;
// eslint-disable-next-line no-undef
browserSupportsPromises_ = false;
}
@ -29,7 +33,7 @@
}
function pageTitle() {
const titleElements = document.getElementsByTagName("title");
const titleElements = document.getElementsByTagName('title');
if (titleElements.length) return titleElements[0].text.trim();
return document.title.trim();
}
@ -179,7 +183,7 @@
}
if (nodeName === 'source' && nodeParentName === 'picture') {
isVisible = false
isVisible = false;
}
if (node.nodeType === 8) { // Comments are just removed since we can't add a class
@ -208,7 +212,7 @@
}
// Given a document, return a <style> tag that contains all the styles
// required to render the page. Not currently used but could be as an
// required to render the page. Not currently used but could be as an
// option to clip pages as HTML.
function getStyleSheets(doc) {
const output = [];
@ -238,14 +242,7 @@
}
function readabilityProcess() {
var uri = {
spec: location.href,
host: location.host,
prePath: location.protocol + "//" + location.host,
scheme: location.protocol.substr(0, location.protocol.indexOf(":")),
pathBase: location.protocol + "//" + location.host + location.pathname.substr(0, location.pathname.lastIndexOf("/") + 1)
};
// eslint-disable-next-line no-undef
const readability = new Readability(documentForReadability());
const article = readability.parse();
@ -254,7 +251,7 @@
return {
title: article.title,
body: article.content,
}
};
}
async function prepareCommandResponse(command) {
@ -276,10 +273,10 @@
source_command: Object.assign({}, command),
convert_to: convertToMarkup,
stylesheets: stylesheets,
};
}
};
};
if (command.name === "simplifiedPageHtml") {
if (command.name === 'simplifiedPageHtml') {
let article = null;
try {
@ -294,12 +291,13 @@
}
return clippedContentResponse(article.title, article.body, getImageSizes(document), getAnchorNames(document));
} else if (command.name === "isProbablyReaderable") {
} else if (command.name === 'isProbablyReaderable') {
// eslint-disable-next-line no-undef
const ok = isProbablyReaderable(documentForReadability());
return { name: 'isProbablyReaderable', value: ok };
} else if (command.name === "completePageHtml") {
} else if (command.name === 'completePageHtml') {
hardcodePreStyles(document);
preProcessDocument(document);
@ -313,7 +311,7 @@
const stylesheets = convertToMarkup === 'html' ? getStyleSheets(document) : null;
return clippedContentResponse(pageTitle(), cleanDocument.innerHTML, imageSizes, getAnchorNames(document), stylesheets);
} else if (command.name === "selectedHtml") {
} else if (command.name === 'selectedHtml') {
hardcodePreStyles(document);
preProcessDocument(document);
@ -342,19 +340,19 @@
const messageComp = document.createElement('div');
const messageCompWidth = 300;
messageComp.style.position = 'fixed'
messageComp.style.opacity = '0.95'
messageComp.style.position = 'fixed';
messageComp.style.opacity = '0.95';
messageComp.style.fontSize = '14px';
messageComp.style.width = messageCompWidth + 'px'
messageComp.style.maxWidth = messageCompWidth + 'px'
messageComp.style.border = '1px solid black'
messageComp.style.background = 'white'
messageComp.style.width = messageCompWidth + 'px';
messageComp.style.maxWidth = messageCompWidth + 'px';
messageComp.style.border = '1px solid black';
messageComp.style.background = 'white';
messageComp.style.color = 'black';
messageComp.style.top = '10px'
messageComp.style.top = '10px';
messageComp.style.textAlign = 'center';
messageComp.style.padding = '10px'
messageComp.style.left = Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) + 'px'
messageComp.style.zIndex = overlay.style.zIndex + 1
messageComp.style.padding = '10px';
messageComp.style.left = Math.round(document.body.clientWidth / 2 - messageCompWidth / 2) + 'px';
messageComp.style.zIndex = overlay.style.zIndex + 1;
messageComp.textContent = 'Drag and release to capture a screenshot';
@ -376,32 +374,32 @@
let draggingStartPos = null;
let selectionArea = {};
function updateSelection() {
const updateSelection = function() {
selection.style.left = selectionArea.x + 'px';
selection.style.top = selectionArea.y + 'px';
selection.style.width = selectionArea.width + 'px';
selection.style.height = selectionArea.height + 'px';
}
};
function setSelectionSizeFromMouse(event) {
const setSelectionSizeFromMouse = function(event) {
selectionArea.width = Math.max(1, event.clientX - draggingStartPos.x);
selectionArea.height = Math.max(1, event.clientY - draggingStartPos.y);
updateSelection();
}
};
function selection_mouseDown(event) {
selectionArea = { x: event.clientX, y: event.clientY, width: 0, height: 0 }
const selection_mouseDown = function(event) {
selectionArea = { x: event.clientX, y: event.clientY, width: 0, height: 0 };
draggingStartPos = { x: event.clientX, y: event.clientY };
isDragging = true;
updateSelection();
}
};
function selection_mouseMove(event) {
const selection_mouseMove = function(event) {
if (!isDragging) return;
setSelectionSizeFromMouse(event);
}
};
function selection_mouseUp(event) {
const selection_mouseUp = function(event) {
setSelectionSizeFromMouse(event);
isDragging = false;
@ -436,7 +434,7 @@
api_base_url: command.api_base_url,
});
}, 100);
}
};
overlay.addEventListener('mousedown', selection_mouseDown);
overlay.addEventListener('mousemove', selection_mouseMove);
@ -444,7 +442,7 @@
return {};
} else if (command.name === "pageUrl") {
} else if (command.name === 'pageUrl') {
let url = pageLocationOrigin() + location.pathname + location.search;
return clippedContentResponse(pageTitle(), url, getImageSizes(document), getAnchorNames(document));
@ -465,4 +463,4 @@
execCommand(command);
});
})();
})();

View File

@ -1,3 +1,5 @@
/* eslint-disable no-unused-vars */
// Add here all the external scripts that the content script might need
// and run browserify on it to create vendor.bundle.js
const Readability = require('readability-node').Readability;
const Readability = require('readability-node').Readability;

View File

@ -3,9 +3,6 @@
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {

View File

@ -1,3 +1,3 @@
const fs = require('fs-extra');
fs.copySync(__dirname + '/../../../../ReactNativeClient/lib/randomClipperPort.js', __dirname + '/../src/randomClipperPort.js');
fs.copySync(__dirname + '/../../../../ReactNativeClient/lib/randomClipperPort.js', __dirname + '/../src/randomClipperPort.js');

View File

@ -45,9 +45,9 @@ class PreviewComponent extends React.PureComponent {
return (
<div className="Preview">
<h2>Title:</h2>
<input className={"Title"} value={this.props.title} onChange={this.props.onTitleChange}/>
<input className={'Title'} value={this.props.title} onChange={this.props.onTitleChange}/>
<p><span>Type:</span> {commandUserString(this.props.command)}</p>
<a className={"Confirm Button"} onClick={this.props.onConfirmClick}>Confirm</a>
<a className={'Confirm Button'} onClick={this.props.onConfirmClick}>Confirm</a>
</div>
);
@ -81,46 +81,46 @@ class AppComponent extends Component {
content.tags = this.state.selectedTags.join(',');
content.parent_id = this.props.selectedFolderId;
bridge().sendContentToJoplin(content);
}
};
this.contentTitle_change = (event) => {
this.props.dispatch({
type: 'CLIPPED_CONTENT_TITLE_SET',
text: event.currentTarget.value
text: event.currentTarget.value,
});
}
};
this.clipSimplified_click = () => {
bridge().sendCommandToActiveTab({
name: 'simplifiedPageHtml',
});
}
};
this.clipComplete_click = () => {
bridge().sendCommandToActiveTab({
name: 'completePageHtml',
preProcessFor: 'markdown',
});
}
};
this.clipCompleteHtml_click = () => {
bridge().sendCommandToActiveTab({
name: 'completePageHtml',
preProcessFor: 'html',
});
}
};
this.clipSelection_click = () => {
bridge().sendCommandToActiveTab({
name: 'selectedHtml',
});
}
};
this.clipUrl_click = () => {
bridge().sendCommandToActiveTab({
name: 'pageUrl',
});
}
};
this.clipScreenshot_click = async () => {
try {
@ -137,18 +137,18 @@ class AppComponent extends Component {
} catch (error) {
this.props.dispatch({ type: 'CONTENT_UPLOAD', operation: { uploading: false, success: false, errorMessage: error.message } });
}
}
};
this.clipperServerHelpLink_click = () => {
bridge().tabsCreate({ url: 'https://joplinapp.org/clipper/' });
}
};
this.folderSelect_change = (event) => {
this.props.dispatch({
type: 'SELECTED_FOLDER_SET',
id: event.target.value,
});
}
};
this.tagCompChanged = this.tagCompChanged.bind(this);
this.onAddTagClick = this.onAddTagClick.bind(this);
@ -181,16 +181,16 @@ class AppComponent extends Component {
if (this.state.selectedTags[index] !== value) {
const newTags = this.state.selectedTags.slice();
newTags[index] = value;
this.setState({ selectedTags: newTags });
this.setState({ selectedTags: newTags });
}
}
}
async loadContentScripts() {
await bridge().tabsExecuteScript({file: "/content_scripts/JSDOMParser.js"});
await bridge().tabsExecuteScript({file: "/content_scripts/Readability.js"});
await bridge().tabsExecuteScript({file: "/content_scripts/Readability-readerable.js"});
await bridge().tabsExecuteScript({file: "/content_scripts/index.js"});
await bridge().tabsExecuteScript({file: '/content_scripts/JSDOMParser.js'});
await bridge().tabsExecuteScript({file: '/content_scripts/Readability.js'});
await bridge().tabsExecuteScript({file: '/content_scripts/Readability-readerable.js'});
await bridge().tabsExecuteScript({file: '/content_scripts/index.js'});
}
async componentDidMount() {
@ -214,7 +214,7 @@ class AppComponent extends Component {
if (folder.id === this.props.selectedFolderId) foundSelectedFolderId = true;
if (folder.children) searchSelectedFolder(folder.children);
}
}
};
searchSelectedFolder(this.props.folders);
@ -249,7 +249,7 @@ class AppComponent extends Component {
return <div style={{padding: 10, fontSize: 12, maxWidth: 200}}>{msg}</div>;
}
const warningComponent = !this.props.warning ? null : <div className="Warning">{ this.props.warning }</div>
const warningComponent = !this.props.warning ? null : <div className="Warning">{ this.props.warning }</div>;
const hasContent = !!this.props.clippedContent;
const content = this.props.clippedContent;
@ -283,7 +283,7 @@ class AppComponent extends Component {
body_html={content.body_html}
onTitleChange={this.contentTitle_change}
command={content.source_command}
/>
/>;
}
const clipperStatusComp = () => {
@ -291,43 +291,43 @@ class AppComponent extends Component {
const stateToString = function(state) {
if (state === 'not_found') return 'Not found';
return state.charAt(0).toUpperCase() + state.slice(1);
}
};
let msg = ''
let msg = '';
let led = null;
let helpLink = null;
const foundState = this.props.clipperServer.foundState
const foundState = this.props.clipperServer.foundState;
if (foundState === 'found') {
msg = "Ready on port " + this.props.clipperServer.port
led = led_green
msg = 'Ready on port ' + this.props.clipperServer.port;
led = led_green;
} else {
msg = stateToString(foundState)
led = foundState === 'searching' ? led_orange : led_red
if (foundState === 'not_found') helpLink = <a className="Help" onClick={this.clipperServerHelpLink_click} href="help">[Help]</a>
msg = stateToString(foundState);
led = foundState === 'searching' ? led_orange : led_red;
if (foundState === 'not_found') helpLink = <a className="Help" onClick={this.clipperServerHelpLink_click} href="help">[Help]</a>;
}
msg = "Service status: " + msg
msg = 'Service status: ' + msg;
return <div className="StatusBar"><img alt={foundState} className="Led" src={led}/><span className="ServerStatus">{ msg }{ helpLink }</span></div>
}
return <div className="StatusBar"><img alt={foundState} className="Led" src={led}/><span className="ServerStatus">{ msg }{ helpLink }</span></div>;
};
const foldersComp = () => {
const optionComps = [];
const nonBreakingSpacify = (s) => {
// https://stackoverflow.com/a/24437562/561309
return s.replace(/ /g, "\u00a0");
}
return s.replace(/ /g, '\u00a0');
};
const addOptions = (folders, depth) => {
for (let i = 0; i < folders.length; i++) {
const folder = folders[i];
optionComps.push(<option key={folder.id} value={folder.id}>{nonBreakingSpacify(' '.repeat(depth) + folder.title)}</option>)
optionComps.push(<option key={folder.id} value={folder.id}>{nonBreakingSpacify(' '.repeat(depth) + folder.title)}</option>);
if (folder.children) addOptions(folder.children, depth + 1);
}
}
};
addOptions(this.props.folders, 0);
@ -339,7 +339,7 @@ class AppComponent extends Component {
</select>
</div>
);
}
};
const tagsComp = () => {
const comps = [];
@ -363,7 +363,7 @@ class AppComponent extends Component {
<a className="AddTagButton" href="#" onClick={this.onAddTagClick}>Add tag</a>
</div>
);
}
};
const tagDataListOptions = [];
for (let i = 0; i < this.props.tags.length; i++) {
@ -380,7 +380,7 @@ class AppComponent extends Component {
return (
<div className="App">
<div className="Controls">
<div className="Controls">
<ul>
<li><a className="Button" onClick={this.clipSimplified_click} title={simplifiedPageButtonTooltip}>{simplifiedPageButtonLabel}</a></li>
<li><a className="Button" onClick={this.clipComplete_click}>Clip complete page</a></li>

View File

@ -1,7 +1,7 @@
const Global = {}
const Global = {};
Global.browser = function() {
return window.browser;
}
};
module.exports = Global;
module.exports = Global;

View File

@ -17,7 +17,7 @@ class Bridge {
this.browser_notify = async (command) => {
console.info('Popup: Got command:', command);
if (command.warning) {
console.warn('Popup: Got warning: ' + command.warning);
this.dispatch({ type: 'WARNING_SET', text: command.warning });
@ -46,7 +46,7 @@ class Bridge {
if (command.name === 'isProbablyReaderable') {
this.dispatch({ type: 'IS_PROBABLY_READERABLE', value: command.value });
}
}
};
this.browser_.runtime.onMessage.addListener(this.browser_notify);
@ -74,7 +74,7 @@ class Bridge {
return new Promise((resolve, reject) => {
browser.runtime.getBackgroundPage((bgp) => {
resolve(bgp);
})
});
});
}
@ -125,7 +125,7 @@ class Bridge {
state = randomClipperPort(state, this.env());
try {
console.info('findClipperServerPort: Trying ' + state.port);
console.info('findClipperServerPort: Trying ' + state.port);
const response = await fetch('http://127.0.0.1:' + state.port + '/ping');
const text = await response.text();
console.info('findClipperServerPort: Got response: ' + text);
@ -166,7 +166,7 @@ class Bridge {
return true;
}
return false;
}
};
if (checkStatus()) return;
@ -192,13 +192,13 @@ class Bridge {
this.browser().tabs.executeScript(options, () => {
const e = this.browser().runtime.lastError;
if (e) {
const msg = ['tabsExecuteScript: Cannot load ' + JSON.stringify(options)]
const msg = ['tabsExecuteScript: Cannot load ' + JSON.stringify(options)];
if (e.message) msg.push(e.message);
reject(new Error(msg.join(': ')));
}
resolve();
});
})
});
}
async tabsQuery(options) {
@ -213,7 +213,7 @@ class Bridge {
async tabsSendMessage(tabId, command) {
if (this.browserSupportsPromises_) return this.browser().tabs.sendMessage(tabId, command);
return new Promise((resolve, reject) => {
this.browser().tabs.sendMessage(tabId, command, (result) => {
resolve(result);
@ -223,7 +223,7 @@ class Bridge {
async tabsCreate(options) {
if (this.browserSupportsPromises_) return this.browser().tabs.create(options);
return new Promise((resolve, reject) => {
this.browser().tabs.create(options, () => {
resolve();
@ -284,9 +284,9 @@ class Bridge {
const fetchOptions = {
method: method,
headers: {
'Content-Type': 'application/json'
'Content-Type': 'application/json',
},
}
};
if (body) fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
@ -301,7 +301,7 @@ class Bridge {
if (queryString) queryString = '?' + queryString;
}
const response = await fetch(baseUrl + "/" + path + queryString, fetchOptions)
const response = await fetch(baseUrl + '/' + path + queryString, fetchOptions);
if (!response.ok) {
const msg = await response.text();
throw new Error(msg);
@ -361,6 +361,6 @@ const bridge_ = new Bridge();
const bridge = function() {
return bridge_;
}
};
export { bridge }
export { bridge };

View File

@ -30,8 +30,8 @@ const reduxMiddleware = store => next => async (action) => {
bridge().scheduleStateSave(newState);
}
return result;
}
return result;
};
function reducer(state = defaultState, action) {
let newState = state;
@ -114,4 +114,4 @@ async function main() {
main().catch((error) => {
console.error('Fatal error on initialisation:', error);
});
});

View File

@ -17,4 +17,4 @@ function randomClipperPort(state, env) {
return state;
}
module.exports = randomClipperPort;
module.exports = randomClipperPort;

View File

@ -1,10 +1,8 @@
const { _ } = require('lib/locale.js');
const { BrowserWindow, Menu, Tray } = require('electron');
const { BrowserWindow, Tray } = require('electron');
const { shim } = require('lib/shim');
const url = require('url')
const path = require('path')
const urlUtils = require('lib/urlUtils.js');
const { dirname, basename } = require('lib/path-utils');
const url = require('url');
const path = require('path');
const { dirname } = require('lib/path-utils');
const fs = require('fs-extra');
class ElectronAppWrapper {
@ -42,7 +40,7 @@ class ElectronAppWrapper {
defaultWidth: 800,
defaultHeight: 600,
file: 'window-state-' + this.env_ + '.json',
}
};
if (this.profilePath_) stateOptions.path = this.profilePath_;
@ -54,7 +52,7 @@ class ElectronAppWrapper {
y: windowState.y,
width: windowState.width,
height: windowState.height,
backgroundColor: '#fff', // required to enable sub pixel rendering, can't be in css
backgroundColor: '#fff', // required to enable sub pixel rendering, can't be in css
webPreferences: {
nodeIntegration: true,
},
@ -72,13 +70,13 @@ class ElectronAppWrapper {
},
});
this.win_ = new BrowserWindow(windowOptions)
this.win_ = new BrowserWindow(windowOptions);
this.win_.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
slashes: true,
}));
// Uncomment this to view errors if the application does not start
if (this.env_ === 'dev') this.win_.webContents.openDevTools();
@ -106,7 +104,7 @@ class ElectronAppWrapper {
this.win_ = null;
}
}
})
});
// Let us register listeners on the window, so we can update the state
// automatically (the listeners will be removed when the window is closed)
@ -166,7 +164,7 @@ class ElectronAppWrapper {
output = '16x16.png';
}
if (this.env_ === 'dev') output = '16x16-dev.png'
if (this.env_ === 'dev') output = '16x16-dev.png';
return output;
}
@ -174,15 +172,15 @@ class ElectronAppWrapper {
// Note: this must be called only after the "ready" event of the app has been dispatched
createTray(contextMenu) {
try {
this.tray_ = new Tray(this.buildDir() + '/icons/' + this.trayIconFilename_())
this.tray_.setToolTip(this.electronApp_.getName())
this.tray_.setContextMenu(contextMenu)
this.tray_ = new Tray(this.buildDir() + '/icons/' + this.trayIconFilename_());
this.tray_.setToolTip(this.electronApp_.getName());
this.tray_.setContextMenu(contextMenu);
this.tray_.on('click', () => {
this.window().show();
});
} catch (error) {
console.error("Cannot create tray", error);
console.error('Cannot create tray', error);
}
}
@ -221,21 +219,21 @@ class ElectronAppWrapper {
await this.waitForElectronAppReady();
const alreadyRunning = this.ensureSingleInstance();
if (alreadyRunning) return;
if (alreadyRunning) return;
this.createWindow();
this.electronApp_.on('before-quit', () => {
this.willQuitApp_ = true;
})
});
this.electronApp_.on('window-all-closed', () => {
this.electronApp_.quit();
})
});
this.electronApp_.on('activate', () => {
this.win_.show();
})
});
}
}

View File

@ -11,7 +11,7 @@ class InteropServiceHelper {
if (module.target === 'file') {
path = bridge().showSaveDialog({
filters: [{ name: module.description, extensions: module.fileExtensions}]
filters: [{ name: module.description, extensions: module.fileExtensions}],
});
} else {
path = bridge().showOpenDialog({
@ -48,4 +48,4 @@ class InteropServiceHelper {
}
module.exports = InteropServiceHelper;
module.exports = InteropServiceHelper;

View File

@ -4,17 +4,13 @@ const { BaseApplication } = require('lib/BaseApplication');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim.js');
const BaseModel = require('lib/BaseModel.js');
const MasterKey = require('lib/models/MasterKey');
const Note = require('lib/models/Note');
const { _, setLocale } = require('lib/locale.js');
const os = require('os');
const { Logger } = require('lib/logger.js');
const fs = require('fs-extra');
const Tag = require('lib/models/Tag.js');
const { reg } = require('lib/registry.js');
const { sprintf } = require('sprintf-js');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
const { ElectronAppWrapper } = require('./ElectronAppWrapper');
const { defaultState } = require('lib/reducer.js');
const packageInfo = require('./packageInfo.js');
const AlarmService = require('lib/services/AlarmService.js');
@ -28,7 +24,6 @@ const ExternalEditWatcher = require('lib/services/ExternalEditWatcher');
const { bridge } = require('electron').remote.require('./bridge');
const { shell } = require('electron');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const PluginManager = require('lib/services/PluginManager');
const RevisionService = require('lib/services/RevisionService');
const MigrationService = require('lib/services/MigrationService');
@ -76,9 +71,10 @@ class Application extends BaseApplication {
try {
switch (action.type) {
case 'NAV_BACK':
case 'NAV_GO':
case 'NAV_BACK':
case 'NAV_GO':
{
const goingBack = action.type === 'NAV_BACK';
if (goingBack && !state.navHistory.length) break;
@ -101,26 +97,30 @@ class Application extends BaseApplication {
}
if (!goingBack) newNavHistory.push(currentRoute);
newState.navHistory = newNavHistory
newState.navHistory = newNavHistory;
newState.route = action;
break;
}
break;
case 'WINDOW_CONTENT_SIZE_SET':
case 'WINDOW_CONTENT_SIZE_SET':
newState = Object.assign({}, state);
newState.windowContentSize = action.size;
break;
newState = Object.assign({}, state);
newState.windowContentSize = action.size;
break;
case 'WINDOW_COMMAND':
case 'WINDOW_COMMAND':
{
newState = Object.assign({}, state);
let command = Object.assign({}, action);
delete command.type;
newState.windowCommand = command;
break;
}
break;
case 'NOTE_VISIBLE_PANES_TOGGLE':
case 'NOTE_VISIBLE_PANES_TOGGLE':
{
let panes = state.noteVisiblePanes.slice();
if (panes.length === 2) {
panes = ['editor'];
@ -134,37 +134,39 @@ class Application extends BaseApplication {
newState = Object.assign({}, state);
newState.noteVisiblePanes = panes;
break;
}
break;
case 'NOTE_VISIBLE_PANES_SET':
case 'NOTE_VISIBLE_PANES_SET':
newState = Object.assign({}, state);
newState.noteVisiblePanes = action.panes;
break;
case 'SIDEBAR_VISIBILITY_TOGGLE':
newState = Object.assign({}, state);
newState.sidebarVisibility = !state.sidebarVisibility;
break;
case 'SIDEBAR_VISIBILITY_SET':
newState = Object.assign({}, state);
newState.sidebarVisibility = action.visibility;
break;
case 'NOTE_FILE_WATCHER_ADD':
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
newState = Object.assign({}, state);
newState.noteVisiblePanes = action.panes;
break;
const watchedNoteFiles = newState.watchedNoteFiles.slice();
watchedNoteFiles.push(action.id);
newState.watchedNoteFiles = watchedNoteFiles;
}
break;
case 'SIDEBAR_VISIBILITY_TOGGLE':
newState = Object.assign({}, state);
newState.sidebarVisibility = !state.sidebarVisibility;
break;
case 'SIDEBAR_VISIBILITY_SET':
newState = Object.assign({}, state);
newState.sidebarVisibility = action.visibility;
break;
case 'NOTE_FILE_WATCHER_ADD':
if (newState.watchedNoteFiles.indexOf(action.id) < 0) {
newState = Object.assign({}, state);
const watchedNoteFiles = newState.watchedNoteFiles.slice();
watchedNoteFiles.push(action.id);
newState.watchedNoteFiles = watchedNoteFiles;
}
break;
case 'NOTE_FILE_WATCHER_REMOVE':
case 'NOTE_FILE_WATCHER_REMOVE':
{
newState = Object.assign({}, state);
const idx = newState.watchedNoteFiles.indexOf(action.id);
if (idx >= 0) {
@ -172,29 +174,32 @@ class Application extends BaseApplication {
watchedNoteFiles.splice(idx, 1);
newState.watchedNoteFiles = watchedNoteFiles;
}
break;
}
break;
case 'NOTE_FILE_WATCHER_CLEAR':
case 'NOTE_FILE_WATCHER_CLEAR':
if (state.watchedNoteFiles.length) {
newState = Object.assign({}, state);
newState.watchedNoteFiles = [];
}
break;
if (state.watchedNoteFiles.length) {
newState = Object.assign({}, state);
newState.watchedNoteFiles = [];
}
break;
case 'EDITOR_SCROLL_PERCENT_SET':
case 'EDITOR_SCROLL_PERCENT_SET':
{
newState = Object.assign({}, state);
const newPercents = Object.assign({}, newState.lastEditorScrollPercents);
newPercents[action.noteId] = action.percent;
newState.lastEditorScrollPercents = newPercents;
break;
}
break;
case 'NOTE_DEVTOOLS_TOGGLE':
case 'NOTE_DEVTOOLS_TOGGLE':
newState = Object.assign({}, state);
newState.noteDevToolsVisible = !newState.noteDevToolsVisible;
break;
newState = Object.assign({}, state);
newState.noteDevToolsVisible = !newState.noteDevToolsVisible;
break;
}
} catch (error) {
@ -278,10 +283,10 @@ class Application extends BaseApplication {
click: () => {
Setting.setValue(type + '.sortOrder.field', field);
this.refreshMenu();
}
},
});
}
sortItems.push({ type: 'separator' });
sortItems.push({
@ -295,7 +300,7 @@ class Application extends BaseApplication {
});
return sortItems;
}
};
const sortNoteItems = sortNoteFolderItems('notes');
const sortFolderItems = sortNoteFolderItems('folders');
@ -304,25 +309,25 @@ class Application extends BaseApplication {
focusItems.push({
label: _('Sidebar'),
click: () => { this.focusElement_('sideBar') },
click: () => { this.focusElement_('sideBar'); },
accelerator: 'CommandOrControl+Shift+S',
});
focusItems.push({
label: _('Note list'),
click: () => { this.focusElement_('noteList') },
click: () => { this.focusElement_('noteList'); },
accelerator: 'CommandOrControl+Shift+L',
});
focusItems.push({
label: _('Note title'),
click: () => { this.focusElement_('noteTitle') },
click: () => { this.focusElement_('noteTitle'); },
accelerator: 'CommandOrControl+Shift+N',
});
focusItems.push({
label: _('Note body'),
click: () => { this.focusElement_('noteBody') },
click: () => { this.focusElement_('noteBody'); },
accelerator: 'CommandOrControl+Shift+B',
});
@ -330,7 +335,7 @@ class Application extends BaseApplication {
const exportItems = [];
const preferencesItems = [];
const toolsItemsFirst = [];
const templateItems = [];
const templateItems = [];
const ioService = new InteropService();
const ioModules = ioService.modules();
for (let i = 0; i < ioModules.length; i++) {
@ -341,7 +346,7 @@ class Application extends BaseApplication {
screens: ['Main'],
click: async () => {
await InteropServiceHelper.export(this.dispatch.bind(this), module);
}
},
});
} else {
for (let j = 0; j < module.sources.length; j++) {
@ -356,7 +361,7 @@ class Application extends BaseApplication {
if (moduleSource === 'file') {
path = bridge().showOpenDialog({
filters: [{ name: module.description, extensions: module.fileExtensions}]
filters: [{ name: module.description, extensions: module.fileExtensions}],
});
} else {
path = bridge().showOpenDialog({
@ -380,7 +385,7 @@ class Application extends BaseApplication {
importOptions.destinationFolderId = !module.isNoteArchive && moduleSource === 'file' ? selectedFolderId : null;
importOptions.onError = (error) => {
console.warn(error);
}
};
const service = new InteropService();
try {
@ -394,7 +399,7 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'hideModalMessage',
});
}
},
});
}
}
@ -408,15 +413,15 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'exportPdf',
});
}
},
});
/* We need a dummy entry, otherwise the ternary operator to show a
* menu item only on a specific OS does not work. */
const noItem = {
type: 'separator',
visible: false
}
visible: false,
};
const syncStatusItem = {
label: _('Synchronisation status'),
@ -425,8 +430,8 @@ class Application extends BaseApplication {
type: 'NAV_GO',
routeName: 'Status',
});
}
}
},
};
const newNoteItem = {
label: _('New note'),
@ -437,8 +442,8 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'newNote',
});
}
}
},
};
const newTodoItem = {
label: _('New to-do'),
@ -449,8 +454,8 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'newTodo',
});
}
}
},
};
const newNotebookItem = {
label: _('New notebook'),
@ -460,8 +465,8 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'newNotebook',
});
}
}
},
};
const printItem = {
label: _('Print'),
@ -472,8 +477,8 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'print',
});
}
}
},
};
preferencesItems.push({
label: _('General Options'),
@ -483,7 +488,7 @@ class Application extends BaseApplication {
type: 'NAV_GO',
routeName: 'Config',
});
}
},
}, {
label: _('Encryption options'),
click: () => {
@ -491,7 +496,7 @@ class Application extends BaseApplication {
type: 'NAV_GO',
routeName: 'EncryptionConfig',
});
}
},
}, {
label: _('Web clipper options'),
click: () => {
@ -499,7 +504,7 @@ class Application extends BaseApplication {
type: 'NAV_GO',
routeName: 'ClipperConfig',
});
}
},
});
toolsItemsFirst.push(syncStatusItem, {
@ -518,7 +523,7 @@ class Application extends BaseApplication {
name: 'selectTemplate',
noteType: 'note',
});
}
},
}, {
label: _('Create to-do from template'),
visible: templateDirExists,
@ -528,7 +533,7 @@ class Application extends BaseApplication {
name: 'selectTemplate',
noteType: 'todo',
});
}
},
}, {
label: _('Insert template'),
visible: templateDirExists,
@ -538,14 +543,14 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'selectTemplate',
});
}
},
}, {
label: _('Open template directory'),
click: () => {
const templateDir = Setting.value('templateDir');
if (!templateDirExists) shim.fsDriver().mkdir(templateDir);
shell.openItem(templateDir);
}
},
}, {
label: _('Refresh templates'),
click: async () => {
@ -553,9 +558,9 @@ class Application extends BaseApplication {
this.store().dispatch({
type: 'TEMPLATE_UPDATE_ALL',
templates: templates
templates: templates,
});
}
},
});
const toolsItems = toolsItemsFirst.concat(preferencesItems);
@ -567,7 +572,7 @@ class Application extends BaseApplication {
function _showAbout() {
const p = packageInfo;
let gitInfo = '';
if ("git" in p) {
if ('git' in p) {
gitInfo = _('Revision: %s (%s)', p.git.hash, p.git.branch);
}
let message = [
@ -576,8 +581,8 @@ class Application extends BaseApplication {
'Copyright © 2016-2019 Laurent Cozic',
_('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), process.platform),
];
if (!!gitInfo) {
message.push("\n" + gitInfo);
if (gitInfo) {
message.push('\n' + gitInfo);
console.info(gitInfo);
}
bridge().showInfoMessageBox(message.join('\n'), {
@ -596,34 +601,34 @@ class Application extends BaseApplication {
submenu: [{
label: _('About Joplin'),
visible: shim.isMac() ? true : false,
click: () => _showAbout()
click: () => _showAbout(),
}, {
type: 'separator',
visible: shim.isMac() ? true : false
visible: shim.isMac() ? true : false,
}, {
label: _('Preferences...'),
visible: shim.isMac() ? true : false,
submenu: preferencesItems
submenu: preferencesItems,
}, {
label: _('Check for updates...'),
visible: shim.isMac() ? true : false,
click: () => _checkForUpdates(this)
click: () => _checkForUpdates(this),
}, {
type: 'separator',
visible: shim.isMac() ? true : false
visible: shim.isMac() ? true : false,
},
shim.isMac() ? noItem : newNoteItem,
shim.isMac() ? noItem : newTodoItem,
shim.isMac() ? noItem : newNotebookItem, {
type: 'separator',
visible: shim.isMac() ? false : true
visible: shim.isMac() ? false : true,
}, {
label: _('Templates'),
visible: shim.isMac() ? false : true,
submenu: templateItems,
}, {
type: 'separator',
visible: shim.isMac() ? false : true
visible: shim.isMac() ? false : true,
}, {
label: _('Import'),
visible: shim.isMac() ? false : true,
@ -643,7 +648,7 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'synchronize',
});
}
},
}, shim.isMac() ? syncStatusItem : noItem, {
type: 'separator',
}, shim.isMac() ? noItem : printItem, {
@ -653,14 +658,14 @@ class Application extends BaseApplication {
label: _('Hide %s', 'Joplin'),
platforms: ['darwin'],
accelerator: 'CommandOrControl+H',
click: () => { bridge().electronApp().hide() }
click: () => { bridge().electronApp().hide(); },
}, {
type: 'separator',
}, {
label: _('Quit'),
accelerator: 'CommandOrControl+Q',
click: () => { bridge().electronApp().quit() }
}]
click: () => { bridge().electronApp().quit(); },
}],
};
const rootMenuFileMacOs = {
@ -690,8 +695,8 @@ class Application extends BaseApplication {
}, {
type: 'separator',
},
printItem
]
printItem,
],
};
const rootMenus = {
@ -846,7 +851,7 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'toggleSidebar',
});
}
},
}, {
label: _('Toggle editor layout'),
screens: ['Main'],
@ -856,7 +861,7 @@ class Application extends BaseApplication {
type: 'WINDOW_COMMAND',
name: 'toggleVisiblePanes',
});
}
},
}, {
type: 'separator',
screens: ['Main'],
@ -902,14 +907,14 @@ class Application extends BaseApplication {
submenu: [{
label: _('Website and documentation'),
accelerator: 'F1',
click () { bridge().openExternal('https://joplinapp.org') }
click () { bridge().openExternal('https://joplinapp.org'); },
}, {
label: _('Make a donation'),
click () { bridge().openExternal('https://joplinapp.org/donate/') }
click () { bridge().openExternal('https://joplinapp.org/donate/'); },
}, {
label: _('Check for updates...'),
visible: shim.isMac() ? false : true,
click: () => _checkForUpdates(this)
click: () => _checkForUpdates(this),
}, {
type: 'separator',
screens: ['Main'],
@ -928,9 +933,9 @@ class Application extends BaseApplication {
}, {
label: _('About Joplin'),
visible: shim.isMac() ? false : true,
click: () => _showAbout()
}]
},
click: () => _showAbout(),
}],
},
};
if (shim.isMac()) {
@ -950,7 +955,7 @@ class Application extends BaseApplication {
output.push(item);
}
return output;
}
};
for (const key in rootMenus) {
if (!rootMenus.hasOwnProperty(key)) continue;
@ -1045,8 +1050,8 @@ class Application extends BaseApplication {
const contextMenu = Menu.buildFromTemplate([
{ label: _('Open %s', app.electronApp().getName()), click: () => { app.window().show(); } },
{ type: 'separator' },
{ label: _('Exit'), click: () => { app.quit() } },
])
{ label: _('Exit'), click: () => { app.quit(); } },
]);
app.createTray(contextMenu);
}
}
@ -1095,7 +1100,7 @@ class Application extends BaseApplication {
AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId }));
AlarmService.setLogger(reg.logger());
reg.setShowErrorMessageBoxHandler((message) => { bridge().showErrorMessageBox(message) });
reg.setShowErrorMessageBoxHandler((message) => { bridge().showErrorMessageBox(message); });
if (Setting.value('openDevTools')) {
bridge().window().webContents.openDevTools();
@ -1144,14 +1149,14 @@ class Application extends BaseApplication {
this.store().dispatch({
type: 'LOAD_CUSTOM_CSS',
css: cssString
css: cssString,
});
const templates = await TemplateUtils.loadTemplates(Setting.value('templateDir'));
this.store().dispatch({
type: 'TEMPLATE_UPDATE_ALL',
templates: templates
templates: templates,
});
// Note: Auto-update currently doesn't work in Linux: it downloads the update
@ -1161,12 +1166,12 @@ class Application extends BaseApplication {
if (Setting.value('autoUpdateEnabled')) {
bridge().checkForUpdates(true, bridge().window(), this.checkForUpdateLoggerPath(), { includePreReleases: Setting.value('autoUpdate.includePreReleases') });
}
}
};
// Initial check on startup
setTimeout(() => { runAutoUpdateCheck() }, 5000);
setTimeout(() => { runAutoUpdateCheck(); }, 5000);
// Then every x hours
setInterval(() => { runAutoUpdateCheck() }, 12 * 60 * 60 * 1000);
setInterval(() => { runAutoUpdateCheck(); }, 12 * 60 * 60 * 1000);
}
this.updateTray();

View File

@ -1,6 +1,5 @@
const { _, setLocale } = require('lib/locale.js');
const { dirname } = require('lib/path-utils.js');
const { Logger } = require('lib/logger.js');
class Bridge {
@ -65,7 +64,6 @@ class Bridge {
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
showMessageBox_(window, options) {
const {dialog} = require('electron');
const nativeImage = require('electron').nativeImage
if (!window) window = this.window();
return dialog.showMessageBox(window, options);
}
@ -112,18 +110,18 @@ class Bridge {
}
openExternal(url) {
return require('electron').shell.openExternal(url)
return require('electron').shell.openExternal(url);
}
openItem(fullPath) {
return require('electron').shell.openItem(fullPath)
return require('electron').shell.openItem(fullPath);
}
checkForUpdates(inBackground, window, logFilePath, options) {
const { checkForUpdates } = require('./checkForUpdates.js');
checkForUpdates(inBackground, window, logFilePath, options);
}
}
let bridge_ = null;
@ -137,6 +135,6 @@ function initBridge(wrapper) {
function bridge() {
if (!bridge_) throw new Error('Bridge not initialized');
return bridge_;
}
}
module.exports = { bridge, initBridge }
module.exports = { bridge, initBridge };

View File

@ -1,4 +1,4 @@
const { dialog } = require('electron')
const { dialog } = require('electron');
const { shim } = require('lib/shim');
const { Logger } = require('lib/logger.js');
const { _ } = require('lib/locale.js');
@ -46,7 +46,7 @@ async function fetchLatestRelease(options) {
}
json = await response.json();
if (!json.length) throw new Error('Cannot get latest release info: ' + responseText.substr(0,500));
if (!json.length) throw new Error('Cannot get latest release info (JSON)');
json = json[0];
} else {
const response = await fetch('https://api.github.com/repos/laurent22/joplin/releases/latest');
@ -58,7 +58,7 @@ async function fetchLatestRelease(options) {
json = await response.json();
}
const version = json.tag_name.substr(1);
let downloadUrl = null;
const platform = process.platform;
@ -121,18 +121,18 @@ function checkForUpdates(inBackground, window, logFilePath, options) {
if (compareVersions(release.version, packageInfo.version) <= 0) {
if (!checkInBackground_) dialog.showMessageBox({
type: 'info',
message: _('Current version is up-to-date.'),
buttons: [_('OK')],
})
type: 'info',
message: _('Current version is up-to-date.'),
buttons: [_('OK')],
});
} else {
const releaseNotes = release.notes.trim() ? "\n\n" + release.notes.trim() : '';
const releaseNotes = release.notes.trim() ? '\n\n' + release.notes.trim() : '';
const newVersionString = release.prerelease ? _('%s (pre-release)', release.version) : release.version;
const buttonIndex = dialog.showMessageBox(parentWindow_, {
type: 'info',
message: _('An update is available, do you want to download it now?') + '\n\n' + _('Your version: %s', packageInfo.version) + '\n' + _('New version: %s', newVersionString) + releaseNotes,
buttons: [_('Yes'), _('No')]
buttons: [_('Yes'), _('No')],
});
if (buttonIndex === 0) require('electron').shell.openExternal(release.downloadUrl ? release.downloadUrl : release.pageUrl);
@ -145,4 +145,4 @@ function checkForUpdates(inBackground, window, logFilePath, options) {
});
}
module.exports.checkForUpdates = checkForUpdates
module.exports.checkForUpdates = checkForUpdates;

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