1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-03 23:50:33 +02:00

All: Add support for application plugins (#3257)

This commit is contained in:
Laurent
2020-10-09 18:35:46 +01:00
committed by GitHub
parent 833fb1264f
commit fe41d37f8f
804 changed files with 95622 additions and 5307 deletions

View File

@ -1,3 +1,5 @@
import shim from 'lib/shim';
export interface QueueItemAction {
(): void,
}
@ -47,10 +49,10 @@ export default class AsyncActionQueue {
if (this.scheduleProcessingIID_) {
if (this.intervalType_ === IntervalType.Fixed) return;
clearTimeout(this.scheduleProcessingIID_);
shim.clearTimeout(this.scheduleProcessingIID_);
}
this.scheduleProcessingIID_ = setTimeout(() => {
this.scheduleProcessingIID_ = shim.setTimeout(() => {
this.scheduleProcessingIID_ = null;
this.processQueue();
}, interval);
@ -77,7 +79,7 @@ export default class AsyncActionQueue {
async reset() {
if (this.scheduleProcessingIID_) {
clearTimeout(this.scheduleProcessingIID_);
shim.clearTimeout(this.scheduleProcessingIID_);
this.scheduleProcessingIID_ = null;
}
@ -97,11 +99,11 @@ export default class AsyncActionQueue {
this.scheduleProcessing(1);
return new Promise((resolve) => {
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
if (this.processing_) return;
if (!this.queue_.length) {
clearInterval(iid);
shim.clearInterval(iid);
resolve();
}
}, 100);

View File

@ -1,5 +1,13 @@
import Setting from 'lib/models/Setting';
import Logger, { TargetType } from 'lib/Logger';
import shim from 'lib/shim';
import BaseService from 'lib/services/BaseService';
import reducer from 'lib/reducer';
import KeychainServiceDriver from 'lib/services/keychain/KeychainServiceDriver.node';
import { _, setLocale } from 'lib/locale';
const { createStore, applyMiddleware } = require('redux');
const { reducer, defaultState, stateUtils } = require('lib/reducer.js');
const { defaultState, stateUtils } = require('lib/reducer');
const { JoplinDatabase } = require('lib/joplin-database.js');
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
@ -8,14 +16,10 @@ const Folder = require('lib/models/Folder.js');
const BaseItem = require('lib/models/BaseItem.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const Setting = require('lib/models/Setting.js');
const { Logger } = require('lib/logger.js');
const { splitCommandString } = require('lib/string-utils.js');
const { reg } = require('lib/registry.js');
const { time } = require('lib/time-utils.js');
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { shim } = require('lib/shim.js');
const { _, setLocale } = require('lib/locale.js');
const reduxSharedMiddleware = require('lib/components/shared/reduxSharedMiddleware');
const os = require('os');
const fs = require('fs-extra');
@ -37,33 +41,44 @@ const SearchEngine = require('lib/services/searchengine/SearchEngine');
const RevisionService = require('lib/services/RevisionService');
const ResourceService = require('lib/services/RevisionService');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const BaseService = require('lib/services/BaseService');
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
const KeychainServiceDriver = require('lib/services/keychain/KeychainServiceDriver.node').default;
const KvStore = require('lib/services/KvStore');
const MigrationService = require('lib/services/MigrationService');
const { toSystemSlashes } = require('lib/path-utils.js');
const { setAutoFreeze } = require('immer');
// const ntpClient = require('lib/vendor/ntp-client');
// ntpClient.dgram = require('dgram');
class BaseApplication {
export default class BaseApplication {
private logger_:Logger;
private dbLogger_:Logger;
private eventEmitter_:any;
private scheduleAutoAddResourcesIID_:any = null;
private database_:any = null;
protected showStackTraces_:boolean = false;
protected showPromptString_:boolean = false;
// Note: this is basically a cache of state.selectedFolderId. It should *only*
// be derived from the state and not set directly since that would make the
// state and UI out of sync.
private currentFolder_:any = null;
protected store_:any = null;
constructor() {
this.logger_ = new Logger();
this.dbLogger_ = new Logger();
this.eventEmitter_ = new EventEmitter();
// Note: this is basically a cache of state.selectedFolderId. It should *only*
// be derived from the state and not set directly since that would make the
// state and UI out of sync.
this.currentFolder_ = null;
this.decryptionWorker_resourceMetadataButNotBlobDecrypted = this.decryptionWorker_resourceMetadataButNotBlobDecrypted.bind(this);
}
async destroy() {
if (this.scheduleAutoAddResourcesIID_) {
clearTimeout(this.scheduleAutoAddResourcesIID_);
shim.clearTimeout(this.scheduleAutoAddResourcesIID_);
this.scheduleAutoAddResourcesIID_ = null;
}
await ResourceFetcher.instance().destroy();
@ -98,7 +113,7 @@ class BaseApplication {
return this.logger_;
}
store() {
public store() {
return this.store_;
}
@ -115,7 +130,7 @@ class BaseApplication {
this.switchCurrentFolder(newFolder);
}
switchCurrentFolder(folder) {
switchCurrentFolder(folder:any) {
if (!this.hasGui()) {
this.currentFolder_ = Object.assign({}, folder);
Setting.setValue('activeFolderId', folder ? folder.id : '');
@ -129,8 +144,8 @@ class BaseApplication {
// Handles the initial flags passed to main script and
// returns the remaining args.
async handleStartFlags_(argv, setDefaults = true) {
const matched = {};
async handleStartFlags_(argv:string[], setDefaults:boolean = true) {
const matched:any = {};
argv = argv.slice(0);
argv.splice(0, 2); // First arguments are the node executable, and the node JS file
@ -209,6 +224,12 @@ class BaseApplication {
continue;
}
if (arg === '--dev-plugins') {
Setting.setConstant('startupDevPlugins', nextArg.split(',').map(p => p.trim()));
argv.splice(0, 2);
continue;
}
if (arg.indexOf('--remote-debugging-port=') === 0) {
// Electron-specific flag used for debugging - ignore it. Electron expects this flag in '--x=y' form, a single string.
argv.splice(0, 1);
@ -233,6 +254,7 @@ class BaseApplication {
if (setDefaults) {
if (!matched.logLevel) matched.logLevel = Logger.LEVEL_INFO;
if (!matched.env) matched.env = 'prod';
if (!matched.devPlugins) matched.devPlugins = [];
}
return {
@ -241,7 +263,7 @@ class BaseApplication {
};
}
on(eventName, callback) {
on(eventName:string, callback:Function) {
return this.eventEmitter_.on(eventName, callback);
}
@ -250,7 +272,7 @@ class BaseApplication {
process.exit(code);
}
async refreshNotes(state, useSelectedNoteId = false, noteHash = '') {
async refreshNotes(state:any, useSelectedNoteId:boolean = false, noteHash:string = '') {
let parentType = state.notesParentType;
let parentId = null;
@ -346,7 +368,7 @@ class BaseApplication {
}
}
resourceFetcher_downloadComplete(event) {
resourceFetcher_downloadComplete(event:any) {
if (event.encrypted) {
DecryptionWorker.instance().scheduleStart();
}
@ -356,7 +378,7 @@ class BaseApplication {
ResourceFetcher.instance().scheduleAutoAddResources();
}
reducerActionToString(action) {
reducerActionToString(action:any) {
const o = [action.type];
if ('id' in action) o.push(action.id);
if ('noteId' in action) o.push(action.noteId);
@ -377,15 +399,15 @@ class BaseApplication {
}
generalMiddlewareFn() {
const middleware = store => next => action => {
const middleware = (store:any) => (next:any) => (action:any) => {
return this.generalMiddleware(store, next, action);
};
return middleware;
}
async applySettingsSideEffects(action = null) {
const sideEffects = {
async applySettingsSideEffects(action:any = null) {
const sideEffects:any = {
'dateFormat': async () => {
time.setLocale(Setting.value('locale'));
time.setDateFormat(Setting.value('dateFormat'));
@ -438,13 +460,13 @@ class BaseApplication {
}
}
async generalMiddleware(store, next, action) {
async generalMiddleware(store:any, next:any, action:any) {
// this.logger().debug('Reducer action', this.reducerActionToString(action));
const result = next(action);
const newState = store.getState();
let refreshNotes = false;
let refreshFolders = false;
let refreshFolders:boolean | string = false;
// let refreshTags = false;
let refreshNotesUseSelectedNoteId = false;
let refreshNotesHash = '';
@ -564,11 +586,11 @@ class BaseApplication {
return result;
}
dispatch(action) {
dispatch(action:any) {
if (this.store()) return this.store().dispatch(action);
}
reducer(state = defaultState, action) {
reducer(state:any = defaultState, action:any) {
return reducer(state, action);
}
@ -592,7 +614,7 @@ class BaseApplication {
ResourceFetcher.instance().dispatch = function() {};
}
async readFlagsFromFile(flagPath) {
async readFlagsFromFile(flagPath:string) {
if (!fs.existsSync(flagPath)) return {};
let flagContent = fs.readFileSync(flagPath, 'utf8');
if (!flagContent) return {};
@ -608,7 +630,7 @@ class BaseApplication {
return flags.matched;
}
determineProfileDir(initArgs) {
determineProfileDir(initArgs:any) {
let output = '';
if (initArgs.profileDir) {
@ -622,7 +644,7 @@ class BaseApplication {
return toSystemSlashes(output, 'linux');
}
async start(argv) {
async start(argv:string[]):Promise<any> {
const startFlags = await this.handleStartFlags_(argv);
argv = startFlags.argv;
@ -633,6 +655,9 @@ class BaseApplication {
if (Setting.value('appId').indexOf('-desktop') >= 0) appName += '-desktop';
Setting.setConstant('appName', appName);
// https://immerjs.github.io/immer/docs/freezing
setAutoFreeze(initArgs.env === 'dev');
const profileDir = this.determineProfileDir(initArgs);
const resourceDirName = 'resources';
const resourceDir = `${profileDir}/${resourceDirName}`;
@ -644,6 +669,7 @@ class BaseApplication {
Setting.setConstant('resourceDirName', resourceDirName);
Setting.setConstant('resourceDir', resourceDir);
Setting.setConstant('tempDir', tempDir);
Setting.setConstant('pluginDir', `${profileDir}/plugins`);
SyncTargetRegistry.addClass(SyncTargetFilesystem);
SyncTargetRegistry.addClass(SyncTargetOneDrive);
@ -671,7 +697,7 @@ class BaseApplication {
const extraFlags = await this.readFlagsFromFile(`${profileDir}/flags.txt`);
initArgs = Object.assign(initArgs, extraFlags);
this.logger_.addTarget('file', { path: `${profileDir}/log.txt` });
this.logger_.addTarget(TargetType.File, { path: `${profileDir}/log.txt` });
this.logger_.setLevel(initArgs.logLevel);
reg.setLogger(this.logger_);
@ -680,12 +706,12 @@ class BaseApplication {
BaseService.logger_ = this.logger_;
// require('lib/ntpDate').setLogger(reg.logger());
this.dbLogger_.addTarget('file', { path: `${profileDir}/log-database.txt` });
this.dbLogger_.addTarget(TargetType.File, { path: `${profileDir}/log-database.txt` });
this.dbLogger_.setLevel(initArgs.logLevel);
if (Setting.value('env') === 'dev' && Setting.value('appType') === 'desktop') {
// this.logger_.addTarget('console', { level: Logger.LEVEL_DEBUG });
this.dbLogger_.addTarget('console', { level: Logger.LEVEL_WARN });
if (Setting.value('appType') === 'desktop') {
this.logger_.addTarget(TargetType.Console, { level: Logger.LEVEL_WARN });
this.dbLogger_.addTarget(TargetType.Console, { level: Logger.LEVEL_WARN });
}
if (Setting.value('env') === 'dev') {
@ -772,7 +798,7 @@ class BaseApplication {
if (!Setting.value('api.token')) {
EncryptionService.instance()
.randomHexString(64)
.then(token => {
.then((token:string) => {
Setting.setValue('api.token', token);
});
}
@ -810,8 +836,7 @@ class BaseApplication {
Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : '');
await MigrationService.instance().run();
return argv;
}
}
module.exports = { BaseApplication };

View File

@ -1,5 +1,5 @@
const { Database } = require('lib/database.js');
const { uuid } = require('lib/uuid.js');
const uuid = require('lib/uuid').default;
const { time } = require('lib/time-utils.js');
const Mutex = require('async-mutex').Mutex;

View File

@ -1,4 +1,5 @@
const EncryptionService = require('lib/services/EncryptionService.js');
const shim = require('lib/shim').default;
class BaseSyncTarget {
constructor(db, options = null) {
@ -87,13 +88,13 @@ class BaseSyncTarget {
if (this.initState_ == 'started') {
// Synchronizer is already being initialized, so wait here till it's done.
return new Promise((resolve, reject) => {
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
if (this.initState_ == 'ready') {
clearInterval(iid);
shim.clearInterval(iid);
resolve(this.synchronizer_);
}
if (this.initState_ == 'error') {
clearInterval(iid);
shim.clearInterval(iid);
reject(new Error('Could not initialise synchroniser'));
}
}, 1000);

View File

@ -1,9 +1,9 @@
const urlParser = require('url');
const Setting = require('lib/models/Setting');
const { Logger } = require('lib/logger.js');
const Setting = require('lib/models/Setting').default;
const Logger = require('lib/Logger').default;
const { randomClipperPort, startPort } = require('lib/randomClipperPort');
const enableServerDestroy = require('server-destroy');
const Api = require('lib/services/rest/Api');
const Api = require('lib/services/rest/Api').default;
const ApiResponse = require('lib/services/rest/ApiResponse');
const multiparty = require('multiparty');

View File

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

View File

@ -1,5 +1,5 @@
const TurndownService = require('joplin-turndown');
const markdownUtils = require('lib/markdownUtils');
const markdownUtils = require('lib/markdownUtils').default;
class HtmlToMd {
parse(html, options = {}) {

View File

@ -1,9 +1,9 @@
const { Logger } = require('lib/logger.js');
const { shim } = require('lib/shim.js');
import shim from 'lib/shim';
import { _ } from 'lib/locale';
const Logger = require('lib/Logger').default;
const JoplinError = require('lib/JoplinError');
const { rtrimSlashes } = require('lib/path-utils.js');
const base64 = require('base-64');
const { _ } = require('lib/locale');
interface JoplinServerApiOptions {
username: Function,

View File

@ -1,22 +1,52 @@
const moment = require('moment');
const { _ } = require('lib/locale.js');
const { time } = require('lib/time-utils.js');
const { FsDriverDummy } = require('lib/fs-driver-dummy.js');
export enum TargetType {
Database = 'database',
File = 'file',
Console = 'console',
}
enum LogLevel {
None = 0,
Error = 10,
Warn = 20,
Info = 30,
Debug = 40,
}
interface Target {
type: TargetType,
level?: LogLevel,
database?: any,
console?: any,
prefix?: string,
path?: string,
source?: string,
}
class Logger {
constructor() {
this.targets_ = [];
this.level_ = Logger.LEVEL_INFO;
this.fileAppendQueue_ = [];
this.lastDbCleanup_ = time.unixMs();
}
// For backward compatibility
public static LEVEL_NONE = LogLevel.None;
public static LEVEL_ERROR = LogLevel.Error;
public static LEVEL_WARN = LogLevel.Warn;
public static LEVEL_INFO = LogLevel.Info;
public static LEVEL_DEBUG = LogLevel.Debug;
public static fsDriver_:any = null;
private targets_:Target[] = [];
private level_:LogLevel = LogLevel.Info;
private lastDbCleanup_:number = time.unixMs();
static fsDriver() {
if (!Logger.fsDriver_) Logger.fsDriver_ = new FsDriverDummy();
return Logger.fsDriver_;
}
setLevel(level) {
setLevel(level:LogLevel) {
this.level_ = level;
}
@ -28,25 +58,22 @@ class Logger {
return this.targets_;
}
clearTargets() {
this.targets_.clear();
}
addTarget(type, options = null) {
addTarget(type:TargetType, options:any = null) {
const target = { type: type };
for (const n in options) {
if (!options.hasOwnProperty(n)) continue;
target[n] = options[n];
(target as any)[n] = options[n];
}
this.targets_.push(target);
}
objectToString(object) {
objectToString(object:any) {
let output = '';
if (typeof object === 'object') {
if (object instanceof Error) {
object = object as any;
output = object.toString();
if (object.code) output += `\nCode: ${object.code}`;
if (object.headers) output += `\nHeader: ${JSON.stringify(object.headers)}`;
@ -62,7 +89,7 @@ class Logger {
return output;
}
objectsToString(...object) {
objectsToString(...object:any[]) {
const output = [];
for (let i = 0; i < object.length; i++) {
output.push(`"${this.objectToString(object[i])}"`);
@ -84,9 +111,9 @@ class Logger {
}
// Only for database at the moment
async lastEntries(limit = 100, options = null) {
async lastEntries(limit:number = 100, options:any = null) {
if (options === null) options = {};
if (!options.levels) options.levels = [Logger.LEVEL_DEBUG, Logger.LEVEL_INFO, Logger.LEVEL_WARN, Logger.LEVEL_ERROR];
if (!options.levels) options.levels = [LogLevel.Debug, LogLevel.Info, LogLevel.Warn, LogLevel.Error];
if (!options.levels.length) return [];
for (let i = 0; i < this.targets_.length; i++) {
@ -100,12 +127,12 @@ class Logger {
return [];
}
targetLevel(target) {
targetLevel(target:Target) {
if ('level' in target) return target.level;
return this.level();
}
log(level, ...object) {
log(level:LogLevel, ...object:any[]) {
if (!this.targets_.length) return;
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
@ -118,11 +145,15 @@ class Logger {
if (target.type == 'console') {
let fn = 'log';
if (level == Logger.LEVEL_ERROR) fn = 'error';
if (level == Logger.LEVEL_WARN) fn = 'warn';
if (level == Logger.LEVEL_INFO) fn = 'info';
if (level == LogLevel.Error) fn = 'error';
if (level == LogLevel.Warn) fn = 'warn';
if (level == LogLevel.Info) fn = 'info';
const consoleObj = target.console ? target.console : console;
const items = [moment().format('HH:mm:ss')].concat(object);
let items = [moment().format('HH:mm:ss')];
if (target.prefix) {
items.push(target.prefix);
}
items = items.concat(...object);
consoleObj[fn](...items);
} else if (target.type == 'file') {
const serializedObject = this.objectsToString(...object);
@ -156,55 +187,41 @@ class Logger {
}
}
error(...object) {
return this.log(Logger.LEVEL_ERROR, ...object);
error(...object:any[]) {
return this.log(LogLevel.Error, ...object);
}
warn(...object) {
return this.log(Logger.LEVEL_WARN, ...object);
warn(...object:any[]) {
return this.log(LogLevel.Warn, ...object);
}
info(...object) {
return this.log(Logger.LEVEL_INFO, ...object);
info(...object:any[]) {
return this.log(LogLevel.Info, ...object);
}
debug(...object) {
return this.log(Logger.LEVEL_DEBUG, ...object);
debug(...object:any[]) {
return this.log(LogLevel.Debug, ...object);
}
static levelStringToId(s) {
if (s == 'none') return Logger.LEVEL_NONE;
if (s == 'error') return Logger.LEVEL_ERROR;
if (s == 'warn') return Logger.LEVEL_WARN;
if (s == 'info') return Logger.LEVEL_INFO;
if (s == 'debug') return Logger.LEVEL_DEBUG;
throw new Error(_('Unknown log level: %s', s));
static levelStringToId(s:string) {
if (s == 'none') return LogLevel.None;
if (s == 'error') return LogLevel.Error;
if (s == 'warn') return LogLevel.Warn;
if (s == 'info') return LogLevel.Info;
if (s == 'debug') return LogLevel.Debug;
throw new Error(`Unknown log level: ${s}`);
}
static levelIdToString(id) {
if (id == Logger.LEVEL_NONE) return 'none';
if (id == Logger.LEVEL_ERROR) return 'error';
if (id == Logger.LEVEL_WARN) return 'warn';
if (id == Logger.LEVEL_INFO) return 'info';
if (id == Logger.LEVEL_DEBUG) return 'debug';
throw new Error(_('Unknown level ID: %s', id));
static levelIdToString(id:LogLevel) {
if (id == LogLevel.None) return 'none';
if (id == LogLevel.Error) return 'error';
if (id == LogLevel.Warn) return 'warn';
if (id == LogLevel.Info) return 'info';
if (id == LogLevel.Debug) return 'debug';
throw new Error(`Unknown level ID: ${id}`);
}
static levelIds() {
return [Logger.LEVEL_NONE, Logger.LEVEL_ERROR, Logger.LEVEL_WARN, Logger.LEVEL_INFO, Logger.LEVEL_DEBUG];
return [LogLevel.None, LogLevel.Error, LogLevel.Warn, LogLevel.Info, LogLevel.Debug];
}
static levelEnum() {
const output = {};
const ids = this.levelIds();
for (let i = 0; i < ids.length; i++) {
output[ids[i]] = this.levelIdToString(ids[i]);
}
return output;
}
}
Logger.LEVEL_NONE = 0;
Logger.LEVEL_ERROR = 10;
Logger.LEVEL_WARN = 20;
Logger.LEVEL_INFO = 30;
Logger.LEVEL_DEBUG = 40;
module.exports = { Logger };
export default Logger;

View File

@ -1,8 +1,8 @@
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale');
const Setting = require('lib/models/Setting').default;
const { FileApi } = require('lib/file-api.js');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
const { FileApiDriverAmazonS3 } = require('lib/file-api-driver-amazon-s3.js');
const S3 = require('aws-sdk/clients/s3');

View File

@ -1,10 +1,10 @@
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const DropboxApi = require('lib/DropboxApi');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { parameters } = require('lib/parameters.js');
const { FileApi } = require('lib/file-api.js');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
const { FileApiDriverDropbox } = require('lib/file-api-driver-dropbox.js');
class SyncTargetDropbox extends BaseSyncTarget {

View File

@ -1,9 +1,9 @@
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale');
const Setting = require('lib/models/Setting').default;
const { FileApi } = require('lib/file-api.js');
const { FileApiDriverLocal } = require('lib/file-api-driver-local.js');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
class SyncTargetFilesystem extends BaseSyncTarget {
static id() {

View File

@ -1,8 +1,8 @@
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { FileApi } = require('lib/file-api.js');
const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
class SyncTargetMemory extends BaseSyncTarget {
static id() {

View File

@ -2,9 +2,9 @@
// thus all the calls to SyncTargetWebDAV to avoid duplicate code.
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting.js');
const { Synchronizer } = require('lib/synchronizer.js');
const { _ } = require('lib/locale');
const Setting = require('lib/models/Setting').default;
const Synchronizer = require('lib/Synchronizer').default;
const SyncTargetWebDAV = require('lib/SyncTargetWebDAV');
const JoplinServerApi = require('lib/JoplinServerApi.js').default;

View File

@ -1,10 +1,10 @@
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { OneDriveApi } = require('lib/onedrive-api.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { parameters } = require('lib/parameters.js');
const { FileApi } = require('lib/file-api.js');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js');
class SyncTargetOneDrive extends BaseSyncTarget {

View File

@ -1,5 +1,5 @@
const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { parameters } = require('lib/parameters.js');
class SyncTargetOneDriveDev extends SyncTargetOneDrive {

View File

@ -1,8 +1,8 @@
const BaseSyncTarget = require('lib/BaseSyncTarget.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale');
const Setting = require('lib/models/Setting').default;
const { FileApi } = require('lib/file-api.js');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
const WebDavApi = require('lib/WebDavApi');
const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav');

View File

@ -1,40 +1,62 @@
import Logger from './Logger';
import LockHandler, { LockType } from 'lib/services/synchronizer/LockHandler';
import Setting from 'lib/models/Setting';
import shim from 'lib/shim';
import MigrationHandler from 'lib/services/synchronizer/MigrationHandler';
import eventManager from 'lib/eventManager';
import { _ } from 'lib/locale';
const BaseItem = require('lib/models/BaseItem.js');
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const ItemChange = require('lib/models/ItemChange.js');
const Setting = require('lib/models/Setting.js');
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
const MasterKey = require('lib/models/MasterKey.js');
const BaseModel = require('lib/BaseModel.js');
const { sprintf } = require('sprintf-js');
const { time } = require('lib/time-utils.js');
const { Logger } = require('lib/logger.js');
const { _ } = require('lib/locale.js');
const { shim } = require('lib/shim.js');
// const { filename, fileExtension } = require('lib/path-utils');
const JoplinError = require('lib/JoplinError');
const TaskQueue = require('lib/TaskQueue');
const LockHandler = require('lib/services/synchronizer/LockHandler').default;
const MigrationHandler = require('lib/services/synchronizer/MigrationHandler').default;
const { Dirnames } = require('lib/services/synchronizer/utils/types');
class Synchronizer {
constructor(db, api, appType) {
this.state_ = 'idle';
interface RemoteItem {
id: string,
path?: string,
type_?: number,
}
export default class Synchronizer {
private db_:any;
private api_:any;
private appType_:string;
private logger_:Logger = new Logger();
private state_:string = 'idle';
private cancelling_:boolean = false;
private maxResourceSize_:number = null;
private downloadQueue_:any = null;
private clientId_:string;
private lockHandler_:LockHandler;
private migrationHandler_:MigrationHandler;
private encryptionService_:any = null;
private syncTargetIsLocked_:boolean = false;
// Debug flags are used to test certain hard-to-test conditions
// such as cancelling in the middle of a loop.
public testingHooks_:string[] = [];
private onProgress_:Function;
private progressReport_:any = {};
public dispatch:Function;
constructor(db:any, api:any, appType:string) {
this.db_ = db;
this.api_ = api;
this.logger_ = new Logger();
this.appType_ = appType;
this.cancelling_ = false;
this.maxResourceSize_ = null;
this.downloadQueue_ = null;
this.clientId_ = Setting.value('clientId');
// Debug flags are used to test certain hard-to-test conditions
// such as cancelling in the middle of a loop.
this.testingHooks_ = [];
this.onProgress_ = function() {};
this.progressReport_ = {};
@ -57,7 +79,7 @@ class Synchronizer {
return this.clientId_;
}
setLogger(l) {
setLogger(l:Logger) {
this.logger_ = l;
}
@ -82,7 +104,7 @@ class Synchronizer {
return this.appType_ === 'mobile' ? 100 * 1000 * 1000 : Infinity;
}
setEncryptionService(v) {
setEncryptionService(v:any) {
this.encryptionService_ = v;
}
@ -99,7 +121,7 @@ class Synchronizer {
}
}
static reportToLines(report) {
static reportToLines(report:any) {
const lines = [];
if (report.createLocal) lines.push(_('Created local items: %d.', report.createLocal));
if (report.updateLocal) lines.push(_('Updated local items: %d.', report.updateLocal));
@ -108,7 +130,6 @@ class Synchronizer {
if (report.deleteLocal) lines.push(_('Deleted local items: %d.', report.deleteLocal));
if (report.deleteRemote) lines.push(_('Deleted remote items: %d.', report.deleteRemote));
if (report.fetchingTotal && report.fetchingProcessed) lines.push(_('Fetched items: %d/%d.', report.fetchingProcessed, report.fetchingTotal));
// if (!report.completedTime && report.state) lines.push(_('State: %s.', Synchronizer.stateToLabel(report.state)));
if (report.cancelling && !report.completedTime) lines.push(_('Cancelling...'));
if (report.completedTime) lines.push(_('Completed: %s', time.formatMsToLocal(report.completedTime)));
if (report.errors && report.errors.length) lines.push(_('Last error: %s', report.errors[report.errors.length - 1].toString().substr(0, 500)));
@ -116,7 +137,7 @@ class Synchronizer {
return lines;
}
logSyncOperation(action, local = null, remote = null, message = null, actionCount = 1) {
logSyncOperation(action:any, local:any = null, remote:RemoteItem = null, message:string = null, actionCount:number = 1) {
const line = ['Sync'];
line.push(action);
if (message) line.push(message);
@ -145,10 +166,16 @@ class Synchronizer {
this.progressReport_.state = this.state();
this.onProgress_(this.progressReport_);
this.dispatch({ type: 'SYNC_REPORT_UPDATE', report: Object.assign({}, this.progressReport_) });
// Make sure we only send a **copy** of the report since it
// is mutated within this class. Should probably use a lib
// for this but for now this simple fix will do.
const reportCopy:any = {};
for (const n in this.progressReport_) reportCopy[n] = this.progressReport_[n];
if (reportCopy.errors) reportCopy.errors = this.progressReport_.errors.slice();
this.dispatch({ type: 'SYNC_REPORT_UPDATE', report: reportCopy });
}
async logSyncSummary(report) {
async logSyncSummary(report:any) {
this.logger().info('Operations completed: ');
for (const n in report) {
if (!report.hasOwnProperty(n)) continue;
@ -186,9 +213,9 @@ class Synchronizer {
this.cancelling_ = true;
return new Promise((resolve) => {
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
if (this.state() == 'idle') {
clearInterval(iid);
shim.clearInterval(iid);
resolve();
}
}, 100);
@ -210,28 +237,27 @@ class Synchronizer {
}
}
static stateToLabel(state) {
static stateToLabel(state:string) {
if (state === 'idle') return _('Idle');
if (state === 'in_progress') return _('In progress');
return state;
}
isFullSync(steps) {
isFullSync(steps:string[]) {
return steps.includes('update_remote') && steps.includes('delete_remote') && steps.includes('delta');
}
// TODO: test lockErrorStatus_
async lockErrorStatus_() {
const hasActiveExclusiveLock = await this.lockHandler().hasActiveLock('exclusive');
const hasActiveExclusiveLock = await this.lockHandler().hasActiveLock(LockType.Exclusive);
if (hasActiveExclusiveLock) return 'hasExclusiveLock';
const hasActiveSyncLock = await this.lockHandler().hasActiveLock('sync', this.appType_, this.clientId_);
const hasActiveSyncLock = await this.lockHandler().hasActiveLock(LockType.Sync, this.appType_, this.clientId_);
if (!hasActiveSyncLock) return 'syncLockGone';
return '';
}
async apiCall(fnName, ...args) {
async apiCall(fnName:string, ...args:any[]) {
if (this.syncTargetIsLocked_) throw new JoplinError('Sync target is locked - aborting API call', 'lockError');
try {
@ -255,11 +281,11 @@ class Synchronizer {
// 1. UPLOAD: Send to the sync target the items that have changed since the last sync.
// 2. DELETE_REMOTE: Delete on the sync target, the items that have been deleted locally.
// 3. DELTA: Find on the sync target the items that have been modified or deleted and apply the changes locally.
async start(options = null) {
async start(options:any = null) {
if (!options) options = {};
if (this.state() != 'idle') {
const error = new Error(sprintf('Synchronisation is already in progress. State: %s', this.state()));
const error:any = new Error(sprintf('Synchronisation is already in progress. State: %s', this.state()));
error.code = 'alreadyStarted';
throw error;
}
@ -292,12 +318,12 @@ class Synchronizer {
this.logSyncOperation('starting', null, null, `Starting synchronisation to target ${syncTargetId}... [${synchronizationId}]`);
const handleCannotSyncItem = async (ItemClass, syncTargetId, item, cannotSyncReason, itemLocation = null) => {
const handleCannotSyncItem = async (ItemClass:any, syncTargetId:any, item:any, cannotSyncReason:string, itemLocation:any = null) => {
await ItemClass.saveSyncDisabled(syncTargetId, item, cannotSyncReason, itemLocation);
this.dispatch({ type: 'SYNC_HAS_DISABLED_SYNC_ITEMS' });
};
const resourceRemotePath = resourceId => {
const resourceRemotePath = (resourceId:string) => {
return `${Dirnames.Resources}/${resourceId}`;
};
@ -323,9 +349,9 @@ class Synchronizer {
throw error;
}
syncLock = await this.lockHandler().acquireLock('sync', this.appType_, this.clientId_);
syncLock = await this.lockHandler().acquireLock(LockType.Sync, this.appType_, this.clientId_);
this.lockHandler().startAutoLockRefresh(syncLock, (error) => {
this.lockHandler().startAutoLockRefresh(syncLock, (error:any) => {
this.logger().warn('Could not refresh lock - cancelling sync. Error was:', error);
this.syncTargetIsLocked_ = true;
this.cancel();
@ -339,9 +365,9 @@ class Synchronizer {
// ========================================================================
if (syncSteps.indexOf('update_remote') >= 0) {
const donePaths = [];
const donePaths:string[] = [];
const completeItemProcessing = path => {
const completeItemProcessing = (path:string) => {
donePaths.push(path);
};
@ -367,13 +393,13 @@ class Synchronizer {
// (by setting an updated_time less than current time).
if (donePaths.indexOf(path) >= 0) throw new JoplinError(sprintf('Processing a path that has already been done: %s. sync_time was not updated? Remote item has an updated_time in the future?', path), 'processingPathTwice');
const remote = await this.apiCall('stat', path);
const remote:RemoteItem = await this.apiCall('stat', path);
let action = null;
let reason = '';
let remoteContent = null;
const getConflictType = (conflictedItem) => {
const getConflictType = (conflictedItem:any) => {
if (conflictedItem.type_ === BaseModel.TYPE_NOTE) return 'noteConflict';
if (conflictedItem.type_ === BaseModel.TYPE_RESOURCE) return 'resourceConflict';
return 'itemConflict';
@ -626,7 +652,7 @@ class Synchronizer {
while (true) {
if (this.cancelling() || hasCancelled) break;
const listResult = await this.apiCall('delta', '', {
const listResult:any = await this.apiCall('delta', '', {
context: context,
// allItemIdsHandler() provides a way for drivers that don't have a delta API to
@ -734,7 +760,7 @@ class Synchronizer {
if (!content.user_updated_time) content.user_updated_time = content.updated_time;
if (!content.user_created_time) content.user_created_time = content.created_time;
const options = {
const options:any = {
autoTimestamp: false,
nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, content, time.unixMs()),
changeSource: ItemChange.SOURCE_SYNC,
@ -857,7 +883,7 @@ class Synchronizer {
if (syncLock) {
this.lockHandler().stopAutoLockRefresh(syncLock);
await this.lockHandler().releaseLock('sync', this.appType_, this.clientId_);
await this.lockHandler().releaseLock(LockType.Sync, this.appType_, this.clientId_);
}
this.syncTargetIsLocked_ = false;
@ -877,6 +903,7 @@ class Synchronizer {
this.progressReport_ = {};
this.dispatch({ type: 'SYNC_COMPLETED', isFullSync: this.isFullSync(syncSteps) });
eventManager.emit('syncComplete');
this.state_ = 'idle';
@ -885,5 +912,3 @@ class Synchronizer {
return outputContext;
}
}
module.exports = { Synchronizer };

View File

@ -1,6 +1,6 @@
const { time } = require('lib/time-utils.js');
const Setting = require('lib/models/Setting');
const { Logger } = require('lib/logger.js');
const Setting = require('lib/models/Setting').default;
const Logger = require('lib/Logger').default;
class TaskQueue {
constructor(name) {

View File

@ -1,4 +1,4 @@
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const { time } = require('lib/time-utils.js');
const Mustache = require('mustache');
@ -47,7 +47,7 @@ TemplateUtils.loadTemplates = async function(filePath) {
// sensitivity ensures that the sort will ignore case
files.sort((a, b) => { return a.path.localeCompare(b.path, undefined, { sensitivity: 'accent' }); });
files.forEach(async file => {
for (const file of files) {
if (file.path.endsWith('.md')) {
try {
const fileString = await shim.fsDriver().readFile(`${filePath}/${file.path}`, 'utf-8');
@ -59,7 +59,7 @@ TemplateUtils.loadTemplates = async function(filePath) {
throw error;
}
}
});
}
}
return templates;

View File

@ -1,5 +1,5 @@
const { Logger } = require('lib/logger.js');
const { shim } = require('lib/shim.js');
const Logger = require('lib/Logger').default;
const shim = require('lib/shim').default;
const parseXmlString = require('xml2js').parseString;
const JoplinError = require('lib/JoplinError');
const URL = require('url-parse');

View File

@ -1,10 +1,10 @@
const welcomeAssets = require('./welcomeAssets');
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const Folder = require('lib/models/Folder');
const Tag = require('lib/models/Tag');
const { shim } = require('lib/shim');
const { uuid } = require('lib/uuid');
const shim = require('lib/shim').default;
const uuid = require('lib/uuid').default;
const { fileExtension, basename } = require('lib/path-utils');
const { pregQuote } = require('lib/string-utils');

View File

@ -1,5 +1,5 @@
import { utils, CommandRuntime, CommandDeclaration } from '../services/CommandService';
const { _ } = require('lib/locale');
import { _ } from 'lib/locale';
export const declaration:CommandDeclaration = {
name: 'historyBackward',
@ -9,22 +9,22 @@ export const declaration:CommandDeclaration = {
};
interface Props {
backwardHistoryNotes: any[],
hasBackwardNotes: boolean,
}
export const runtime = ():CommandRuntime => {
return {
execute: async (props:Props) => {
if (!props.backwardHistoryNotes.length) return;
if (!props.hasBackwardNotes) return;
utils.store.dispatch({
type: 'HISTORY_BACKWARD',
});
},
isEnabled: (props:Props) => {
return props.backwardHistoryNotes.length > 0;
return props.hasBackwardNotes;
},
mapStateToProps: (state:any) => {
return { backwardHistoryNotes: state.backwardHistoryNotes };
return { hasBackwardNotes: state.backwardHistoryNotes.length > 0 };
},
};
};

View File

@ -1,5 +1,5 @@
import { utils, CommandRuntime, CommandDeclaration } from '../services/CommandService';
const { _ } = require('lib/locale');
import { _ } from 'lib/locale';
export const declaration:CommandDeclaration = {
name: 'historyForward',
@ -8,22 +8,22 @@ export const declaration:CommandDeclaration = {
};
interface Props {
forwardHistoryNotes: any[],
hasForwardNotes: boolean,
}
export const runtime = ():CommandRuntime => {
return {
execute: async (props:Props) => {
if (!props.forwardHistoryNotes.length) return;
if (!props.hasForwardNotes) return;
utils.store.dispatch({
type: 'HISTORY_FORWARD',
});
},
isEnabled: (props:Props) => {
return props.forwardHistoryNotes.length > 0;
return props.hasForwardNotes;
},
mapStateToProps: (state:any) => {
return { forwardHistoryNotes: state.forwardHistoryNotes };
return { hasForwardNotes: state.forwardHistoryNotes.length > 0 };
},
};
};

View File

@ -1,5 +1,5 @@
import { utils, CommandRuntime, CommandDeclaration } from '../services/CommandService';
const { _ } = require('lib/locale');
import { _ } from 'lib/locale';
const { reg } = require('lib/registry.js');
export const declaration:CommandDeclaration = {

View File

@ -4,9 +4,9 @@ const Component = React.Component;
const { connect } = require('react-redux');
const { View, TouchableOpacity, Text, Dimensions } = require('react-native');
const Icon = require('react-native-vector-icons/Ionicons').default;
const { _ } = require('lib/locale.js');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting');
const { _ } = require('lib/locale');
const shim = require('lib/shim').default;
const Setting = require('lib/models/Setting').default;
Icon.loadFont();

View File

@ -5,7 +5,7 @@ const Note = require('lib/models/Note');
const Icon = require('react-native-vector-icons/Ionicons').default;
const ReactNativeActionButton = require('react-native-action-button').default;
const { connect } = require('react-redux');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
Icon.loadFont();

View File

@ -1,4 +1,4 @@
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { Platform } = require('react-native');
const { themeById } = require('lib/theme');

View File

@ -5,9 +5,9 @@ const Component = React.Component;
const { Platform, View, Text } = require('react-native');
const { WebView } = require('react-native-webview');
const { themeStyle } = require('lib/components/global-style.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { reg } = require('lib/registry.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const { assetsToHeaders } = require('lib/joplin-renderer');
const shared = require('lib/components/shared/note-screen-shared.js');
const markupLanguageUtils = require('lib/markupLanguageUtils');
@ -51,11 +51,11 @@ class NoteBodyViewer extends Component {
const mdOptions = {
onResourceLoaded: () => {
if (this.resourceLoadedTimeoutId_) {
clearTimeout(this.resourceLoadedTimeoutId_);
shim.clearTimeout(this.resourceLoadedTimeoutId_);
this.resourceLoadedTimeoutId_ = null;
}
this.resourceLoadedTimeoutId_ = setTimeout(() => {
this.resourceLoadedTimeoutId_ = shim.setTimeout(() => {
this.resourceLoadedTimeoutId_ = null;
this.forceUpdate();
}, 100);
@ -87,16 +87,16 @@ class NoteBodyViewer extends Component {
injectedJs.push('window.joplinPostMessage_ = (msg, args) => { return window.ReactNativeWebView.postMessage(msg); };');
injectedJs.push('webviewLib.initialize({ postMessage: msg => { return window.ReactNativeWebView.postMessage(msg); } });');
injectedJs.push(`
const readyStateCheckInterval = setInterval(function() {
const readyStateCheckInterval = shim.setInterval(function() {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
shim.clearInterval(readyStateCheckInterval);
if ("${resourceDownloadMode}" === "manual") webviewLib.setupResourceManualDownload();
const hash = "${this.props.noteHash}";
// Gives it a bit of time before scrolling to the anchor
// so that images are loaded.
if (hash) {
setTimeout(() => {
shim.setTimeout(() => {
const e = document.getElementById(hash);
if (!e) {
console.warn('Cannot find hash', hash);
@ -152,7 +152,7 @@ class NoteBodyViewer extends Component {
}
onLoadEnd() {
setTimeout(() => {
shim.setTimeout(() => {
if (this.props.onLoadEnd) this.props.onLoadEnd();
}, 100);
@ -160,7 +160,7 @@ class NoteBodyViewer extends Component {
// Need to display after a delay to avoid a white flash before
// the content is displayed.
setTimeout(() => {
shim.setTimeout(() => {
if (!this.isMounted_) return;
this.setState({ webViewLoaded: true });
}, 100);

View File

@ -2,7 +2,7 @@ const React = require('react');
const Component = React.Component;
const { connect } = require('react-redux');
const { FlatList, Text, StyleSheet, Button, View } = require('react-native');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { NoteItem } = require('lib/components/note-item.js');
const { time } = require('lib/time-utils.js');
const { themeStyle } = require('lib/components/global-style.js');

View File

@ -6,8 +6,8 @@ const Icon = require('react-native-vector-icons/Ionicons').default;
const { BackButtonService } = require('lib/services/back-button.js');
const NavService = require('lib/services/NavService.js');
const { Menu, MenuOptions, MenuOption, MenuTrigger } = require('react-native-popup-menu');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting.js');
const { _ } = require('lib/locale');
const Setting = require('lib/models/Setting').default;
const Note = require('lib/models/Note.js');
const Folder = require('lib/models/Folder.js');
const { themeStyle } = require('lib/components/global-style.js');

View File

@ -3,7 +3,7 @@ const React = require('react');
const { StyleSheet, View, Text, FlatList, TouchableOpacity, TextInput } = require('react-native');
const { connect } = require('react-redux');
const Tag = require('lib/models/Tag.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { themeStyle } = require('lib/components/global-style.js');
const Icon = require('react-native-vector-icons/Ionicons').default;
const ModalDialog = require('lib/components/ModalDialog');

View File

@ -1,11 +1,11 @@
import * as React from 'react';
import useSyncTargetUpgrade from 'lib/services/synchronizer/gui/useSyncTargetUpgrade';
import { _ } from 'lib/locale';
const { View, Text, ScrollView } = require('react-native');
const { connect } = require('react-redux');
const { themeStyle } = require('lib/components/global-style.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { _ } = require('lib/locale.js');
function UpgradeSyncTargetScreen(props:any) {
const upgradeResult = useSyncTargetUpgrade();

View File

@ -3,11 +3,11 @@ const React = require('react');
const { Platform, TouchableOpacity, Linking, View, Switch, StyleSheet, Text, Button, ScrollView, TextInput, Alert, PermissionsAndroid } = require('react-native');
const { connect } = require('react-redux');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { Dropdown } = require('lib/components/Dropdown.js');
const { themeStyle } = require('lib/components/global-style.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const shared = require('lib/components/shared/config-shared.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const { reg } = require('lib/registry.js');
@ -15,7 +15,7 @@ const NavService = require('lib/services/NavService.js');
const VersionInfo = require('react-native-version-info').default;
const { ReportService } = require('lib/services/report.js');
const { time } = require('lib/time-utils');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const RNFS = require('react-native-fs');
const checkPermissions = require('lib/checkPermissions.js').default;

View File

@ -3,7 +3,7 @@ const React = require('react');
const { View, Button, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView } = require('react-native');
const { connect } = require('react-redux');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const DialogBox = require('react-native-dialogbox').default;
const { dialogs } = require('lib/dialogs.js');

View File

@ -4,7 +4,7 @@ const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, Sc
const EncryptionService = require('lib/services/EncryptionService');
const { connect } = require('react-redux');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { themeStyle } = require('lib/components/global-style.js');
const { time } = require('lib/time-utils.js');
@ -17,8 +17,8 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
return { header: null };
}
constructor() {
super();
constructor(props) {
super(props);
this.state = {
passwordPromptShow: false,
@ -26,7 +26,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
passwordPromptConfirmAnswer: '',
};
shared.constructor(this);
shared.constructor(this, props);
this.styles_ = {};
}
@ -107,7 +107,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent {
return shared.onPasswordChange(this, mk, text);
};
const password = this.props.passwords[mk.id] ? this.props.passwords[mk.id] : '';
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
const passwordOk = this.state.passwordChecks[mk.id] === true ? '✔' : '❌';
const inputStyle = { flex: 1, marginRight: 10, color: theme.color };

View File

@ -8,7 +8,7 @@ const { ScreenHeader } = require('lib/components/screen-header.js');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { dialogs } = require('lib/dialogs.js');
const { themeStyle } = require('lib/components/global-style.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
class FolderScreenComponent extends BaseScreenComponent {
static navigationOptions() {

View File

@ -6,9 +6,9 @@ const { reg } = require('lib/registry.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { time } = require('lib/time-utils');
const { themeStyle } = require('lib/components/global-style.js');
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
class LogScreenComponent extends BaseScreenComponent {
static navigationOptions() {

View File

@ -4,13 +4,13 @@ import AsyncActionQueue from '../../AsyncActionQueue';
const React = require('react');
const { Platform, Clipboard, Keyboard, View, TextInput, StyleSheet, Linking, Image, Share } = require('react-native');
const { connect } = require('react-redux');
const { uuid } = require('lib/uuid.js');
const uuid = require('lib/uuid').default;
const { MarkdownEditor } = require('../../../MarkdownEditor/index.js');
const RNFS = require('react-native-fs');
const Note = require('lib/models/Note.js');
const UndoRedoService = require('lib/services/UndoRedoService.js').default;
const BaseItem = require('lib/models/BaseItem.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Resource = require('lib/models/Resource.js');
const Folder = require('lib/models/Folder.js');
const md5 = require('md5');
@ -24,9 +24,9 @@ const { ScreenHeader } = require('lib/components/screen-header.js');
const NoteTagsDialog = require('lib/components/screens/NoteTagsDialog');
const { time } = require('lib/time-utils.js');
const { Checkbox } = require('lib/components/checkbox.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { reg } = require('lib/registry.js');
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const ResourceFetcher = require('lib/services/ResourceFetcher');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { themeStyle, editorFont } = require('lib/components/global-style.js');
@ -167,7 +167,7 @@ class NoteScreenComponent extends BaseScreenComponent {
type: 'NAV_BACK',
});
setTimeout(() => {
shim.setTimeout(() => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Note',
@ -894,16 +894,16 @@ class NoteScreenComponent extends BaseScreenComponent {
}
scheduleFocusUpdate() {
if (this.focusUpdateIID_) clearTimeout(this.focusUpdateIID_);
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = setTimeout(() => {
this.focusUpdateIID_ = shim.setTimeout(() => {
this.focusUpdateIID_ = null;
this.focusUpdate();
}, 100);
}
focusUpdate() {
if (this.focusUpdateIID_) clearTimeout(this.focusUpdateIID_);
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = null;
if (!this.state.note) return;
@ -1005,9 +1005,9 @@ class NoteScreenComponent extends BaseScreenComponent {
}}
onMarkForDownload={this.onMarkForDownload}
onLoadEnd={() => {
setTimeout(() => {
shim.setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 1 });
setTimeout(() => {
shim.setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 0 });
}, 50);
}, 5);
@ -1062,9 +1062,9 @@ class NoteScreenComponent extends BaseScreenComponent {
},
onMarkForDownload: this.onMarkForDownload,
onLoadEnd: () => {
setTimeout(() => {
shim.setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 1 });
setTimeout(() => {
shim.setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 0 });
}, 50);
}, 5);

View File

@ -1,16 +1,16 @@
const React = require('react');
const { AppState, View, StyleSheet } = require('react-native');
const { stateUtils } = require('lib/reducer.js');
const { stateUtils } = require('lib/reducer');
const { connect } = require('react-redux');
const { NoteList } = require('lib/components/note-list.js');
const Folder = require('lib/models/Folder.js');
const Tag = require('lib/models/Tag.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { themeStyle } = require('lib/components/global-style.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { ActionButton } = require('lib/components/action-button.js');
const { dialogs } = require('lib/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;

View File

@ -6,10 +6,11 @@ const { WebView } = require('react-native-webview');
const { connect } = require('react-redux');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { reg } = require('lib/registry.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const parseUri = require('lib/parseUri');
const { themeStyle } = require('lib/components/global-style.js');
const shim = require('lib/shim').default;
class OneDriveLoginScreenComponent extends BaseScreenComponent {
static navigationOptions() {
@ -91,7 +92,7 @@ class OneDriveLoginScreenComponent extends BaseScreenComponent {
});
this.forceUpdate();
setTimeout(() => {
shim.setTimeout(() => {
this.setState({
webviewUrl: this.startUrl(),
});

View File

@ -4,7 +4,7 @@ const { StyleSheet, View, TextInput, FlatList, TouchableHighlight } = require('r
const { connect } = require('react-redux');
const { ScreenHeader } = require('lib/components/screen-header.js');
const Icon = require('react-native-vector-icons/Ionicons').default;
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const Note = require('lib/models/Note.js');
const { NoteItem } = require('lib/components/note-item.js');
const { BaseScreenComponent } = require('lib/components/base-screen.js');

View File

@ -1,11 +1,11 @@
const React = require('react');
const { View, Text, Button, FlatList } = require('react-native');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { connect } = require('react-redux');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { ReportService } = require('lib/services/report.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { themeStyle } = require('lib/components/global-style.js');

View File

@ -5,7 +5,7 @@ const { connect } = require('react-redux');
const Tag = require('lib/models/Tag.js');
const { themeStyle } = require('lib/components/global-style.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
class TagsScreenComponent extends BaseScreenComponent {

View File

@ -1,7 +1,7 @@
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const ObjectUtils = require('lib/ObjectUtils');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { createSelector } = require('reselect');
const { reg } = require('lib/registry');

View File

@ -1,8 +1,8 @@
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const { reg } = require('lib/registry.js');
const { _ } = require('lib/locale.js');
const Setting = require('lib/models/Setting');
const { _ } = require('lib/locale');
const Setting = require('lib/models/Setting').default;
class Shared {
constructor(comp, showInfoMessageBox, showErrorMessageBox) {

View File

@ -1,19 +1,21 @@
const EncryptionService = require('lib/services/EncryptionService');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseItem = require('lib/models/BaseItem.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const MasterKey = require('lib/models/MasterKey.js');
const { reg } = require('lib/registry.js');
const shim = require('lib/shim').default;
const shared = {};
shared.constructor = function(comp) {
shared.constructor = function(comp, props) {
comp.state = {
passwordChecks: {},
stats: {
encrypted: null,
total: null,
},
passwords: Object.assign({}, props.passwords),
};
comp.isMounted_ = false;
@ -49,7 +51,7 @@ shared.upgradeMasterKey = async function(comp, masterKey) {
}
try {
const password = comp.props.passwords[masterKey.id];
const password = comp.state.passwords[masterKey.id];
const newMasterKey = await EncryptionService.instance().upgradeMasterKey(masterKey, password);
await MasterKey.save(newMasterKey);
reg.waitForSyncFinishedThenSync();
@ -65,13 +67,13 @@ shared.componentDidMount = async function(comp) {
shared.refreshStats(comp);
if (shared.refreshStatsIID_) {
clearInterval(shared.refreshStatsIID_);
shim.clearInterval(shared.refreshStatsIID_);
shared.refreshStatsIID_ = null;
}
shared.refreshStatsIID_ = setInterval(() => {
shared.refreshStatsIID_ = shim.setInterval(() => {
if (!comp.isMounted_) {
clearInterval(shared.refreshStatsIID_);
shim.clearInterval(shared.refreshStatsIID_);
shared.refreshStatsIID_ = null;
return;
}
@ -80,6 +82,10 @@ shared.componentDidMount = async function(comp) {
};
shared.componentDidUpdate = async function(comp, prevProps = null) {
if (prevProps && comp.props.passwords !== prevProps.passwords) {
comp.setState({ passwords: Object.assign({}, comp.props.passwords) });
}
if (!prevProps || comp.props.masterKeys !== prevProps.masterKeys || comp.props.passwords !== prevProps.passwords) {
comp.checkPasswords();
}
@ -87,7 +93,7 @@ shared.componentDidUpdate = async function(comp, prevProps = null) {
shared.componentWillUnmount = function() {
if (shared.refreshStatsIID_) {
clearInterval(shared.refreshStatsIID_);
shim.clearInterval(shared.refreshStatsIID_);
shared.refreshStatsIID_ = null;
}
};
@ -96,7 +102,7 @@ shared.checkPasswords = async function(comp) {
const passwordChecks = Object.assign({}, comp.state.passwordChecks);
for (let i = 0; i < comp.props.masterKeys.length; i++) {
const mk = comp.props.masterKeys[i];
const password = comp.props.passwords[mk.id];
const password = comp.state.passwords[mk.id];
const ok = password ? await EncryptionService.instance().checkMasterKeyPassword(mk, password) : false;
passwordChecks[mk.id] = ok;
}
@ -111,18 +117,18 @@ shared.decryptedStatText = function(comp) {
};
shared.onSavePasswordClick = function(comp, mk) {
const password = comp.props.passwords[mk.id];
const password = comp.state.passwords[mk.id];
if (!password) {
Setting.deleteObjectKey('encryption.passwordCache', mk.id);
Setting.deleteObjectValue('encryption.passwordCache', mk.id);
} else {
Setting.setObjectKey('encryption.passwordCache', mk.id, password);
Setting.setObjectValue('encryption.passwordCache', mk.id, password);
}
comp.checkPasswords();
};
shared.onPasswordChange = function(comp, mk, password) {
const passwords = comp.props.passwords;
const passwords = Object.assign({}, comp.state.passwords);
passwords[mk.id] = password;
comp.setState({ passwords: passwords });
};

View File

@ -5,7 +5,7 @@ const Note = require('lib/models/Note.js');
const Resource = require('lib/models/Resource.js');
const ResourceFetcher = require('lib/services/ResourceFetcher.js');
const DecryptionWorker = require('lib/services/DecryptionWorker.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const Mutex = require('async-mutex').Mutex;
const shared = {};

View File

@ -1,14 +1,17 @@
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const Tag = require('lib/models/Tag');
const BaseModel = require('lib/BaseModel');
const Note = require('lib/models/Note');
const { reg } = require('lib/registry.js');
const ResourceFetcher = require('lib/services/ResourceFetcher');
const DecryptionWorker = require('lib/services/DecryptionWorker');
const eventManager = require('lib/eventManager').default;
const reduxSharedMiddleware = async function(store, next, action) {
const newState = store.getState();
eventManager.appStateEmit(newState);
let refreshTags = false;
if (action.type == 'FOLDER_SET_COLLAPSED' || action.type == 'FOLDER_TOGGLE') {

View File

@ -4,9 +4,9 @@ const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View,
const { connect } = require('react-redux');
const Icon = require('react-native-vector-icons/Ionicons').default;
const Folder = require('lib/models/Folder.js');
const { Synchronizer } = require('lib/synchronizer.js');
const Synchronizer = require('lib/Synchronizer').default;
const NavService = require('lib/services/NavService.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { themeStyle } = require('lib/components/global-style.js');
const shared = require('lib/components/shared/side-menu-shared.js');

View File

@ -1,6 +1,7 @@
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const { time } = require('lib/time-utils.js');
const Mutex = require('async-mutex').Mutex;
const shim = require('lib/shim').default;
class Database {
constructor(driver) {
@ -83,7 +84,7 @@ class Database {
if (this.profilingEnabled_) {
console.info(`SQL START ${queryId}`, sql, params);
profilingTimeoutId = setInterval(() => {
profilingTimeoutId = shim.setInterval(() => {
console.warn(`SQL ${queryId} has been running for ${Date.now() - callStartTime}: ${sql}`);
}, 3000);
}
@ -91,7 +92,7 @@ class Database {
const result = await this.driver()[callName](sql, params);
if (this.profilingEnabled_) {
clearInterval(profilingTimeoutId);
shim.clearInterval(profilingTimeoutId);
profilingTimeoutId = null;
const elapsed = Date.now() - callStartTime;
if (elapsed > 10) console.info(`SQL END ${queryId}`, elapsed, sql, params);
@ -112,7 +113,7 @@ class Database {
throw this.sqliteErrorToJsError(error, sql, params);
}
} finally {
if (profilingTimeoutId) clearInterval(profilingTimeoutId);
if (profilingTimeoutId) shim.clearInterval(profilingTimeoutId);
}
}
}

View File

@ -0,0 +1,31 @@
/* eslint-disable import/prefer-default-export */
// This wraps an error message, allowing to set a prefix,
// while preserving all the important properties
// in particular the stack trace and original error message.
export function wrapError(prefix:string, error:any) {
if (!error) throw new Error('Unknown error');
const newError:any = new Error([prefix, error.message || ''].join(': '));
if ('name' in error) newError.name = error.name;
if ('fileName' in error) newError.fileName = error.fileName;
if ('lineNumber' in error) newError.lineNumber = error.lineNumber;
if ('columnNumber' in error) newError.columnNumber = error.columnNumber;
// "code" is a non-standard property that is used in Joplin
if ('code' in error) newError.code = error.code;
// The stack is a string in this format:
//
// Error message
// Stack line 1
// Stack line 2
// etc.
//
// And when console.error is used to print the error, it will take the message
// from the stack (not from the "message" property), so it means we also need
// to add the prefix error message to the stack.
if ('stack' in error) newError.stack = [prefix, error.stack].join(': ');
return newError;
}

View File

@ -1,29 +0,0 @@
const events = require('events');
class EventManager {
constructor() {
this.emitter_ = new events.EventEmitter();
}
on(eventName, callback) {
return this.emitter_.on(eventName, callback);
}
emit(eventName, object = null) {
return this.emitter_.emit(eventName, object);
}
removeListener(eventName, callback) {
return this.emitter_.removeListener(eventName, callback);
}
off(eventName, callback) {
return this.removeListener(eventName, callback);
}
}
const eventManager = new EventManager();
module.exports = eventManager;

View File

@ -0,0 +1,129 @@
const events = require('events');
class EventManager {
private emitter_:any;
private appStatePrevious_:any;
private appStateWatchedProps_:string[];
private appStateListeners_:any;
constructor() {
this.reset();
}
reset() {
this.emitter_ = new events.EventEmitter();
this.appStatePrevious_ = {};
this.appStateWatchedProps_ = [];
this.appStateListeners_ = {};
}
on(eventName:string, callback:Function) {
return this.emitter_.on(eventName, callback);
}
emit(eventName:string, object:any = null) {
return this.emitter_.emit(eventName, object);
}
removeListener(eventName:string, callback:Function) {
return this.emitter_.removeListener(eventName, callback);
}
off(eventName:string, callback:Function) {
return this.removeListener(eventName, callback);
}
filterOn(filterName:string, callback:Function) {
return this.emitter_.on(`filter:${filterName}`, callback);
}
filterOff(filterName:string, callback:Function) {
return this.removeListener(`filter:${filterName}`, callback);
}
filterEmit(filterName:string, object:any) {
// We freeze the object we pass to the listeners so that they
// don't modify it directly. Instead they must return a
// modified copy (or the input itself).
let output = Object.freeze(object);
const listeners = this.emitter_.listeners(`filter:${filterName}`);
for (const listener of listeners) {
const newOutput = listener(output);
if (newOutput === undefined) {
throw new Error(`Filter "${filterName}": Filter must return a value or the unmodified input. Returning nothing or "undefined" is not supported.`);
}
if (newOutput !== output) {
output = Object.freeze(newOutput);
}
}
return output;
}
appStateOn(propName:string, callback:Function) {
if (!this.appStateListeners_[propName]) {
this.appStateListeners_[propName] = [];
this.appStateWatchedProps_.push(propName);
}
this.appStateListeners_[propName].push(callback);
}
appStateOff(propName:string, callback:Function) {
if (!this.appStateListeners_[propName]) {
throw new Error('EventManager: Trying to unregister a state prop watch for a non-watched prop (1)');
}
const idx = this.appStateListeners_[propName].indexOf(callback);
if (idx < 0) throw new Error('EventManager: Trying to unregister a state prop watch for a non-watched prop (2)');
this.appStateListeners_[propName].splice(idx, 1);
}
stateValue_(state:any, propName:string) {
const parts = propName.split('.');
let s = state;
for (const p of parts) {
if (!(p in s)) throw new Error(`Invalid state property path: ${propName}`);
s = s[p];
}
return s;
}
// This function works by keeping a copy of the watched props and, whenever this function
// is called, comparing the previous and new values and emitting events if they have changed.
// The appStateEmit function should be called from a middleware.
appStateEmit(state:any) {
if (!this.appStateWatchedProps_.length) return;
for (const propName of this.appStateWatchedProps_) {
let emit = false;
const stateValue = this.stateValue_(state, propName);
if (!(propName in this.appStatePrevious_) || this.appStatePrevious_[propName] !== stateValue) {
this.appStatePrevious_[propName] = stateValue;
emit = true;
}
if (emit) {
const listeners = this.appStateListeners_[propName];
if (!listeners || !listeners.length) continue;
const eventValue = Object.freeze(stateValue);
for (const listener of listeners) {
listener({ value: eventValue });
}
}
}
}
}
const eventManager = new EventManager();
export default eventManager;

View File

@ -1,6 +1,6 @@
const { basicDelta } = require('lib/file-api');
const { basename } = require('lib/path-utils');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const JoplinError = require('lib/JoplinError');
const S3_MAX_DELETES = 1000;

View File

@ -1,5 +1,5 @@
const { time } = require('lib/time-utils.js');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const JoplinError = require('lib/JoplinError');
class FileApiDriverDropbox {

View File

@ -1,6 +1,6 @@
const moment = require('moment');
const { dirname, basename } = require('lib/path-utils.js');
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
class FileApiDriverOneDrive {
constructor(api) {

View File

@ -1,6 +1,6 @@
const { isHidden } = require('lib/path-utils.js');
const { Logger } = require('lib/logger.js');
const { shim } = require('lib/shim');
const Logger = require('lib/Logger').default;
const shim = require('lib/shim').default;
const BaseItem = require('lib/models/BaseItem.js');
const JoplinError = require('lib/JoplinError');
const ArrayUtils = require('lib/ArrayUtils');

View File

@ -1,5 +1,6 @@
const Folder = require('lib/models/Folder.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const shim = require('lib/shim').default;
class FoldersScreenUtils {
static async allForDisplay(options = {}) {
@ -48,8 +49,8 @@ class FoldersScreenUtils {
}
static scheduleRefreshFolders() {
if (this.scheduleRefreshFoldersIID_) clearTimeout(this.scheduleRefreshFoldersIID_);
this.scheduleRefreshFoldersIID_ = setTimeout(() => {
if (this.scheduleRefreshFoldersIID_) shim.clearTimeout(this.scheduleRefreshFoldersIID_);
this.scheduleRefreshFoldersIID_ = shim.setTimeout(() => {
this.scheduleRefreshFoldersIID_ = null;
this.refreshFolders();
}, 1000);
@ -57,13 +58,13 @@ class FoldersScreenUtils {
static async cancelTimers() {
if (this.scheduleRefreshFoldersIID_) {
clearTimeout(this.scheduleRefreshFoldersIID_);
shim.clearTimeout(this.scheduleRefreshFoldersIID_);
this.scheduleRefreshFoldersIID_ = null;
}
return new Promise((resolve) => {
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
if (!FoldersScreenUtils.refreshCalls_.length) {
clearInterval(iid);
shim.clearInterval(iid);
resolve();
}
}, 100);

View File

@ -1,6 +1,6 @@
const { filename, fileExtension } = require('lib/path-utils');
const { time } = require('lib/time-utils.js');
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const md5 = require('md5');
class FsDriverBase {

View File

@ -188,6 +188,11 @@ class FsDriverNode extends FsDriverBase {
if (encoding === 'ascii') return buffer.toString('ascii');
throw new Error(`Unsupported encoding: ${encoding}`);
}
resolve(path) {
return require('path').resolve(path);
}
}
module.exports.FsDriverNode = FsDriverNode;

View File

@ -156,6 +156,10 @@ class FsDriverRN extends FsDriverBase {
handle.offset += length;
return output ? output : null;
}
resolve(path) {
throw new Error(`Not implemented: resolve(): ${path}`);
}
}
module.exports.FsDriverRN = FsDriverRN;

View File

@ -1,4 +1,4 @@
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
class GeolocationNode {
static async currentPosition(options = null) {

View File

@ -1,6 +1,6 @@
import Geolocation from '@react-native-community/geolocation';
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
class GeolocationReact {
static currentPosition_testResponse() {

View File

@ -1,10 +1,10 @@
const { uuid } = require('lib/uuid.js');
const uuid = require('lib/uuid').default;
const moment = require('moment');
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const Resource = require('lib/models/Resource.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const { MarkupToHtml } = require('lib/joplin-renderer');
const { enexXmlToMd } = require('./import-enex-md-gen.js');
const { enexXmlToHtml } = require('./import-enex-html-gen.js');
@ -13,6 +13,7 @@ const Levenshtein = require('levenshtein');
const md5 = require('md5');
const { Base64Decode } = require('base64-stream');
const md5File = require('md5-file');
const shim = require('lib/shim').default;
// const Promise = require('promise');
const fs = require('fs-extra');
@ -484,10 +485,10 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
saxStream.on('end', function() {
// Wait till there is no more notes to process.
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
processNotes().then(allDone => {
if (allDone) {
clearTimeout(iid);
shim.clearTimeout(iid);
resolve();
}
});

View File

@ -2,7 +2,7 @@ const { promiseChain } = require('lib/promise-utils.js');
const { Database } = require('lib/database.js');
const { sprintf } = require('sprintf-js');
const Resource = require('lib/models/Resource');
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const structureSql = `
CREATE TABLE folders (
@ -866,7 +866,7 @@ class JoplinDatabase extends Database {
this.logger().warn('Could not upgrade to database v15 or v18 or v33 - FTS feature will not be used', error);
saveVersionAgain = true;
} else if (targetVersion === 34) {
this.logger().warn('Could not upgrade to database v34 - fuzzy search will not be used', error);
if (!shim.isTestingEnv()) this.logger().warn('Could not upgrade to database v34 - fuzzy search will not be used', error);
saveVersionAgain = true;
} else {
throw error;

View File

@ -1,7 +1,7 @@
const htmlUtils = require('./htmlUtils');
const utils = require('./utils');
const noteStyle = require('./noteStyle');
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const { themeStyle } = require('lib/theme');
const memoryCache = require('memory-cache');
const md5 = require('md5');

View File

@ -19,6 +19,7 @@ const rules = {
mermaid: require('./MdToHtml/rules/mermaid').default,
};
// const eventManager = require('lib/eventManager').default;
const setupLinkify = require('./MdToHtml/setupLinkify');
const hljs = require('highlight.js');
const nodeSlug = require('slug');
@ -298,9 +299,16 @@ class MdToHtml {
markdownIt.use(markdownItAnchor, { slugify: slugify });
for (const key in plugins) {
if (this.pluginEnabled(key)) markdownIt.use(plugins[key].module, plugins[key].options);
if (this.pluginEnabled(key)) {
markdownIt.use(plugins[key].module, plugins[key].options);
}
}
// const extraPlugins = eventManager.filterEmit('mdToHtmlPlugins', {});
// for (const key in extraPlugins) {
// markdownIt.use(extraPlugins[key].module, extraPlugins[key].options);
// }
setupLinkify(markdownIt);
const renderedBody = markdownIt.render(body);

View File

@ -1,9 +1,4 @@
function formatCssSize(v) {
if (typeof v === 'string') {
if (v.includes('px') || v.includes('em') || v.includes('%')) return v;
}
return `${v}px`;
}
const { formatCssSize } = require('lib/string-utils');
module.exports = function(theme) {
theme = theme ? theme : {};

View File

@ -1,6 +1,10 @@
const { sprintf } = require('sprintf-js');
const codeToLanguageE_ = {};
interface StringToStringMap {
[key:string]: string,
}
const codeToLanguageE_:StringToStringMap = {};
codeToLanguageE_['aa'] = 'Afar';
codeToLanguageE_['ab'] = 'Abkhazian';
codeToLanguageE_['af'] = 'Afrikaans';
@ -144,7 +148,7 @@ codeToLanguageE_['za'] = 'Zhuang';
codeToLanguageE_['zh'] = 'Chinese';
codeToLanguageE_['zu'] = 'Zulu';
const codeToLanguage_ = {};
const codeToLanguage_:StringToStringMap = {};
codeToLanguage_['an'] = 'Aragonés';
codeToLanguage_['da'] = 'Dansk';
codeToLanguage_['de'] = 'Deutsch';
@ -172,17 +176,17 @@ codeToLanguage_['et'] = 'Eesti Keel';
codeToLanguage_['vi'] = 'Tiếng Việt';
codeToLanguage_['hu'] = 'Magyar';
const codeToCountry_ = {};
const codeToCountry_:StringToStringMap = {};
codeToCountry_['BR'] = 'Brasil';
codeToCountry_['CR'] = 'Costa Rica';
codeToCountry_['CN'] = '中国';
codeToCountry_['GB'] = 'UK';
codeToCountry_['US'] = 'US';
let supportedLocales_ = null;
let localeStats_ = null;
let supportedLocales_:any = null;
let localeStats_:any = null;
const loadedLocales_ = {};
const loadedLocales_:any = {};
const defaultLocale_ = 'en_GB';
@ -197,7 +201,7 @@ function localeStats() {
return localeStats_;
}
function supportedLocales() {
function supportedLocales():string[] {
if (!supportedLocales_) supportedLocales_ = require('../locales/index.js').locales;
const output = [];
@ -208,11 +212,15 @@ function supportedLocales() {
return output;
}
function supportedLocalesToLanguages(options = null) {
interface SupportedLocalesToLanguagesOptions {
includeStats?: boolean,
}
function supportedLocalesToLanguages(options:SupportedLocalesToLanguagesOptions = null) {
if (!options) options = {};
const stats = localeStats();
const locales = supportedLocales();
const output = {};
const output:StringToStringMap = {};
for (let i = 0; i < locales.length; i++) {
const locale = locales[i];
output[locale] = countryDisplayName(locale);
@ -225,7 +233,7 @@ function supportedLocalesToLanguages(options = null) {
return output;
}
function closestSupportedLocale(canonicalName, defaultToEnglish = true, locales = null) {
function closestSupportedLocale(canonicalName:string, defaultToEnglish:boolean = true, locales:string[] = null) {
locales = locales === null ? supportedLocales() : locales;
if (locales.indexOf(canonicalName) >= 0) return canonicalName;
@ -240,31 +248,31 @@ function closestSupportedLocale(canonicalName, defaultToEnglish = true, locales
return defaultToEnglish ? 'en_GB' : null;
}
function countryName(countryCode) {
function countryName(countryCode:string) {
return codeToCountry_[countryCode] ? codeToCountry_[countryCode] : '';
}
function languageNameInEnglish(languageCode) {
function languageNameInEnglish(languageCode:string) {
return codeToLanguageE_[languageCode] ? codeToLanguageE_[languageCode] : '';
}
function languageName(languageCode, defaultToEnglish = true) {
function languageName(languageCode:string, defaultToEnglish:boolean = true) {
if (codeToLanguage_[languageCode]) return codeToLanguage_[languageCode];
if (defaultToEnglish) return languageNameInEnglish(languageCode);
return '';
}
function languageCodeOnly(canonicalName) {
function languageCodeOnly(canonicalName:string) {
if (canonicalName.length < 2) return canonicalName;
return canonicalName.substr(0, 2);
}
function countryCodeOnly(canonicalName) {
function countryCodeOnly(canonicalName:string) {
if (canonicalName.length <= 2) return '';
return canonicalName.substr(3);
}
function countryDisplayName(canonicalName) {
function countryDisplayName(canonicalName:string) {
const languageCode = languageCodeOnly(canonicalName);
const countryCode = countryCodeOnly(canonicalName);
@ -287,7 +295,7 @@ function countryDisplayName(canonicalName) {
return output;
}
function localeStrings(canonicalName) {
function localeStrings(canonicalName:string) {
const locale = closestSupportedLocale(canonicalName);
if (loadedLocales_[locale]) return loadedLocales_[locale];
@ -297,7 +305,7 @@ function localeStrings(canonicalName) {
return loadedLocales_[locale];
}
function setLocale(canonicalName) {
function setLocale(canonicalName:string) {
if (currentLocale_ == canonicalName) return;
currentLocale_ = closestSupportedLocale(canonicalName);
}
@ -306,7 +314,7 @@ function languageCode() {
return languageCodeOnly(currentLocale_);
}
function _(s, ...args) {
function _(s:string, ...args:any[]) {
const strings = localeStrings(currentLocale_);
let result = strings[s];
if (result === '' || result === undefined) result = s;
@ -317,9 +325,9 @@ function _(s, ...args) {
}
}
function _n(singular, plural, n, ...args) {
function _n(singular:string, plural:string, n:number, ...args:any[]) {
if (n > 1) return _(plural, ...args);
return _(singular, ...args);
}
module.exports = { _, _n, supportedLocales, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };
export { _, _n, supportedLocales, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };

View File

@ -7,35 +7,45 @@ const { setupLinkify } = require('lib/joplin-renderer');
const listRegex = /^(\s*)([*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]\s))(\s*)/;
const emptyListRegex = /^(\s*)([*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/;
export interface MarkdownTableHeader {
name: string,
label: string,
filter?: Function,
}
export interface MarkdownTableRow {
[key:string]: string,
}
const markdownUtils = {
// Titles for markdown links only need escaping for [ and ]
escapeTitleText(text) {
escapeTitleText(text:string) {
return text.replace(/(\[|\])/g, '\\$1');
},
escapeLinkUrl(url) {
escapeLinkUrl(url:string) {
url = url.replace(/\(/g, '%28');
url = url.replace(/\)/g, '%29');
url = url.replace(/ /g, '%20');
return url;
},
prependBaseUrl(md, baseUrl) {
prependBaseUrl(md:string, baseUrl:string) {
// eslint-disable-next-line no-useless-escape
return md.replace(/(\]\()([^\s\)]+)(.*?\))/g, (match, before, url, after) => {
return md.replace(/(\]\()([^\s\)]+)(.*?\))/g, (_match:any, before:string, url:string, after:string) => {
return before + urlUtils.prependBaseUrl(url, baseUrl) + after;
});
},
extractImageUrls(md) {
extractImageUrls(md:string) {
const markdownIt = new MarkdownIt();
setupLinkify(markdownIt); // Necessary to support file:/// links
const env = {};
const tokens = markdownIt.parse(md, env);
const output = [];
const output:string[] = [];
const searchUrls = tokens => {
const searchUrls = (tokens:any[]) => {
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
@ -62,25 +72,25 @@ const markdownUtils = {
// The match results has 5 items
// Full match array is
// [Full match, whitespace, list token, ol line number, whitespace following token]
olLineNumber(line) {
olLineNumber(line:string) {
const match = line.match(listRegex);
return match ? Number(match[3]) : 0;
},
extractListToken(line) {
extractListToken(line:string) {
const match = line.match(listRegex);
return match ? match[2] : '';
},
isListItem(line) {
isListItem(line:string) {
return listRegex.test(line);
},
isEmptyListItem(line) {
isEmptyListItem(line:string) {
return emptyListRegex.test(line);
},
createMarkdownTable(headers, rows) {
createMarkdownTable(headers:MarkdownTableHeader[], rows:MarkdownTableRow[]):string {
const output = [];
const headersMd = [];
@ -108,7 +118,7 @@ const markdownUtils = {
return output.join('\n');
},
titleFromBody(body) {
titleFromBody(body:string) {
if (!body) return '';
const mdLinkRegex = /!?\[([^\]]+?)\]\(.+?\)/g;
const emptyMdLinkRegex = /!?\[\]\((.+?)\)/g;
@ -119,4 +129,4 @@ const markdownUtils = {
},
};
module.exports = markdownUtils;
export default markdownUtils;

View File

@ -1,8 +1,8 @@
const markdownUtils = require('lib/markdownUtils');
const markdownUtils = require('lib/markdownUtils').default;
const htmlUtils = require('lib/htmlUtils');
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const Resource = require('lib/models/Resource');
const { shim } = require('lib/shim');
const shim = require('lib/shim').default;
const { MarkupToHtml } = require('lib/joplin-renderer');
class MarkupLanguageUtils {

View File

@ -1,6 +1,6 @@
const Resource = require('lib/models/Resource');
const Setting = require('lib/models/Setting');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting').default;
const shim = require('lib/shim').default;
const { reg } = require('lib/registry.js');
const { fileExtension } = require('lib/path-utils.js');

View File

@ -1,4 +1,4 @@
const Setting = require('lib/models/Setting');
const Setting = require('lib/models/Setting').default;
const script = {};

View File

@ -1,7 +1,15 @@
const BaseModel = require('lib/BaseModel.js');
const Note = require('lib/models/Note.js');
class Alarm extends BaseModel {
export interface Notification {
id: string,
noteId: string,
date: Date,
title: string,
body?: string,
}
export default class Alarm extends BaseModel {
static tableName() {
return 'alarms';
}
@ -10,7 +18,7 @@ class Alarm extends BaseModel {
return BaseModel.TYPE_ALARM;
}
static byNoteId(noteId) {
static byNoteId(noteId:string) {
return this.modelSelectOne('SELECT * FROM alarms WHERE note_id = ?', [noteId]);
}
@ -21,12 +29,12 @@ class Alarm extends BaseModel {
static async alarmIdsWithoutNotes() {
// https://stackoverflow.com/a/4967229/561309
const alarms = await this.db().selectAll('SELECT alarms.id FROM alarms LEFT JOIN notes ON alarms.note_id = notes.id WHERE notes.id IS NULL');
return alarms.map(a => {
return alarms.map((a:any) => {
return a.id;
});
}
static async makeNotification(alarm, note = null) {
static async makeNotification(alarm:any, note:any = null):Promise<Notification> {
if (!note) {
note = await Note.load(alarm.note_id);
} else if (!note.todo_due) {
@ -35,8 +43,9 @@ class Alarm extends BaseModel {
this.logger().warn('Reloaded note:', note);
}
const output = {
const output:Notification = {
id: alarm.id,
noteId: alarm.note_id,
date: new Date(note.todo_due),
title: note.title.substr(0, 128),
};
@ -50,5 +59,3 @@ class Alarm extends BaseModel {
return this.modelSelectAll('SELECT * FROM alarms WHERE trigger_time >= ?', [Date.now()]);
}
}
module.exports = Alarm;

View File

@ -1,13 +1,13 @@
const BaseModel = require('lib/BaseModel.js');
const { Database } = require('lib/database.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const ItemChange = require('lib/models/ItemChange.js');
const JoplinError = require('lib/JoplinError.js');
const { time } = require('lib/time-utils.js');
const { sprintf } = require('sprintf-js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const moment = require('moment');
const markdownUtils = require('lib/markdownUtils');
const markdownUtils = require('lib/markdownUtils').default;
class BaseItem extends BaseModel {
static useUuid() {

View File

@ -2,7 +2,7 @@ const BaseModel = require('lib/BaseModel.js');
const { time } = require('lib/time-utils.js');
const Note = require('lib/models/Note.js');
const { Database } = require('lib/database.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const BaseItem = require('lib/models/BaseItem.js');
const { substrWithEllipsis } = require('lib/string-utils.js');
@ -305,7 +305,7 @@ class Folder extends BaseItem {
static buildTree(folders) {
const idToFolders = {};
for (let i = 0; i < folders.length; i++) {
idToFolders[folders[i].id] = folders[i];
idToFolders[folders[i].id] = Object.assign({}, folders[i]);
idToFolders[folders[i].id].children = [];
}

View File

@ -1,5 +1,6 @@
const BaseModel = require('lib/BaseModel.js');
const Mutex = require('async-mutex').Mutex;
const shim = require('lib/shim').default;
class ItemChange extends BaseModel {
static tableName() {
@ -37,9 +38,9 @@ class ItemChange extends BaseModel {
// can be used for synchronous code, in particular when unit testing.
static async waitForAllSaved() {
return new Promise((resolve) => {
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
if (!ItemChange.saveCalls_.length) {
clearInterval(iid);
shim.clearInterval(iid);
resolve();
}
}, 100);

View File

@ -3,15 +3,15 @@ const { sprintf } = require('sprintf-js');
const BaseItem = require('lib/models/BaseItem.js');
const ItemChange = require('lib/models/ItemChange.js');
const Resource = require('lib/models/Resource.js');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim.js');
const Setting = require('lib/models/Setting').default;
const shim = require('lib/shim').default;
const { pregQuote } = require('lib/string-utils.js');
const { time } = require('lib/time-utils.js');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const ArrayUtils = require('lib/ArrayUtils.js');
const lodash = require('lodash');
const urlUtils = require('lib/urlUtils.js');
const markdownUtils = require('lib/markdownUtils.js');
const markdownUtils = require('lib/markdownUtils').default;
const { MarkupToHtml } = require('lib/joplin-renderer');
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');

View File

@ -3,14 +3,14 @@ const BaseItem = require('lib/models/BaseItem.js');
const ItemChange = require('lib/models/ItemChange.js');
const NoteResource = require('lib/models/NoteResource.js');
const ResourceLocalState = require('lib/models/ResourceLocalState.js');
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const pathUtils = require('lib/path-utils.js');
const { mime } = require('lib/mime-utils.js');
const { filename, safeFilename } = require('lib/path-utils.js');
const { FsDriverDummy } = require('lib/fs-driver-dummy.js');
const markdownUtils = require('lib/markdownUtils');
const markdownUtils = require('lib/markdownUtils').default;
const JoplinError = require('lib/JoplinError');
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
class Resource extends BaseItem {
static tableName() {

View File

@ -1,4 +1,4 @@
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const netUtils = {};

View File

@ -1,5 +1,5 @@
const ntpClient = require('lib/vendor/ntp-client');
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const Mutex = require('async-mutex').Mutex;
let nextSyncTime = 0;

View File

@ -1,5 +1,6 @@
const { _ } = require('lib/locale.js');
const { _ } = require('lib/locale');
const { netUtils } = require('lib/net-utils.js');
const shim = require('lib/shim').default;
const http = require('http');
const urlParser = require('url');
@ -71,7 +72,7 @@ class OneDriveApiNodeUtils {
// away or the browser might display a connection reset error (even
// though it worked).
const waitAndDestroy = () => {
setTimeout(() => {
shim.setTimeout(() => {
this.oauthServer_.destroy();
this.oauthServer_ = null;
}, 1000);

View File

@ -1,8 +1,8 @@
const { shim } = require('lib/shim.js');
const shim = require('lib/shim').default;
const { stringify } = require('query-string');
const { time } = require('lib/time-utils.js');
const { Logger } = require('lib/logger.js');
const { _ } = require('lib/locale.js');
const Logger = require('lib/Logger').default;
const { _ } = require('lib/locale');
class OneDriveApi {
// `isPublic` is to tell OneDrive whether the application is a "public" one (Mobile and desktop

View File

@ -1,4 +1,4 @@
const Setting = require('lib/models/Setting.js');
const Setting = require('lib/models/Setting').default;
const parameters_ = {};

View File

@ -1,4 +1,4 @@
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
class ReactLogger extends Logger {}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
const { Logger } = require('lib/logger.js');
const Setting = require('lib/models/Setting.js');
const { shim } = require('lib/shim.js');
const Logger = require('lib/Logger').default;
const Setting = require('lib/models/Setting').default;
const shim = require('lib/shim').default;
const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
const reg = {};
@ -78,7 +78,7 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
});
if (reg.scheduleSyncId_) {
clearTimeout(reg.scheduleSyncId_);
shim.clearTimeout(reg.scheduleSyncId_);
reg.scheduleSyncId_ = null;
}
@ -152,7 +152,7 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
if (delay === 0) {
timeoutCallback();
} else {
reg.scheduleSyncId_ = setTimeout(timeoutCallback, delay);
reg.scheduleSyncId_ = shim.setTimeout(timeoutCallback, delay);
}
return promise;
@ -204,7 +204,7 @@ reg.cancelTimers_ = () => {
this.recurrentSyncId_ = null;
}
if (reg.scheduleSyncId_) {
clearTimeout(reg.scheduleSyncId_);
shim.clearTimeout(reg.scheduleSyncId_);
reg.scheduleSyncId_ = null;
}
};
@ -214,7 +214,7 @@ reg.cancelTimers = async () => {
reg.cancelTimers_();
return new Promise((resolve) => {
setInterval(() => {
shim.setInterval(() => {
// ensure processing complete
if (!reg.setupRecurrentCalls_.length && !reg.schedSyncCalls_.length && !reg.timerCallbackCalls_.length && !reg.waitForReSyncCalls_.length) {
reg.cancelTimers_();

View File

@ -1,8 +1,15 @@
const Note = require('lib/models/Note.js');
const Alarm = require('lib/models/Alarm.js');
import Logger from 'lib/Logger';
import Alarm from 'lib/models/Alarm';
class AlarmService {
static setDriver(v) {
const Note = require('lib/models/Note.js');
export default class AlarmService {
private static driver_:any;
private static logger_:Logger;
// private static inAppNotificationHandler_:any;
static setDriver(v:any) {
this.driver_ = v;
if (this.driver_.setService) this.driver_.setService(this);
@ -13,7 +20,7 @@ class AlarmService {
return this.driver_;
}
static setLogger(v) {
static setLogger(v:Logger) {
this.logger_ = v;
}
@ -21,8 +28,8 @@ class AlarmService {
return this.logger_;
}
static setInAppNotificationHandler(v) {
this.inAppNotificationHandler_ = v;
static setInAppNotificationHandler(v:any) {
// this.inAppNotificationHandler_ = v;
if (this.driver_.setInAppNotificationHandler) this.driver_.setInAppNotificationHandler(v);
}
@ -43,7 +50,7 @@ class AlarmService {
// 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) {
static async updateNoteNotification(noteOrId:any, isDeleted:boolean = false) {
try {
let note = null;
let noteId = null;
@ -118,5 +125,3 @@ class AlarmService {
}
}
}
module.exports = AlarmService;

View File

@ -1,11 +1,16 @@
import { Notification } from 'lib/models/Alarm';
const PushNotification = require('react-native-push-notification');
class AlarmServiceDriver {
export default class AlarmServiceDriver {
private PushNotification_:any = null;
PushNotificationHandler_() {
if (!this.PushNotification_) {
PushNotification.configure({
// (required) Called when a remote or local notification is opened or received
onNotification: function(notification) {
onNotification: function(notification:any) {
console.info('Notification was opened: ', notification);
// process the notification
},
@ -27,11 +32,11 @@ class AlarmServiceDriver {
throw new Error('Available only for non-persistent alarms');
}
async clearNotification(id) {
async clearNotification(id:any) {
return this.PushNotificationHandler_().cancelLocalNotifications({ id: `${id}` });
}
async scheduleNotification(notification) {
async scheduleNotification(notification:Notification) {
const config = {
id: `${notification.id}`,
message: notification.title,
@ -41,5 +46,3 @@ class AlarmServiceDriver {
this.PushNotificationHandler_().localNotificationSchedule(config);
}
}
module.exports = AlarmServiceDriver;

View File

@ -1,11 +1,13 @@
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import { Notification } from 'lib/models/Alarm';
const PushNotificationIOS = require('@react-native-community/push-notification-ios').default;
export default class AlarmServiceDriver {
private hasPermission_:boolean = null;
private inAppNotificationHandler_:any = null;
class AlarmServiceDriver {
constructor() {
this.hasPermission_ = null;
this.inAppNotificationHandler_ = null;
PushNotificationIOS.addEventListener('localNotification', instance => {
PushNotificationIOS.addEventListener('localNotification', (instance:any) => {
if (!this.inAppNotificationHandler_) return;
if (!instance || !instance._data || !instance._data.id) {
@ -26,17 +28,17 @@ class AlarmServiceDriver {
throw new Error('Available only for non-persistent alarms');
}
setInAppNotificationHandler(v) {
setInAppNotificationHandler(v:any) {
this.inAppNotificationHandler_ = v;
}
async hasPermissions(perm = null) {
async hasPermissions(perm:any = null) {
if (perm !== null) return perm.alert && perm.badge && perm.sound;
if (this.hasPermission_ !== null) return this.hasPermission_;
return new Promise((resolve) => {
PushNotificationIOS.checkPermissions(async perm => {
PushNotificationIOS.checkPermissions(async (perm:any) => {
const ok = await this.hasPermissions(perm);
this.hasPermission_ = ok;
resolve(ok);
@ -45,27 +47,28 @@ class AlarmServiceDriver {
}
async requestPermissions() {
const newPerm = await PushNotificationIOS.requestPermissions({
const options:any = {
alert: 1,
badge: 1,
sound: 1,
});
};
const newPerm = await PushNotificationIOS.requestPermissions(options);
this.hasPermission_ = null;
return this.hasPermissions(newPerm);
}
async clearNotification(id) {
async clearNotification(id:any) {
PushNotificationIOS.cancelLocalNotifications({ id: `${id}` });
}
async scheduleNotification(notification) {
async scheduleNotification(notification:Notification) {
if (!(await this.hasPermissions())) {
const ok = await this.requestPermissions();
if (!ok) return;
}
// ID must be a string and userInfo must be supplied otherwise cancel won't work
const iosNotification = {
const iosNotification:any = {
id: `${notification.id}`,
alertTitle: notification.title,
fireDate: notification.date.toISOString(),
@ -77,5 +80,3 @@ class AlarmServiceDriver {
PushNotificationIOS.scheduleLocalNotification(iosNotification);
}
}
module.exports = AlarmServiceDriver;

View File

@ -1,16 +1,27 @@
const notifier = require('node-notifier');
const { bridge } = require('electron').remote.require('./bridge');
import eventManager from 'lib/eventManager';
import { Notification } from 'lib/models/Alarm';
import shim from 'lib/shim';
class AlarmServiceDriverNode {
constructor(options) {
const notifier = require('node-notifier');
const bridge = require('electron').remote.require('./bridge').default;
interface Options {
appName: string,
}
export default class AlarmServiceDriverNode {
private appName_:string;
private notifications_:any = {};
private service_:any = null;
constructor(options: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
this.appName_ = options.appName;
this.notifications_ = {};
this.service_ = null;
}
setService(s) {
setService(s:any) {
this.service_ = s;
}
@ -22,17 +33,17 @@ class AlarmServiceDriverNode {
return false;
}
notificationIsSet(id) {
notificationIsSet(id:string) {
return id in this.notifications_;
}
async clearNotification(id) {
async clearNotification(id:string) {
if (!this.notificationIsSet(id)) return;
clearTimeout(this.notifications_[id].timeoutId);
shim.clearTimeout(this.notifications_[id].timeoutId);
delete this.notifications_[id];
}
async scheduleNotification(notification) {
async scheduleNotification(notification:Notification) {
const now = Date.now();
const interval = notification.date.getTime() - now;
if (interval < 0) return;
@ -43,7 +54,7 @@ class AlarmServiceDriverNode {
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} with interval: ${interval}ms`);
if (this.notifications_[notification.id]) clearTimeout(this.notifications_[notification.id].timeoutId);
if (this.notifications_[notification.id]) shim.clearTimeout(this.notifications_[notification.id].timeoutId);
let timeoutId = null;
@ -56,7 +67,7 @@ class AlarmServiceDriverNode {
if (interval >= maxInterval) {
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification interval is greater than ${maxInterval}ms - will reschedule in ${maxInterval}ms`);
timeoutId = setTimeout(() => {
timeoutId = shim.setTimeout(() => {
if (!this.notifications_[notification.id]) {
this.logger().info(`AlarmServiceDriverNode::scheduleNotification: Notification ${notification.id} has been deleted - not rescheduling it`);
return;
@ -64,8 +75,8 @@ class AlarmServiceDriverNode {
this.scheduleNotification(this.notifications_[notification.id]);
}, maxInterval);
} else {
timeoutId = setTimeout(() => {
const o = {
timeoutId = shim.setTimeout(() => {
const o:any = {
appID: this.appName_,
title: notification.title,
icon: `${bridge().electronApp().buildDir()}/icons/512x512.png`,
@ -79,11 +90,13 @@ class AlarmServiceDriverNode {
this.logger().info('AlarmServiceDriverNode::scheduleNotification: Triggering notification:', o);
notifier.notify(o, (error, response) => {
notifier.notify(o, (error:any, response:any) => {
this.logger().info('AlarmServiceDriverNode::scheduleNotification: node-notifier response:', error, response);
});
this.clearNotification(notification.id);
eventManager.emit('noteAlarmTrigger', { noteId: notification.noteId });
}, interval);
}
@ -91,5 +104,3 @@ class AlarmServiceDriverNode {
this.notifications_[notification.id].timeoutId = timeoutId;
}
}
module.exports = AlarmServiceDriverNode;

View File

@ -1,15 +1,17 @@
class BaseService {
logger() {
import Logger from 'lib/Logger';
export default class BaseService {
static logger_:Logger = null;
protected instanceLogger_:Logger = null;
logger():Logger {
if (this.instanceLogger_) return this.instanceLogger_;
if (!BaseService.logger_) throw new Error('BaseService.logger_ not set!!');
return BaseService.logger_;
}
setLogger(v) {
setLogger(v:Logger) {
this.instanceLogger_ = v;
}
}
BaseService.logger_ = null;
module.exports = BaseService;

View File

@ -0,0 +1,31 @@
import { ContextKeyExpr, ContextKeyExpression } from './contextkey/contextkey';
export default class BooleanExpression {
private expression_:string;
private rules_:ContextKeyExpression = null;
constructor(expression:string) {
this.expression_ = expression;
}
private createContext(ctx: any) {
return {
getValue: (key: string) => {
return ctx[key];
},
};
}
private get rules():ContextKeyExpression {
if (!this.rules_) {
this.rules_ = ContextKeyExpr.deserialize(this.expression_);
}
return this.rules_;
}
public evaluate(context:any):boolean {
return this.rules.evaluate(this.createContext(context));
}
}

View File

@ -1,21 +1,38 @@
import KeymapService from './KeymapService';
const BaseService = require('lib/services/BaseService');
const eventManager = require('lib/eventManager');
import eventManager from 'lib/eventManager';
import markdownUtils, { MarkdownTableHeader, MarkdownTableRow } from 'lib/markdownUtils';
import BaseService from 'lib/services/BaseService';
import shim from 'lib/shim';
type LabelFunction = () => string;
export interface CommandRuntime {
execute(props:any):void
execute(props:any):Promise<any>
isEnabled?(props:any):boolean
// "state" type is "AppState" but in order not to introduce a
// dependency to the desktop app (so that the service can
// potentially be used by the mobile app too), we keep it as "any".
// Individual commands can define it as state:AppState when relevant.
//
// In general this method should reduce the provided state to only
// what's absolutely necessary. For example, if the property of a
// note is needed, return only that particular property and not the
// whole note object. This will ensure that components that depends
// on this command are not uncessarily re-rendered. A note object for
// example might change frequently but its markdown_language property
// will almost never change.
mapStateToProps?(state:any):any
// Used for the (optional) toolbar button title
title?(props:any):string,
props?:any
// props?:any
}
export interface CommandDeclaration {
name: string
// Used for the menu item label, and toolbar button tooltip
label?():string,
label?: LabelFunction | string,
// This is a bit of a hack because some labels don't make much sense in isolation. For example,
// the commmand to focus the note list is called just "Note list". This makes sense within the menu
@ -25,8 +42,16 @@ export interface CommandDeclaration {
// label() => _('Note list'),
// parentLabel() => _('Focus'),
// Which will be displayed as "Focus: Note list" in the keymap config screen.
parentLabel?():string,
parentLabel?:LabelFunction | string,
// All free Font Awesome icons are available: https://fontawesome.com/icons?d=gallery&m=free
iconName?: string,
// Will be used by TinyMCE (which doesn't support Font Awesome icons).
// Defaults to the "preferences" icon (a cog) if not specified.
// https://www.tiny.cloud/docs/advanced/editor-icon-identifiers/
tinymceIconName?: string,
// Same as `role` key in Electron MenuItem:
// https://www.electronjs.org/docs/api/menu-item#new-menuitemoptions
// Note that due to a bug in Electron, menu items with a role cannot
@ -39,15 +64,6 @@ export interface Command {
runtime?: CommandRuntime,
}
export interface ToolbarButtonInfo {
name: string,
tooltip: string,
iconName: string,
enabled: boolean,
onClick():void,
title: string,
}
interface Commands {
[key:string]: Command;
}
@ -94,13 +110,9 @@ export default class CommandService extends BaseService {
private commands_:Commands = {};
private commandPreviousStates_:CommandStates = {};
private mapStateToPropsIID_:any = null;
private keymapService:KeymapService = null;
initialize(store:any, keymapService:KeymapService) {
initialize(store:any) {
utils.store = store;
this.keymapService = keymapService;
}
public on(eventName:string, callback:Function) {
@ -111,69 +123,7 @@ export default class CommandService extends BaseService {
eventManager.off(eventName, callback);
}
private propsHaveChanged(previous:any, next:any) {
if (!previous && next) return true;
for (const n in previous) {
if (previous[n] !== next[n]) return true;
}
return false;
}
scheduleMapStateToProps(state:any) {
if (this.mapStateToPropsIID_) clearTimeout(this.mapStateToPropsIID_);
this.mapStateToPropsIID_ = setTimeout(() => {
this.mapStateToProps(state);
}, 50);
}
private mapStateToProps(state:any) {
const newState = state;
const changedCommands:any = {};
for (const name in this.commands_) {
const command = this.commands_[name];
if (!command.runtime) continue;
if (!command.runtime.mapStateToProps) {
command.runtime.props = {};
continue;
}
const newProps = command.runtime.mapStateToProps(state);
const haveChanged = this.propsHaveChanged(command.runtime.props, newProps);
if (haveChanged) {
const previousState = this.commandPreviousStates_[name];
command.runtime.props = newProps;
const newState:CommandState = {
enabled: this.isEnabled(name),
title: this.title(name),
};
if (!previousState || previousState.title !== newState.title || previousState.enabled !== newState.enabled) {
changedCommands[name] = newState;
}
this.commandPreviousStates_[name] = newState;
}
}
if (Object.keys(changedCommands).length) {
eventManager.emit('commandsEnabledStateChange', { commands: changedCommands });
}
return newState;
}
private commandByName(name:string, options:CommandByNameOptions = null):Command {
public commandByName(name:string, options:CommandByNameOptions = null):Command {
options = {
mustExist: true,
runtimeMustBeRegistered: false,
@ -192,17 +142,10 @@ export default class CommandService extends BaseService {
}
registerDeclaration(declaration:CommandDeclaration) {
// if (this.commands_[declaration.name]) throw new Error(`There is already a command with name ${declaration.name}`);
declaration = { ...declaration };
if (!declaration.label) declaration.label = () => '';
if (!declaration.label) declaration.label = '';
if (!declaration.iconName) declaration.iconName = '';
// In TypeScript it's not an issue, but in JavaScript it's easy to accidentally set the label
// to a string instead of a function, and it will cause strange errors that are hard to debug.
// So here check early that we have the right type.
if (typeof declaration.label !== 'function') throw new Error(`declaration.label must be a function: ${declaration.name}`);
this.commands_[declaration.name] = {
declaration: declaration,
};
@ -243,35 +186,42 @@ export default class CommandService extends BaseService {
delete this.commandPreviousStates_[commandName];
}
execute(commandName:string, args:any = null) {
console.info('CommandService::execute:', commandName, args);
async execute(commandName:string, props:any = null):Promise<any> {
const command = this.commandByName(commandName);
command.runtime.execute(args ? args : {});
this.logger().info('CommandService::execute:', commandName, props);
return command.runtime.execute(props ? props : {});
}
scheduleExecute(commandName:string, args:any = null) {
setTimeout(() => {
scheduleExecute(commandName:string, args:any) {
shim.setTimeout(() => {
this.execute(commandName, args);
}, 10);
}
isEnabled(commandName:string):boolean {
isEnabled(commandName:string, props:any):boolean {
const command = this.commandByName(commandName);
if (!command || !command.runtime) return false;
if (!command.runtime.props) return false;
return command.runtime.isEnabled(command.runtime.props);
// if (!command.runtime.props) return false;
return command.runtime.isEnabled(props);
}
title(commandName:string):string {
commandMapStateToProps(commandName:string, state:any):any {
const command = this.commandByName(commandName);
if (!command || !command.runtime || !command.runtime.props) return null;
return command.runtime.title(command.runtime.props);
if (!command.runtime) return null;
if (!command.runtime.mapStateToProps) return {};
return command.runtime.mapStateToProps(state);
}
iconName(commandName:string):string {
title(commandName:string, props:any):string {
const command = this.commandByName(commandName);
if (!command || !command.runtime) return null;
return command.runtime.title(props);
}
iconName(commandName:string, variant:string = null):string {
const command = this.commandByName(commandName);
if (!command) throw new Error(`No such command: ${commandName}`);
if (variant === 'tinymce') return command.declaration.tinymceIconName ? command.declaration.tinymceIconName : 'preferences';
return command.declaration.iconName;
}
@ -279,8 +229,15 @@ export default class CommandService extends BaseService {
const command = this.commandByName(commandName);
if (!command) throw new Error(`Command: ${commandName} is not declared`);
const output = [];
if (fullLabel && command.declaration.parentLabel && command.declaration.parentLabel()) output.push(command.declaration.parentLabel());
output.push(command.declaration.label());
const parentLabel = (d:CommandDeclaration):string => {
if (!d.parentLabel) return '';
if (typeof d.parentLabel === 'function') return d.parentLabel();
return d.parentLabel as string;
};
if (fullLabel && parentLabel(command.declaration)) output.push(parentLabel(command.declaration));
output.push(typeof command.declaration.label === 'function' ? command.declaration.label() : command.declaration.label);
return output.join(': ');
}
@ -289,58 +246,37 @@ export default class CommandService extends BaseService {
return !!command;
}
private extractExecuteArgs(command:Command, executeArgs:any) {
if (executeArgs) return executeArgs;
if (!command.runtime) throw new Error(`Command: ${command.declaration.name}: Runtime is not defined - make sure it has been registered.`);
if (command.runtime.props) return command.runtime.props;
return {};
}
commandToToolbarButton(commandName:string, executeArgs:any = null):ToolbarButtonInfo {
const command = this.commandByName(commandName, { runtimeMustBeRegistered: true });
return {
name: commandName,
tooltip: this.label(commandName),
iconName: command.declaration.iconName,
enabled: this.isEnabled(commandName),
onClick: () => {
this.execute(commandName, this.extractExecuteArgs(command, executeArgs));
public commandsToMarkdownTable(state:any):string {
const headers:MarkdownTableHeader[] = [
{
name: 'commandName',
label: 'Name',
},
title: this.title(commandName),
};
}
commandToMenuItem(commandName:string, executeArgs:any = null) {
const command = this.commandByName(commandName);
const item:any = {
id: command.declaration.name,
label: this.label(commandName),
click: () => {
this.execute(commandName, this.extractExecuteArgs(command, executeArgs));
{
name: 'description',
label: 'Description',
},
};
{
name: 'props',
label: 'Props',
},
];
if (command.declaration.role) item.role = command.declaration.role;
if (this.keymapService.acceleratorExists(commandName)) {
item.accelerator = this.keymapService.getAccelerator(commandName);
const rows:MarkdownTableRow[] = [];
for (const commandName in this.commands_) {
const props = this.commandMapStateToProps(commandName, state);
const row:MarkdownTableRow = {
commandName: commandName,
description: this.label(commandName),
props: JSON.stringify(props),
};
rows.push(row);
}
return item;
}
commandsEnabledState(previousState:any = null):any {
const output:any = {};
for (const name in this.commands_) {
const enabled = this.isEnabled(name);
if (!previousState || previousState[name] !== enabled) {
output[name] = enabled;
}
}
return output;
return markdownUtils.createMarkdownTable(headers, rows);
}
}

View File

@ -3,8 +3,9 @@ const BaseModel = require('lib/BaseModel');
const MasterKey = require('lib/models/MasterKey');
const Resource = require('lib/models/Resource');
const ResourceService = require('lib/services/ResourceService');
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const EventEmitter = require('events');
const shim = require('lib/shim').default;
class DecryptionWorker {
constructor() {
@ -64,7 +65,7 @@ class DecryptionWorker {
async scheduleStart() {
if (this.scheduleId_) return;
this.scheduleId_ = setTimeout(() => {
this.scheduleId_ = shim.setTimeout(() => {
this.scheduleId_ = null;
this.start({
masterKeyNotLoadedHandler: 'dispatch',
@ -280,16 +281,16 @@ class DecryptionWorker {
async destroy() {
this.eventEmitter_.removeAllListeners();
if (this.scheduleId_) {
clearTimeout(this.scheduleId_);
shim.clearTimeout(this.scheduleId_);
this.scheduleId_ = null;
}
this.eventEmitter_ = null;
DecryptionWorker.instance_ = null;
return new Promise((resolve) => {
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
if (!this.startCalls_.length) {
clearInterval(iid);
shim.clearInterval(iid);
resolve();
}
}, 100);

View File

@ -1,7 +1,7 @@
const { padLeft } = require('lib/string-utils.js');
const { Logger } = require('lib/logger.js');
const { shim } = require('lib/shim.js');
const Setting = require('lib/models/Setting.js');
const Logger = require('lib/Logger').default;
const shim = require('lib/shim').default;
const Setting = require('lib/models/Setting').default;
const MasterKey = require('lib/models/MasterKey');
const BaseItem = require('lib/models/BaseItem');
const JoplinError = require('lib/JoplinError');

View File

@ -1,13 +1,13 @@
const { Logger } = require('lib/logger.js');
const Logger = require('lib/Logger').default;
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting');
const { shim } = require('lib/shim');
const Setting = require('lib/models/Setting').default;
const shim = require('lib/shim').default;
const EventEmitter = require('events');
const { splitCommandString } = require('lib/string-utils');
const { fileExtension, basename } = require('lib/path-utils');
const spawn = require('child_process').spawn;
const chokidar = require('chokidar');
const { bridge } = require('electron').remote.require('./bridge');
const bridge = require('electron').remote.require('./bridge').default;
const { time } = require('lib/time-utils.js');
const { ErrorNotFound } = require('./rest/errors');
@ -249,16 +249,16 @@ class ExternalEditWatcher {
try {
const subProcess = spawn(path, args, options);
const iid = setInterval(() => {
const iid = shim.setInterval(() => {
if (subProcess && subProcess.pid) {
/* was_debug */ this.logger().info(`Started editor with PID ${subProcess.pid}`);
clearInterval(iid);
shim.clearInterval(iid);
resolve();
}
}, 100);
subProcess.on('error', error => {
clearInterval(iid);
shim.clearInterval(iid);
reject(wrapError(error));
});
} catch (error) {

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