1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-06 09:19:22 +02:00

First pass at linting lib dir

This commit is contained in:
Laurent Cozic
2019-07-29 15:43:53 +02:00
parent 64b7bc3d62
commit 86dc72b204
170 changed files with 4140 additions and 3119 deletions

View File

@@ -2,7 +2,6 @@ const Note = require('lib/models/Note.js');
const Alarm = require('lib/models/Alarm.js');
class AlarmService {
static setDriver(v) {
this.driver_ = v;
}
@@ -40,7 +39,7 @@ class AlarmService {
await Alarm.batchDelete(alarmIds);
}
// When passing a note, make sure it has all the required properties
// When passing a note, make sure it has all the required properties
// (better to pass a complete note or else just the ID)
static async updateNoteNotification(noteOrId, isDeleted = false) {
try {
@@ -62,14 +61,12 @@ class AlarmService {
let alarm = noteId ? await Alarm.byNoteId(noteId) : null;
let clearAlarm = false;
if (isDeleted ||
!Note.needAlarm(note) ||
(alarm && alarm.trigger_time !== note.todo_due))
{
if (isDeleted || !Note.needAlarm(note) || (alarm && alarm.trigger_time !== note.todo_due)) {
clearAlarm = !!alarm;
}
if (!clearAlarm && alarm) { // Alarm already exists and set at the right time
if (!clearAlarm && alarm) {
// Alarm already exists and set at the right time
// For persistent notifications (those that stay active after the app has been closed, like on mobile), if we have
// an alarm object we can be sure that the notification has already been set, so there's nothing to do.
@@ -118,7 +115,6 @@ class AlarmService {
await this.updateNoteNotification(dueNotes[i]);
}
}
}
module.exports = AlarmService;
module.exports = AlarmService;

View File

@@ -1,17 +1,16 @@
const PushNotification = require('react-native-push-notification');
class AlarmServiceDriver {
PushNotificationHandler_() {
if (!this.PushNotification_) {
PushNotification.configure({
// (required) Called when a remote or local notification is opened or received
onNotification: function(notification) {
console.info('Notification was opened: ', notification );
// process the notification
},
popInitialNotification: true,
requestPermissions: true,
// (required) Called when a remote or local notification is opened or received
onNotification: function(notification) {
console.info('Notification was opened: ', notification);
// process the notification
},
popInitialNotification: true,
requestPermissions: true,
});
this.PushNotification_ = PushNotification;
@@ -25,13 +24,13 @@ class AlarmServiceDriver {
}
notificationIsSet(alarmId) {
throw new Error('Available only for non-persistent alarms');
throw new Error('Available only for non-persistent alarms');
}
async clearNotification(id) {
return this.PushNotificationHandler_().cancelLocalNotifications({id: id+''});
return this.PushNotificationHandler_().cancelLocalNotifications({ id: id + '' });
}
async scheduleNotification(notification) {
const config = {
id: notification.id + '',
@@ -41,7 +40,6 @@ class AlarmServiceDriver {
this.PushNotificationHandler_().localNotificationSchedule(config);
}
}
module.exports = AlarmServiceDriver;
module.exports = AlarmServiceDriver;

View File

@@ -1,12 +1,11 @@
const { PushNotificationIOS } = require('react-native');
class AlarmServiceDriver {
constructor() {
this.hasPermission_ = null;
this.inAppNotificationHandler_ = null;
PushNotificationIOS.addEventListener('localNotification', (instance) => {
PushNotificationIOS.addEventListener('localNotification', instance => {
if (!this.inAppNotificationHandler_) return;
if (!instance || !instance._data || !instance._data.id) {
@@ -24,7 +23,7 @@ class AlarmServiceDriver {
}
notificationIsSet(alarmId) {
throw new Error('Available only for non-persistent alarms');
throw new Error('Available only for non-persistent alarms');
}
setInAppNotificationHandler(v) {
@@ -37,7 +36,7 @@ class AlarmServiceDriver {
if (this.hasPermission_ !== null) return this.hasPermission_;
return new Promise((resolve, reject) => {
PushNotificationIOS.checkPermissions(async (perm) => {
PushNotificationIOS.checkPermissions(async perm => {
const ok = await this.hasPermissions(perm);
this.hasPermission_ = ok;
resolve(ok);
@@ -47,9 +46,9 @@ class AlarmServiceDriver {
async requestPermissions() {
const newPerm = await PushNotificationIOS.requestPermissions({
alert:1,
badge:1,
sound:1,
alert: 1,
badge: 1,
sound: 1,
});
this.hasPermission_ = null;
return this.hasPermissions(newPerm);
@@ -58,7 +57,7 @@ class AlarmServiceDriver {
async clearNotification(id) {
PushNotificationIOS.cancelLocalNotifications({ id: id + '' });
}
async scheduleNotification(notification) {
if (!(await this.hasPermissions())) {
const ok = await this.requestPermissions();
@@ -77,7 +76,6 @@ class AlarmServiceDriver {
PushNotificationIOS.scheduleLocalNotification(iosNotification);
}
}
module.exports = AlarmServiceDriver;
module.exports = AlarmServiceDriver;

View File

@@ -1,7 +1,6 @@
const notifier = require('node-notifier');
class AlarmServiceDriverNode {
constructor(options) {
// Note: appName is required to get the notification to work. It must be the same as the appId defined in package.json
// https://github.com/mikaelbr/node-notifier/issues/144#issuecomment-319324058
@@ -22,7 +21,7 @@ class AlarmServiceDriverNode {
clearTimeout(this.notifications_[id].timeoutId);
delete this.notifications_[id];
}
async scheduleNotification(notification) {
const now = Date.now();
const interval = notification.date.getTime() - now;
@@ -45,7 +44,6 @@ class AlarmServiceDriverNode {
this.notifications_[notification.id] = Object.assign({}, notification);
this.notifications_[notification.id].timeoutId = timeoutId;
}
}
module.exports = AlarmServiceDriverNode;
module.exports = AlarmServiceDriverNode;

View File

@@ -1,12 +1,10 @@
class BaseService {
logger() {
if (!BaseService.logger_) throw new Error('BaseService.logger_ not set!!');
return BaseService.logger_;
}
}
BaseService.logger_ = null;
module.exports = BaseService;
module.exports = BaseService;

View File

@@ -7,12 +7,11 @@ const { Logger } = require('lib/logger.js');
const EventEmitter = require('events');
class DecryptionWorker {
constructor() {
this.state_ = 'idle';
this.logger_ = new Logger();
this.dispatch = (action) => {
this.dispatch = action => {
//console.warn('DecryptionWorker.dispatch is not defined');
};
@@ -158,8 +157,8 @@ class DecryptionWorker {
const clearDecryptionCounter = async () => {
await this.kvStore().deleteValue(counterKey);
}
};
// Don't log in production as it results in many messages when importing many items
// this.logger().debug('DecryptionWorker: decrypting: ' + item.id + ' (' + ItemClass.tableName() + ')');
try {
@@ -177,7 +176,7 @@ class DecryptionWorker {
if (decryptedItem.type_ === Resource.modelType() && !!decryptedItem.encryption_blob_encrypted) {
// itemsThatNeedDecryption() will return the resource again if the blob has not been decrypted,
// but that will result in an infinite loop if the blob simply has not been downloaded yet.
// So skip the ID for now, and the service will try to decrypt the blob again the next time.
// So skip the ID for now, and the service will try to decrypt the blob again the next time.
excludedIds.push(decryptedItem.id);
}
@@ -242,7 +241,6 @@ class DecryptionWorker {
this.scheduleStart();
}
}
}
module.exports = DecryptionWorker;
module.exports = DecryptionWorker;

View File

@@ -12,7 +12,6 @@ function hexPad(s, length) {
}
class EncryptionService {
constructor() {
// Note: 1 MB is very slow with Node and probably even worse on mobile.
//
@@ -35,10 +34,7 @@ class EncryptionService {
this.headerTemplates_ = {
1: {
fields: [
[ 'encryptionMethod', 2, 'int' ],
[ 'masterKeyId', 32, 'hex' ],
],
fields: [['encryptionMethod', 2, 'int'], ['masterKeyId', 32, 'hex']],
},
};
}
@@ -71,8 +67,8 @@ class EncryptionService {
if (password) {
let passwordCache = Setting.value('encryption.passwordCache');
passwordCache[masterKey.id] = password;
Setting.setValue('encryption.passwordCache', passwordCache);
passwordCache[masterKey.id] = password;
Setting.setValue('encryption.passwordCache', passwordCache);
}
// Mark only the non-encrypted ones for sync since, if there are encrypted ones,
@@ -88,7 +84,7 @@ class EncryptionService {
// const hasEncryptedItems = await BaseItem.hasEncryptedItems();
// if (hasEncryptedItems) throw new Error(_('Encryption cannot currently be disabled because some items are still encrypted. Please wait for all the items to be decrypted and try again.'));
Setting.setValue('encryption.enabled', false);
// The only way to make sure everything gets decrypted on the sync target is
// to re-sync everything.
@@ -195,7 +191,7 @@ class EncryptionService {
sha256(string) {
const sjcl = shim.sjclModule;
const bitArray = sjcl.hash.sha256.hash(string);
const bitArray = sjcl.hash.sha256.hash(string);
return sjcl.codec.hex.fromBits(bitArray);
}
@@ -207,20 +203,30 @@ class EncryptionService {
// we use shim.randomBytes directly to generate master keys.
const sjcl = shim.sjclModule;
const randomBytes = await shim.randomBytes(1024/8);
const hexBytes = randomBytes.map((a) => { return a.toString(16) });
const randomBytes = await shim.randomBytes(1024 / 8);
const hexBytes = randomBytes.map(a => {
return a.toString(16);
});
const hexSeed = sjcl.codec.hex.toBits(hexBytes.join(''));
sjcl.random.addEntropy(hexSeed, 1024, 'shim.randomBytes');
}
async randomHexString(byteCount) {
const bytes = await shim.randomBytes(byteCount);
return bytes.map((a) => { return hexPad(a.toString(16), 2); }).join('');
return bytes
.map(a => {
return hexPad(a.toString(16), 2);
})
.join('');
}
async generateMasterKey(password) {
const bytes = await shim.randomBytes(256);
const hexaBytes = bytes.map((a) => { return hexPad(a.toString(16), 2); }).join('');
const hexaBytes = bytes
.map(a => {
return hexPad(a.toString(16), 2);
})
.join('');
const checksum = this.sha256(hexaBytes);
const encryptionMethod = EncryptionService.METHOD_SJCL_2;
const cipherText = await this.encrypt(encryptionMethod, password, hexaBytes);
@@ -267,9 +273,9 @@ class EncryptionService {
iter: 1000, // Defaults to 10000 in sjcl but since we're running this on mobile devices, use a lower value. Maybe review this after some time. https://security.stackexchange.com/questions/3959/recommended-of-iterations-when-using-pkbdf2-sha256
ks: 128, // Key size - "128 bits should be secure enough"
ts: 64, // ???
mode: "ocb2", // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented.
mode: 'ocb2', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented.
//"adata":"", // Associated Data - not needed?
cipher: "aes"
cipher: 'aes',
});
} catch (error) {
// SJCL returns a string as error which means stack trace is missing so convert to an error object here
@@ -285,8 +291,8 @@ class EncryptionService {
iter: 10000,
ks: 256,
ts: 64,
mode: "ocb2",
cipher: "aes"
mode: 'ocb2',
cipher: 'aes',
});
} catch (error) {
// SJCL returns a string as error which means stack trace is missing so convert to an error object here
@@ -302,7 +308,7 @@ class EncryptionService {
if (!key) throw new Error('Encryption key is required');
const sjcl = shim.sjclModule;
if (method === EncryptionService.METHOD_SJCL || method === EncryptionService.METHOD_SJCL_2) {
try {
return sjcl.json.decrypt(key, cipherText);
@@ -413,7 +419,7 @@ class EncryptionService {
const handle = await this.fsDriver().open(path, 'r');
const reader = {
handle: handle,
read: async (size) => {
read: async size => {
return this.fsDriver().readFileChunk(reader.handle, size, encoding);
},
close: async () => {
@@ -425,7 +431,7 @@ class EncryptionService {
async fileWriter_(path, encoding) {
return {
append: async (data) => {
append: async data => {
return this.fsDriver().appendFile(path, data, encoding);
},
close: function() {},
@@ -455,7 +461,7 @@ class EncryptionService {
if (destination) await destination.close();
source = null;
destination = null;
}
};
try {
await this.fsDriver().unlink(destPath);
@@ -478,7 +484,7 @@ class EncryptionService {
if (destination) await destination.close();
source = null;
destination = null;
}
};
try {
await this.fsDriver().unlink(destPath);
@@ -518,7 +524,7 @@ class EncryptionService {
const reader = this.stringReader_(headerHexaBytes, true);
const identifier = reader.read(3);
const version = parseInt(reader.read(2), 16);
if (identifier !== 'JED') throw new Error('Invalid header (missing identifier): ' + headerHexaBytes.substr(0,64));
if (identifier !== 'JED') throw new Error('Invalid header (missing identifier): ' + headerHexaBytes.substr(0, 64));
const template = this.headerTemplate(version);
const size = parseInt(reader.read(6), 16);
@@ -563,7 +569,6 @@ class EncryptionService {
await this.fsDriver().close(handle);
return this.isValidHeaderIdentifier(headerIdentifier);
}
}
EncryptionService.METHOD_SJCL = 1;
@@ -571,4 +576,4 @@ EncryptionService.METHOD_SJCL_2 = 2;
EncryptionService.fsDriver_ = null;
module.exports = EncryptionService;
module.exports = EncryptionService;

View File

@@ -1,9 +1,5 @@
class EncryptionServiceDriverRN {
encryptFile(srcPath, destPath) {
}
encryptFile(srcPath, destPath) {}
}
module.exports = EncryptionServiceDriverRN;
module.exports = EncryptionServiceDriverRN;

View File

@@ -5,14 +5,13 @@ const { shim } = require('lib/shim');
const EventEmitter = require('events');
const { splitCommandString } = require('lib/string-utils');
const { fileExtension, basename } = require('lib/path-utils');
const spawn = require('child_process').spawn;
const spawn = require('child_process').spawn;
const chokidar = require('chokidar');
class ExternalEditWatcher {
constructor() {
this.logger_ = new Logger();
this.dispatch = (action) => {};
this.dispatch = action => {};
this.watcher_ = null;
this.eventEmitter_ = new EventEmitter();
this.skipNextChangeEvent_ = {};
@@ -59,7 +58,6 @@ class ExternalEditWatcher {
// another file with the same name, as it happens with emacs. So because of this
// we keep watching anyway.
// See: https://github.com/laurent22/joplin/issues/710#issuecomment-420997167
// this.watcher_.unwatch(path);
} else if (event === 'change') {
const id = this.noteFilePathToId_(path);
@@ -84,12 +82,12 @@ class ExternalEditWatcher {
this.skipNextChangeEvent_ = {};
} else if (event === 'error') {
this.logger().error('ExternalEditWatcher:');
this.logger().error(error)
this.logger().error(error);
}
});
// Hack to support external watcher on some linux applications (gedit, gvim, etc)
// taken from https://github.com/paulmillr/chokidar/issues/591
this.watcher_.on('raw', async (event, path, {watchedPath}) => {
this.watcher_.on('raw', async (event, path, { watchedPath }) => {
if (event === 'rename') {
this.watcher_.unwatch(watchedPath);
this.watcher_.add(watchedPath);
@@ -163,7 +161,7 @@ class ExternalEditWatcher {
const editorCommand = Setting.value('editor');
if (!editorCommand) return null;
const s = splitCommandString(editorCommand, {handleEscape: false});
const s = splitCommandString(editorCommand, { handleEscape: false });
const path = s.splice(0, 1);
if (!path.length) throw new Error('Invalid editor command: ' + editorCommand);
@@ -175,9 +173,8 @@ class ExternalEditWatcher {
async spawnCommand(path, args, options) {
return new Promise((resolve, reject) => {
// App bundles need to be opened using the `open` command.
// Additional args can be specified after --args, and the
// Additional args can be specified after --args, and the
// -n flag is needed to ensure that the app is always launched
// with the arguments. Without it, if the app is already opened,
// it will just bring it to the foreground without opening the file.
@@ -193,13 +190,13 @@ class ExternalEditWatcher {
path = 'open';
}
const wrapError = (error) => {
const wrapError = error => {
if (!error) return error;
let msg = error.message ? [error.message] : [];
msg.push('Command was: "' + path + '" ' + args.join(' '));
error.message = msg.join('\n\n');
return error;
}
};
try {
const subProcess = spawn(path, args, options);
@@ -212,7 +209,7 @@ class ExternalEditWatcher {
}
}, 100);
subProcess.on('error', (error) => {
subProcess.on('error', error => {
clearInterval(iid);
reject(wrapError(error));
});
@@ -295,14 +292,13 @@ class ExternalEditWatcher {
if (!note || !note.id) {
this.logger().warn('ExternalEditWatcher: Cannot update note file: ', note);
return;
}
}
const filePath = this.noteIdToFilePath_(note.id);
const noteContent = await Note.serializeForEdit(note);
await shim.fsDriver().writeFile(filePath, noteContent, 'utf-8');
return filePath;
}
}
module.exports = ExternalEditWatcher;

View File

@@ -16,7 +16,6 @@ const { uuid } = require('lib/uuid.js');
const { toTitleCase } = require('lib/string-utils');
class InteropService {
constructor() {
this.modules_ = null;
}
@@ -30,17 +29,20 @@ class InteropService {
fileExtensions: ['jex'],
sources: ['file'],
description: _('Joplin Export File'),
}, {
},
{
format: 'md',
fileExtensions: ['md', 'markdown'],
sources: ['file', 'directory'],
isNoteArchive: false, // Tells whether the file can contain multiple notes (eg. Enex or Jex format)
description: _('Markdown'),
}, {
},
{
format: 'raw',
sources: ['directory'],
description: _('Joplin Export Directory'),
}, {
},
{
format: 'enex',
fileExtensions: ['enex'],
sources: ['file'],
@@ -54,42 +56,53 @@ class InteropService {
fileExtensions: ['jex'],
target: 'file',
description: _('Joplin Export File'),
}, {
},
{
format: 'raw',
target: 'directory',
description: _('Joplin Export Directory'),
}, {
},
{
format: 'json',
target: 'directory',
description: _('Json Export Directory'),
}, {
},
{
format: 'md',
target: 'directory',
description: _('Markdown'),
},
];
importModules = importModules.map((a) => {
importModules = importModules.map(a => {
const className = 'InteropService_Importer_' + toTitleCase(a.format);
const output = Object.assign({}, {
type: 'importer',
path: 'lib/services/' + className,
}, a);
const output = Object.assign(
{},
{
type: 'importer',
path: 'lib/services/' + className,
},
a
);
if (!('isNoteArchive' in output)) output.isNoteArchive = true;
return output;
});
exportModules = exportModules.map((a) => {
exportModules = exportModules.map(a => {
const className = 'InteropService_Exporter_' + toTitleCase(a.format);
return Object.assign({}, {
type: 'exporter',
path: 'lib/services/' + className,
}, a);
return Object.assign(
{},
{
type: 'exporter',
path: 'lib/services/' + className,
},
a
);
});
this.modules_ = importModules.concat(exportModules);
this.modules_ = this.modules_.map((a) => {
this.modules_ = this.modules_.map(a => {
a.fullLabel = function(moduleSource = null) {
const label = [this.format.toUpperCase() + ' - ' + this.description];
if (moduleSource && this.sources.length > 1) {
@@ -136,13 +149,17 @@ class InteropService {
}
async import(options) {
if (!await shim.fsDriver().exists(options.path)) throw new Error(_('Cannot find "%s".', options.path));
if (!(await shim.fsDriver().exists(options.path))) throw new Error(_('Cannot find "%s".', options.path));
options = Object.assign({}, {
format: 'auto',
destinationFolderId: null,
destinationFolder: null,
}, options);
options = Object.assign(
{},
{
format: 'auto',
destinationFolderId: null,
destinationFolder: null,
},
options
);
if (options.format === 'auto') {
const module = this.moduleByFileExtension_('importer', fileExtension(options.path));
@@ -156,7 +173,7 @@ class InteropService {
options.destinationFolder = folder;
}
let result = { warnings: [] }
let result = { warnings: [] };
const importer = this.newModule_('importer', options.format);
await importer.init(options.path, options);
@@ -170,15 +187,15 @@ class InteropService {
let sourceFolderIds = options.sourceFolderIds ? options.sourceFolderIds : [];
const sourceNoteIds = options.sourceNoteIds ? options.sourceNoteIds : [];
const exportFormat = options.format ? options.format : 'jex';
const result = { warnings: [] }
const result = { warnings: [] };
const itemsToExport = [];
const queueExportItem = (itemType, itemOrId) => {
itemsToExport.push({
type: itemType,
itemOrId: itemOrId
itemOrId: itemOrId,
});
}
};
let exportedNoteIds = [];
let resourceIds = [];
@@ -283,7 +300,6 @@ class InteropService {
return result;
}
}
module.exports = InteropService;
module.exports = InteropService;

View File

@@ -1,5 +1,4 @@
class InteropService_Exporter_Base {
async init(destDir) {}
async processItem(ItemClass, item) {}
async processResource(resource, filePath) {}
@@ -27,7 +26,6 @@ class InteropService_Exporter_Base {
if (createIt) await require('fs-extra').mkdirp(tempDir);
return tempDir;
}
}
module.exports = InteropService_Exporter_Base;
module.exports = InteropService_Exporter_Base;

View File

@@ -18,7 +18,6 @@ const { uuid } = require('lib/uuid.js');
const { importEnex } = require('lib/import-enex');
class InteropService_Exporter_Jex extends InteropService_Exporter_Base {
async init(destPath) {
if (await shim.fsDriver().isDirectory(destPath)) throw new Error('Path is a directory: ' + destPath);
@@ -38,20 +37,22 @@ class InteropService_Exporter_Jex extends InteropService_Exporter_Base {
async close() {
const stats = await shim.fsDriver().readDirStats(this.tempDir_, { recursive: true });
const filePaths = stats.filter((a) => !a.isDirectory()).map((a) => a.path);
const filePaths = stats.filter(a => !a.isDirectory()).map(a => a.path);
if (!filePaths.length) throw new Error(_('There is no data to export.'));
await require('tar').create({
strict: true,
portable: true,
file: this.destPath_,
cwd: this.tempDir_,
}, filePaths);
await require('tar').create(
{
strict: true,
portable: true,
file: this.destPath_,
cwd: this.tempDir_,
},
filePaths
);
await fs.remove(this.tempDir_);
}
}
module.exports = InteropService_Exporter_Jex;
module.exports = InteropService_Exporter_Jex;

View File

@@ -3,7 +3,6 @@ const { basename, filename } = require('lib/path-utils.js');
const { shim } = require('lib/shim');
class InteropService_Exporter_Json extends InteropService_Exporter_Base {
async init(destDir) {
this.destDir_ = destDir;
this.resourceDir_ = destDir ? destDir + '/resources' : null;
@@ -13,7 +12,7 @@ class InteropService_Exporter_Json extends InteropService_Exporter_Base {
}
async processItem(ItemClass, item) {
const fileName = ItemClass.systemPath(item, "json");
const fileName = ItemClass.systemPath(item, 'json');
const filePath = this.destDir_ + '/' + fileName;
const serialized = JSON.stringify(item);
await shim.fsDriver().writeFile(filePath, serialized, 'utf-8');
@@ -27,4 +26,4 @@ class InteropService_Exporter_Json extends InteropService_Exporter_Base {
async close() {}
}
module.exports = InteropService_Exporter_Json;
module.exports = InteropService_Exporter_Json;

View File

@@ -6,7 +6,6 @@ const Note = require('lib/models/Note');
const { shim } = require('lib/shim');
class InteropService_Exporter_Md extends InteropService_Exporter_Base {
async init(destDir) {
this.destDir_ = destDir;
this.resourceDir_ = destDir ? destDir + '/_resources' : null;
@@ -75,7 +74,6 @@ class InteropService_Exporter_Md extends InteropService_Exporter_Base {
}
async close() {}
}
module.exports = InteropService_Exporter_Md;
module.exports = InteropService_Exporter_Md;

View File

@@ -3,7 +3,6 @@ const { basename, filename } = require('lib/path-utils.js');
const { shim } = require('lib/shim');
class InteropService_Exporter_Raw extends InteropService_Exporter_Base {
async init(destDir) {
this.destDir_ = destDir;
this.resourceDir_ = destDir ? destDir + '/resources' : null;
@@ -24,7 +23,6 @@ class InteropService_Exporter_Raw extends InteropService_Exporter_Base {
}
async close() {}
}
module.exports = InteropService_Exporter_Raw;
module.exports = InteropService_Exporter_Raw;

View File

@@ -1,5 +1,4 @@
class InteropService_Importer_Base {
setMetadata(md) {
this.metadata_ = md;
}
@@ -21,7 +20,6 @@ class InteropService_Importer_Base {
if (createIt) await require('fs-extra').mkdirp(tempDir);
return tempDir;
}
}
module.exports = InteropService_Importer_Base;
module.exports = InteropService_Importer_Base;

View File

@@ -16,7 +16,6 @@ const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js');
class InteropService_Importer_Enex extends InteropService_Importer_Base {
async exec(result) {
const { importEnex } = require('lib/import-enex');
@@ -31,7 +30,6 @@ class InteropService_Importer_Enex extends InteropService_Importer_Base {
return result;
}
}
module.exports = InteropService_Importer_Enex;
module.exports = InteropService_Importer_Enex;

View File

@@ -18,7 +18,6 @@ const { uuid } = require('lib/uuid.js');
const { importEnex } = require('lib/import-enex');
class InteropService_Importer_Jex extends InteropService_Importer_Base {
async exec(result) {
const tempDir = await this.temporaryDirectory_(true);
@@ -46,7 +45,6 @@ class InteropService_Importer_Jex extends InteropService_Importer_Base {
return result;
}
}
module.exports = InteropService_Importer_Jex;
module.exports = InteropService_Importer_Jex;

View File

@@ -17,7 +17,6 @@ const { uuid } = require('lib/uuid.js');
const { importEnex } = require('lib/import-enex');
class InteropService_Importer_Md extends InteropService_Importer_Base {
async exec(result) {
let parentFolderId = null;
@@ -36,7 +35,7 @@ class InteropService_Importer_Md extends InteropService_Importer_Base {
this.importDirectory(sourcePath, parentFolderId);
} else {
if (!this.options_.destinationFolder) throw new Error(_('Please specify the notebook where the notes should be imported to.'));
parentFolderId = this.options_.destinationFolder.id
parentFolderId = this.options_.destinationFolder.id;
filePaths.push(sourcePath);
}
@@ -81,7 +80,6 @@ class InteropService_Importer_Md extends InteropService_Importer_Base {
};
await Note.save(note, { autoTimestamp: false });
}
}
module.exports = InteropService_Importer_Md;
module.exports = InteropService_Importer_Md;

View File

@@ -16,14 +16,13 @@ const { fileExtension } = require('lib/path-utils');
const { uuid } = require('lib/uuid.js');
class InteropService_Importer_Raw extends InteropService_Importer_Base {
async exec(result) {
const itemIdMap = {};
const createdResources = {};
const noteTagsToCreate = [];
const destinationFolderId = this.options_.destinationFolderId;
const replaceLinkedItemIds = async (noteBody) => {
const replaceLinkedItemIds = async noteBody => {
let output = noteBody;
const itemIds = Note.linkedItemIds(noteBody);
@@ -34,7 +33,7 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
}
return output;
}
};
const stats = await shim.fsDriver().readDirStats(this.sourcePath_);
@@ -46,7 +45,7 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
if (statId.toLowerCase() === folderId) return true;
}
return false;
}
};
let defaultFolder_ = null;
const defaultFolder = async () => {
@@ -54,9 +53,9 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
const folderTitle = await Folder.findUniqueItemTitle(this.options_.defaultFolderTitle ? this.options_.defaultFolderTitle : 'Imported');
defaultFolder_ = await Folder.save({ title: folderTitle });
return defaultFolder_;
}
};
const setFolderToImportTo = async (itemParentId) => {
const setFolderToImportTo = async itemParentId => {
// Logic is a bit complex here:
// - If a destination folder was specified, move the note to it.
// - Otherwise, if the associated folder exists, use this.
@@ -73,7 +72,7 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
itemIdMap[itemParentId] = uuid.create();
}
}
}
};
for (let i = 0; i < stats.length; i++) {
const stat = stats[i];
@@ -88,7 +87,6 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
delete item.type_;
if (itemType === BaseModel.TYPE_NOTE) {
await setFolderToImportTo(item.parent_id);
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
@@ -111,7 +109,7 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
item.id = itemIdMap[item.id];
createdResources[item.id] = item;
} else if (itemType === BaseModel.TYPE_TAG) {
const tag = await Tag.loadByTitle(item.title);
const tag = await Tag.loadByTitle(item.title);
if (tag) {
itemIdMap[item.id] = tag.id;
continue;
@@ -170,7 +168,6 @@ class InteropService_Importer_Raw extends InteropService_Importer_Base {
return result;
}
}
module.exports = InteropService_Importer_Raw;
module.exports = InteropService_Importer_Raw;

View File

@@ -1,19 +1,13 @@
const Setting = require('lib/models/Setting');
const ItemChange = require('lib/models/ItemChange');
const Setting = require('lib/models/Setting');
const ItemChange = require('lib/models/ItemChange');
class ItemChangeUtils {
static async deleteProcessedChanges() {
const lastProcessedChangeIds = [
Setting.value('resourceService.lastProcessedChangeId'),
Setting.value('searchEngine.lastProcessedChangeId'),
Setting.value('revisionService.lastProcessedChangeId'),
];
const lastProcessedChangeIds = [Setting.value('resourceService.lastProcessedChangeId'), Setting.value('searchEngine.lastProcessedChangeId'), Setting.value('revisionService.lastProcessedChangeId')];
const lowestChangeId = Math.min(...lastProcessedChangeIds);
await ItemChange.deleteOldChanges(lowestChangeId);
}
}
module.exports = ItemChangeUtils;
module.exports = ItemChangeUtils;

View File

@@ -2,7 +2,6 @@ const BaseService = require('lib/services/BaseService.js');
const Mutex = require('async-mutex').Mutex;
class KvStore extends BaseService {
static instance() {
if (this.instance_) return this.instance_;
this.instance_ = new KvStore();
@@ -26,7 +25,7 @@ class KvStore extends BaseService {
typeFromValue_(value) {
if (typeof value === 'string') return KvStore.TYPE_TEXT;
if (typeof value === 'number') return KvStore.TYPE_INT;
throw new Error('Unsupported value type: ' + (typeof value));
throw new Error('Unsupported value type: ' + typeof value);
}
formatValues_(kvs) {
@@ -81,7 +80,7 @@ class KvStore extends BaseService {
} catch (error) {
release();
throw error;
}
}
}
async searchByPrefix(prefix) {
@@ -93,10 +92,9 @@ class KvStore extends BaseService {
const r = await this.db().selectOne('SELECT count(*) as total FROM key_values');
return r.total ? r.total : 0;
}
}
KvStore.TYPE_INT = 1;
KvStore.TYPE_TEXT = 2;
module.exports = KvStore;
module.exports = KvStore;

View File

@@ -2,10 +2,8 @@ const BaseService = require('lib/services/BaseService');
const Migration = require('lib/models/Migration');
class MigrationService extends BaseService {
constructor() {
super();
}
static instance() {
@@ -34,7 +32,6 @@ class MigrationService extends BaseService {
}
}
}
}
module.exports = MigrationService;
module.exports = MigrationService;

View File

@@ -1,7 +1,6 @@
const BaseItem = require('lib/models/BaseItem');
class ModelCache {
constructor() {
this.cache_ = {};
}
@@ -25,14 +24,13 @@ class ModelCache {
this.cache_[models[i].id] = {
model: models[i],
timestamp: Date.now(),
}
};
output.push(models[i]);
}
return output;
}
}
module.exports = ModelCache;
module.exports = ModelCache;

View File

@@ -1,5 +1,4 @@
class NavService {
static async go(routeName) {
if (this.handlers_.length) {
let r = await this.handlers_[this.handlers_.length - 1]();
@@ -9,7 +8,7 @@ class NavService {
this.dispatch({
type: 'NAV_GO',
routeName: routeName,
});
});
}
static addHandler(handler) {
@@ -27,9 +26,8 @@ class NavService {
if (h === hanlder) this.handlers_.splice(i, 1);
}
}
}
NavService.handlers_ = [];
module.exports = NavService;
module.exports = NavService;

View File

@@ -1,9 +1,8 @@
const { Logger } = require('lib/logger.js');
class PluginManager {
constructor() {
this.plugins_ = {};
this.plugins_ = {};
this.logger_ = new Logger();
}
@@ -26,7 +25,7 @@ class PluginManager {
for (let i = 0; i < classes.length; i++) {
const PluginClass = classes[i];
if (this.plugins_[PluginClass.manifest.name]) throw new Error('Already registered: ' + PluginClass.manifest.name);
this.plugins_[PluginClass.manifest.name] = {
@@ -40,7 +39,7 @@ class PluginManager {
const p = this.plugins_[name];
if (p.instance) return p.instance;
p.instance = new p.Class();
p.instance.dispatch = (action) => this.dispatch_(action);
p.instance.dispatch = action => this.dispatch_(action);
return p.instance;
}
@@ -66,7 +65,7 @@ class PluginManager {
return {
Dialog: Class.Dialog,
props: this.dialogProps_(name),
}
};
}
return null;
@@ -74,7 +73,7 @@ class PluginManager {
dialogProps_(name) {
return {
dispatch: (action) => this.dispatch_(action),
dispatch: action => this.dispatch_(action),
plugin: this.pluginInstance_(name),
};
}
@@ -91,7 +90,7 @@ class PluginManager {
pluginName: name,
itemName: item.name,
});
}
};
}
output = output.concat(menuItems);
@@ -99,7 +98,6 @@ class PluginManager {
return output;
}
}
module.exports = PluginManager;
module.exports = PluginManager;

View File

@@ -8,11 +8,10 @@ const EventEmitter = require('events');
const { shim } = require('lib/shim');
class ResourceFetcher extends BaseService {
constructor(fileApi = null) {
super();
this.dispatch = (action) => {};
this.dispatch = action => {};
this.setFileApi(fileApi);
this.logger_ = new Logger();
@@ -47,7 +46,7 @@ class ResourceFetcher extends BaseService {
}
setFileApi(v) {
if (v !== null && typeof v !== 'function') throw new Error('fileApi must be a function that returns the API. Type is ' + (typeof v));
if (v !== null && typeof v !== 'function') throw new Error('fileApi must be a function that returns the API. Type is ' + typeof v);
this.fileApi_ = v;
}
@@ -123,7 +122,6 @@ class ResourceFetcher extends BaseService {
const localState = await Resource.localState(resource);
const completeDownload = async (emitDownloadComplete = true, localResourceContentPath = '') => {
// 2019-05-12: This is only necessary to set the file size of the resources that come via
// sync. The other ones have been done using migrations/20.js. This code can be removed
// after a few months.
@@ -141,7 +139,7 @@ class ResourceFetcher extends BaseService {
// encrypted it's not useful. Probably, the views should listen to DecryptionWorker events instead.
if (resource && emitDownloadComplete) this.eventEmitter_.emit('downloadComplete', { id: resource.id, encrypted: !!resource.encryption_blob_encrypted });
this.updateReport();
}
};
if (!resource) {
this.logger().info('ResourceFetcher: Attempting to download a resource that does not exist (has been deleted?): ' + resourceId);
@@ -159,7 +157,7 @@ class ResourceFetcher extends BaseService {
this.fetchingItems_[resourceId] = resource;
const localResourceContentPath = Resource.fullPath(resource, !!resource.encryption_blob_encrypted);
const remoteResourceContentPath = this.resourceDirName_ + "/" + resource.id;
const remoteResourceContentPath = this.resourceDirName_ + '/' + resource.id;
await Resource.setLocalState(resource, { fetch_status: Resource.FETCH_STATUS_STARTED });
@@ -167,17 +165,20 @@ class ResourceFetcher extends BaseService {
this.logger().debug('ResourceFetcher: Downloading resource: ' + resource.id);
this.eventEmitter_.emit('downloadStarted', { id: resource.id })
this.eventEmitter_.emit('downloadStarted', { id: resource.id });
fileApi.get(remoteResourceContentPath, { path: localResourceContentPath, target: "file" }).then(async () => {
await Resource.setLocalState(resource, { fetch_status: Resource.FETCH_STATUS_DONE });
this.logger().debug('ResourceFetcher: Resource downloaded: ' + resource.id);
await completeDownload(true, localResourceContentPath);
}).catch(async (error) => {
this.logger().error('ResourceFetcher: Could not download resource: ' + resource.id, error);
await Resource.setLocalState(resource, { fetch_status: Resource.FETCH_STATUS_ERROR, fetch_error: error.message });
await completeDownload();
});
fileApi
.get(remoteResourceContentPath, { path: localResourceContentPath, target: 'file' })
.then(async () => {
await Resource.setLocalState(resource, { fetch_status: Resource.FETCH_STATUS_DONE });
this.logger().debug('ResourceFetcher: Resource downloaded: ' + resource.id);
await completeDownload(true, localResourceContentPath);
})
.catch(async error => {
this.logger().error('ResourceFetcher: Could not download resource: ' + resource.id, error);
await Resource.setLocalState(resource, { fetch_status: Resource.FETCH_STATUS_ERROR, fetch_error: error.message });
await completeDownload();
});
}
processQueue_() {
@@ -243,7 +244,6 @@ class ResourceFetcher extends BaseService {
await Resource.resetStartedFetchStatus();
this.autoAddResources(null);
}
}
module.exports = ResourceFetcher;
module.exports = ResourceFetcher;

View File

@@ -11,7 +11,6 @@ const ItemChangeUtils = require('lib/services/ItemChangeUtils');
const { sprintf } = require('sprintf-js');
class ResourceService extends BaseService {
async indexNoteResources() {
this.logger().info('ResourceService::indexNoteResources: Start');
@@ -20,21 +19,24 @@ class ResourceService extends BaseService {
let foundNoteWithEncryption = false;
while (true) {
const changes = await ItemChange.modelSelectAll(`
const changes = await ItemChange.modelSelectAll(
`
SELECT id, item_id, type
FROM item_changes
WHERE item_type = ?
AND id > ?
ORDER BY id ASC
LIMIT 10
`, [BaseModel.TYPE_NOTE, Setting.value('resourceService.lastProcessedChangeId')]);
`,
[BaseModel.TYPE_NOTE, Setting.value('resourceService.lastProcessedChangeId')]
);
if (!changes.length) break;
const noteIds = changes.map(a => a.item_id);
const notes = await Note.modelSelectAll('SELECT id, title, body, encryption_applied FROM notes WHERE id IN ("' + noteIds.join('","') + '")');
const noteById = (noteId) => {
const noteById = noteId => {
for (let i = 0; i < notes.length; i++) {
if (notes[i].id === noteId) return notes[i];
}
@@ -44,15 +46,15 @@ class ResourceService extends BaseService {
// - ResourceService indexer runs.
// In that case, there will be a change for the note, but the note will be gone.
return null;
}
};
for (let i = 0; i < changes.length; i++) {
const change = changes[i];
if (change.type === ItemChange.TYPE_CREATE || change.type === ItemChange.TYPE_UPDATE) {
const note = noteById(change.item_id);
if (!!note.encryption_applied) {
if (note.encryption_applied) {
// If we hit an encrypted note, abort processing for now.
// Note will eventually get decrypted and processing can resume then.
// This is a limitation of the change tracking system - we cannot skip a change
@@ -143,12 +145,11 @@ class ResourceService extends BaseService {
setTimeout(() => {
service.maintenance();
}, 1000 * 30);
shim.setInterval(() => {
service.maintenance();
}, 1000 * 60 * 60 * 4);
}
}
module.exports = ResourceService;
module.exports = ResourceService;

View File

@@ -13,7 +13,6 @@ const ArrayUtils = require('lib/ArrayUtils.js');
const { sprintf } = require('sprintf-js');
class RevisionService extends BaseService {
constructor() {
super();
@@ -57,8 +56,8 @@ class RevisionService extends BaseService {
}
isEmptyRevision_(rev) {
if (!!rev.title_diff) return false;
if (!!rev.body_diff) return false;
if (rev.title_diff) return false;
if (rev.body_diff) return false;
const md = JSON.parse(rev.metadata_diff);
if (md.new && Object.keys(md.new).length) return false;
@@ -113,7 +112,8 @@ class RevisionService extends BaseService {
while (true) {
// See synchronizer test units to see why changes coming
// from sync are skipped.
const changes = await ItemChange.modelSelectAll(`
const changes = await ItemChange.modelSelectAll(
`
SELECT id, item_id, type, before_change_item
FROM item_changes
WHERE item_type = ?
@@ -122,7 +122,9 @@ class RevisionService extends BaseService {
AND id > ?
ORDER BY id ASC
LIMIT 10
`, [BaseModel.TYPE_NOTE, ItemChange.SOURCE_SYNC, ItemChange.SOURCE_DECRYPTION, Setting.value('revisionService.lastProcessedChangeId')]);
`,
[BaseModel.TYPE_NOTE, ItemChange.SOURCE_SYNC, ItemChange.SOURCE_DECRYPTION, Setting.value('revisionService.lastProcessedChangeId')]
);
if (!changes.length) break;
@@ -176,7 +178,7 @@ class RevisionService extends BaseService {
}
await Setting.saveAll();
await ItemChangeUtils.deleteProcessedChanges();
await ItemChangeUtils.deleteProcessedChanges();
this.isCollecting_ = false;
@@ -193,10 +195,13 @@ class RevisionService extends BaseService {
const rev = revisions[index];
const merged = await Revision.mergeDiffs(rev, revisions);
const output = Object.assign({
title: merged.title,
body: merged.body,
}, merged.metadata);
const output = Object.assign(
{
title: merged.title,
body: merged.body,
},
merged.metadata
);
output.updated_time = output.user_updated_time;
output.created_time = output.user_created_time;
output.type_ = BaseModel.TYPE_NOTE;
@@ -237,7 +242,7 @@ class RevisionService extends BaseService {
if (!Setting.value('revisionService.enabled')) {
this.logger().info('RevisionService::maintenance: Service is disabled');
// We do as if we had processed all the latest changes so that they can be cleaned up
// We do as if we had processed all the latest changes so that they can be cleaned up
// later on by ItemChangeUtils.deleteProcessedChanges().
Setting.setValue('revisionService.lastProcessedChangeId', await ItemChange.lastChangeId());
await this.deleteOldRevisions(Setting.value('revisionService.ttlDays') * 24 * 60 * 60 * 1000);
@@ -261,12 +266,11 @@ class RevisionService extends BaseService {
setTimeout(() => {
this.maintenance();
}, 1000 * 4);
shim.setInterval(() => {
this.maintenance();
}, collectRevisionInterval);
}
}
module.exports = RevisionService;
module.exports = RevisionService;

View File

@@ -10,9 +10,8 @@ const removeDiacritics = require('diacritics').remove;
const { sprintf } = require('sprintf-js');
class SearchEngine {
constructor() {
this.dispatch = (action) => {};
this.dispatch = action => {};
this.logger_ = new Logger();
this.db_ = null;
this.isIndexing_ = false;
@@ -52,7 +51,6 @@ class SearchEngine {
return null;
}
async rebuildIndex_() {
let noteIds = await this.db().selectAll('SELECT id FROM notes WHERE is_conflict = 0 AND encryption_applied = 0');
noteIds = noteIds.map(n => n.id);
@@ -64,7 +62,7 @@ class SearchEngine {
while (noteIds.length) {
const currentIds = noteIds.splice(0, 100);
const notes = await Note.modelSelectAll('SELECT id, title, body FROM notes WHERE id IN ("' + currentIds.join('","') + '") AND is_conflict = 0 AND encryption_applied = 0');
const notes = await Note.modelSelectAll('SELECT id, title, body FROM notes WHERE id IN ("' + currentIds.join('","') + '") AND is_conflict = 0 AND encryption_applied = 0');
const queries = [];
for (let i = 0; i < notes.length; i++) {
@@ -93,7 +91,7 @@ class SearchEngine {
}
async rebuildIndex() {
Setting.setValue('searchEngine.lastProcessedChangeId', 0)
Setting.setValue('searchEngine.lastProcessedChangeId', 0);
Setting.setValue('searchEngine.initialIndexingDone', false);
return this.syncTables();
}
@@ -118,21 +116,24 @@ class SearchEngine {
const report = {
inserted: 0,
deleted: 0,
deleted: 0,
};
let lastChangeId = Setting.value('searchEngine.lastProcessedChangeId');
try {
while (true) {
const changes = await ItemChange.modelSelectAll(`
const changes = await ItemChange.modelSelectAll(
`
SELECT id, item_id, type
FROM item_changes
WHERE item_type = ?
AND id > ?
ORDER BY id ASC
LIMIT 10
`, [BaseModel.TYPE_NOTE, lastChangeId]);
`,
[BaseModel.TYPE_NOTE, lastChangeId]
);
const maxRow = await ItemChange.db().selectOne('SELECT max(id) FROM item_changes');
@@ -179,7 +180,7 @@ class SearchEngine {
}
async countRows() {
const sql = 'SELECT count(*) as total FROM notes_fts'
const sql = 'SELECT count(*) as total FROM notes_fts';
const row = await this.db().selectOne(sql);
return row && row['total'] ? row['total'] : 0;
}
@@ -202,7 +203,7 @@ class SearchEngine {
// - If there's only one term in the query string, the content with the most matches goes on top
// - If there are multiple terms, the result with the most occurences that are closest to each others go on top.
// eg. if query is "abcd efgh", "abcd efgh" will go before "abcd XX efgh".
const occurenceCount = Math.floor(offsets.length / 4);
if (termCount === 1) return occurenceCount;
@@ -262,8 +263,8 @@ class SearchEngine {
}
parseQuery(query) {
const terms = {_:[]};
const terms = { _: [] };
let inQuote = false;
let currentCol = '_';
let currentTerm = '';
@@ -362,7 +363,7 @@ class SearchEngine {
normalizeNote_(note) {
const n = Object.assign({}, note);
n.title = this.normalizeText_(n.title);
n.title = this.normalizeText_(n.title);
n.body = this.normalizeText_(n.body);
return n;
}
@@ -393,7 +394,7 @@ class SearchEngine {
return this.basicSearch(query);
} else {
const parsedQuery = this.parseQuery(query);
const sql = 'SELECT notes_fts.id, notes_fts.title AS normalized_title, offsets(notes_fts) AS offsets, notes.title, notes.user_updated_time, notes.is_todo, notes.todo_completed, notes.parent_id FROM notes_fts LEFT JOIN notes ON notes_fts.id = notes.id WHERE notes_fts MATCH ?'
const sql = 'SELECT notes_fts.id, notes_fts.title AS normalized_title, offsets(notes_fts) AS offsets, notes.title, notes.user_updated_time, notes.is_todo, notes.todo_completed, notes.parent_id FROM notes_fts LEFT JOIN notes ON notes_fts.id = notes.id WHERE notes_fts MATCH ?';
try {
const rows = await this.db().selectAll(sql, [query]);
this.orderResults_(rows, parsedQuery);
@@ -404,7 +405,6 @@ class SearchEngine {
}
}
}
}
module.exports = SearchEngine;
module.exports = SearchEngine;

View File

@@ -2,7 +2,6 @@ const SearchEngine = require('lib/services/SearchEngine');
const Note = require('lib/models/Note');
class SearchEngineUtils {
static async notesForQuery(query, options = null) {
if (!options) options = {};
@@ -19,11 +18,15 @@ class SearchEngineUtils {
idWasAutoAdded = true;
}
const previewOptions = Object.assign({}, {
order: [],
fields: fields,
conditions: ['id IN ("' + noteIds.join('","') + '")'],
}, options);
const previewOptions = Object.assign(
{},
{
order: [],
fields: fields,
conditions: ['id IN ("' + noteIds.join('","') + '")'],
},
options
);
const notes = await Note.previews(null, previewOptions);
@@ -39,7 +42,6 @@ class SearchEngineUtils {
return sortedNotes;
}
}
module.exports = SearchEngineUtils;
module.exports = SearchEngineUtils;

View File

@@ -1,7 +1,6 @@
const { BackHandler } = require('react-native');
class BackButtonService {
static initialize(defaultHandler) {
this.defaultHandler_ = defaultHandler;
@@ -34,10 +33,9 @@ class BackButtonService {
if (h === hanlder) this.handlers_.splice(i, 1);
}
}
}
BackButtonService.defaultHandler_ = null;
BackButtonService.handlers_ = [];
module.exports = { BackButtonService };
module.exports = { BackButtonService };

View File

@@ -9,7 +9,6 @@ const { _ } = require('lib/locale.js');
const { toTitleCase } = require('lib/string-utils.js');
class ReportService {
csvEscapeCell(cell) {
cell = this.csvValueToString(cell);
let output = cell.replace(/"/, '""');
@@ -61,7 +60,7 @@ class ReportService {
for (let j = 0; j < items.length; j++) {
const item = items[j];
let row = [itemType, item.id, item.updated_time, item.sync_time];
row.push(('is_conflict' in item) ? item.is_conflict : '');
row.push('is_conflict' in item ? item.is_conflict : '');
output.push(row);
}
}
@@ -143,13 +142,17 @@ class ReportService {
section.body.push(_('Joplin failed to decrypt these items multiple times, possibly because they are corrupted or too large. These items will remain on the device but Joplin will no longer attempt to decrypt them.'));
section.body.push('');
for (let i = 0; i < decryptionDisabledItems.length; i++) {
const row = decryptionDisabledItems[i];
section.body.push({ text: _('%s: %s', toTitleCase(BaseModel.modelTypeToName(row.type_)), row.id), canRetry: true, retryHandler: async () => {
await DecryptionWorker.instance().clearDisabledItem(row.type_, row.id);
DecryptionWorker.instance().scheduleStart();
}});
section.body.push({
text: _('%s: %s', toTitleCase(BaseModel.modelTypeToName(row.type_)), row.id),
canRetry: true,
retryHandler: async () => {
await DecryptionWorker.instance().clearDisabledItem(row.type_, row.id);
DecryptionWorker.instance().scheduleStart();
},
});
}
sections.push(section);
@@ -199,7 +202,6 @@ class ReportService {
return sections;
}
}
module.exports = { ReportService };
module.exports = { ReportService };

View File

@@ -24,7 +24,6 @@ const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const uri2path = require('file-uri-to-path');
class ApiError extends Error {
constructor(message, httpCode = 400) {
super(message);
this.httpCode_ = httpCode;
@@ -33,16 +32,30 @@ class ApiError extends Error {
get httpCode() {
return this.httpCode_;
}
}
class ErrorMethodNotAllowed extends ApiError { constructor(message = 'Method Not Allowed') { super(message, 405); } }
class ErrorNotFound extends ApiError { constructor(message = 'Not Found') { super(message, 404); } }
class ErrorForbidden extends ApiError { constructor(message = 'Forbidden') { super(message, 403); } }
class ErrorBadRequest extends ApiError { constructor(message = 'Bad Request') { super(message, 400); } }
class ErrorMethodNotAllowed extends ApiError {
constructor(message = 'Method Not Allowed') {
super(message, 405);
}
}
class ErrorNotFound extends ApiError {
constructor(message = 'Not Found') {
super(message, 404);
}
}
class ErrorForbidden extends ApiError {
constructor(message = 'Forbidden') {
super(message, 403);
}
}
class ErrorBadRequest extends ApiError {
constructor(message = 'Bad Request') {
super(message, 400);
}
}
class Api {
constructor(token = null) {
this.token_ = token;
this.knownNounces_ = {};
@@ -58,7 +71,7 @@ class Api {
if (!path) return { callName: '', params: [] };
const pathParts = path.split('/');
const callSuffix = pathParts.splice(0,1)[0];
const callSuffix = pathParts.splice(0, 1)[0];
let callName = 'action_' + callSuffix;
return {
callName: callName,
@@ -80,7 +93,7 @@ class Api {
}
this.knownNounces_[query.nounce] = requestMd5;
}
const request = {
method: method,
path: ltrimSlashes(path),
@@ -102,7 +115,7 @@ class Api {
return this.bodyJson_;
},
files: files,
}
};
let id = null;
let link = null;
@@ -146,19 +159,17 @@ class Api {
fields_(request, defaultFields) {
const query = request.query;
if (!query || !query.fields) return defaultFields;
const fields = query.fields.split(',').map(f => f.trim()).filter(f => !!f);
const fields = query.fields
.split(',')
.map(f => f.trim())
.filter(f => !!f);
return fields.length ? fields : defaultFields;
}
checkToken_(request) {
// For now, whitelist some calls to allow the web clipper to work
// without an extra auth step
const whiteList = [
[ 'GET', 'ping' ],
[ 'GET', 'tags' ],
[ 'GET', 'folders' ],
[ 'POST', 'notes' ],
];
const whiteList = [['GET', 'ping'], ['GET', 'tags'], ['GET', 'folders'], ['POST', 'notes']];
for (let i = 0; i < whiteList.length; i++) {
if (whiteList[i][0] === request.method && whiteList[i][1] === request.path) return;
@@ -178,9 +189,9 @@ class Api {
const getOneModel = async () => {
const model = await ModelClass.load(id);
if (!model) throw new ErrorNotFound();
if (!model) throw new ErrorNotFound();
return model;
}
};
if (request.method === 'GET') {
if (id) {
@@ -308,7 +319,7 @@ class Api {
const filePath = Resource.fullPath(resource);
const buffer = await shim.fsDriver().readFile(filePath, 'Buffer');
const response = new ApiResponse();
response.type = 'attachment';
response.body = buffer;
@@ -345,9 +356,8 @@ class Api {
async action_notes(request, id = null, link = null) {
this.checkToken_(request);
if (request.method === 'GET') {
if (link && link === 'tags') {
return Tag.tagsByNoteId(id);
} else if (link) {
@@ -411,10 +421,6 @@ class Api {
return this.defaultAction_(BaseModel.TYPE_NOTE, request, id, link);
}
// ========================================================================================================================
// UTILIY FUNCTIONS
// ========================================================================================================================
@@ -462,7 +468,8 @@ class Api {
output.body = styleTag + minify(requestNote.body_html, minifyOptions);
output.body = htmlUtils.prependBaseUrl(output.body, baseUrl);
output.markup_language = Note.MARKUP_LANGUAGE_HTML;
} else { // Convert to Markdown
} else {
// Convert to Markdown
// Parsing will not work if the HTML is not wrapped in a top level tag, which is not guaranteed
// when getting the content from elsewhere. So here wrap it - it won't change anything to the final
// rendering but it makes sure everything will be parsed.
@@ -555,7 +562,7 @@ class Api {
if (!mimeUtils.fromFileExtension(fileExt)) fileExt = ''; // If the file extension is unknown - clear it.
if (fileExt) fileExt = '.' + fileExt;
let imagePath = tempDir + '/' + safeFilename(name) + fileExt;
if (await shim.fsDriver().exists(imagePath)) imagePath = tempDir + '/' + safeFilename(name) + '_' + md5(Math.random() + '_' + Date.now()).substr(0,10) + fileExt;
if (await shim.fsDriver().exists(imagePath)) imagePath = tempDir + '/' + safeFilename(name) + '_' + md5(Math.random() + '_' + Date.now()).substr(0, 10) + fileExt;
try {
if (isDataUrl) {
@@ -580,7 +587,7 @@ class Api {
}
async downloadImages_(urls, allowFileProtocolImages) {
const PromisePool = require('es6-promise-pool')
const PromisePool = require('es6-promise-pool');
const output = {};
@@ -590,7 +597,7 @@ class Api {
if (imagePath) output[url] = { path: imagePath, originalUrl: url };
resolve();
});
}
};
let urlIndex = 0;
const promiseProducer = () => {
@@ -598,11 +605,11 @@ class Api {
const url = urls[urlIndex++];
return downloadOne(url);
}
};
const concurrency = 10;
const pool = new PromisePool(promiseProducer, concurrency)
await pool.start()
const pool = new PromisePool(promiseProducer, concurrency);
await pool.start();
return output;
}
@@ -641,8 +648,8 @@ class Api {
const urlInfo = urls[imageUrl];
if (!urlInfo || !urlInfo.resource) return imageUrl;
return Resource.internalUrl(urlInfo.resource);
});
} else {
});
} else {
let output = md.replace(/(!\[.*?\]\()([^\s\)]+)(.*?\))/g, (match, before, imageUrl, after) => {
const urlInfo = urls[imageUrl];
if (!urlInfo || !urlInfo.resource) return before + imageUrl + after;
@@ -661,8 +668,6 @@ class Api {
return output;
}
}
}
module.exports = Api;

View File

@@ -1,7 +1,5 @@
class ApiResponse {
constructor() {}
}
module.exports = ApiResponse;
module.exports = ApiResponse;