Tools: Add class member accessibility modifiers and converted rule @typescript-eslint/explicit-member-accessibility to an error

pull/7883/head
Laurent Cozic 2023-03-06 14:22:01 +00:00
parent aa4af69afc
commit c1db7182ac
129 changed files with 1252 additions and 1296 deletions

View File

@ -175,9 +175,7 @@ module.exports = {
'project': './tsconfig.eslint.json',
},
'rules': {
// Warn only because it would make it difficult to convert JS classes to TypeScript, unless we
// make everything public which is not great. New code however should specify member accessibility.
'@typescript-eslint/explicit-member-accessibility': ['warn'],
'@typescript-eslint/explicit-member-accessibility': ['error'],
'@typescript-eslint/type-annotation-spacing': ['error', { 'before': false, 'after': true }],
'@typescript-eslint/no-inferrable-types': ['error', { 'ignoreParameters': true, 'ignoreProperties': true }],
'@typescript-eslint/comma-dangle': ['error', {

View File

@ -6,14 +6,14 @@ interface LinkStoreEntry {
}
class LinkSelector {
noteId_: string;
scrollTop_: number;
renderedText_: string;
currentLinkIndex_: number;
linkStore_: LinkStoreEntry[];
linkRegex_: RegExp;
private noteId_: string;
private scrollTop_: number;
private renderedText_: string;
private currentLinkIndex_: number;
private linkStore_: LinkStoreEntry[];
private linkRegex_: RegExp;
constructor() {
public constructor() {
this.noteId_ = null;
this.scrollTop_ = null; // used so 'o' won't open unhighlighted link after scrolling
this.renderedText_ = null;
@ -22,22 +22,22 @@ class LinkSelector {
this.linkRegex_ = /http:\/\/[0-9.]+:[0-9]+\/[0-9]+/g;
}
get link(): string | null {
public get link(): string | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].link;
}
get noteX(): number | null {
public get noteX(): number | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteX;
}
get noteY(): number | null {
public get noteY(): number | null {
if (this.currentLinkIndex_ === null) return null;
return this.linkStore_[this.currentLinkIndex_].noteY;
}
findLinks(renderedText: string): LinkStoreEntry[] {
public findLinks(renderedText: string): LinkStoreEntry[] {
const newLinkStore: LinkStoreEntry[] = [];
const lines: string[] = renderedText.split('\n');
for (let i = 0; i < lines.length; i++) {
@ -56,19 +56,19 @@ class LinkSelector {
return newLinkStore;
}
updateText(renderedText: string): void {
public updateText(renderedText: string): void {
this.currentLinkIndex_ = null;
this.renderedText_ = renderedText;
this.linkStore_ = this.findLinks(this.renderedText_);
}
updateNote(textWidget: any): void {
public updateNote(textWidget: any): void {
this.noteId_ = textWidget.noteId;
this.scrollTop_ = textWidget.scrollTop_;
this.updateText(textWidget.renderedText_);
}
scrollWidget(textWidget: any): void {
public scrollWidget(textWidget: any): void {
if (this.currentLinkIndex_ === null) return;
const noteY = this.linkStore_[this.currentLinkIndex_].noteY;
@ -93,7 +93,7 @@ class LinkSelector {
return;
}
changeLink(textWidget: any, offset: number): void | null {
public changeLink(textWidget: any, offset: number): void | null {
if (textWidget.noteId !== this.noteId_) {
this.updateNote(textWidget);
this.changeLink(textWidget, offset);
@ -123,7 +123,7 @@ class LinkSelector {
return;
}
openLink(textWidget: any): void {
public openLink(textWidget: any): void {
if (textWidget.noteId !== this.noteId_) return;
if (textWidget.renderedText_ !== this.renderedText_) return;
if (textWidget.scrollTop_ !== this.scrollTop_) return;

View File

@ -7,80 +7,80 @@ export default class BaseCommand {
protected prompt_: any = null;
protected dispatcher_: any;
usage(): string {
public usage(): string {
throw new Error('Usage not defined');
}
encryptionCheck(item: any) {
public encryptionCheck(item: any) {
if (item && item.encryption_applied) throw new Error(_('Cannot change encrypted item'));
}
description() {
public description() {
throw new Error('Description not defined');
}
async action(_args: any) {
public async action(_args: any) {
throw new Error('Action not defined');
}
compatibleUis() {
public compatibleUis() {
return ['cli', 'gui'];
}
supportsUi(ui: string) {
public supportsUi(ui: string) {
return this.compatibleUis().indexOf(ui) >= 0;
}
options(): any[] {
public options(): any[] {
return [];
}
hidden() {
public hidden() {
return false;
}
enabled() {
public enabled() {
return true;
}
cancellable() {
public cancellable() {
return false;
}
async cancel() {}
public async cancel() {}
name() {
public name() {
const r = this.usage().split(' ');
return r[0];
}
setDispatcher(fn: Function) {
public setDispatcher(fn: Function) {
this.dispatcher_ = fn;
}
dispatch(action: any) {
public dispatch(action: any) {
if (!this.dispatcher_) throw new Error('Dispatcher not defined');
return this.dispatcher_(action);
}
setStdout(fn: Function) {
public setStdout(fn: Function) {
this.stdout_ = fn;
}
stdout(text: string) {
public stdout(text: string) {
if (this.stdout_) this.stdout_(text);
}
setPrompt(fn: Function) {
public setPrompt(fn: Function) {
this.prompt_ = fn;
}
async prompt(message: string, options: any = null) {
public async prompt(message: string, options: any = null) {
if (!this.prompt_) throw new Error('Prompt is undefined');
return await this.prompt_(message, options);
}
metadata() {
public metadata() {
return {
name: this.name(),
usage: this.usage(),
@ -89,7 +89,7 @@ export default class BaseCommand {
};
}
logger() {
public logger() {
return reg.logger();
}
}

View File

@ -12,15 +12,15 @@ const imageType = require('image-type');
const readChunk = require('read-chunk');
class Command extends BaseCommand {
usage() {
public usage() {
return 'e2ee <command> [path]';
}
description() {
public description() {
return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status`, `decrypt-file`, and `target-status`.'); // `generate-ppk`
}
options() {
public options() {
return [
// This is here mostly for testing - shouldn't be used
['-p, --password <password>', 'Use this password as master password (For security reasons, it is not recommended to use this option).'],
@ -30,7 +30,7 @@ class Command extends BaseCommand {
];
}
async action(args: any) {
public async action(args: any) {
const options = args.options;
const askForMasterKey = async (error: any) => {

View File

@ -6,22 +6,22 @@ import Folder from '@joplin/lib/models/Folder';
import { FolderEntity } from '@joplin/lib/services/database/types';
class Command extends BaseCommand {
usage() {
public usage() {
return 'mkbook <new-notebook>';
}
description() {
public description() {
return _('Creates a new notebook.');
}
options() {
public options() {
return [
['-p, --parent <parent-notebook>', _('Create a new notebook under a parent notebook.')],
];
}
// validDestinationFolder check for presents and ambiguous folders
async validDestinationFolder(targetFolder: string) {
public async validDestinationFolder(targetFolder: string) {
const destinationFolder = await app().loadItem(BaseModel.TYPE_FOLDER, targetFolder);
if (!destinationFolder) {
@ -36,14 +36,14 @@ class Command extends BaseCommand {
return destinationFolder;
}
async saveAndSwitchFolder(newFolder: FolderEntity) {
public async saveAndSwitchFolder(newFolder: FolderEntity) {
const folder = await Folder.save(newFolder, { userSideValidation: true });
app().switchCurrentFolder(folder);
}
async action(args: any) {
public async action(args: any) {
const targetFolder = args.options.parent;
const newFolder: FolderEntity = {

View File

@ -23,19 +23,19 @@ function settingTypeToSchemaType(type: SettingItemType): string {
}
class Command extends BaseCommand {
usage() {
public usage() {
return 'settingschema <file>';
}
description() {
public description() {
return 'Build the setting schema file';
}
enabled() {
public enabled() {
return false;
}
async action(args: any) {
public async action(args: any) {
const schema: Record<string, any> = {
title: 'JSON schema for Joplin setting files',
'$id': Setting.schemaUrl,

View File

@ -21,15 +21,15 @@ class Command extends BaseCommand {
private releaseLockFn_: Function = null;
private oneDriveApiUtils_: any = null;
usage() {
public usage() {
return 'sync';
}
description() {
public description() {
return _('Synchronises with remote storage.');
}
options() {
public options() {
return [
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
['--upgrade', _('Upgrade the sync target to the latest version.')],
@ -45,7 +45,7 @@ class Command extends BaseCommand {
return locker.check(filePath);
}
async doAuth() {
public async doAuth() {
const syncTarget = reg.syncTarget(this.syncTargetId_);
const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_);
@ -89,18 +89,18 @@ class Command extends BaseCommand {
return false;
}
cancelAuth() {
public cancelAuth() {
if (this.oneDriveApiUtils_) {
this.oneDriveApiUtils_.cancelOAuthDance();
return;
}
}
doingAuth() {
public doingAuth() {
return !!this.oneDriveApiUtils_;
}
async action(args: any) {
public async action(args: any) {
this.releaseLockFn_ = null;
// Lock is unique per profile/database
@ -238,7 +238,7 @@ class Command extends BaseCommand {
cleanUp();
}
async cancel() {
public async cancel() {
if (this.doingAuth()) {
this.cancelAuth();
return;
@ -263,7 +263,7 @@ class Command extends BaseCommand {
this.syncTargetId_ = null;
}
cancellable() {
public cancellable() {
return true;
}
}

View File

@ -18,19 +18,19 @@ function itemCount(args: any) {
}
class Command extends BaseCommand {
usage() {
public usage() {
return 'testing <command> [arg0]';
}
description() {
public description() {
return 'testing';
}
enabled() {
public enabled() {
return false;
}
options(): any[] {
public options(): any[] {
return [
['--folder-count <count>', 'Folders to create'],
['--note-count <count>', 'Notes to create'],
@ -40,7 +40,7 @@ class Command extends BaseCommand {
];
}
async action(args: any) {
public async action(args: any) {
const { command, options } = args;
if (command === 'populate') {

View File

@ -5,7 +5,7 @@ const stripAnsi = require('strip-ansi');
const { handleAutocompletion } = require('../autocompletion.js');
export default class StatusBarWidget extends BaseWidget {
constructor() {
public constructor() {
super();
this.promptState_ = null;
@ -14,20 +14,20 @@ export default class StatusBarWidget extends BaseWidget {
this.items_ = [];
}
get name() {
public get name() {
return 'statusBar';
}
get canHaveFocus() {
public get canHaveFocus() {
return false;
}
setItemAt(index: number, text: string) {
public setItemAt(index: number, text: string) {
this.items_[index] = stripAnsi(text).trim();
this.invalidate();
}
async prompt(initialText = '', promptString: any = null, options: any = null) {
public async prompt(initialText = '', promptString: any = null, options: any = null) {
if (this.promptState_) throw new Error('Another prompt already active');
if (promptString === null) promptString = ':';
if (options === null) options = {};
@ -53,15 +53,15 @@ export default class StatusBarWidget extends BaseWidget {
return this.promptState_.promise;
}
get promptActive() {
public get promptActive() {
return !!this.promptState_;
}
get history() {
public get history() {
return this.history_;
}
resetCursor() {
public resetCursor() {
if (!this.promptActive) return;
if (!this.inputEventEmitter_) return;
@ -70,7 +70,7 @@ export default class StatusBarWidget extends BaseWidget {
this.term.moveTo(this.absoluteInnerX + termutils.textLength(this.promptState_.promptString) + this.inputEventEmitter_.getInput().length, this.absoluteInnerY);
}
render() {
public render() {
super.render();
const doSaveCursor = !this.promptActive;

View File

@ -35,7 +35,7 @@ export default class PluginRunner extends BasePluginRunner {
private eventHandlers_: EventHandlers = {};
private activeSandboxCalls_: any = {};
constructor() {
public constructor() {
super();
this.eventHandler = this.eventHandler.bind(this);
@ -64,7 +64,7 @@ export default class PluginRunner extends BasePluginRunner {
};
}
async run(plugin: Plugin, sandbox: Global): Promise<void> {
public async run(plugin: Plugin, sandbox: Global): Promise<void> {
return new Promise((resolve: Function, reject: Function) => {
const onStarted = () => {
plugin.off('started', onStarted);

View File

@ -33,7 +33,7 @@ export default class ElectronAppWrapper {
private pluginWindows_: PluginWindows = {};
private initialCallbackUrl_: string = null;
constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
public constructor(electronApp: any, env: string, profilePath: string, isDebugMode: boolean, initialCallbackUrl: string) {
this.electronApp_ = electronApp;
this.env_ = env;
this.isDebugMode_ = isDebugMode;
@ -41,31 +41,31 @@ export default class ElectronAppWrapper {
this.initialCallbackUrl_ = initialCallbackUrl;
}
electronApp() {
public electronApp() {
return this.electronApp_;
}
setLogger(v: Logger) {
public setLogger(v: Logger) {
this.logger_ = v;
}
logger() {
public logger() {
return this.logger_;
}
window() {
public window() {
return this.win_;
}
env() {
public env() {
return this.env_;
}
initialCallbackUrl() {
public initialCallbackUrl() {
return this.initialCallbackUrl_;
}
createWindow() {
public createWindow() {
// Set to true to view errors if the application does not start
const debugEarlyBugs = this.env_ === 'dev' || this.isDebugMode_;
@ -236,11 +236,11 @@ export default class ElectronAppWrapper {
}
}
registerPluginWindow(pluginId: string, window: any) {
public registerPluginWindow(pluginId: string, window: any) {
this.pluginWindows_[pluginId] = window;
}
async waitForElectronAppReady() {
public async waitForElectronAppReady() {
if (this.electronApp().isReady()) return Promise.resolve();
return new Promise<void>((resolve) => {
@ -253,25 +253,25 @@ export default class ElectronAppWrapper {
});
}
quit() {
public quit() {
this.electronApp_.quit();
}
exit(errorCode = 0) {
public exit(errorCode = 0) {
this.electronApp_.exit(errorCode);
}
trayShown() {
public trayShown() {
return !!this.tray_;
}
// This method is used in macOS only to hide the whole app (and not just the main window)
// including the menu bar. This follows the macOS way of hiding an app.
hide() {
public hide() {
this.electronApp_.hide();
}
buildDir() {
public buildDir() {
if (this.buildDir_) return this.buildDir_;
let dir = `${__dirname}/build`;
if (!fs.pathExistsSync(dir)) {
@ -283,7 +283,7 @@ export default class ElectronAppWrapper {
return dir;
}
trayIconFilename_() {
private trayIconFilename_() {
let output = '';
if (process.platform === 'darwin') {
@ -298,7 +298,7 @@ export default class ElectronAppWrapper {
}
// Note: this must be called only after the "ready" event of the app has been dispatched
createTray(contextMenu: any) {
public createTray(contextMenu: any) {
try {
this.tray_ = new Tray(`${this.buildDir()}/icons/${this.trayIconFilename_()}`);
this.tray_.setToolTip(this.electronApp_.name);
@ -312,13 +312,13 @@ export default class ElectronAppWrapper {
}
}
destroyTray() {
public destroyTray() {
if (!this.tray_) return;
this.tray_.destroy();
this.tray_ = null;
}
ensureSingleInstance() {
public ensureSingleInstance() {
if (this.env_ === 'dev') return false;
const gotTheLock = this.electronApp_.requestSingleInstanceLock();
@ -347,7 +347,7 @@ export default class ElectronAppWrapper {
return false;
}
async start() {
public async start() {
// Since we are doing other async things before creating the window, we might miss
// the "ready" event. So we use the function below to make sure that the app is ready.
await this.waitForElectronAppReady();
@ -375,7 +375,7 @@ export default class ElectronAppWrapper {
});
}
async openCallbackUrl(url: string) {
public async openCallbackUrl(url: string) {
this.win_.webContents.send('asynchronous-message', 'openCallbackUrl', {
url: url,
});

View File

@ -21,7 +21,7 @@ export class Bridge {
private electronWrapper_: ElectronAppWrapper;
private lastSelectedPaths_: LastSelectedPath;
constructor(electronWrapper: ElectronAppWrapper) {
public constructor(electronWrapper: ElectronAppWrapper) {
this.electronWrapper_ = electronWrapper;
this.lastSelectedPaths_ = {
file: null,
@ -29,11 +29,11 @@ export class Bridge {
};
}
electronApp() {
public electronApp() {
return this.electronWrapper_;
}
electronIsDev() {
public electronIsDev() {
return !this.electronApp().electronApp().isPackaged;
}
@ -60,11 +60,11 @@ export class Bridge {
return `${__dirname}/vendor`;
}
env() {
public env() {
return this.electronWrapper_.env();
}
processArgv() {
public processArgv() {
return process.argv;
}
@ -114,44 +114,44 @@ export class Bridge {
});
}
window() {
public window() {
return this.electronWrapper_.window();
}
showItemInFolder(fullPath: string) {
public showItemInFolder(fullPath: string) {
return require('electron').shell.showItemInFolder(toSystemSlashes(fullPath));
}
newBrowserWindow(options: any) {
public newBrowserWindow(options: any) {
return new BrowserWindow(options);
}
windowContentSize() {
public windowContentSize() {
if (!this.window()) return { width: 0, height: 0 };
const s = this.window().getContentSize();
return { width: s[0], height: s[1] };
}
windowSize() {
public windowSize() {
if (!this.window()) return { width: 0, height: 0 };
const s = this.window().getSize();
return { width: s[0], height: s[1] };
}
windowSetSize(width: number, height: number) {
public windowSetSize(width: number, height: number) {
if (!this.window()) return;
return this.window().setSize(width, height);
}
openDevTools() {
public openDevTools() {
return this.window().webContents.openDevTools();
}
closeDevTools() {
public closeDevTools() {
return this.window().webContents.closeDevTools();
}
async showSaveDialog(options: any) {
public async showSaveDialog(options: any) {
const { dialog } = require('electron');
if (!options) options = {};
if (!('defaultPath' in options) && this.lastSelectedPaths_.file) options.defaultPath = this.lastSelectedPaths_.file;
@ -162,7 +162,7 @@ export class Bridge {
return filePath;
}
async showOpenDialog(options: OpenDialogOptions = null) {
public async showOpenDialog(options: OpenDialogOptions = null) {
const { dialog } = require('electron');
if (!options) options = {};
let fileType = 'file';
@ -177,13 +177,13 @@ export class Bridge {
}
// Don't use this directly - call one of the showXxxxxxxMessageBox() instead
showMessageBox_(window: any, options: any): number {
private showMessageBox_(window: any, options: any): number {
const { dialog } = require('electron');
if (!window) window = this.window();
return dialog.showMessageBoxSync(window, options);
}
showErrorMessageBox(message: string) {
public showErrorMessageBox(message: string) {
return this.showMessageBox_(this.window(), {
type: 'error',
message: message,
@ -191,7 +191,7 @@ export class Bridge {
});
}
showConfirmMessageBox(message: string, options: any = null) {
public showConfirmMessageBox(message: string, options: any = null) {
options = {
buttons: [_('OK'), _('Cancel')],
...options,
@ -208,7 +208,7 @@ export class Bridge {
}
/* returns the index of the clicked button */
showMessageBox(message: string, options: any = null) {
public showMessageBox(message: string, options: any = null) {
if (options === null) options = {};
const result = this.showMessageBox_(this.window(), Object.assign({}, {
@ -220,7 +220,7 @@ export class Bridge {
return result;
}
showInfoMessageBox(message: string, options: any = {}) {
public showInfoMessageBox(message: string, options: any = {}) {
const result = this.showMessageBox_(this.window(), Object.assign({}, {
type: 'info',
message: message,
@ -229,35 +229,35 @@ export class Bridge {
return result === 0;
}
setLocale(locale: string) {
public setLocale(locale: string) {
setLocale(locale);
}
get Menu() {
public get Menu() {
return require('electron').Menu;
}
get MenuItem() {
public get MenuItem() {
return require('electron').MenuItem;
}
openExternal(url: string) {
public openExternal(url: string) {
return require('electron').shell.openExternal(url);
}
async openItem(fullPath: string) {
public async openItem(fullPath: string) {
return require('electron').shell.openPath(toSystemSlashes(fullPath));
}
screen() {
public screen() {
return require('electron').screen;
}
shouldUseDarkColors() {
public shouldUseDarkColors() {
return nativeTheme.shouldUseDarkColors;
}
addEventListener(name: string, fn: Function) {
public addEventListener(name: string, fn: Function) {
if (name === 'nativeThemeUpdated') {
nativeTheme.on('updated', fn);
} else {
@ -265,7 +265,7 @@ export class Bridge {
}
}
restart(linuxSafeRestart = true) {
public restart(linuxSafeRestart = true) {
// Note that in this case we are not sending the "appClose" event
// to notify services and component that the app is about to close
// but for the current use-case it's not really needed.

View File

@ -2,7 +2,6 @@ const React = require('react');
const { connect } = require('react-redux');
const { clipboard } = require('electron');
import ExtensionBadge from './ExtensionBadge';
import bridge from '../services/bridge';
import { themeStyle } from '@joplin/lib/theme';
import { _ } from '@joplin/lib/locale';
import ClipperServer from '@joplin/lib/ClipperServer';
@ -11,37 +10,29 @@ import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { AppState } from '../app.reducer';
class ClipperConfigScreenComponent extends React.Component {
constructor() {
public constructor() {
super();
this.copyToken_click = this.copyToken_click.bind(this);
}
disableClipperServer_click() {
private disableClipperServer_click() {
Setting.setValue('clipperServer.autoStart', false);
void ClipperServer.instance().stop();
}
enableClipperServer_click() {
private enableClipperServer_click() {
Setting.setValue('clipperServer.autoStart', true);
void ClipperServer.instance().start();
}
chromeButton_click() {
void bridge().openExternal('https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek');
}
firefoxButton_click() {
void bridge().openExternal('https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/');
}
copyToken_click() {
private copyToken_click() {
clipboard.writeText(this.props.apiToken);
alert(_('Token has been copied to the clipboard!'));
}
renewToken_click() {
private renewToken_click() {
if (confirm(_('Are you sure you want to renew the authorisation token?'))) {
void EncryptionService.instance()
.generateApiToken()
@ -52,7 +43,7 @@ class ClipperConfigScreenComponent extends React.Component {
}
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const containerStyle = Object.assign({}, theme.containerStyle, {

View File

@ -26,9 +26,9 @@ const settingKeyToControl: any = {
class ConfigScreenComponent extends React.Component<any, any> {
rowStyle_: any = null;
private rowStyle_: any = null;
constructor(props: any) {
public constructor(props: any) {
super(props);
shared.init(this, reg);
@ -55,15 +55,15 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.handleSettingButton = this.handleSettingButton.bind(this);
}
async checkSyncConfig_() {
private async checkSyncConfig_() {
await shared.checkSyncConfig(this, this.state.settings);
}
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
this.setState({ settings: this.props.settings });
}
componentDidMount() {
public componentDidMount() {
if (this.props.defaultSection) {
this.setState({ selectedSectionName: this.props.defaultSection }, () => {
this.switchSection(this.props.defaultSection);
@ -93,7 +93,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
}
}
sectionByName(name: string) {
public sectionByName(name: string) {
const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings });
for (const section of sections) {
if (section.name === name) return section;
@ -102,7 +102,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
throw new Error(`Invalid section name: ${name}`);
}
screenFromName(screenName: string) {
public screenFromName(screenName: string) {
if (screenName === 'encryption') return <EncryptionConfigScreen/>;
if (screenName === 'server') return <ClipperConfigScreen themeId={this.props.themeId}/>;
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.themeId}/>;
@ -110,7 +110,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
throw new Error(`Invalid screen name: ${screenName}`);
}
switchSection(name: string) {
public switchSection(name: string) {
const section = this.sectionByName(name);
let screenName = '';
if (section.isScreen) {
@ -125,11 +125,11 @@ class ConfigScreenComponent extends React.Component<any, any> {
this.setState({ selectedSectionName: section.name, screenName: screenName });
}
sidebar_selectionChange(event: any) {
private sidebar_selectionChange(event: any) {
this.switchSection(event.section.name);
}
renderSectionDescription(section: any) {
public renderSectionDescription(section: any) {
const description = Setting.sectionDescription(section.name);
if (!description) return null;
@ -141,7 +141,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
);
}
sectionToComponent(key: string, section: any, settings: any, selected: boolean) {
public sectionToComponent(key: string, section: any, settings: any, selected: boolean) {
const theme = themeStyle(this.props.themeId);
const createSettingComponents = (advanced: boolean) => {
@ -284,7 +284,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
return description ? <div style={this.descriptionStyle(themeId)}>{description}</div> : null;
}
settingToComponent(key: string, value: any) {
public settingToComponent(key: string, value: any) {
const theme = themeStyle(this.props.themeId);
const output: any = null;
@ -657,26 +657,26 @@ class ConfigScreenComponent extends React.Component<any, any> {
}
}
async onApplyClick() {
public async onApplyClick() {
shared.saveSettings(this);
await this.checkNeedRestart();
}
async onSaveClick() {
public async onSaveClick() {
shared.saveSettings(this);
await this.checkNeedRestart();
this.props.dispatch({ type: 'NAV_BACK' });
}
onCancelClick() {
public onCancelClick() {
this.props.dispatch({ type: 'NAV_BACK' });
}
hasChanges() {
public hasChanges() {
return !!this.state.changedSettingKeys.length;
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({},

View File

@ -13,19 +13,19 @@ interface Props {
class DropboxLoginScreenComponent extends React.Component<any, any> {
shared_: any;
private shared_: any;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.shared_ = new Shared(this, (msg: string) => bridge().showInfoMessageBox(msg), (msg: string) => bridge().showErrorMessageBox(msg));
}
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
this.shared_.refreshUrl();
}
render() {
public render() {
const style = this.props.style;
const theme = themeStyle(this.props.themeId);

View File

@ -32,7 +32,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
public state: State = { error: null, errorInfo: null, pluginInfos: [], plugins: {} };
componentDidCatch(error: any, errorInfo: ErrorInfo) {
public componentDidCatch(error: any, errorInfo: ErrorInfo) {
if (typeof error === 'string') error = { message: error };
const pluginInfos: PluginInfo[] = [];
@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
this.setState({ error, errorInfo, pluginInfos, plugins });
}
componentDidMount() {
public componentDidMount() {
const onAppClose = () => {
ipcRenderer.send('asynchronous-message', 'appCloseReply', {
canClose: true,
@ -68,12 +68,12 @@ export default class ErrorBoundary extends React.Component<Props, State> {
ipcRenderer.on('appClose', onAppClose);
}
renderMessage() {
public renderMessage() {
const message = this.props.message || 'Joplin encountered a fatal error and could not continue.';
return <p>{message}</p>;
}
render() {
public render() {
if (this.state.error) {
const safeMode_click = async () => {
Setting.setValue('isSafeMode', true);

View File

@ -11,17 +11,17 @@ interface Props {
}
class HelpButtonComponent extends React.Component<Props> {
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
public onClick() {
if (this.props.onClick) this.props.onClick();
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };

View File

@ -9,7 +9,7 @@ interface Props {
}
class IconButton extends React.Component<Props> {
render() {
public render() {
const style = this.props.style;
const theme = themeStyle(this.props.themeId);
const iconStyle = {

View File

@ -24,7 +24,7 @@ interface State {
}
class ImportScreenComponent extends React.Component<Props, State> {
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
this.setState({
doImport: true,
filePath: this.props.filePath,
@ -32,7 +32,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
});
}
UNSAFE_componentWillReceiveProps(newProps: Props) {
public UNSAFE_componentWillReceiveProps(newProps: Props) {
if (newProps.filePath) {
this.setState(
{
@ -47,13 +47,13 @@ class ImportScreenComponent extends React.Component<Props, State> {
}
}
componentDidMount() {
public componentDidMount() {
if (this.state.filePath && this.state.doImport) {
void this.doImport();
}
}
addMessage(key: string, text: string) {
public addMessage(key: string, text: string) {
const messages = this.state.messages.slice();
messages.push({ key: key, text: text });
@ -61,7 +61,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
this.setState({ messages: messages });
}
uniqueMessages() {
public uniqueMessages() {
const output = [];
const messages = this.state.messages.slice();
const foundKeys = [];
@ -74,7 +74,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
return output;
}
async doImport() {
public async doImport() {
const filePath = this.props.filePath;
const folderTitle = await Folder.findUniqueItemTitle(filename(filePath));
@ -109,7 +109,7 @@ class ImportScreenComponent extends React.Component<Props, State> {
this.setState({ doImport: false });
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const messages = this.uniqueMessages();

View File

@ -21,7 +21,7 @@ class ItemList extends React.Component<Props, State> {
private scrollTop_: number;
private listRef: any;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.scrollTop_ = 0;
@ -33,12 +33,12 @@ class ItemList extends React.Component<Props, State> {
this.onDrop = this.onDrop.bind(this);
}
visibleItemCount(props: Props = undefined) {
public visibleItemCount(props: Props = undefined) {
if (typeof props === 'undefined') props = this.props;
return Math.ceil(props.style.height / props.itemHeight);
}
updateStateItemIndexes(props: Props = undefined) {
public updateStateItemIndexes(props: Props = undefined) {
if (typeof props === 'undefined') props = this.props;
const topItemIndex = Math.floor(this.scrollTop_ / props.itemHeight);
@ -53,36 +53,36 @@ class ItemList extends React.Component<Props, State> {
});
}
offsetTop() {
public offsetTop() {
return this.listRef.current ? this.listRef.current.offsetTop : 0;
}
offsetScroll() {
public offsetScroll() {
return this.scrollTop_;
}
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
this.updateStateItemIndexes();
}
UNSAFE_componentWillReceiveProps(newProps: Props) {
public UNSAFE_componentWillReceiveProps(newProps: Props) {
this.updateStateItemIndexes(newProps);
}
onScroll(event: any) {
public onScroll(event: any) {
this.scrollTop_ = event.target.scrollTop;
this.updateStateItemIndexes();
}
onKeyDown(event: any) {
public onKeyDown(event: any) {
if (this.props.onKeyDown) this.props.onKeyDown(event);
}
onDrop(event: any) {
public onDrop(event: any) {
if (this.props.onNoteDrop) this.props.onNoteDrop(event);
}
makeItemIndexVisible(itemIndex: number) {
public makeItemIndexVisible(itemIndex: number) {
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex);
const bottom = Math.max(0, this.state.bottomItemIndex);
@ -119,7 +119,7 @@ class ItemList extends React.Component<Props, State> {
// return true;
// }
render() {
public render() {
const items = this.props.items;
const style = Object.assign({}, this.props.style, {
overflowX: 'hidden',

View File

@ -123,7 +123,7 @@ class MainScreenComponent extends React.Component<Props, State> {
private styles_: any;
private promptOnClose_: Function;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.state = {
@ -250,11 +250,11 @@ class MainScreenComponent extends React.Component<Props, State> {
return this.updateLayoutPluginViews(output, plugins);
}
window_resize() {
private window_resize() {
this.updateRootLayoutSize();
}
setupAppCloseHandling() {
public setupAppCloseHandling() {
this.waitForNotesSavedIID_ = null;
// This event is dispached from the main process when the app is about
@ -289,11 +289,11 @@ class MainScreenComponent extends React.Component<Props, State> {
});
}
notePropertiesDialog_close() {
private notePropertiesDialog_close() {
this.setState({ notePropertiesDialogOptions: {} });
}
noteContentPropertiesDialog_close() {
private noteContentPropertiesDialog_close() {
this.setState({ noteContentPropertiesDialogOptions: {} });
}
@ -305,14 +305,14 @@ class MainScreenComponent extends React.Component<Props, State> {
this.setState({ shareFolderDialogOptions: { visible: false, folderId: '' } });
}
updateMainLayout(layout: LayoutItem) {
public updateMainLayout(layout: LayoutItem) {
this.props.dispatch({
type: 'MAIN_LAYOUT_SET',
value: layout,
});
}
updateRootLayoutSize() {
public updateRootLayoutSize() {
this.updateMainLayout(produce(this.props.mainLayout, (draft: any) => {
const s = this.rootLayoutSize();
draft.width = s.width;
@ -320,7 +320,7 @@ class MainScreenComponent extends React.Component<Props, State> {
}));
}
componentDidUpdate(prevProps: Props, prevState: State) {
public componentDidUpdate(prevProps: Props, prevState: State) {
if (prevProps.style.width !== this.props.style.width ||
prevProps.style.height !== this.props.style.height ||
this.messageBoxVisible(prevProps) !== this.messageBoxVisible(this.props)
@ -383,24 +383,24 @@ class MainScreenComponent extends React.Component<Props, State> {
}
}
layoutModeListenerKeyDown(event: any) {
public layoutModeListenerKeyDown(event: any) {
if (event.key !== 'Escape') return;
if (!this.props.layoutMoveMode) return;
void CommandService.instance().execute('toggleLayoutMoveMode');
}
componentDidMount() {
public componentDidMount() {
window.addEventListener('keydown', this.layoutModeListenerKeyDown);
}
componentWillUnmount() {
public componentWillUnmount() {
this.unregisterCommands();
window.removeEventListener('resize', this.window_resize);
window.removeEventListener('keydown', this.layoutModeListenerKeyDown);
}
async waitForNoteToSaved(noteId: string) {
public async waitForNoteToSaved(noteId: string) {
while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') {
// eslint-disable-next-line no-console
console.info('Waiting for note to be saved...', this.props.editorNoteStatuses);
@ -408,7 +408,7 @@ class MainScreenComponent extends React.Component<Props, State> {
}
}
async printTo_(target: string, options: any) {
public async printTo_(target: string, options: any) {
// Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion
if (this.isPrinting_) {
// eslint-disable-next-line no-console
@ -449,23 +449,23 @@ class MainScreenComponent extends React.Component<Props, State> {
this.isPrinting_ = false;
}
rootLayoutSize() {
public rootLayoutSize() {
return {
width: window.innerWidth,
height: this.rowHeight(),
};
}
rowHeight() {
public rowHeight() {
if (!this.props) return 0;
return this.props.style.height - (this.messageBoxVisible() ? this.messageBoxHeight() : 0);
}
messageBoxHeight() {
public messageBoxHeight() {
return 50;
}
styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
public styles(themeId: number, width: number, height: number, messageBoxVisible: boolean) {
const styleKey = [themeId, width, height, messageBoxVisible].join('_');
if (styleKey === this.styleKey_) return this.styles_;
@ -539,7 +539,7 @@ class MainScreenComponent extends React.Component<Props, State> {
);
}
renderNotification(theme: any, styles: any) {
public renderNotification(theme: any, styles: any) {
if (!this.messageBoxVisible()) return null;
const onViewStatusScreen = () => {
@ -658,33 +658,33 @@ class MainScreenComponent extends React.Component<Props, State> {
);
}
messageBoxVisible(props: Props = null) {
public messageBoxVisible(props: Props = null) {
if (!props) props = this.props;
return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin;
}
registerCommands() {
public registerCommands() {
for (const command of commands) {
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this));
}
}
unregisterCommands() {
public unregisterCommands() {
for (const command of commands) {
CommandService.instance().unregisterRuntime(command.declaration.name);
}
}
resizableLayout_resize(event: any) {
private resizableLayout_resize(event: any) {
this.updateMainLayout(event.layout);
}
resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
private resizableLayout_moveButtonClick(event: MoveButtonClickEvent) {
const newLayout = move(this.props.mainLayout, event.itemKey, event.direction);
this.updateMainLayout(newLayout);
}
resizableLayout_renderItem(key: string, event: any) {
private resizableLayout_renderItem(key: string, event: any) {
// Key should never be undefined but somehow it can happen, also not
// clear how. For now in this case render nothing so that the app
// doesn't crash.
@ -770,7 +770,7 @@ class MainScreenComponent extends React.Component<Props, State> {
}
}
renderPluginDialogs() {
public renderPluginDialogs() {
const output = [];
const infos = pluginUtils.viewInfosByType(this.props.plugins, 'webview');
@ -801,7 +801,7 @@ class MainScreenComponent extends React.Component<Props, State> {
);
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign(
{

View File

@ -9,7 +9,7 @@ interface Props {
}
class NavigatorComponent extends React.Component<Props> {
UNSAFE_componentWillReceiveProps(newProps: Props) {
public UNSAFE_componentWillReceiveProps(newProps: Props) {
if (newProps.route) {
const screenInfo = this.props.screens[newProps.route.routeName];
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
@ -21,7 +21,7 @@ class NavigatorComponent extends React.Component<Props> {
}
}
updateWindowTitle(title: string) {
public updateWindowTitle(title: string) {
try {
if (bridge().window()) bridge().window().setTitle(title);
} catch (error) {
@ -29,7 +29,7 @@ class NavigatorComponent extends React.Component<Props> {
}
}
render() {
public render() {
if (!this.props.route) throw new Error('Route must not be null');
const route = this.props.route;

View File

@ -31,7 +31,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
private styleKey_: number;
private styles_: any;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.revisionsLink_click = this.revisionsLink_click.bind(this);
@ -56,17 +56,17 @@ class NotePropertiesDialog extends React.Component<Props, State> {
};
}
componentDidMount() {
public componentDidMount() {
void this.loadNote(this.props.noteId);
}
componentDidUpdate() {
public componentDidUpdate() {
if (this.state.editedKey === null) {
this.okButton.current.focus();
}
}
async loadNote(noteId: string) {
public async loadNote(noteId: string) {
if (!noteId) {
this.setState({ formNote: null });
} else {
@ -76,7 +76,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
}
}
latLongFromLocation(location: string) {
public latLongFromLocation(location: string) {
const o: any = {};
const l = location.split(',');
if (l.length === 2) {
@ -89,7 +89,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return o;
}
noteToFormNote(note: NoteEntity) {
public noteToFormNote(note: NoteEntity) {
const formNote: any = {};
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
@ -113,7 +113,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return formNote;
}
formNoteToNote(formNote: any) {
public formNoteToNote(formNote: any) {
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
@ -127,7 +127,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return note;
}
styles(themeId: number) {
public styles(themeId: number) {
const styleKey = themeId;
if (styleKey === this.styleKey_) return this.styles_;
@ -168,7 +168,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return this.styles_;
}
async closeDialog(applyChanges: boolean) {
public async closeDialog(applyChanges: boolean) {
if (applyChanges) {
await this.saveProperty();
const note = this.formNoteToNote(this.state.formNote);
@ -183,16 +183,16 @@ class NotePropertiesDialog extends React.Component<Props, State> {
}
}
buttonRow_click(event: any) {
private buttonRow_click(event: any) {
void this.closeDialog(event.buttonName === 'ok');
}
revisionsLink_click() {
private revisionsLink_click() {
void this.closeDialog(false);
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
}
editPropertyButtonClick(key: string, initialValue: any) {
public editPropertyButtonClick(key: string, initialValue: any) {
this.setState({
editedKey: key,
editedValue: initialValue,
@ -207,7 +207,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
}, 100);
}
async saveProperty() {
public async saveProperty() {
if (!this.state.editedKey) return;
return new Promise((resolve: Function) => {
@ -233,7 +233,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
});
}
async cancelProperty() {
public async cancelProperty() {
return new Promise((resolve: Function) => {
this.okButton.current.focus();
this.setState({
@ -245,7 +245,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
});
}
createNoteField(key: string, value: any) {
public createNoteField(key: string, value: any) {
const styles = this.styles(this.props.themeId);
const theme = themeStyle(this.props.themeId);
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
@ -364,12 +364,12 @@ class NotePropertiesDialog extends React.Component<Props, State> {
);
}
formatLabel(key: string) {
public formatLabel(key: string) {
if (this.keyToLabel_[key]) return this.keyToLabel_[key];
return key;
}
formatValue(key: string, note: NoteEntity) {
public formatValue(key: string, note: NoteEntity) {
if (key === 'location') {
if (!Number(note.latitude) && !Number(note.longitude)) return null;
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
@ -383,7 +383,7 @@ class NotePropertiesDialog extends React.Component<Props, State> {
return (note as any)[key];
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const formNote = this.state.formNote;

View File

@ -38,7 +38,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
private viewerRef_: any;
private helpButton_onClick: Function;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.state = {
@ -57,7 +57,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
this.webview_ipcMessage = this.webview_ipcMessage.bind(this);
}
style() {
public style() {
const theme = themeStyle(this.props.themeId);
const style = {
@ -74,7 +74,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
return style;
}
async viewer_domReady() {
private async viewer_domReady() {
// this.viewerRef_.current.openDevTools();
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
@ -90,7 +90,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
);
}
async importButton_onClick() {
private async importButton_onClick() {
if (!this.state.note) return;
this.setState({ restoring: true });
await RevisionService.instance().importRevisionNote(this.state.note);
@ -98,11 +98,11 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
alert(RevisionService.instance().restoreSuccessMessage(this.state.note));
}
backButton_click() {
private backButton_click() {
if (this.props.onBack) this.props.onBack();
}
revisionList_onChange(event: any) {
private revisionList_onChange(event: any) {
const value = event.target.value;
if (!value) {
@ -119,7 +119,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
}
}
async reloadNote() {
public async reloadNote() {
let noteBody = '';
let markupLanguage = MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN;
if (!this.state.revisions.length || !this.state.currentRevId) {
@ -153,7 +153,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
});
}
async webview_ipcMessage(event: any) {
private async webview_ipcMessage(event: any) {
// For the revision view, we only suppport a minimal subset of the IPC messages.
// For example, we don't need interactive checkboxes or sync between viewer and editor view.
// We try to get most links work though, except for internal (joplin://) links.
@ -183,7 +183,7 @@ class NoteRevisionViewerComponent extends React.PureComponent<Props, State> {
}
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style = this.style();

View File

@ -20,7 +20,7 @@ class NoteSearchBar extends React.Component<Props> {
private backgroundColor: any;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.searchInput_change = this.searchInput_change.bind(this);
@ -33,7 +33,7 @@ class NoteSearchBar extends React.Component<Props> {
this.backgroundColor = undefined;
}
style() {
public style() {
const theme = themeStyle(this.props.themeId);
const style = {
@ -46,7 +46,7 @@ class NoteSearchBar extends React.Component<Props> {
return style;
}
buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
public buttonIconComponent(iconName: string, clickHandler: any, isEnabled: boolean) {
const theme = themeStyle(this.props.themeId);
const searchButton = {
@ -74,12 +74,12 @@ class NoteSearchBar extends React.Component<Props> {
);
}
searchInput_change(event: any) {
private searchInput_change(event: any) {
const query = event.currentTarget.value;
this.triggerOnChange(query);
}
searchInput_keyDown(event: any) {
private searchInput_keyDown(event: any) {
if (event.keyCode === 13) {
// ENTER
event.preventDefault();
@ -106,28 +106,28 @@ class NoteSearchBar extends React.Component<Props> {
}
}
previousButton_click() {
private previousButton_click() {
if (this.props.onPrevious) this.props.onPrevious();
}
nextButton_click() {
private nextButton_click() {
if (this.props.onNext) this.props.onNext();
}
closeButton_click() {
private closeButton_click() {
if (this.props.onClose) this.props.onClose();
}
triggerOnChange(query: string) {
public triggerOnChange(query: string) {
if (this.props.onChange) this.props.onChange(query);
}
focus() {
public focus() {
(this.refs.searchInput as any).focus();
(this.refs.searchInput as any).select();
}
render() {
public render() {
const query = this.props.query ? this.props.query : '';
// backgroundColor needs to cached to a local variable to prevent the

View File

@ -11,7 +11,7 @@ interface Props {
}
class NoteStatusBarComponent extends React.Component<Props> {
style() {
public style() {
const theme = themeStyle(this.props.themeId);
const style = {
@ -24,7 +24,7 @@ class NoteStatusBarComponent extends React.Component<Props> {
return style;
}
render() {
public render() {
const note = this.props.note;
return <div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>;
}

View File

@ -17,7 +17,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
private webviewRef_: any;
private webviewListeners_: any = null;
constructor(props: any) {
public constructor(props: any) {
super(props);
this.webviewRef_ = React.createRef();
@ -41,20 +41,20 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
this.webview_message = this.webview_message.bind(this);
}
webview_domReady(event: any) {
private webview_domReady(event: any) {
this.domReady_ = true;
if (this.props.onDomReady) this.props.onDomReady(event);
}
webview_ipcMessage(event: any) {
private webview_ipcMessage(event: any) {
if (this.props.onIpcMessage) this.props.onIpcMessage(event);
}
webview_load() {
private webview_load() {
this.webview_domReady({});
}
webview_message(event: any) {
private webview_message(event: any) {
if (!event.data || event.data.target !== 'main') return;
const callName = event.data.name;
@ -68,11 +68,11 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
}
}
domReady() {
public domReady() {
return this.domReady_;
}
initWebview() {
public initWebview() {
const wv = this.webviewRef_.current;
if (!this.webviewListeners_) {
@ -92,7 +92,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
this.webviewRef_.current.contentWindow.addEventListener('message', this.webview_message);
}
destroyWebview() {
public destroyWebview() {
const wv = this.webviewRef_.current;
if (!wv || !this.initialized_) return;
@ -115,28 +115,28 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
this.domReady_ = false;
}
focus() {
public focus() {
if (this.webviewRef_.current) {
this.webviewRef_.current.focus();
}
}
tryInit() {
public tryInit() {
if (!this.initialized_ && this.webviewRef_.current) {
this.initWebview();
this.initialized_ = true;
}
}
componentDidMount() {
public componentDidMount() {
this.tryInit();
}
componentDidUpdate() {
public componentDidUpdate() {
this.tryInit();
}
componentWillUnmount() {
public componentWillUnmount() {
this.destroyWebview();
}
@ -144,7 +144,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
// Wrap WebView functions
// ----------------------------------------------------------------
send(channel: string, arg0: any = null, arg1: any = null) {
public send(channel: string, arg0: any = null, arg1: any = null) {
const win = this.webviewRef_.current.contentWindow;
if (channel === 'focus') {
@ -172,7 +172,7 @@ export default class NoteTextViewerComponent extends React.Component<Props, any>
// Wrap WebView functions (END)
// ----------------------------------------------------------------
render() {
public render() {
const viewerStyle = Object.assign({}, { border: 'none' }, this.props.viewerStyle);
return <iframe className="noteTextViewer" ref={this.webviewRef_} style={viewerStyle} src="gui/note-viewer/index.html"></iframe>;
}

View File

@ -14,7 +14,7 @@ interface Props {
}
class OneDriveLoginScreenComponent extends React.Component<any, any> {
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.state = {
@ -22,7 +22,7 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
};
}
async componentDidMount() {
public async componentDidMount() {
const log = (s: any) => {
this.setState((state: any) => {
const authLog = state.authLog.slice();
@ -48,15 +48,15 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> {
}
}
startUrl() {
public startUrl() {
return reg.syncTarget().api().authCodeUrl(this.redirectUrl());
}
redirectUrl() {
public redirectUrl() {
return reg.syncTarget().api().nativeClientRedirectUrl();
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const logComps = [];

View File

@ -27,13 +27,13 @@ export default class PromptDialog extends React.Component<Props, any> {
private styles_: any;
private styleKey_: string;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.answerInput_ = React.createRef();
}
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
this.setState({
visible: false,
answer: this.props.defaultValue ? this.props.defaultValue : '',
@ -41,7 +41,7 @@ export default class PromptDialog extends React.Component<Props, any> {
this.focusInput_ = true;
}
UNSAFE_componentWillReceiveProps(newProps: Props) {
public UNSAFE_componentWillReceiveProps(newProps: Props) {
if ('visible' in newProps && newProps.visible !== this.props.visible) {
this.setState({ visible: newProps.visible });
if (newProps.visible) this.focusInput_ = true;
@ -52,12 +52,12 @@ export default class PromptDialog extends React.Component<Props, any> {
}
}
componentDidUpdate() {
public componentDidUpdate() {
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
this.focusInput_ = false;
}
styles(themeId: number, width: number, height: number, visible: boolean) {
public styles(themeId: number, width: number, height: number, visible: boolean) {
const styleKey = `${themeId}_${width}_${height}_${visible}`;
if (styleKey === this.styleKey_) return this.styles_;
@ -181,7 +181,7 @@ export default class PromptDialog extends React.Component<Props, any> {
return this.styles_;
}
render() {
public render() {
const style = this.props.style;
const theme = themeStyle(this.props.themeId);
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];

View File

@ -135,7 +135,7 @@ const getNextSortingOrderType = (s: SortingType): SortingType => {
const MAX_RESOURCES = 10000;
class ResourceScreenComponent extends React.Component<Props, State> {
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.state = {
resources: undefined,
@ -147,7 +147,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
};
}
async reloadResources(sorting: ActiveSorting) {
public async reloadResources(sorting: ActiveSorting) {
this.setState({ isLoading: true });
const resources = await Resource.all({
order: [{
@ -161,11 +161,11 @@ class ResourceScreenComponent extends React.Component<Props, State> {
this.setState({ resources, isLoading: false });
}
componentDidMount() {
public componentDidMount() {
void this.reloadResources(this.state.sorting);
}
onResourceDelete(resource: InnerResource) {
public onResourceDelete(resource: InnerResource) {
const ok = bridge().showConfirmMessageBox(_('Delete attachment "%s"?', resource.title), {
buttons: [_('Delete'), _('Cancel')],
defaultId: 1,
@ -184,7 +184,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
});
}
openResource(resource: InnerResource) {
public openResource(resource: InnerResource) {
const resourcePath = Resource.fullPath(resource);
const ok = bridge().openExternal(`file://${resourcePath}`);
if (!ok) {
@ -192,7 +192,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
}
}
onToggleSortOrder(sortOrder: SortingOrder) {
public onToggleSortOrder(sortOrder: SortingOrder) {
let newSorting = { ...this.state.sorting };
if (sortOrder === this.state.sorting.order) {
newSorting.type = getNextSortingOrderType(newSorting.type);
@ -206,7 +206,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
void this.reloadResources(newSorting);
}
render() {
public render() {
const style = this.props.style;
const theme = themeStyle(this.props.themeId);

View File

@ -5,7 +5,7 @@ import CommandService from '@joplin/lib/services/CommandService';
import { AppState } from '../app.reducer';
class TagItemComponent extends React.Component {
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.tagStyle);
const { title, id } = this.props;

View File

@ -13,7 +13,7 @@ interface Props {
class ToolbarBaseComponent extends React.Component<Props, any> {
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style: any = Object.assign({

View File

@ -6,7 +6,7 @@ interface Props {
}
class ToolbarSpace extends React.Component<Props> {
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style = Object.assign({}, theme.toolbarStyle);
style.minWidth = style.height / 2;

View File

@ -7,11 +7,11 @@ const smalltalk = require('smalltalk');
const logger = Logger.create('dialogs');
class Dialogs {
async alert(message: string, title = '') {
public async alert(message: string, title = '') {
await smalltalk.alert(title, message);
}
async confirm(message: string, title = '', options: any = {}) {
public async confirm(message: string, title = '', options: any = {}) {
try {
await smalltalk.confirm(title, message, options);
return true;
@ -21,7 +21,7 @@ class Dialogs {
}
}
async prompt(message: string, title = '', defaultValue = '', options: any = null) {
public async prompt(message: string, title = '', defaultValue = '', options: any = null) {
if (options === null) options = {};
try {

View File

@ -26,7 +26,7 @@ interface ContextMenuProps {
}
export default class NoteListUtils {
static makeContextMenu(noteIds: string[], props: ContextMenuProps) {
public static makeContextMenu(noteIds: string[], props: ContextMenuProps) {
const cmdService = CommandService.instance();
const menuUtils = new MenuUtils(cmdService);
@ -212,7 +212,7 @@ export default class NoteListUtils {
return menu;
}
static async confirmDeleteNotes(noteIds: string[]) {
public static async confirmDeleteNotes(noteIds: string[]) {
if (!noteIds.length) return;
const msg = await Note.deleteMessage(noteIds);

View File

@ -65,7 +65,7 @@ class GotoAnything {
public static Dialog: any;
public static manifest: any;
onTrigger(event: any) {
public onTrigger(event: any) {
this.dispatch({
type: 'PLUGINLEGACY_DIALOG_SET',
open: true,
@ -85,7 +85,7 @@ class Dialog extends React.PureComponent<Props, State> {
private markupToHtml_: MarkupToHtml;
private userCallback_: any = null;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
const startString = props?.userData?.startString ? props?.userData?.startString : '';
@ -119,7 +119,7 @@ class Dialog extends React.PureComponent<Props, State> {
if (startString) this.scheduleListUpdate();
}
style() {
public style() {
const styleKey = [this.props.themeId, this.state.listType, this.state.resultsInBody ? '1' : '0'].join('-');
if (this.styles_[styleKey]) return this.styles_[styleKey];
@ -184,7 +184,7 @@ class Dialog extends React.PureComponent<Props, State> {
return this.styles_[styleKey];
}
componentDidMount() {
public componentDidMount() {
document.addEventListener('keydown', this.onKeyDown);
this.props.dispatch({
@ -193,7 +193,7 @@ class Dialog extends React.PureComponent<Props, State> {
});
}
componentWillUnmount() {
public componentWillUnmount() {
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
document.removeEventListener('keydown', this.onKeyDown);
@ -203,7 +203,7 @@ class Dialog extends React.PureComponent<Props, State> {
});
}
onKeyDown(event: any) {
public onKeyDown(event: any) {
if (event.keyCode === 27) { // ESCAPE
this.props.dispatch({
pluginName: PLUGIN_NAME,
@ -213,7 +213,7 @@ class Dialog extends React.PureComponent<Props, State> {
}
}
modalLayer_onClick(event: any) {
private modalLayer_onClick(event: any) {
if (event.currentTarget === event.target) {
this.props.dispatch({
pluginName: PLUGIN_NAME,
@ -223,17 +223,17 @@ class Dialog extends React.PureComponent<Props, State> {
}
}
helpButton_onClick() {
private helpButton_onClick() {
this.setState({ showHelp: !this.state.showHelp });
}
input_onChange(event: any) {
private input_onChange(event: any) {
this.setState({ query: event.target.value });
this.scheduleListUpdate();
}
scheduleListUpdate() {
public scheduleListUpdate() {
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_);
this.listUpdateIID_ = shim.setTimeout(async () => {
@ -242,12 +242,12 @@ class Dialog extends React.PureComponent<Props, State> {
}, 100);
}
async keywords(searchQuery: string) {
public async keywords(searchQuery: string) {
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery);
return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
}
markupToHtml() {
public markupToHtml() {
if (this.markupToHtml_) return this.markupToHtml_;
this.markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
return this.markupToHtml_;
@ -262,7 +262,7 @@ class Dialog extends React.PureComponent<Props, State> {
};
}
async updateList() {
public async updateList() {
let resultsInBody = false;
if (!this.state.query) {
@ -402,7 +402,7 @@ class Dialog extends React.PureComponent<Props, State> {
this.itemListRef.current.makeItemIndexVisible(index);
}
async gotoItem(item: any) {
public async gotoItem(item: any) {
this.props.dispatch({
pluginName: PLUGIN_NAME,
type: 'PLUGINLEGACY_DIALOG_SET',
@ -465,7 +465,7 @@ class Dialog extends React.PureComponent<Props, State> {
}
}
listItem_onClick(event: any) {
private listItem_onClick(event: any) {
const itemId = event.currentTarget.getAttribute('data-id');
const parentId = event.currentTarget.getAttribute('data-parent-id');
const itemType = Number(event.currentTarget.getAttribute('data-type'));
@ -478,7 +478,7 @@ class Dialog extends React.PureComponent<Props, State> {
});
}
renderItem(item: SearchResult) {
public renderItem(item: SearchResult) {
const theme = themeStyle(this.props.themeId);
const style = this.style();
const isSelected = item.id === this.state.selectedItemId;
@ -502,7 +502,7 @@ class Dialog extends React.PureComponent<Props, State> {
);
}
selectedItemIndex(results: any[] = undefined, itemId: string = undefined) {
public selectedItemIndex(results: any[] = undefined, itemId: string = undefined) {
if (typeof results === 'undefined') results = this.state.results;
if (typeof itemId === 'undefined') itemId = this.state.selectedItemId;
for (let i = 0; i < results.length; i++) {
@ -512,13 +512,13 @@ class Dialog extends React.PureComponent<Props, State> {
return -1;
}
selectedItem() {
public selectedItem() {
const index = this.selectedItemIndex();
if (index < 0) return null;
return { ...this.state.results[index], commandArgs: this.state.commandArgs };
}
input_onKeyDown(event: any) {
private input_onKeyDown(event: any) {
const keyCode = event.keyCode;
if (this.state.results.length > 0 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
@ -554,7 +554,7 @@ class Dialog extends React.PureComponent<Props, State> {
return maxItemCount * itemHeight;
}
renderList() {
public renderList() {
const style = this.style();
const itemListStyle = {
@ -573,7 +573,7 @@ class Dialog extends React.PureComponent<Props, State> {
);
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
const style = this.style();
const helpComp = !this.state.showHelp ? null : <div className="help-text" style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. Or type : to search for commands.')}</div>;

View File

@ -6,24 +6,24 @@ import KvStore from '@joplin/lib/services/KvStore';
export default class PluginAssetsLoader {
static instance_: PluginAssetsLoader = null;
logger_: any = null;
private static instance_: PluginAssetsLoader = null;
private logger_: any = null;
static instance() {
public static instance() {
if (PluginAssetsLoader.instance_) return PluginAssetsLoader.instance_;
PluginAssetsLoader.instance_ = new PluginAssetsLoader();
return PluginAssetsLoader.instance_;
}
setLogger(logger: any) {
public setLogger(logger: any) {
this.logger_ = logger;
}
logger() {
public logger() {
return this.logger_;
}
async importAssets() {
public async importAssets() {
const destDir = `${Setting.value('resourceDir')}/pluginAssets`;
await shim.fsDriver().mkdir(destDir);

View File

@ -2,7 +2,7 @@ const { BackButtonService } = require('../services/back-button.js');
const DialogBox = require('react-native-dialogbox').default;
export default class BackButtonDialogBox extends DialogBox {
constructor(props: any) {
public constructor(props: any) {
super(props);
this.backHandler_ = () => {
@ -14,7 +14,7 @@ export default class BackButtonDialogBox extends DialogBox {
};
}
async componentDidUpdate() {
public async componentDidUpdate() {
if (this.state.isVisible) {
BackButtonService.addHandler(this.backHandler_);
} else {

View File

@ -14,7 +14,7 @@ import Setting from '@joplin/lib/models/Setting';
Icon.loadFont().catch((error: any) => { console.info(error); });
class CameraView extends Component {
constructor() {
public constructor() {
super();
const dimensions = Dimensions.get('window');
@ -34,18 +34,18 @@ class CameraView extends Component {
this.onLayout = this.onLayout.bind(this);
}
onLayout(event: any) {
public onLayout(event: any) {
this.setState({
screenWidth: event.nativeEvent.layout.width,
screenHeight: event.nativeEvent.layout.height,
});
}
back_onPress() {
private back_onPress() {
if (this.props.onCancel) this.props.onCancel();
}
reverse_onPress() {
private reverse_onPress() {
if (this.props.cameraType === RNCamera.Constants.Type.back) {
Setting.setValue('camera.type', RNCamera.Constants.Type.front);
} else {
@ -53,7 +53,7 @@ class CameraView extends Component {
}
}
ratio_onPress() {
private ratio_onPress() {
if (this.state.ratios.length <= 1) return;
let index = this.state.ratios.indexOf(this.props.cameraRatio);
@ -62,7 +62,7 @@ class CameraView extends Component {
Setting.setValue('camera.ratio', this.state.ratios[index]);
}
async photo_onPress() {
private async photo_onPress() {
if (!this.camera || !this.props.onPhoto) return;
this.setState({ snapping: true });
@ -79,14 +79,14 @@ class CameraView extends Component {
}
async onCameraReady() {
public async onCameraReady() {
if (this.supportsRatios()) {
const ratios = await this.camera.getSupportedRatiosAsync();
this.setState({ ratios: ratios });
}
}
renderButton(onPress: Function, iconNameOrIcon: any, style: any) {
public renderButton(onPress: Function, iconNameOrIcon: any, style: any) {
let icon = null;
if (typeof iconNameOrIcon === 'string') {
@ -112,7 +112,7 @@ class CameraView extends Component {
);
}
fitRectIntoBounds(rect: any, bounds: any) {
public fitRectIntoBounds(rect: any, bounds: any) {
const rectRatio = rect.width / rect.height;
const boundsRatio = bounds.width / bounds.height;
@ -130,7 +130,7 @@ class CameraView extends Component {
return newDimensions;
}
cameraRect(ratio: string) {
public cameraRect(ratio: string) {
// To keep the calculations simpler, it's assumed that the phone is in
// portrait orientation. Then at the end we swap the values if needed.
const splitted = ratio.split(':');
@ -152,11 +152,11 @@ class CameraView extends Component {
return output;
}
supportsRatios() {
public supportsRatios() {
return shim.mobilePlatform() === 'android';
}
render() {
public render() {
const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera';
const displayRatios = this.supportsRatios() && this.state.ratios.length > 1;

View File

@ -52,7 +52,7 @@ const styles = StyleSheet.create({
export default class SelectDateTimeDialog extends React.PureComponent<any, any> {
constructor(props: any) {
public constructor(props: any) {
super(props);
this.state = {
@ -67,37 +67,37 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
this.onSetDate = this.onSetDate.bind(this);
}
UNSAFE_componentWillReceiveProps(newProps: any) {
public UNSAFE_componentWillReceiveProps(newProps: any) {
if (newProps.date !== this.state.date) {
this.setState({ date: newProps.date });
}
}
onAccept() {
public onAccept() {
if (this.props.onAccept) this.props.onAccept(this.state.date);
}
onReject() {
public onReject() {
if (this.props.onReject) this.props.onReject();
}
onClear() {
public onClear() {
if (this.props.onAccept) this.props.onAccept(null);
}
onPickerConfirm(selectedDate: Date) {
public onPickerConfirm(selectedDate: Date) {
this.setState({ date: selectedDate, showPicker: false });
}
onPickerCancel() {
public onPickerCancel() {
this.setState({ showPicker: false });
}
onSetDate() {
public onSetDate() {
this.setState({ showPicker: true });
}
renderContent() {
public renderContent() {
const theme = themeStyle(this.props.themeId);
return (
@ -118,7 +118,7 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
);
}
render() {
public render() {
const modalVisible = this.props.shown;
if (!modalVisible) return null;

View File

@ -4,7 +4,7 @@ import { Dimensions } from 'react-native';
import { State } from '@joplin/lib/reducer';
class SideMenuComponent extends SideMenu_ {
onLayoutChange(e: any) {
public onLayoutChange(e: any) {
const { width, height } = e.nativeEvent.layout;
const openMenuOffsetPercentage = this.props.openMenuOffset / Dimensions.get('window').width;
const openMenuOffset = width * openMenuOffsetPercentage;

View File

@ -25,7 +25,7 @@ class AppNavComponent extends Component<Props, State> {
private keyboardDidHideListener: EmitterSubscription|null = null;
private keyboardWillChangeFrameListener: EmitterSubscription|null = null;
constructor(props: Props) {
public constructor(props: Props) {
super(props);
this.previousRouteName_ = null;
@ -35,7 +35,7 @@ class AppNavComponent extends Component<Props, State> {
};
}
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
if (Platform.OS === 'ios') {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this));
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this));
@ -43,7 +43,7 @@ class AppNavComponent extends Component<Props, State> {
}
}
componentWillUnmount() {
public componentWillUnmount() {
this.keyboardDidShowListener?.remove();
this.keyboardDidHideListener?.remove();
this.keyboardWillChangeFrameListener?.remove();
@ -53,15 +53,15 @@ class AppNavComponent extends Component<Props, State> {
this.keyboardWillChangeFrameListener = null;
}
keyboardDidShow() {
public keyboardDidShow() {
this.setState({ autoCompletionBarExtraHeight: 30 });
}
keyboardDidHide() {
public keyboardDidHide() {
this.setState({ autoCompletionBarExtraHeight: 0 });
}
keyboardWillChangeFrame = (evt: KeyboardEvent) => {
private keyboardWillChangeFrame = (evt: KeyboardEvent) => {
const windowWidth = Dimensions.get('window').width;
// If the keyboard isn't as wide as the window, the floating keyboard is diabled.
@ -71,7 +71,7 @@ class AppNavComponent extends Component<Props, State> {
});
};
render() {
public render() {
if (!this.props.route) throw new Error('Route must not be null');
// Note: certain screens are kept into memory, in particular Notes and Search

View File

@ -25,7 +25,7 @@ import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
import { openDocumentTree } from '@joplin/react-native-saf-x';
class ConfigScreenComponent extends BaseScreenComponent {
static navigationOptions(): any {
public static navigationOptions(): any {
return { header: null };
}
@ -200,7 +200,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
};
}
async checkFilesystemPermission() {
public async checkFilesystemPermission() {
if (Platform.OS !== 'android') {
// Not implemented yet
return true;
@ -212,11 +212,11 @@ class ConfigScreenComponent extends BaseScreenComponent {
});
}
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
this.setState({ settings: this.props.settings });
}
styles() {
public styles() {
const themeId = this.props.themeId;
const theme = themeStyle(themeId);
@ -376,7 +376,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
BackButtonService.removeHandler(this.handleBackButtonPress);
}
renderHeader(key: string, title: string) {
public renderHeader(key: string, title: string) {
const theme = themeStyle(this.props.themeId);
return (
<View key={key} style={this.styles().headerWrapperStyle} onLayout={(event: any) => this.onHeaderLayout(key, event)}>
@ -410,7 +410,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
);
}
sectionToComponent(key: string, section: any, settings: any) {
public sectionToComponent(key: string, section: any, settings: any) {
const settingComps = [];
for (let i = 0; i < section.metadatas.length; i++) {
@ -474,7 +474,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
}
settingToComponent(key: string, value: any) {
public settingToComponent(key: string, value: any) {
const themeId = this.props.themeId;
const theme = themeStyle(themeId);
const output: any = null;
@ -599,7 +599,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
return output;
}
render() {
public render() {
const settings = this.state.settings;
const theme = themeStyle(this.props.themeId);

View File

@ -51,11 +51,11 @@ const emptyArray: any[] = [];
const logger = Logger.create('screens/Note');
class NoteScreenComponent extends BaseScreenComponent {
static navigationOptions(): any {
public static navigationOptions(): any {
return { header: null };
}
constructor() {
public constructor() {
super();
this.state = {
note: Note.new(),
@ -280,7 +280,7 @@ class NoteScreenComponent extends BaseScreenComponent {
});
}
screenHeader_undoButtonPress() {
private screenHeader_undoButtonPress() {
if (this.useEditorBeta()) {
this.editorRef.current.undo();
} else {
@ -288,7 +288,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
screenHeader_redoButtonPress() {
private screenHeader_redoButtonPress() {
if (this.useEditorBeta()) {
this.editorRef.current.redo();
} else {
@ -296,13 +296,13 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
undoState(noteBody: string = null) {
public undoState(noteBody: string = null) {
return {
body: noteBody === null ? this.state.note.body : noteBody,
};
}
styles() {
public styles() {
const themeId = this.props.themeId;
const theme = themeStyle(themeId);
@ -392,11 +392,11 @@ class NoteScreenComponent extends BaseScreenComponent {
return this.styles_[cacheKey];
}
isModified() {
public isModified() {
return shared.isModified(this);
}
async requestGeoLocationPermissions() {
public async requestGeoLocationPermissions() {
if (!Setting.value('trackLocation')) return;
const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, {
@ -413,7 +413,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
async componentDidMount() {
public async componentDidMount() {
BackButtonService.addHandler(this.backHandler);
NavService.addHandler(this.navHandler);
@ -436,11 +436,11 @@ class NoteScreenComponent extends BaseScreenComponent {
void this.requestGeoLocationPermissions();
}
onMarkForDownload(event: any) {
public onMarkForDownload(event: any) {
void ResourceFetcher.instance().markForDownload(event.resourceId);
}
componentDidUpdate(prevProps: any) {
public componentDidUpdate(prevProps: any) {
if (this.doFocusUpdate_) {
this.doFocusUpdate_ = false;
this.focusUpdate();
@ -454,7 +454,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
componentWillUnmount() {
public componentWillUnmount() {
BackButtonService.removeHandler(this.backHandler);
NavService.removeHandler(this.navHandler);
@ -467,13 +467,13 @@ class NoteScreenComponent extends BaseScreenComponent {
if (this.undoRedoService_) this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange);
}
title_changeText(text: string) {
private title_changeText(text: string) {
shared.noteComponent_change(this, 'title', text);
this.setState({ newAndNoTitleChangeNoteId: null });
this.scheduleSave();
}
body_changeText(text: string) {
private body_changeText(text: string) {
if (!this.undoRedoService_.canUndo) {
this.undoRedoService_.push(this.undoState());
} else {
@ -484,7 +484,7 @@ class NoteScreenComponent extends BaseScreenComponent {
this.scheduleSave();
}
body_selectionChange(event: any) {
private body_selectionChange(event: any) {
if (this.useEditorBeta()) {
this.selection = event.selection;
} else {
@ -492,34 +492,34 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
makeSaveAction() {
public makeSaveAction() {
return async () => {
return shared.saveNoteButton_press(this, null, null);
};
}
saveActionQueue(noteId: string) {
public saveActionQueue(noteId: string) {
if (!this.saveActionQueues_[noteId]) {
this.saveActionQueues_[noteId] = new AsyncActionQueue(500);
}
return this.saveActionQueues_[noteId];
}
scheduleSave() {
public scheduleSave() {
this.saveActionQueue(this.state.note.id).push(this.makeSaveAction());
}
async saveNoteButton_press(folderId: string = null) {
private async saveNoteButton_press(folderId: string = null) {
await shared.saveNoteButton_press(this, folderId, null);
Keyboard.dismiss();
}
async saveOneProperty(name: string, value: any) {
public async saveOneProperty(name: string, value: any) {
await shared.saveOneProperty(this, name, value);
}
async deleteNote_onPress() {
private async deleteNote_onPress() {
const note = this.state.note;
if (!note.id) return;
@ -546,7 +546,7 @@ class NoteScreenComponent extends BaseScreenComponent {
return result;
}
async imageDimensions(uri: string) {
public async imageDimensions(uri: string) {
return new Promise((resolve, reject) => {
Image.getSize(
uri,
@ -560,7 +560,7 @@ class NoteScreenComponent extends BaseScreenComponent {
});
}
async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
public async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
const maxSize = Resource.IMAGE_MAX_DIMENSION;
const dimensions: any = await this.imageDimensions(localFilePath);
@ -609,7 +609,7 @@ class NoteScreenComponent extends BaseScreenComponent {
return true;
}
async attachFile(pickerResponse: any, fileType: string) {
public async attachFile(pickerResponse: any, fileType: string) {
if (!pickerResponse) {
// User has cancelled
return;
@ -727,11 +727,11 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
takePhoto_onPress() {
private takePhoto_onPress() {
this.setState({ showCamera: true });
}
cameraView_onPhoto(data: any) {
private cameraView_onPhoto(data: any) {
void this.attachFile(
{
uri: data.uri,
@ -743,7 +743,7 @@ class NoteScreenComponent extends BaseScreenComponent {
this.setState({ showCamera: false });
}
cameraView_onCancel() {
private cameraView_onCancel() {
this.setState({ showCamera: false });
}
@ -754,34 +754,30 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
toggleIsTodo_onPress() {
private toggleIsTodo_onPress() {
shared.toggleIsTodo_onPress(this);
this.scheduleSave();
}
tags_onPress() {
private tags_onPress() {
if (!this.state.note || !this.state.note.id) return;
this.setState({ noteTagDialogShown: true });
}
async share_onPress() {
private async share_onPress() {
await Share.share({
message: `${this.state.note.title}\n\n${this.state.note.body}`,
title: this.state.note.title,
});
}
properties_onPress() {
private properties_onPress() {
this.props.dispatch({ type: 'SIDE_MENU_OPEN' });
}
setAlarm_onPress() {
this.setState({ alarmDialogShown: true });
}
async onAlarmDialogAccept(date: Date) {
public async onAlarmDialogAccept(date: Date) {
const newNote = Object.assign({}, this.state.note);
newNote.todo_due = date ? date.getTime() : 0;
@ -790,11 +786,11 @@ class NoteScreenComponent extends BaseScreenComponent {
this.setState({ alarmDialogShown: false });
}
onAlarmDialogReject() {
public onAlarmDialogReject() {
this.setState({ alarmDialogShown: false });
}
async showOnMap_onPress() {
private async showOnMap_onPress() {
if (!this.state.note.id) return;
const note = await Note.load(this.state.note.id);
@ -807,7 +803,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
async showSource_onPress() {
private async showSource_onPress() {
if (!this.state.note.id) return;
const note = await Note.load(this.state.note.id);
@ -818,12 +814,12 @@ class NoteScreenComponent extends BaseScreenComponent {
}
}
copyMarkdownLink_onPress() {
private copyMarkdownLink_onPress() {
const note = this.state.note;
Clipboard.setString(Note.markdownTag(note));
}
sideMenuOptions() {
public sideMenuOptions() {
const note = this.state.note;
if (!note) return [];
@ -854,7 +850,7 @@ class NoteScreenComponent extends BaseScreenComponent {
return output;
}
async showAttachMenu() {
public async showAttachMenu() {
const buttons = [];
// On iOS, it will show "local files", which means certain files saved from the browser
@ -875,7 +871,7 @@ class NoteScreenComponent extends BaseScreenComponent {
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
}
menuOptions() {
public menuOptions() {
const note = this.state.note;
const isTodo = note && !!note.is_todo;
const isSaved = note && note.id;
@ -959,11 +955,11 @@ class NoteScreenComponent extends BaseScreenComponent {
return output;
}
async todoCheckbox_change(checked: boolean) {
private async todoCheckbox_change(checked: boolean) {
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
}
scheduleFocusUpdate() {
public scheduleFocusUpdate() {
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = shim.setTimeout(() => {
@ -972,7 +968,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}, 100);
}
focusUpdate() {
public focusUpdate() {
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = null;
@ -990,7 +986,7 @@ class NoteScreenComponent extends BaseScreenComponent {
// }
}
async folderPickerOptions_valueChanged(itemValue: any) {
private async folderPickerOptions_valueChanged(itemValue: any) {
const note = this.state.note;
const isProvisionalNote = this.props.provisionalNoteIds.includes(note.id);
@ -1011,7 +1007,7 @@ class NoteScreenComponent extends BaseScreenComponent {
});
}
folderPickerOptions() {
public folderPickerOptions() {
const options = {
enabled: true,
selectedFolderId: this.state.folder ? this.state.folder.id : null,
@ -1024,7 +1020,7 @@ class NoteScreenComponent extends BaseScreenComponent {
return this.folderPickerOptions_;
}
onBodyViewerLoadEnd() {
public onBodyViewerLoadEnd() {
shim.setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 1 });
shim.setTimeout(() => {
@ -1033,11 +1029,11 @@ class NoteScreenComponent extends BaseScreenComponent {
}, 5);
}
onBodyViewerCheckboxChange(newBody: string) {
public onBodyViewerCheckboxChange(newBody: string) {
void this.saveOneProperty('body', newBody);
}
render() {
public render() {
if (this.state.isLoading) {
return (
<View style={this.styles().screen}>

View File

@ -21,7 +21,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
private onAppStateChangeSub_: NativeEventSubscription = null;
constructor() {
public constructor() {
super();
this.onAppStateChange_ = async () => {
@ -78,7 +78,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
};
}
styles() {
public styles() {
if (!this.styles_) this.styles_ = {};
const themeId = this.props.themeId;
const cacheKey = themeId;
@ -96,24 +96,24 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
return this.styles_[cacheKey];
}
async componentDidMount() {
public async componentDidMount() {
BackButtonService.addHandler(this.backHandler);
await this.refreshNotes();
this.onAppStateChangeSub_ = RNAppState.addEventListener('change', this.onAppStateChange_);
}
async componentWillUnmount() {
public async componentWillUnmount() {
if (this.onAppStateChangeSub_) this.onAppStateChangeSub_.remove();
BackButtonService.removeHandler(this.backHandler);
}
async componentDidUpdate(prevProps: any) {
public async componentDidUpdate(prevProps: any) {
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType) {
await this.refreshNotes(this.props);
}
}
async refreshNotes(props: any = null) {
public async refreshNotes(props: any = null) {
if (props === null) props = this.props;
const options = {
@ -149,36 +149,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
});
}
deleteFolder_onPress(folderId: string) {
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
dialogs.confirm(this, _('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.')).then((ok: boolean) => {
if (!ok) return;
Folder.delete(folderId)
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
.then(() => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Notes',
smartFilterId: 'c3176726992c11e9ac940492261af972',
});
})
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
.catch(error => {
alert(error.message);
});
});
}
editFolder_onPress(folderId: string) {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Folder',
folderId: folderId,
});
}
newNoteNavigate = async (folderId: string, isTodo: boolean) => {
public newNoteNavigate = async (folderId: string, isTodo: boolean) => {
const newNote = await Note.save({
parent_id: folderId,
is_todo: isTodo ? 1 : 0,
@ -191,7 +162,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
});
};
parentItem(props: any = null) {
public parentItem(props: any = null) {
if (!props) props = this.props;
let output = null;
@ -208,7 +179,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
return output;
}
folderPickerOptions() {
public folderPickerOptions() {
const options = {
enabled: this.props.noteSelectionEnabled,
mustSelect: true,
@ -220,7 +191,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
return this.folderPickerOptions_;
}
render() {
public render() {
const parent = this.parentItem();
const theme = themeStyle(this.props.themeId);

View File

@ -27,11 +27,11 @@ class SearchScreenComponent extends BaseScreenComponent {
private styles_: any = {};
private scheduleSearchTimer_: any = null;
static navigationOptions() {
public static navigationOptions() {
return { header: null } as any;
}
constructor() {
public constructor() {
super();
this.state = {
query: '',
@ -39,7 +39,7 @@ class SearchScreenComponent extends BaseScreenComponent {
};
}
styles() {
public styles() {
const theme = themeStyle(this.props.themeId);
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
@ -72,17 +72,17 @@ class SearchScreenComponent extends BaseScreenComponent {
return this.styles_[this.props.themeId];
}
componentDidMount() {
public componentDidMount() {
this.setState({ query: this.props.query });
void this.refreshSearch(this.props.query);
this.isMounted_ = true;
}
componentWillUnmount() {
public componentWillUnmount() {
this.isMounted_ = false;
}
clearButton_press() {
private clearButton_press() {
this.props.dispatch({
type: 'SEARCH_QUERY',
query: '',
@ -92,7 +92,7 @@ class SearchScreenComponent extends BaseScreenComponent {
void this.refreshSearch('');
}
async refreshSearch(query: string = null) {
public async refreshSearch(query: string = null) {
if (!this.props.visible) return;
query = gotoAnythingStyleQuery(query);
@ -130,7 +130,7 @@ class SearchScreenComponent extends BaseScreenComponent {
this.setState({ notes: notes });
}
scheduleSearch() {
public scheduleSearch() {
if (this.scheduleSearchTimer_) clearTimeout(this.scheduleSearchTimer_);
this.scheduleSearchTimer_ = setTimeout(() => {
@ -139,7 +139,7 @@ class SearchScreenComponent extends BaseScreenComponent {
}, 200);
}
searchTextInput_changeText(text: string) {
private searchTextInput_changeText(text: string) {
this.setState({ query: text });
this.props.dispatch({
@ -150,7 +150,7 @@ class SearchScreenComponent extends BaseScreenComponent {
this.scheduleSearch();
}
render() {
public render() {
if (!this.isMounted_) return null;
const theme = themeStyle(this.props.themeId);

View File

@ -8,7 +8,7 @@ export default class AlarmServiceDriver {
private inAppNotificationHandler_: any = null;
private logger_: Logger;
constructor(logger: Logger) {
public constructor(logger: Logger) {
this.logger_ = logger;
PushNotificationIOS.addEventListener('localNotification', (instance: any) => {
if (!this.inAppNotificationHandler_) return;
@ -23,19 +23,19 @@ export default class AlarmServiceDriver {
});
}
hasPersistentNotifications() {
public hasPersistentNotifications() {
return true;
}
notificationIsSet() {
public notificationIsSet() {
throw new Error('Available only for non-persistent alarms');
}
setInAppNotificationHandler(v: any) {
public setInAppNotificationHandler(v: any) {
this.inAppNotificationHandler_ = v;
}
async hasPermissions(perm: any = null) {
public async hasPermissions(perm: any = null) {
if (perm !== null) return perm.alert && perm.badge && perm.sound;
if (this.hasPermission_ !== null) return this.hasPermission_;
@ -49,7 +49,7 @@ export default class AlarmServiceDriver {
});
}
async requestPermissions() {
public async requestPermissions() {
const options: any = {
alert: 1,
badge: 1,
@ -60,11 +60,11 @@ export default class AlarmServiceDriver {
return this.hasPermissions(newPerm);
}
async clearNotification(id: number) {
public async clearNotification(id: number) {
PushNotificationIOS.cancelLocalNotifications({ id: `${id}` });
}
async scheduleNotification(notification: Notification) {
public async scheduleNotification(notification: Notification) {
if (!(await this.hasPermissions())) {
const ok = await this.requestPermissions();
if (!ok) return;

View File

@ -418,7 +418,7 @@ export default class BaseApplication {
ResourceFetcher.instance().scheduleAutoAddResources();
}
reducerActionToString(action: any) {
public reducerActionToString(action: any) {
const o = [action.type];
if ('id' in action) o.push(action.id);
if ('noteId' in action) o.push(action.noteId);

View File

@ -85,19 +85,19 @@ class BaseModel {
private static db_: JoplinDatabase;
static modelType(): ModelType {
public static modelType(): ModelType {
throw new Error('Must be overriden');
}
static tableName(): string {
public static tableName(): string {
throw new Error('Must be overriden');
}
static setDb(db: any) {
public static setDb(db: any) {
this.db_ = db;
}
static addModelMd(model: any): any {
public static addModelMd(model: any): any {
if (!model) return model;
if (Array.isArray(model)) {
@ -113,22 +113,22 @@ class BaseModel {
}
}
static logger() {
public static logger() {
return this.db().logger();
}
static useUuid() {
public static useUuid() {
return false;
}
static byId(items: any[], id: string) {
public static byId(items: any[], id: string) {
for (let i = 0; i < items.length; i++) {
if (items[i].id === id) return items[i];
}
return null;
}
static defaultValues(fieldNames: string[]) {
public static defaultValues(fieldNames: string[]) {
const output: any = {};
for (const n of fieldNames) {
output[n] = this.db().fieldDefaultValue(this.tableName(), n);
@ -136,14 +136,14 @@ class BaseModel {
return output;
}
static modelIndexById(items: any[], id: string) {
public static modelIndexById(items: any[], id: string) {
for (let i = 0; i < items.length; i++) {
if (items[i].id === id) return i;
}
return -1;
}
static modelsByIds(items: any[], ids: string[]) {
public static modelsByIds(items: any[], ids: string[]) {
const output = [];
for (let i = 0; i < items.length; i++) {
if (ids.indexOf(items[i].id) >= 0) {
@ -155,14 +155,14 @@ class BaseModel {
// Prefer the use of this function to compare IDs as it handles the case where
// one ID is null and the other is "", in which case they are actually considered to be the same.
static idsEqual(id1: string, id2: string) {
public static idsEqual(id1: string, id2: string) {
if (!id1 && !id2) return true;
if (!id1 && !!id2) return false;
if (!!id1 && !id2) return false;
return id1 === id2;
}
static modelTypeToName(type: number) {
public static modelTypeToName(type: number) {
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
const e = BaseModel.typeEnum_[i];
if (e[1] === type) return e[0].substr(5).toLowerCase();
@ -170,7 +170,7 @@ class BaseModel {
throw new Error(`Unknown model type: ${type}`);
}
static modelNameToType(name: string) {
public static modelNameToType(name: string) {
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
const e = BaseModel.typeEnum_[i];
const eName = e[0].substr(5).toLowerCase();
@ -179,12 +179,12 @@ class BaseModel {
throw new Error(`Unknown model name: ${name}`);
}
static hasField(name: string) {
public static hasField(name: string) {
const fields = this.fieldNames();
return fields.indexOf(name) >= 0;
}
static fieldNames(withPrefix: boolean = false) {
public static fieldNames(withPrefix: boolean = false) {
const output = this.db().tableFieldNames(this.tableName());
if (!withPrefix) return output;
@ -197,7 +197,7 @@ class BaseModel {
return temp;
}
static fieldType(name: string, defaultValue: any = null) {
public static fieldType(name: string, defaultValue: any = null) {
const fields = this.fields();
for (let i = 0; i < fields.length; i++) {
if (fields[i].name === name) return fields[i].type;
@ -206,11 +206,11 @@ class BaseModel {
throw new Error(`Unknown field: ${name}`);
}
static fields(): TableField[] {
public static fields(): TableField[] {
return this.db().tableFields(this.tableName());
}
static removeUnknownFields(model: any) {
public static removeUnknownFields(model: any) {
const newModel: any = {};
for (const n in model) {
if (!model.hasOwnProperty(n)) continue;
@ -220,7 +220,7 @@ class BaseModel {
return newModel;
}
static new() {
public static new() {
const fields = this.fields();
const output: any = {};
for (let i = 0; i < fields.length; i++) {
@ -230,7 +230,7 @@ class BaseModel {
return output;
}
static modOptions(options: any) {
public static modOptions(options: any) {
if (!options) {
options = {};
} else {
@ -242,7 +242,7 @@ class BaseModel {
return options;
}
static count(options: any = null) {
public static count(options: any = null) {
if (!options) options = {};
let sql = `SELECT count(*) as total FROM \`${this.tableName()}\``;
if (options.where) sql += ` WHERE ${options.where}`;
@ -254,19 +254,19 @@ class BaseModel {
});
}
static load(id: string, options: any = null) {
public static load(id: string, options: any = null) {
return this.loadByField('id', id, options);
}
static shortId(id: string) {
public static shortId(id: string) {
return id.substr(0, 5);
}
static loadByPartialId(partialId: string) {
public static loadByPartialId(partialId: string) {
return this.modelSelectAll(`SELECT * FROM \`${this.tableName()}\` WHERE \`id\` LIKE ?`, [`${partialId}%`]);
}
static applySqlOptions(options: any, sql: string, params: any[] = null) {
public static applySqlOptions(options: any, sql: string, params: any[] = null) {
if (!options) options = {};
if (options.order && options.order.length) {
@ -278,13 +278,13 @@ class BaseModel {
return { sql: sql, params: params };
}
static async allIds(options: any = null) {
public static async allIds(options: any = null) {
const q = this.applySqlOptions(options, `SELECT id FROM \`${this.tableName()}\``);
const rows = await this.db().selectAll(q.sql, q.params);
return rows.map((r: any) => r.id);
}
static async all(options: any = null) {
public static async all(options: any = null) {
if (!options) options = {};
if (!options.fields) options.fields = '*';
@ -299,7 +299,7 @@ class BaseModel {
return this.modelSelectAll(q.sql, q.params);
}
static async byIds(ids: string[], options: any = null) {
public static async byIds(ids: string[], options: any = null) {
if (!ids.length) return [];
if (!options) options = {};
if (!options.fields) options.fields = '*';
@ -310,7 +310,7 @@ class BaseModel {
return this.modelSelectAll(q.sql);
}
static async search(options: any = null) {
public static async search(options: any = null) {
if (!options) options = {};
if (!options.fields) options.fields = '*';
@ -332,7 +332,7 @@ class BaseModel {
return this.modelSelectAll(query.sql, query.params);
}
static modelSelectOne(sql: string, params: any[] = null) {
public static modelSelectOne(sql: string, params: any[] = null) {
if (params === null) params = [];
return this.db()
.selectOne(sql, params)
@ -342,7 +342,7 @@ class BaseModel {
});
}
static modelSelectAll(sql: string, params: any[] = null) {
public static modelSelectAll(sql: string, params: any[] = null) {
if (params === null) params = [];
return this.db()
.selectAll(sql, params)
@ -352,7 +352,7 @@ class BaseModel {
});
}
static loadByField(fieldName: string, fieldValue: any, options: any = null) {
public static loadByField(fieldName: string, fieldValue: any, options: any = null) {
if (!options) options = {};
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
if (!options.fields) options.fields = '*';
@ -361,7 +361,7 @@ class BaseModel {
return this.modelSelectOne(sql, [fieldValue]);
}
static loadByFields(fields: any, options: any = null) {
public static loadByFields(fields: any, options: any = null) {
if (!options) options = {};
if (!('caseInsensitive' in options)) options.caseInsensitive = false;
if (!options.fields) options.fields = '*';
@ -376,11 +376,11 @@ class BaseModel {
return this.modelSelectOne(sql, params);
}
static loadByTitle(fieldValue: any) {
public static loadByTitle(fieldValue: any) {
return this.modelSelectOne(`SELECT * FROM \`${this.tableName()}\` WHERE \`title\` = ?`, [fieldValue]);
}
static diffObjects(oldModel: any, newModel: any) {
public static diffObjects(oldModel: any, newModel: any) {
const output: any = {};
const fields = this.diffObjectsFields(oldModel, newModel);
for (let i = 0; i < fields.length; i++) {
@ -390,7 +390,7 @@ class BaseModel {
return output;
}
static diffObjectsFields(oldModel: any, newModel: any) {
public static diffObjectsFields(oldModel: any, newModel: any) {
const output = [];
for (const n in newModel) {
if (!newModel.hasOwnProperty(n)) continue;
@ -402,13 +402,13 @@ class BaseModel {
return output;
}
static modelsAreSame(oldModel: any, newModel: any) {
public static modelsAreSame(oldModel: any, newModel: any) {
const diff = this.diffObjects(oldModel, newModel);
delete diff.type_;
return !Object.getOwnPropertyNames(diff).length;
}
static saveMutex(modelOrId: any) {
public static saveMutex(modelOrId: any) {
const noLockMutex = {
acquire: function(): any {
return null;
@ -429,7 +429,7 @@ class BaseModel {
return mutex;
}
static releaseSaveMutex(modelOrId: any, release: Function) {
public static releaseSaveMutex(modelOrId: any, release: Function) {
if (!release) return;
if (!modelOrId) return release();
@ -444,7 +444,7 @@ class BaseModel {
release();
}
static saveQuery(o: any, options: any) {
public static saveQuery(o: any, options: any) {
let temp: any = {};
const fieldNames = this.fieldNames();
for (let i = 0; i < fieldNames.length; i++) {
@ -521,7 +521,7 @@ class BaseModel {
return query;
}
static userSideValidation(o: any) {
public static userSideValidation(o: any) {
if (o.id && !o.id.match(/^[a-f0-9]{32}$/)) {
throw new Error('Validation error: ID must a 32-characters lowercase hexadecimal string');
}
@ -532,7 +532,7 @@ class BaseModel {
}
}
static async save(o: any, options: any = null) {
public static async save(o: any, options: any = null) {
// When saving, there's a mutex per model ID. This is because the model returned from this function
// is basically its input `o` (instead of being read from the database, for performance reasons).
// This works well in general except if that model is saved simultaneously in two places. In that
@ -602,7 +602,7 @@ class BaseModel {
return output;
}
static isNew(object: any, options: any) {
public static isNew(object: any, options: any) {
if (options && 'isNew' in options) {
// options.isNew can be "auto" too
if (options.isNew === true) return true;
@ -612,7 +612,7 @@ class BaseModel {
return !object.id;
}
static filterArray(models: any[]) {
public static filterArray(models: any[]) {
const output = [];
for (let i = 0; i < models.length; i++) {
output.push(this.filter(models[i]));
@ -620,7 +620,7 @@ class BaseModel {
return output;
}
static filter(model: any) {
public static filter(model: any) {
if (!model) return model;
const output = Object.assign({}, model);
@ -643,12 +643,12 @@ class BaseModel {
return output;
}
static delete(id: string) {
public static delete(id: string) {
if (!id) throw new Error('Cannot delete object without an ID');
return this.db().exec(`DELETE FROM ${this.tableName()} WHERE id = ?`, [id]);
}
static async batchDelete(ids: string[], options: DeleteOptions = null) {
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
if (!ids.length) return;
options = this.modOptions(options);
const idFieldName = options.idFieldName ? options.idFieldName : 'id';
@ -656,7 +656,7 @@ class BaseModel {
await this.db().exec(sql);
}
static db() {
public static db() {
if (!this.db_) throw new Error('Accessing database before it has been initialised');
return this.db_;
}

View File

@ -24,11 +24,11 @@ export default class ClipperServer {
private static instance_: ClipperServer = null;
constructor() {
public constructor() {
this.logger_ = new Logger();
}
static instance() {
public static instance() {
if (this.instance_) return this.instance_;
this.instance_ = new ClipperServer();
return this.instance_;
@ -38,30 +38,30 @@ export default class ClipperServer {
return this.api_;
}
initialize(actionApi: any = null) {
public initialize(actionApi: any = null) {
this.api_ = new Api(() => {
return Setting.value('api.token');
}, (action: any) => { this.dispatch(action); }, actionApi);
}
setLogger(l: Logger) {
public setLogger(l: Logger) {
this.logger_ = l;
}
logger() {
public logger() {
return this.logger_;
}
setDispatch(d: Function) {
public setDispatch(d: Function) {
this.dispatch_ = d;
}
dispatch(action: any) {
public dispatch(action: any) {
if (!this.dispatch_) throw new Error('dispatch not set!');
this.dispatch_(action);
}
setStartState(v: StartState) {
public setStartState(v: StartState) {
if (this.startState_ === v) return;
this.startState_ = v;
this.dispatch({
@ -70,7 +70,7 @@ export default class ClipperServer {
});
}
setPort(v: number) {
public setPort(v: number) {
if (this.port_ === v) return;
this.port_ = v;
this.dispatch({
@ -79,7 +79,7 @@ export default class ClipperServer {
});
}
async findAvailablePort() {
public async findAvailablePort() {
const tcpPortUsed = require('tcp-port-used');
let state = null;
@ -92,14 +92,14 @@ export default class ClipperServer {
throw new Error('All potential ports are in use or not available.');
}
async isRunning() {
public async isRunning() {
const tcpPortUsed = require('tcp-port-used');
const port = Setting.value('api.port') ? Setting.value('api.port') : startPort(Setting.value('env'));
const inUse = await tcpPortUsed.check(port);
return inUse ? port : 0;
}
async start() {
public async start() {
this.setPort(null);
this.setStartState(StartState.Starting);
@ -237,7 +237,7 @@ export default class ClipperServer {
return new Promise(() => {});
}
async stop() {
public async stop() {
this.server_.destroy();
this.server_ = null;
this.setStartState(StartState.Idle);

View File

@ -29,7 +29,7 @@ export default class Cache {
private expirableKeys_: ExpirableKeys = {};
private recordKeyHistory_: string[] = [];
constructor(maxRecords: number = 50) {
public constructor(maxRecords: number = 50) {
this.maxRecords_ = maxRecords;
}

View File

@ -63,7 +63,7 @@ class Logger {
private lastDbCleanup_: number = time.unixMs();
private enabled_: boolean = true;
static fsDriver() {
public static fsDriver() {
if (!Logger.fsDriver_) Logger.fsDriver_ = new FsDriverDummy();
return Logger.fsDriver_;
}
@ -103,7 +103,7 @@ class Logger {
return this.globalLogger_;
}
static create(prefix: string): LoggerWrapper {
public static create(prefix: string): LoggerWrapper {
return {
debug: (...object: any[]) => this.globalLogger.log(LogLevel.Debug, prefix, ...object),
info: (...object: any[]) => this.globalLogger.log(LogLevel.Info, prefix, ...object),
@ -118,15 +118,15 @@ class Logger {
return previous;
}
level() {
public level() {
return this.level_;
}
targets() {
public targets() {
return this.targets_;
}
addTarget(type: TargetType, options: TargetOptions = null) {
public addTarget(type: TargetType, options: TargetOptions = null) {
const target = { type: type };
for (const n in options) {
if (!options.hasOwnProperty(n)) continue;
@ -136,7 +136,7 @@ class Logger {
this.targets_.push(target);
}
objectToString(object: any) {
public objectToString(object: any) {
let output = '';
if (typeof object === 'object') {
@ -157,7 +157,7 @@ class Logger {
return output;
}
objectsToString(...object: any[]) {
public objectsToString(...object: any[]) {
const output = [];
for (let i = 0; i < object.length; i++) {
output.push(`"${this.objectToString(object[i])}"`);
@ -165,7 +165,7 @@ class Logger {
return output.join(', ');
}
static databaseCreateTableSql() {
public static databaseCreateTableSql() {
const output = `
CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
@ -179,7 +179,7 @@ class Logger {
}
// Only for database at the moment
async lastEntries(limit: number = 100, options: any = null) {
public async lastEntries(limit: number = 100, options: any = null) {
if (options === null) options = {};
if (!options.levels) options.levels = [LogLevel.Debug, LogLevel.Info, LogLevel.Warn, LogLevel.Error];
if (!options.levels.length) return [];
@ -195,7 +195,7 @@ class Logger {
return [];
}
targetLevel(target: Target) {
public targetLevel(target: Target) {
if ('level' in target) return target.level;
return this.level();
}
@ -287,20 +287,20 @@ class Logger {
}
}
error(...object: any[]) {
public error(...object: any[]) {
return this.log(LogLevel.Error, null, ...object);
}
warn(...object: any[]) {
public warn(...object: any[]) {
return this.log(LogLevel.Warn, null, ...object);
}
info(...object: any[]) {
public info(...object: any[]) {
return this.log(LogLevel.Info, null, ...object);
}
debug(...object: any[]) {
public debug(...object: any[]) {
return this.log(LogLevel.Debug, null, ...object);
}
static levelStringToId(s: string) {
public static levelStringToId(s: string) {
if (s === 'none') return LogLevel.None;
if (s === 'error') return LogLevel.Error;
if (s === 'warn') return LogLevel.Warn;
@ -309,7 +309,7 @@ class Logger {
throw new Error(`Unknown log level: ${s}`);
}
static levelIdToString(id: LogLevel) {
public static levelIdToString(id: LogLevel) {
if (id === LogLevel.None) return 'none';
if (id === LogLevel.Error) return 'error';
if (id === LogLevel.Warn) return 'warn';
@ -318,7 +318,7 @@ class Logger {
throw new Error(`Unknown level ID: ${id}`);
}
static levelIds() {
public static levelIds() {
return [LogLevel.None, LogLevel.Error, LogLevel.Warn, LogLevel.Info, LogLevel.Debug];
}

View File

@ -12,20 +12,20 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
private api_: any;
static id() {
public static id() {
return 3;
}
constructor(db: any, options: any = null) {
public constructor(db: any, options: any = null) {
super(db, options);
this.api_ = null;
}
static targetName() {
public static targetName() {
return 'onedrive';
}
static label() {
public static label() {
return _('OneDrive');
}
@ -37,30 +37,30 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
return false;
}
async isAuthenticated() {
public async isAuthenticated() {
return !!this.api().auth();
}
syncTargetId() {
public syncTargetId() {
return SyncTargetOneDrive.id();
}
isTesting() {
public isTesting() {
const p = parameters();
return !!p.oneDriveTest;
}
oneDriveParameters() {
public oneDriveParameters() {
const p = parameters();
if (p.oneDriveTest) return p.oneDriveTest;
return p.oneDrive;
}
authRouteName() {
public authRouteName() {
return 'OneDriveLogin';
}
api() {
public api() {
if (this.isTesting()) {
return this.fileApi_.driver().api();
}
@ -92,7 +92,7 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
return this.api_;
}
async initFileApi() {
public async initFileApi() {
let context = Setting.value(`sync.${this.syncTargetId()}.context`);
context = context === '' ? null : JSON.parse(context);
let accountProperties = context ? context.accountProperties : null;
@ -109,7 +109,7 @@ export default class SyncTargetOneDrive extends BaseSyncTarget {
return fileApi;
}
async initSynchronizer() {
public async initSynchronizer() {
try {
if (!(await this.isAuthenticated())) throw new Error('User is not authentified');
return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType'));

View File

@ -91,31 +91,31 @@ export default class Synchronizer {
this.apiCall = this.apiCall.bind(this);
}
state() {
public state() {
return this.state_;
}
db() {
public db() {
return this.db_;
}
api() {
public api() {
return this.api_;
}
clientId() {
public clientId() {
return this.clientId_;
}
setLogger(l: Logger) {
public setLogger(l: Logger) {
this.logger_ = l;
}
logger() {
public logger() {
return this.logger_;
}
lockHandler() {
public lockHandler() {
if (this.lockHandler_) return this.lockHandler_;
this.lockHandler_ = new LockHandler(this.api());
return this.lockHandler_;
@ -127,13 +127,13 @@ export default class Synchronizer {
return this.lockClientType_;
}
migrationHandler() {
public migrationHandler() {
if (this.migrationHandler_) return this.migrationHandler_;
this.migrationHandler_ = new MigrationHandler(this.api(), this.db(), this.lockHandler(), this.lockClientType(), this.clientId_);
return this.migrationHandler_;
}
maxResourceSize() {
public maxResourceSize() {
if (this.maxResourceSize_ !== null) return this.maxResourceSize_;
return this.appType_ === AppType.Mobile ? 100 * 1000 * 1000 : Infinity;
}
@ -146,7 +146,7 @@ export default class Synchronizer {
this.encryptionService_ = v;
}
encryptionService() {
public encryptionService() {
return this.encryptionService_;
}
@ -158,7 +158,7 @@ export default class Synchronizer {
return this.resourceService_;
}
async waitForSyncToFinish() {
public async waitForSyncToFinish() {
if (this.state() === 'idle') return;
while (true) {
@ -177,7 +177,7 @@ export default class Synchronizer {
return `${duration}ms`;
}
static reportToLines(report: any) {
public 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));
@ -193,7 +193,7 @@ export default class Synchronizer {
return lines;
}
logSyncOperation(action: string, local: any = null, remote: RemoteItem = null, message: string = null, actionCount: number = 1) {
public logSyncOperation(action: string, local: any = null, remote: RemoteItem = null, message: string = null, actionCount: number = 1) {
const line = ['Sync'];
line.push(action);
if (message) line.push(message);
@ -237,7 +237,7 @@ export default class Synchronizer {
this.dispatch({ type: 'SYNC_REPORT_UPDATE', report: reportCopy });
}
async logSyncSummary(report: any) {
public async logSyncSummary(report: any) {
logger.info('Operations completed: ');
for (const n in report) {
if (!report.hasOwnProperty(n)) continue;
@ -265,7 +265,7 @@ export default class Synchronizer {
}
}
async cancel() {
public async cancel() {
if (this.cancelling_ || this.state() === 'idle') return;
// Stop queue but don't set it to null as it may be used to
@ -285,11 +285,11 @@ export default class Synchronizer {
});
}
cancelling() {
public cancelling() {
return this.cancelling_;
}
logLastRequests() {
public logLastRequests() {
const lastRequests = this.api().lastRequests();
if (!lastRequests || !lastRequests.length) return;
@ -300,17 +300,17 @@ export default class Synchronizer {
}
}
static stateToLabel(state: string) {
public static stateToLabel(state: string) {
if (state === 'idle') return _('Idle');
if (state === 'in_progress') return _('In progress');
return state;
}
isFullSync(steps: string[]) {
public isFullSync(steps: string[]) {
return steps.includes('update_remote') && steps.includes('delete_remote') && steps.includes('delta');
}
async lockErrorStatus_() {
private async lockErrorStatus_() {
const locks = await this.lockHandler().locks();
const currentDate = await this.lockHandler().currentDate();

View File

@ -23,16 +23,16 @@ export default class TaskQueue {
private name_: string;
private logger_: Logger;
constructor(name: string, logger: Logger = null) {
public constructor(name: string, logger: Logger = null) {
this.name_ = name;
this.logger_ = logger ? logger : new Logger();
}
concurrency() {
public concurrency() {
return Setting.value('sync.maxConcurrentConnections');
}
push(id: string, callback: Function) {
public push(id: string, callback: Function) {
if (this.stopping_) throw new Error('Cannot push task when queue is stopping');
this.waitingTasks_.push({
@ -42,7 +42,7 @@ export default class TaskQueue {
this.processQueue_();
}
processQueue_() {
private processQueue_() {
if (this.processingQueue_ || this.stopping_) return;
this.processingQueue_ = true;
@ -84,19 +84,19 @@ export default class TaskQueue {
this.processingQueue_ = false;
}
isWaiting(taskId: string) {
public isWaiting(taskId: string) {
return this.waitingTasks_.find(task => task.id === taskId);
}
isProcessing(taskId: string) {
public isProcessing(taskId: string) {
return taskId in this.processingTasks_;
}
isDone(taskId: string) {
public isDone(taskId: string) {
return taskId in this.results_;
}
async waitForAll() {
public async waitForAll() {
return new Promise((resolve) => {
const checkIID = setInterval(() => {
if (this.waitingTasks_.length) return;
@ -107,16 +107,16 @@ export default class TaskQueue {
});
}
taskExists(taskId: string) {
public taskExists(taskId: string) {
return this.isWaiting(taskId) || this.isProcessing(taskId) || this.isDone(taskId);
}
taskResult(taskId: string) {
public taskResult(taskId: string) {
if (!this.taskExists(taskId)) throw new Error(`No such task: ${taskId}`);
return this.results_[taskId];
}
async waitForResult(taskId: string) {
public async waitForResult(taskId: string) {
if (!this.taskExists(taskId)) throw new Error(`No such task: ${taskId}`);
while (true) {
@ -126,7 +126,7 @@ export default class TaskQueue {
}
}
async stop() {
public async stop() {
this.stopping_ = true;
this.logger_.info(`TaskQueue.stop: ${this.name_}: waiting for tasks to complete: ${Object.keys(this.processingTasks_).length}`);
@ -146,7 +146,7 @@ export default class TaskQueue {
this.logger_.info(`TaskQueue.stop: ${this.name_}: Done, waited for ${Date.now() - startTime}`);
}
isStopping() {
public isStopping() {
return this.stopping_;
}
}

View File

@ -46,7 +46,7 @@ export default class DatabaseDriverBetterSqlite {
return this.db_.prepare(sql).run(params ? params : []);
}
lastInsertId() {
public lastInsertId() {
throw new Error('NOT IMPLEMENTED');
}
}

View File

@ -35,30 +35,30 @@ export default class Database {
this.driver_ = driver;
}
setLogExcludedQueryTypes(v: string[]) {
public setLogExcludedQueryTypes(v: string[]) {
this.logExcludedQueryTypes_ = v;
}
// Converts the SQLite error to a regular JS error
// so that it prints a stacktrace when passed to
// console.error()
sqliteErrorToJsError(error: any, sql: string = null, params: SqlParams = null) {
public sqliteErrorToJsError(error: any, sql: string = null, params: SqlParams = null) {
return this.driver().sqliteErrorToJsError(error, sql, params);
}
setLogger(l: Logger) {
public setLogger(l: Logger) {
this.logger_ = l;
}
logger() {
public logger() {
return this.logger_;
}
driver() {
public driver() {
return this.driver_;
}
async open(options: any) {
public async open(options: any) {
try {
await this.driver().open(options);
} catch (error) {
@ -97,7 +97,7 @@ export default class Database {
return output.join(',');
}
async tryCall(callName: string, inputSql: StringOrSqlQuery, inputParams: SqlParams) {
public async tryCall(callName: string, inputSql: StringOrSqlQuery, inputParams: SqlParams) {
let sql: string = null;
let params: SqlParams = null;
@ -157,11 +157,11 @@ export default class Database {
}
}
async selectOne(sql: string, params: SqlParams = null): Promise<Row> {
public async selectOne(sql: string, params: SqlParams = null): Promise<Row> {
return this.tryCall('selectOne', sql, params);
}
async loadExtension(/* path */) {
public async loadExtension(/* path */) {
return; // Disabled for now as fuzzy search extension is not in use
// let result = null;
@ -173,11 +173,11 @@ export default class Database {
// }
}
async selectAll(sql: string, params: SqlParams = null): Promise<Row[]> {
public async selectAll(sql: string, params: SqlParams = null): Promise<Row[]> {
return this.tryCall('selectAll', sql, params);
}
async selectAllFields(sql: string, params: SqlParams, field: string): Promise<any[]> {
public async selectAllFields(sql: string, params: SqlParams, field: string): Promise<any[]> {
const rows = await this.tryCall('selectAll', sql, params);
const output = [];
for (let i = 0; i < rows.length; i++) {
@ -188,11 +188,11 @@ export default class Database {
return output;
}
async exec(sql: StringOrSqlQuery, params: SqlParams = null) {
public async exec(sql: StringOrSqlQuery, params: SqlParams = null) {
return this.tryCall('exec', sql, params);
}
async transactionExecBatch(queries: StringOrSqlQuery[]) {
public async transactionExecBatch(queries: StringOrSqlQuery[]) {
if (queries.length <= 0) return;
if (queries.length === 1) {
@ -221,7 +221,7 @@ export default class Database {
}
}
static enumId(type: string, s: string) {
public static enumId(type: string, s: string) {
if (type === 'settings') {
if (s === 'int') return 1;
if (s === 'string') return 2;
@ -240,7 +240,7 @@ export default class Database {
throw new Error(`Unknown enum type or value: ${type}, ${s}`);
}
static enumName(type: string, id: number) {
public static enumName(type: string, id: number) {
if (type === 'fieldType') {
if (id === Database.TYPE_UNKNOWN) return 'unknown';
if (id === Database.TYPE_INT) return 'int';
@ -253,7 +253,7 @@ export default class Database {
return undefined;
}
static formatValue(type: number, value: any) {
public static formatValue(type: number, value: any) {
if (value === null || value === undefined) return null;
if (type === this.TYPE_INT) return Number(value);
if (type === this.TYPE_TEXT) return value;
@ -261,7 +261,7 @@ export default class Database {
throw new Error(`Unknown type: ${type}`);
}
sqlStringToLines(sql: string) {
public sqlStringToLines(sql: string) {
const output = [];
const lines = sql.split('\n');
let statement = '';
@ -279,7 +279,7 @@ export default class Database {
return output;
}
logQuery(sql: string, params: SqlParams = null) {
public logQuery(sql: string, params: SqlParams = null) {
if (!this.sqlQueryLogEnabled_) return;
if (this.logExcludedQueryTypes_.length) {
@ -293,7 +293,7 @@ export default class Database {
if (params !== null && params.length) this.logger().debug(JSON.stringify(params));
}
static insertQuery(tableName: string, data: Record<string, any>) {
public static insertQuery(tableName: string, data: Record<string, any>) {
if (!data || !Object.keys(data).length) throw new Error('Data is empty');
let keySql = '';
@ -314,7 +314,7 @@ export default class Database {
};
}
static updateQuery(tableName: string, data: Record<string, any>, where: string | Record<string, any>) {
public static updateQuery(tableName: string, data: Record<string, any>, where: string | Record<string, any>) {
if (!data || !Object.keys(data).length) throw new Error('Data is empty');
let sql = '';
@ -343,7 +343,7 @@ export default class Database {
};
}
alterColumnQueries(tableName: string, fields: Record<string, string>) {
public alterColumnQueries(tableName: string, fields: Record<string, string>) {
const fieldsNoType = [];
for (const n in fields) {
if (!fields.hasOwnProperty(n)) continue;
@ -373,7 +373,7 @@ export default class Database {
return sql.trim().split('\n');
}
wrapQueries(queries: any[]) {
public wrapQueries(queries: any[]) {
const output = [];
for (let i = 0; i < queries.length; i++) {
output.push(this.wrapQuery(queries[i]));
@ -381,7 +381,7 @@ export default class Database {
return output;
}
wrapQuery(sql: any, params: SqlParams = null): SqlQuery {
public wrapQuery(sql: any, params: SqlParams = null): SqlQuery {
if (!sql) throw new Error(`Cannot wrap empty string: ${sql}`);
if (Array.isArray(sql)) {

View File

@ -9,11 +9,11 @@ export class EventManager {
private appStateWatchedProps_: string[];
private appStateListeners_: any;
constructor() {
public constructor() {
this.reset();
}
reset() {
public reset() {
this.emitter_ = new events.EventEmitter();
this.appStatePrevious_ = {};
@ -21,27 +21,27 @@ export class EventManager {
this.appStateListeners_ = {};
}
on(eventName: string, callback: Function) {
public on(eventName: string, callback: Function) {
return this.emitter_.on(eventName, callback);
}
emit(eventName: string, object: any = null) {
public emit(eventName: string, object: any = null) {
return this.emitter_.emit(eventName, object);
}
removeListener(eventName: string, callback: Function) {
public removeListener(eventName: string, callback: Function) {
return this.emitter_.removeListener(eventName, callback);
}
off(eventName: string, callback: Function) {
public off(eventName: string, callback: Function) {
return this.removeListener(eventName, callback);
}
filterOn(filterName: string, callback: Function) {
public filterOn(filterName: string, callback: Function) {
return this.emitter_.on(`filter:${filterName}`, callback);
}
filterOff(filterName: string, callback: Function) {
public filterOff(filterName: string, callback: Function) {
return this.removeListener(`filter:${filterName}`, callback);
}
@ -67,7 +67,7 @@ export class EventManager {
return output;
}
appStateOn(propName: string, callback: Function) {
public appStateOn(propName: string, callback: Function) {
if (!this.appStateListeners_[propName]) {
this.appStateListeners_[propName] = [];
this.appStateWatchedProps_.push(propName);
@ -76,7 +76,7 @@ export class EventManager {
this.appStateListeners_[propName].push(callback);
}
appStateOff(propName: string, callback: Function) {
public 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)');
}
@ -87,7 +87,7 @@ export class EventManager {
this.appStateListeners_[propName].splice(idx, 1);
}
stateValue_(state: any, propName: string) {
private stateValue_(state: any, propName: string) {
const parts = propName.split('.');
let s = state;
for (const p of parts) {
@ -100,7 +100,7 @@ export class EventManager {
// 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) {
public appStateEmit(state: any) {
if (!this.appStateWatchedProps_.length) return;
for (const propName of this.appStateWatchedProps_) {

View File

@ -7,12 +7,12 @@ export default class FileApiDriverMemory {
private items_: any[];
private deletedItems_: any[];
constructor() {
public constructor() {
this.items_ = [];
this.deletedItems_ = [];
}
encodeContent_(content: any) {
private encodeContent_(content: any) {
if (content instanceof Buffer) {
return content.toString('base64');
} else {
@ -28,23 +28,23 @@ export default class FileApiDriverMemory {
return true;
}
decodeContent_(content: any) {
private decodeContent_(content: any) {
return Buffer.from(content, 'base64').toString('utf-8');
}
itemIndexByPath(path: string) {
public itemIndexByPath(path: string) {
for (let i = 0; i < this.items_.length; i++) {
if (this.items_[i].path === path) return i;
}
return -1;
}
itemByPath(path: string) {
public itemByPath(path: string) {
const index = this.itemIndexByPath(path);
return index < 0 ? null : this.items_[index];
}
newItem(path: string, isDir = false) {
public newItem(path: string, isDir = false) {
const now = time.unixMs();
return {
path: path,
@ -55,18 +55,18 @@ export default class FileApiDriverMemory {
};
}
stat(path: string) {
public stat(path: string) {
const item = this.itemByPath(path);
return Promise.resolve(item ? Object.assign({}, item) : null);
}
async setTimestamp(path: string, timestampMs: number): Promise<any> {
public async setTimestamp(path: string, timestampMs: number): Promise<any> {
const item = this.itemByPath(path);
if (!item) return Promise.reject(new Error(`File not found: ${path}`));
item.updated_time = timestampMs;
}
async list(path: string) {
public async list(path: string) {
const output = [];
for (let i = 0; i < this.items_.length; i++) {
@ -89,7 +89,7 @@ export default class FileApiDriverMemory {
});
}
async get(path: string, options: any) {
public async get(path: string, options: any) {
const item = this.itemByPath(path);
if (!item) return Promise.resolve(null);
if (item.isDir) return Promise.reject(new Error(`${path} is a directory, not a file`));
@ -105,13 +105,13 @@ export default class FileApiDriverMemory {
return output;
}
async mkdir(path: string) {
public async mkdir(path: string) {
const index = this.itemIndexByPath(path);
if (index >= 0) return;
this.items_.push(this.newItem(path, true));
}
async put(path: string, content: any, options: any = null) {
public async put(path: string, content: any, options: any = null) {
if (!options) options = {};
if (options.source === 'file') content = await fs.readFile(options.path);
@ -152,7 +152,7 @@ export default class FileApiDriverMemory {
return output;
}
async delete(path: string) {
public async delete(path: string) {
const index = this.itemIndexByPath(path);
if (index >= 0) {
const item = Object.assign({}, this.items_[index]);
@ -163,18 +163,18 @@ export default class FileApiDriverMemory {
}
}
async move(oldPath: string, newPath: string): Promise<any> {
public async move(oldPath: string, newPath: string): Promise<any> {
const sourceItem = this.itemByPath(oldPath);
if (!sourceItem) return Promise.reject(new Error(`Path not found: ${oldPath}`));
await this.delete(newPath); // Overwrite if newPath already exists
sourceItem.path = newPath;
}
async format() {
public async format() {
this.items_ = [];
}
async delta(path: string, options: any = null) {
public async delta(path: string, options: any = null) {
const getStatFn = async (path: string) => {
const output = this.items_.slice();
for (let i = 0; i < output.length; i++) {
@ -189,7 +189,7 @@ export default class FileApiDriverMemory {
return output;
}
async clearRoot() {
public async clearRoot() {
this.items_ = [];
}
}

View File

@ -99,13 +99,13 @@ class FileApi {
private remoteDateMutex_ = new Mutex();
private initialized_ = false;
constructor(baseDir: string | Function, driver: any) {
public constructor(baseDir: string | Function, driver: any) {
this.baseDir_ = baseDir;
this.driver_ = driver;
this.driver_.fileApi_ = this;
}
async initialize() {
public async initialize() {
if (this.initialized_) return;
this.initialized_ = true;
if (this.driver_.initialize) return this.driver_.initialize(this.fullPath(''));
@ -135,7 +135,7 @@ class FileApi {
return !!this.driver().supportsLocks;
}
async fetchRemoteDateOffset_() {
private async fetchRemoteDateOffset_() {
const tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;
const startTime = Date.now();
await this.put(tempFile, 'timeCheck');
@ -161,7 +161,7 @@ class FileApi {
// Approximates the current time on the sync target. It caches the time offset to
// improve performance.
async remoteDate() {
public async remoteDate() {
const shouldSyncTime = () => {
return !this.remoteDateNextCheckTime_ || Date.now() > this.remoteDateNextCheckTime_;
};
@ -193,60 +193,60 @@ class FileApi {
// Ideally all requests repeating should be done at the FileApi level to remove duplicate code in the drivers, but
// historically some drivers (eg. OneDrive) are already handling request repeating, so this is optional, per driver,
// and it defaults to no repeating.
requestRepeatCount() {
public requestRepeatCount() {
if (this.requestRepeatCount_ !== null) return this.requestRepeatCount_;
if (this.driver_.requestRepeatCount) return this.driver_.requestRepeatCount();
return 0;
}
lastRequests() {
public lastRequests() {
return this.driver_.lastRequests ? this.driver_.lastRequests() : [];
}
clearLastRequests() {
public clearLastRequests() {
if (this.driver_.clearLastRequests) this.driver_.clearLastRequests();
}
baseDir() {
public baseDir() {
return typeof this.baseDir_ === 'function' ? this.baseDir_() : this.baseDir_;
}
tempDirName() {
public tempDirName() {
if (this.tempDirName_ === null) throw Error('Temp dir not set!');
return this.tempDirName_;
}
setTempDirName(v: string) {
public setTempDirName(v: string) {
this.tempDirName_ = v;
}
fsDriver() {
public fsDriver() {
return shim.fsDriver();
}
driver() {
public driver() {
return this.driver_;
}
setSyncTargetId(v: number) {
public setSyncTargetId(v: number) {
this.syncTargetId_ = v;
}
syncTargetId() {
public syncTargetId() {
if (this.syncTargetId_ === null) throw new Error('syncTargetId has not been set!!');
return this.syncTargetId_;
}
setLogger(l: Logger) {
public setLogger(l: Logger) {
if (!l) l = new Logger();
this.logger_ = l;
}
logger() {
public logger() {
return this.logger_;
}
fullPath(path: string) {
public fullPath(path: string) {
const output = [];
if (this.baseDir()) output.push(this.baseDir());
if (path) output.push(path);
@ -286,18 +286,18 @@ class FileApi {
}
// Deprectated
setTimestamp(path: string, timestampMs: number) {
public setTimestamp(path: string, timestampMs: number) {
logger.debug(`setTimestamp ${this.fullPath(path)}`);
return tryAndRepeat(() => this.driver_.setTimestamp(this.fullPath(path), timestampMs), this.requestRepeatCount());
// return this.driver_.setTimestamp(this.fullPath(path), timestampMs);
}
mkdir(path: string) {
public mkdir(path: string) {
logger.debug(`mkdir ${this.fullPath(path)}`);
return tryAndRepeat(() => this.driver_.mkdir(this.fullPath(path)), this.requestRepeatCount());
}
async stat(path: string) {
public async stat(path: string) {
logger.debug(`stat ${this.fullPath(path)}`);
const output = await tryAndRepeat(() => this.driver_.stat(this.fullPath(path)), this.requestRepeatCount());
@ -308,14 +308,14 @@ class FileApi {
}
// Returns UTF-8 encoded string by default, or a Response if `options.target = 'file'`
get(path: string, options: any = null) {
public get(path: string, options: any = null) {
if (!options) options = {};
if (!options.encoding) options.encoding = 'utf8';
logger.debug(`get ${this.fullPath(path)}`);
return tryAndRepeat(() => this.driver_.get(this.fullPath(path), options), this.requestRepeatCount());
}
async put(path: string, content: any, options: any = null) {
public async put(path: string, content: any, options: any = null) {
logger.debug(`put ${this.fullPath(path)}`, options);
if (options && options.source === 'file') {
@ -330,27 +330,27 @@ class FileApi {
return tryAndRepeat(() => this.driver_.multiPut(items, options), this.requestRepeatCount());
}
delete(path: string) {
public delete(path: string) {
logger.debug(`delete ${this.fullPath(path)}`);
return tryAndRepeat(() => this.driver_.delete(this.fullPath(path)), this.requestRepeatCount());
}
// Deprectated
move(oldPath: string, newPath: string) {
public move(oldPath: string, newPath: string) {
logger.debug(`move ${this.fullPath(oldPath)} => ${this.fullPath(newPath)}`);
return tryAndRepeat(() => this.driver_.move(this.fullPath(oldPath), this.fullPath(newPath)), this.requestRepeatCount());
}
// Deprectated
format() {
public format() {
return tryAndRepeat(() => this.driver_.format(), this.requestRepeatCount());
}
clearRoot() {
public clearRoot() {
return tryAndRepeat(() => this.driver_.clearRoot(this.baseDir()), this.requestRepeatCount());
}
delta(path: string, options: any = null) {
public delta(path: string, options: any = null) {
logger.debug(`delta ${this.fullPath(path)}`);
return tryAndRepeat(() => this.driver_.delta(this.fullPath(path), options), this.requestRepeatCount());
}

View File

@ -58,7 +58,7 @@ const geoipServices: Record<string, GeoipService> = {
};
export default class {
static async currentPosition(options: CurrentPositionOptions = null) {
public static async currentPosition(options: CurrentPositionOptions = null) {
if (!options) options = {};
for (const [serviceName, handler] of Object.entries(geoipServices)) {

View File

@ -10,23 +10,23 @@ export interface Notification {
}
export default class Alarm extends BaseModel {
static tableName() {
public static tableName() {
return 'alarms';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_ALARM;
}
static byNoteId(noteId: string) {
public static byNoteId(noteId: string) {
return this.modelSelectOne('SELECT * FROM alarms WHERE note_id = ?', [noteId]);
}
static async deleteExpiredAlarms() {
public static async deleteExpiredAlarms() {
return this.db().exec('DELETE FROM alarms WHERE trigger_time <= ?', [Date.now()]);
}
static async alarmIdsWithoutNotes() {
public 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: any) => {
@ -34,7 +34,7 @@ export default class Alarm extends BaseModel {
});
}
static async makeNotification(alarm: any, note: any = null): Promise<Notification> {
public static async makeNotification(alarm: any, note: any = null): Promise<Notification> {
if (!note) {
note = await Note.load(alarm.note_id);
} else if (!note.todo_due) {
@ -55,7 +55,7 @@ export default class Alarm extends BaseModel {
return output;
}
static async allDue() {
public static async allDue() {
return this.modelSelectAll('SELECT * FROM alarms WHERE trigger_time >= ?', [Date.now()]);
}
}

View File

@ -63,15 +63,15 @@ export default class BaseItem extends BaseModel {
public static SYNC_ITEM_LOCATION_REMOTE = 2;
static useUuid() {
public static useUuid() {
return true;
}
static encryptionSupported() {
public static encryptionSupported() {
return true;
}
static loadClass(className: string, classRef: any) {
public static loadClass(className: string, classRef: any) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].className === className) {
BaseItem.syncItemDefinitions_[i].classRef = classRef;
@ -82,7 +82,7 @@ export default class BaseItem extends BaseModel {
throw new Error(`Invalid class name: ${className}`);
}
static async findUniqueItemTitle(title: string, parentId: string = null) {
public static async findUniqueItemTitle(title: string, parentId: string = null) {
let counter = 1;
let titleToTry = title;
while (true) {
@ -106,7 +106,7 @@ export default class BaseItem extends BaseModel {
}
// Need to dynamically load the classes like this to avoid circular dependencies
static getClass(name: string) {
public static getClass(name: string) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].className === name) {
const classRef = BaseItem.syncItemDefinitions_[i].classRef;
@ -118,7 +118,7 @@ export default class BaseItem extends BaseModel {
throw new Error(`Invalid class name: ${name}`);
}
static getClassByItemType(itemType: ModelType) {
public static getClassByItemType(itemType: ModelType) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].type === itemType) {
return BaseItem.syncItemDefinitions_[i].classRef;
@ -128,7 +128,7 @@ export default class BaseItem extends BaseModel {
throw new Error(`Invalid item type: ${itemType}`);
}
static async syncedCount(syncTarget: number) {
public static async syncedCount(syncTarget: number) {
const ItemClass = this.itemClass(this.modelType());
const itemType = ItemClass.modelType();
// The fact that we don't check if the item_id still exist in the corresponding item table, means
@ -145,7 +145,7 @@ export default class BaseItem extends BaseModel {
else return `${itemOrId.id}.${extension}`;
}
static isSystemPath(path: string) {
public static isSystemPath(path: string) {
// 1b175bb38bba47baac22b0b47f778113.md
if (!path || !path.length) return false;
let p: any = path.split('/');
@ -155,7 +155,7 @@ export default class BaseItem extends BaseModel {
return p[0].length === 32 && p[1] === 'md';
}
static itemClass(item: any): any {
public static itemClass(item: any): any {
if (!item) throw new Error('Item cannot be null');
if (typeof item === 'object') {
@ -171,7 +171,7 @@ export default class BaseItem extends BaseModel {
}
// Returns the IDs of the items that have been synced at least once
static async syncedItemIds(syncTarget: number) {
public static async syncedItemIds(syncTarget: number) {
if (!syncTarget) throw new Error('No syncTarget specified');
const temp = await this.db().selectAll('SELECT item_id FROM sync_items WHERE sync_time > 0 AND sync_target = ?', [syncTarget]);
const output = [];
@ -181,12 +181,12 @@ export default class BaseItem extends BaseModel {
return output;
}
static async allSyncItems(syncTarget: number) {
public static async allSyncItems(syncTarget: number) {
const output = await this.db().selectAll('SELECT * FROM sync_items WHERE sync_target = ?', [syncTarget]);
return output;
}
static pathToId(path: string) {
public static pathToId(path: string) {
const p = path.split('/');
const s = p[p.length - 1].split('.');
let name: any = s[0];
@ -195,11 +195,11 @@ export default class BaseItem extends BaseModel {
return name[name.length - 1];
}
static loadItemByPath(path: string) {
public static loadItemByPath(path: string) {
return this.loadItemById(this.pathToId(path));
}
static async loadItemById(id: string) {
public static async loadItemById(id: string) {
const classes = this.syncItemClassNames();
for (let i = 0; i < classes.length; i++) {
const item = await this.getClass(classes[i]).load(id);
@ -208,7 +208,7 @@ export default class BaseItem extends BaseModel {
return null;
}
static async loadItemsByIds(ids: string[]) {
public static async loadItemsByIds(ids: string[]) {
if (!ids.length) return [];
const classes = this.syncItemClassNames();
@ -222,26 +222,26 @@ export default class BaseItem extends BaseModel {
return output;
}
static loadItemByField(itemType: number, field: string, value: any) {
public static loadItemByField(itemType: number, field: string, value: any) {
const ItemClass = this.itemClass(itemType);
return ItemClass.loadByField(field, value);
}
static loadItem(itemType: ModelType, id: string) {
public static loadItem(itemType: ModelType, id: string) {
const ItemClass = this.itemClass(itemType);
return ItemClass.load(id);
}
static deleteItem(itemType: ModelType, id: string) {
public static deleteItem(itemType: ModelType, id: string) {
const ItemClass = this.itemClass(itemType);
return ItemClass.delete(id);
}
static async delete(id: string, options: DeleteOptions = null) {
public static async delete(id: string, options: DeleteOptions = null) {
return this.batchDelete([id], options);
}
static async batchDelete(ids: string[], options: DeleteOptions = null) {
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
if (!options) options = {};
let trackDeleted = true;
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
@ -287,20 +287,20 @@ export default class BaseItem extends BaseModel {
// - Client 1 syncs with target 2 only => the note is *not* deleted from target 2 because no information
// that it was previously deleted exist (deleted_items entry has been deleted).
// The solution would be to permanently store the list of deleted items on each client.
static deletedItems(syncTarget: number) {
public static deletedItems(syncTarget: number) {
return this.db().selectAll('SELECT * FROM deleted_items WHERE sync_target = ?', [syncTarget]);
}
static async deletedItemCount(syncTarget: number) {
public static async deletedItemCount(syncTarget: number) {
const r = await this.db().selectOne('SELECT count(*) as total FROM deleted_items WHERE sync_target = ?', [syncTarget]);
return r['total'];
}
static remoteDeletedItem(syncTarget: number, itemId: string) {
public static remoteDeletedItem(syncTarget: number, itemId: string) {
return this.db().exec('DELETE FROM deleted_items WHERE item_id = ? AND sync_target = ?', [itemId, syncTarget]);
}
static serialize_format(propName: string, propValue: any) {
public static serialize_format(propName: string, propValue: any) {
if (['created_time', 'updated_time', 'sync_time', 'user_updated_time', 'user_created_time'].indexOf(propName) >= 0) {
if (!propValue) return '';
propValue = `${moment.unix(propValue / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS')}Z`;
@ -322,7 +322,7 @@ export default class BaseItem extends BaseModel {
.replace(/\r/g, '\\r');
}
static unserialize_format(type: ModelType, propName: string, propValue: any) {
public static unserialize_format(type: ModelType, propName: string, propValue: any) {
if (propName[propName.length - 1] === '_') return propValue; // Private property
const ItemClass = this.itemClass(type);
@ -350,7 +350,7 @@ export default class BaseItem extends BaseModel {
: propValue;
}
static async serialize(item: any, shownKeys: any[] = null) {
public static async serialize(item: any, shownKeys: any[] = null) {
if (shownKeys === null) {
shownKeys = this.itemClass(item).fieldNames();
shownKeys.push('type_');
@ -395,12 +395,12 @@ export default class BaseItem extends BaseModel {
return temp.join('\n\n');
}
static encryptionService() {
public static encryptionService() {
if (!this.encryptionService_) throw new Error('BaseItem.encryptionService_ is not set!!');
return this.encryptionService_;
}
static revisionService() {
public static revisionService() {
if (!this.revisionService_) throw new Error('BaseItem.revisionService_ is not set!!');
return this.revisionService_;
}
@ -460,7 +460,7 @@ export default class BaseItem extends BaseModel {
return ItemClass.serialize(reducedItem);
}
static async decrypt(item: any) {
public static async decrypt(item: any) {
if (!item.encryption_cipher_text) throw new Error(`Item is not encrypted: ${item.id}`);
const ItemClass = this.itemClass(item);
@ -474,7 +474,7 @@ export default class BaseItem extends BaseModel {
return ItemClass.save(plainItem, { autoTimestamp: false, changeSource: ItemChange.SOURCE_DECRYPTION });
}
static async unserialize(content: string) {
public static async unserialize(content: string) {
const lines = content.split('\n');
let output: any = {};
let state = 'readingProps';
@ -539,7 +539,7 @@ export default class BaseItem extends BaseModel {
};
}
static async encryptedItemsCount() {
public static async encryptedItemsCount() {
const classNames = this.encryptableItemClassNames();
let output = 0;
@ -553,7 +553,7 @@ export default class BaseItem extends BaseModel {
return output;
}
static async hasEncryptedItems() {
public static async hasEncryptedItems() {
const classNames = this.encryptableItemClassNames();
for (let i = 0; i < classNames.length; i++) {
@ -567,7 +567,7 @@ export default class BaseItem extends BaseModel {
return false;
}
static async itemsThatNeedDecryption(exclusions: string[] = [], limit = 100): Promise<ItemsThatNeedDecryptionResult> {
public static async itemsThatNeedDecryption(exclusions: string[] = [], limit = 100): Promise<ItemsThatNeedDecryptionResult> {
const classNames = this.encryptableItemClassNames();
for (let i = 0; i < classNames.length; i++) {
@ -703,13 +703,13 @@ export default class BaseItem extends BaseModel {
throw new Error('Unreachable');
}
static syncItemClassNames(): string[] {
public static syncItemClassNames(): string[] {
return BaseItem.syncItemDefinitions_.map((def: any) => {
return def.className;
});
}
static encryptableItemClassNames() {
public static encryptableItemClassNames() {
const temp = this.syncItemClassNames();
const output = [];
for (let i = 0; i < temp.length; i++) {
@ -725,14 +725,14 @@ export default class BaseItem extends BaseModel {
});
}
static modelTypeToClassName(type: number) {
public static modelTypeToClassName(type: number) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].type === type) return BaseItem.syncItemDefinitions_[i].className;
}
throw new Error(`Invalid type: ${type}`);
}
static async syncDisabledItems(syncTargetId: number) {
public static async syncDisabledItems(syncTargetId: number) {
const rows = await this.db().selectAll('SELECT * FROM sync_items WHERE sync_disabled = 1 AND sync_target = ?', [syncTargetId]);
const output = [];
for (let i = 0; i < rows.length; i++) {
@ -749,7 +749,7 @@ export default class BaseItem extends BaseModel {
return output;
}
static updateSyncTimeQueries(syncTarget: number, item: any, syncTime: number, syncDisabled = false, syncDisabledReason = '', itemLocation: number = null) {
public static updateSyncTimeQueries(syncTarget: number, item: any, syncTime: number, syncDisabled = false, syncDisabledReason = '', itemLocation: number = null) {
const itemType = item.type_;
const itemId = item.id;
if (!itemType || !itemId || syncTime === undefined) throw new Error(sprintf('Invalid parameters in updateSyncTimeQueries(): %d, %s, %d', syncTarget, JSON.stringify(item), syncTime));
@ -768,12 +768,12 @@ export default class BaseItem extends BaseModel {
];
}
static async saveSyncTime(syncTarget: number, item: any, syncTime: number) {
public static async saveSyncTime(syncTarget: number, item: any, syncTime: number) {
const queries = this.updateSyncTimeQueries(syncTarget, item, syncTime);
return this.db().transactionExecBatch(queries);
}
static async saveSyncDisabled(syncTargetId: number, item: any, syncDisabledReason: string, itemLocation: number = null) {
public static async saveSyncDisabled(syncTargetId: number, item: any, syncDisabledReason: string, itemLocation: number = null) {
const syncTime = 'sync_time' in item ? item.sync_time : 0;
const queries = this.updateSyncTimeQueries(syncTargetId, item, syncTime, true, syncDisabledReason, itemLocation);
return this.db().transactionExecBatch(queries);
@ -786,7 +786,7 @@ export default class BaseItem extends BaseModel {
// When an item is deleted, its associated sync_items data is not immediately deleted for
// performance reason. So this function is used to look for these remaining sync_items and
// delete them.
static async deleteOrphanSyncItems() {
public static async deleteOrphanSyncItems() {
const classNames = this.syncItemClassNames();
const queries = [];
@ -803,13 +803,13 @@ export default class BaseItem extends BaseModel {
await this.db().transactionExecBatch(queries);
}
static displayTitle(item: any) {
public static displayTitle(item: any) {
if (!item) return '';
if (item.encryption_applied) return `🔑 ${_('Encrypted')}`;
return item.title ? item.title : _('Untitled');
}
static async markAllNonEncryptedForSync() {
public static async markAllNonEncryptedForSync() {
const classNames = this.encryptableItemClassNames();
for (let i = 0; i < classNames.length; i++) {
@ -834,7 +834,7 @@ export default class BaseItem extends BaseModel {
}
}
static async updateShareStatus(item: BaseItemEntity, isShared: boolean) {
public static async updateShareStatus(item: BaseItemEntity, isShared: boolean) {
if (!item.id || !item.type_) throw new Error('Item must have an ID and a type');
if (!!item.is_shared === !!isShared) return false;
const ItemClass = this.getClassByItemType(item.type_);
@ -853,15 +853,15 @@ export default class BaseItem extends BaseModel {
return true;
}
static async forceSync(itemId: string) {
public static async forceSync(itemId: string) {
await this.db().exec('UPDATE sync_items SET force_sync = 1 WHERE item_id = ?', [itemId]);
}
static async forceSyncAll() {
public static async forceSyncAll() {
await this.db().exec('UPDATE sync_items SET force_sync = 1');
}
static async save(o: any, options: any = null) {
public static async save(o: any, options: any = null) {
if (!options) options = {};
if (options.userSideValidation === true) {
@ -871,7 +871,7 @@ export default class BaseItem extends BaseModel {
return super.save(o, options);
}
static markdownTag(itemOrId: any) {
public static markdownTag(itemOrId: any) {
const item = typeof itemOrId === 'object' ? itemOrId : {
id: itemOrId,
title: '',
@ -885,7 +885,7 @@ export default class BaseItem extends BaseModel {
return output.join('');
}
static isMarkdownTag(md: any) {
public static isMarkdownTag(md: any) {
if (!md) return false;
return !!md.match(/^\[.*?\]\(:\/[0-9a-zA-Z]{32}\)$/);
}

View File

@ -19,22 +19,22 @@ export interface FolderEntityWithChildren extends FolderEntity {
}
export default class Folder extends BaseItem {
static tableName() {
public static tableName() {
return 'folders';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_FOLDER;
}
static newFolder(): FolderEntity {
public static newFolder(): FolderEntity {
return {
id: null,
title: '',
};
}
static fieldToLabel(field: string) {
public static fieldToLabel(field: string) {
const fieldsToLabels: any = {
title: _('title'),
last_note_user_updated_time: _('updated date'),
@ -43,7 +43,7 @@ export default class Folder extends BaseItem {
return field in fieldsToLabels ? fieldsToLabels[field] : field;
}
static noteIds(parentId: string, options: any = null) {
public static noteIds(parentId: string, options: any = null) {
options = Object.assign({}, {
includeConflicts: false,
}, options);
@ -66,17 +66,17 @@ export default class Folder extends BaseItem {
});
}
static async subFolderIds(parentId: string) {
public static async subFolderIds(parentId: string) {
const rows = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [parentId]);
return rows.map((r: FolderEntity) => r.id);
}
static async noteCount(parentId: string) {
public static async noteCount(parentId: string) {
const r = await this.db().selectOne('SELECT count(*) as total FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]);
return r ? r.total : 0;
}
static markNotesAsConflict(parentId: string) {
public static markNotesAsConflict(parentId: string) {
const query = Database.updateQuery('notes', { is_conflict: 1 }, { parent_id: parentId });
return this.db().exec(query);
}
@ -108,15 +108,15 @@ export default class Folder extends BaseItem {
});
}
static conflictFolderTitle() {
public static conflictFolderTitle() {
return _('Conflicts');
}
static conflictFolderId() {
public static conflictFolderId() {
return 'c04f1c7c04f1c7c04f1c7c04f1c7c04f';
}
static conflictFolder(): FolderEntity {
public static conflictFolder(): FolderEntity {
return {
type_: this.TYPE_FOLDER,
id: this.conflictFolderId(),
@ -129,7 +129,7 @@ export default class Folder extends BaseItem {
// Calculates note counts for all folders and adds the note_count attribute to each folder
// Note: this only calculates the overall number of nodes for this folder and all its descendants
static async addNoteCounts(folders: any[], includeCompletedTodos = true) {
public static async addNoteCounts(folders: any[], includeCompletedTodos = true) {
const foldersById: any = {};
for (const f of folders) {
foldersById[f.id] = f;
@ -170,7 +170,7 @@ export default class Folder extends BaseItem {
// Folders that contain notes that have been modified recently go on top.
// The remaining folders, that don't contain any notes are sorted by their own user_updated_time
static async orderByLastModified(folders: FolderEntity[], dir = 'DESC') {
public static async orderByLastModified(folders: FolderEntity[], dir = 'DESC') {
dir = dir.toUpperCase();
const sql = 'select parent_id, max(user_updated_time) content_updated_time from notes where parent_id != "" group by parent_id';
const rows = await this.db().selectAll(sql);
@ -228,7 +228,7 @@ export default class Folder extends BaseItem {
return output;
}
static async all(options: any = null) {
public static async all(options: any = null) {
const output = await super.all(options);
if (options && options.includeConflictFolder) {
const conflictCount = await Note.conflictedCount();
@ -237,7 +237,7 @@ export default class Folder extends BaseItem {
return output;
}
static async childrenIds(folderId: string) {
public static async childrenIds(folderId: string) {
const folders = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [folderId]);
let output: string[] = [];
@ -252,7 +252,7 @@ export default class Folder extends BaseItem {
return output;
}
static async expandTree(folders: FolderEntity[], parentId: string) {
public static async expandTree(folders: FolderEntity[], parentId: string) {
const folderPath = await this.folderPath(folders, parentId);
folderPath.pop(); // We don't expand the leaft notebook
@ -542,7 +542,7 @@ export default class Folder extends BaseItem {
logger.debug('updateNoLongerSharedItems:', report);
}
static async allAsTree(folders: FolderEntity[] = null, options: any = null) {
public static async allAsTree(folders: FolderEntity[] = null, options: any = null) {
const all = folders ? folders : await this.all(options);
if (options && options.includeNotes) {
@ -576,7 +576,7 @@ export default class Folder extends BaseItem {
return getNestedChildren(all, '');
}
static folderPath(folders: FolderEntity[], folderId: string) {
public static folderPath(folders: FolderEntity[], folderId: string) {
const idToFolders: Record<string, FolderEntity> = {};
for (let i = 0; i < folders.length; i++) {
idToFolders[folders[i].id] = folders[i];
@ -595,7 +595,7 @@ export default class Folder extends BaseItem {
return path;
}
static folderPathString(folders: FolderEntity[], folderId: string, maxTotalLength = 80) {
public static folderPathString(folders: FolderEntity[], folderId: string, maxTotalLength = 80) {
const path = this.folderPath(folders, folderId);
let currentTotalLength = 0;
@ -616,7 +616,7 @@ export default class Folder extends BaseItem {
return output.join(' / ');
}
static buildTree(folders: FolderEntity[]): FolderEntityWithChildren[] {
public static buildTree(folders: FolderEntity[]): FolderEntityWithChildren[] {
const idToFolders: Record<string, any> = {};
for (let i = 0; i < folders.length; i++) {
idToFolders[folders[i].id] = Object.assign({}, folders[i]);
@ -644,7 +644,7 @@ export default class Folder extends BaseItem {
return rootFolders;
}
static async sortFolderTree(folders: FolderEntityWithChildren[] = null) {
public static async sortFolderTree(folders: FolderEntityWithChildren[] = null) {
const output = folders ? folders : await this.allAsTree();
const sortFoldersAlphabetically = (folders: FolderEntityWithChildren[]) => {
@ -672,16 +672,16 @@ export default class Folder extends BaseItem {
return output;
}
static load(id: string, _options: any = null): Promise<FolderEntity> {
public static load(id: string, _options: any = null): Promise<FolderEntity> {
if (id === this.conflictFolderId()) return Promise.resolve(this.conflictFolder());
return super.load(id);
}
static defaultFolder() {
public static defaultFolder() {
return this.modelSelectOne('SELECT * FROM folders ORDER BY created_time DESC LIMIT 1');
}
static async canNestUnder(folderId: string, targetFolderId: string) {
public static async canNestUnder(folderId: string, targetFolderId: string) {
if (folderId === targetFolderId) return false;
const folder = await Folder.load(folderId);
@ -702,7 +702,7 @@ export default class Folder extends BaseItem {
return true;
}
static async moveToFolder(folderId: string, targetFolderId: string) {
public static async moveToFolder(folderId: string, targetFolderId: string) {
if (!(await this.canNestUnder(folderId, targetFolderId))) throw new Error(_('Cannot move notebook to this location'));
// When moving a note to a different folder, the user timestamp is not updated.
@ -721,7 +721,7 @@ export default class Folder extends BaseItem {
// manually creating a folder. They shouldn't be done for example when the folders
// are being synced to avoid any strange side-effects. Technically it's possible to
// have folders and notes with duplicate titles (or no title), or with reserved words.
static async save(o: FolderEntity, options: any = null) {
public static async save(o: FolderEntity, options: any = null) {
if (!options) options = {};
if (options.userSideValidation === true) {

View File

@ -22,11 +22,11 @@ export default class ItemChange extends BaseModel {
public static SOURCE_SYNC = 2;
public static SOURCE_DECRYPTION = 2; // CAREFUL - SAME ID AS SOURCE_SYNC!
static tableName() {
public static tableName() {
return 'item_changes';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_ITEM_CHANGE;
}

View File

@ -5,15 +5,15 @@ import BaseItem from './BaseItem';
import uuid from '../uuid';
export default class MasterKey extends BaseItem {
static tableName() {
public static tableName() {
return 'master_keys';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_MASTER_KEY;
}
static encryptionSupported() {
public static encryptionSupported() {
return false;
}
@ -28,7 +28,7 @@ export default class MasterKey extends BaseItem {
return output;
}
static allWithoutEncryptionMethod(masterKeys: MasterKeyEntity[], methods: number[]) {
public static allWithoutEncryptionMethod(masterKeys: MasterKeyEntity[], methods: number[]) {
return masterKeys.filter(m => !methods.includes(m.encryption_method));
}

View File

@ -10,19 +10,19 @@ const migrationScripts: Record<number, any> = {
};
export default class Migration extends BaseModel {
static tableName() {
public static tableName() {
return 'migrations';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_MIGRATION;
}
static migrationsToDo() {
public static migrationsToDo() {
return this.modelSelectAll('SELECT * FROM migrations ORDER BY number ASC');
}
static script(number: number) {
public static script(number: number) {
if (!migrationScripts[number]) throw new Error('Migration script has not been added to "migrationScripts" array');
return migrationScripts[number];
}

View File

@ -26,11 +26,11 @@ export default class Note extends BaseItem {
private static geolocationCache_: any;
private static dueDateObjects_: any;
static tableName() {
public static tableName() {
return 'notes';
}
static fieldToLabel(field: string) {
public static fieldToLabel(field: string) {
const fieldsToLabels: Record<string, string> = {
title: _('title'),
user_updated_time: _('updated date'),
@ -41,11 +41,11 @@ export default class Note extends BaseItem {
return field in fieldsToLabels ? fieldsToLabels[field] : field;
}
static async serializeForEdit(note: NoteEntity) {
public static async serializeForEdit(note: NoteEntity) {
return this.replaceResourceInternalToExternalLinks(await super.serialize(note, ['title', 'body']));
}
static async unserializeForEdit(content: string) {
public static async unserializeForEdit(content: string) {
content += `\n\ntype_: ${BaseModel.TYPE_NOTE}`;
const output = await super.unserialize(content);
if (!output.title) output.title = '';
@ -54,14 +54,14 @@ export default class Note extends BaseItem {
return output;
}
static async serializeAllProps(note: NoteEntity) {
public static async serializeAllProps(note: NoteEntity) {
const fieldNames = this.fieldNames();
fieldNames.push('type_');
pull(fieldNames, 'title', 'body');
return super.serialize(note, fieldNames);
}
static minimalSerializeForDisplay(note: NoteEntity) {
public static minimalSerializeForDisplay(note: NoteEntity) {
const n = Object.assign({}, note);
const fieldNames = this.fieldNames();
@ -89,25 +89,25 @@ export default class Note extends BaseItem {
return super.serialize(n, fieldNames);
}
static defaultTitle(noteBody: string) {
public static defaultTitle(noteBody: string) {
return this.defaultTitleFromBody(noteBody);
}
static defaultTitleFromBody(body: string) {
public static defaultTitleFromBody(body: string) {
return markdownUtils.titleFromBody(body);
}
static geolocationUrl(note: NoteEntity) {
public static geolocationUrl(note: NoteEntity) {
if (!('latitude' in note) || !('longitude' in note)) throw new Error('Latitude or longitude is missing');
if (!Number(note.latitude) && !Number(note.longitude)) throw new Error(_('This note does not have geolocation information.'));
return this.geoLocationUrlFromLatLong(note.latitude, note.longitude);
}
static geoLocationUrlFromLatLong(lat: number, long: number) {
public static geoLocationUrlFromLatLong(lat: number, long: number) {
return sprintf('https://www.openstreetmap.org/?lat=%s&lon=%s&zoom=20', lat, long);
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_NOTE;
}
@ -119,13 +119,13 @@ export default class Note extends BaseItem {
return unique(itemIds);
}
static async linkedItems(body: string) {
public static async linkedItems(body: string) {
const itemIds = this.linkedItemIds(body);
const r = await BaseItem.loadItemsByIds(itemIds);
return r;
}
static async linkedItemIdsByType(type: ModelType, body: string) {
public static async linkedItemIdsByType(type: ModelType, body: string) {
const items = await this.linkedItems(body);
const output: string[] = [];
@ -137,15 +137,15 @@ export default class Note extends BaseItem {
return output;
}
static async linkedResourceIds(body: string) {
public static async linkedResourceIds(body: string) {
return this.linkedItemIdsByType(BaseModel.TYPE_RESOURCE, body);
}
static async linkedNoteIds(body: string) {
public static async linkedNoteIds(body: string) {
return this.linkedItemIdsByType(BaseModel.TYPE_NOTE, body);
}
static async replaceResourceInternalToExternalLinks(body: string, options: any = null) {
public static async replaceResourceInternalToExternalLinks(body: string, options: any = null) {
options = Object.assign({}, {
useAbsolutePaths: false,
}, options);
@ -175,7 +175,7 @@ export default class Note extends BaseItem {
return body;
}
static async replaceResourceExternalToInternalLinks(body: string, options: any = null) {
public static async replaceResourceExternalToInternalLinks(body: string, options: any = null) {
options = Object.assign({}, {
useAbsolutePaths: false,
}, options);
@ -239,20 +239,20 @@ export default class Note extends BaseItem {
return body;
}
static new(parentId = '') {
public static new(parentId = '') {
const output = super.new();
output.parent_id = parentId;
return output;
}
static newTodo(parentId = '') {
public static newTodo(parentId = '') {
const output = this.new(parentId);
output.is_todo = true;
return output;
}
// Note: sort logic must be duplicated in previews().
static sortNotes(notes: NoteEntity[], orders: any[], uncompletedTodosOnTop: boolean) {
public static sortNotes(notes: NoteEntity[], orders: any[], uncompletedTodosOnTop: boolean) {
const noteOnTop = (note: NoteEntity) => {
return uncompletedTodosOnTop && note.is_todo && !note.todo_completed;
};
@ -308,11 +308,11 @@ export default class Note extends BaseItem {
});
}
static previewFieldsWithDefaultValues(options: any = null) {
public static previewFieldsWithDefaultValues(options: any = null) {
return Note.defaultValues(this.previewFields(options));
}
static previewFields(options: any = null) {
public static previewFields(options: any = null) {
options = Object.assign({
includeTimestamps: true,
}, options);
@ -328,13 +328,13 @@ export default class Note extends BaseItem {
return output;
}
static previewFieldsSql(fields: string[] = null) {
public static previewFieldsSql(fields: string[] = null) {
if (fields === null) fields = this.previewFields();
const escaped = this.db().escapeFields(fields);
return Array.isArray(escaped) ? escaped.join(',') : escaped;
}
static async loadFolderNoteByField(folderId: string, field: string, value: any) {
public static async loadFolderNoteByField(folderId: string, field: string, value: any) {
if (!folderId) throw new Error('folderId is undefined');
const options = {
@ -347,7 +347,7 @@ export default class Note extends BaseItem {
return results.length ? results[0] : null;
}
static async previews(parentId: string, options: any = null) {
public static async previews(parentId: string, options: any = null) {
// Note: ordering logic must be duplicated in sortNotes(), which is used
// to sort already loaded notes.
@ -436,12 +436,12 @@ export default class Note extends BaseItem {
return results;
}
static preview(noteId: string, options: any = null) {
public static preview(noteId: string, options: any = null) {
if (!options) options = { fields: null };
return this.modelSelectOne(`SELECT ${this.previewFieldsSql(options.fields)} FROM notes WHERE is_conflict = 0 AND id = ?`, [noteId]);
}
static async search(options: any = null) {
public static async search(options: any = null) {
if (!options) options = {};
if (!options.conditions) options.conditions = [];
if (!options.conditionsParams) options.conditionsParams = [];
@ -455,16 +455,16 @@ export default class Note extends BaseItem {
return super.search(options);
}
static conflictedNotes() {
public static conflictedNotes() {
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 1');
}
static async conflictedCount() {
public static async conflictedCount() {
const r = await this.db().selectOne('SELECT count(*) as total FROM notes WHERE is_conflict = 1');
return r && r.total ? r.total : 0;
}
static unconflictedNotes() {
public static unconflictedNotes() {
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 0');
}
@ -518,7 +518,7 @@ export default class Note extends BaseItem {
return note;
}
static filter(note: NoteEntity) {
public static filter(note: NoteEntity) {
if (!note) return note;
const output = super.filter(note);
@ -528,7 +528,7 @@ export default class Note extends BaseItem {
return output;
}
static async copyToFolder(noteId: string, folderId: string) {
public static async copyToFolder(noteId: string, folderId: string) {
if (folderId === this.getClass('Folder').conflictFolderId()) throw new Error(_('Cannot copy note to "%s" notebook', this.getClass('Folder').conflictFolderTitle()));
return Note.duplicate(noteId, {
@ -540,7 +540,7 @@ export default class Note extends BaseItem {
});
}
static async moveToFolder(noteId: string, folderId: string) {
public static async moveToFolder(noteId: string, folderId: string) {
if (folderId === this.getClass('Folder').conflictFolderId()) throw new Error(_('Cannot move note to "%s" notebook', this.getClass('Folder').conflictFolderTitle()));
// When moving a note to a different folder, the user timestamp is not updated.
@ -557,7 +557,7 @@ export default class Note extends BaseItem {
return Note.save(modifiedNote, { autoTimestamp: false });
}
static changeNoteType(note: NoteEntity, type: string) {
public static changeNoteType(note: NoteEntity, type: string) {
if (!('is_todo' in note)) throw new Error('Missing "is_todo" property');
const newIsTodo = type === 'todo' ? 1 : 0;
@ -572,11 +572,11 @@ export default class Note extends BaseItem {
return output;
}
static toggleIsTodo(note: NoteEntity) {
public static toggleIsTodo(note: NoteEntity) {
return this.changeNoteType(note, note.is_todo ? 'note' : 'todo');
}
static toggleTodoCompleted(note: NoteEntity) {
public static toggleTodoCompleted(note: NoteEntity) {
if (!('todo_completed' in note)) throw new Error('Missing "todo_completed" property');
note = Object.assign({}, note);
@ -589,7 +589,7 @@ export default class Note extends BaseItem {
return note;
}
static async duplicateMultipleNotes(noteIds: string[], options: any = null) {
public static async duplicateMultipleNotes(noteIds: string[], options: any = null) {
// if options.uniqueTitle is true, a unique title for the duplicated file will be assigned.
const ensureUniqueTitle = options && options.ensureUniqueTitle;
@ -655,7 +655,7 @@ export default class Note extends BaseItem {
return this.save(newNoteSaved);
}
static async noteIsOlderThan(noteId: string, date: number) {
public static async noteIsOlderThan(noteId: string, date: number) {
const n = await this.db().selectOne('SELECT updated_time FROM notes WHERE id = ?', [noteId]);
if (!n) throw new Error(`No such note: ${noteId}`);
return n.updated_time < date;
@ -737,7 +737,7 @@ export default class Note extends BaseItem {
return note;
}
static async batchDelete(ids: string[], options: any = null) {
public static async batchDelete(ids: string[], options: any = null) {
ids = ids.slice();
while (ids.length) {
@ -763,7 +763,7 @@ export default class Note extends BaseItem {
}
}
static async deleteMessage(noteIds: string[]): Promise<string|null> {
public static async deleteMessage(noteIds: string[]): Promise<string|null> {
let msg = '';
if (noteIds.length === 1) {
const note = await Note.load(noteIds[0]);
@ -775,15 +775,15 @@ export default class Note extends BaseItem {
return msg;
}
static dueNotes() {
public static dueNotes() {
return this.modelSelectAll('SELECT id, title, body, is_todo, todo_due, todo_completed, is_conflict FROM notes WHERE is_conflict = 0 AND is_todo = 1 AND todo_completed = 0 AND todo_due > ?', [time.unixMs()]);
}
static needAlarm(note: NoteEntity) {
public static needAlarm(note: NoteEntity) {
return note.is_todo && !note.todo_completed && note.todo_due >= time.unixMs() && !note.is_conflict;
}
static dueDateObject(note: NoteEntity) {
public static dueDateObject(note: NoteEntity) {
if (!!note.is_todo && note.todo_due) {
if (!this.dueDateObjects_) this.dueDateObjects_ = {};
if (this.dueDateObjects_[note.todo_due]) return this.dueDateObjects_[note.todo_due];
@ -795,7 +795,7 @@ export default class Note extends BaseItem {
}
// Tells whether the conflict between the local and remote note can be ignored.
static mustHandleConflict(localNote: NoteEntity, remoteNote: NoteEntity) {
public static mustHandleConflict(localNote: NoteEntity, remoteNote: NoteEntity) {
// That shouldn't happen so throw an exception
if (localNote.id !== remoteNote.id) throw new Error('Cannot handle conflict for two different notes');
@ -809,7 +809,7 @@ export default class Note extends BaseItem {
return false;
}
static markupLanguageToLabel(markupLanguageId: number) {
public static markupLanguageToLabel(markupLanguageId: number) {
if (markupLanguageId === MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN) return 'Markdown';
if (markupLanguageId === MarkupToHtml.MARKUP_LANGUAGE_HTML) return 'HTML';
throw new Error(`Invalid markup language ID: ${markupLanguageId}`);
@ -818,13 +818,13 @@ export default class Note extends BaseItem {
// When notes are sorted in "custom order", they are sorted by the "order" field first and,
// in those cases, where the order field is the same for some notes, by created time.
// Further sorting by todo completion status, if enabled, is handled separately.
static customOrderByColumns() {
public static customOrderByColumns() {
return [{ by: 'order', dir: 'DESC' }, { by: 'user_created_time', dir: 'DESC' }];
}
// Update the note "order" field without changing the user timestamps,
// which is generally what we want.
static async updateNoteOrder_(note: NoteEntity, order: any) {
private static async updateNoteOrder_(note: NoteEntity, order: any) {
return Note.save(Object.assign({}, note, {
order: order,
user_updated_time: note.user_updated_time,
@ -836,7 +836,7 @@ export default class Note extends BaseItem {
// of unecessary updates, so it's the caller's responsability to update
// the UI once the call is finished. This is done by listening to the
// NOTE_IS_INSERTING_NOTES action in the application middleware.
static async insertNotesAt(folderId: string, noteIds: string[], index: number, uncompletedTodosOnTop: boolean, showCompletedTodos: boolean) {
public static async insertNotesAt(folderId: string, noteIds: string[], index: number, uncompletedTodosOnTop: boolean, showCompletedTodos: boolean) {
if (!noteIds.length) return;
const defer = () => {
@ -985,19 +985,19 @@ export default class Note extends BaseItem {
}
}
static handleTitleNaturalSorting(items: NoteEntity[], options: any) {
public static handleTitleNaturalSorting(items: NoteEntity[], options: any) {
if (options.order.length > 0 && options.order[0].by === 'title') {
const collator = this.getNaturalSortingCollator();
items.sort((a, b) => ((options.order[0].dir === 'ASC') ? 1 : -1) * collator.compare(a.title, b.title));
}
}
static getNaturalSortingCollator() {
public static getNaturalSortingCollator() {
return new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
}
static async createConflictNote(sourceNote: NoteEntity, changeSource: number): Promise<NoteEntity> {
public static async createConflictNote(sourceNote: NoteEntity, changeSource: number): Promise<NoteEntity> {
const conflictNote = Object.assign({}, sourceNote);
delete conflictNote.id;
conflictNote.is_conflict = 1;

View File

@ -8,11 +8,11 @@ import BaseItem from './BaseItem';
// - If last_seen_time is 0, it means the resource has never been associated with any note.
export default class NoteResource extends BaseModel {
static tableName() {
public static tableName() {
return 'note_resources';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_NOTE_RESOURCE;
}
@ -71,12 +71,12 @@ export default class NoteResource extends BaseModel {
// await this.db().transactionExecBatch(queries);
// }
static async associatedNoteIds(resourceId: string): Promise<string[]> {
public static async associatedNoteIds(resourceId: string): Promise<string[]> {
const rows = await this.modelSelectAll('SELECT note_id FROM note_resources WHERE resource_id = ? AND is_associated = 1', [resourceId]);
return rows.map((r: any) => r.note_id);
}
static async setAssociatedResources(noteId: string, resourceIds: string[]) {
public static async setAssociatedResources(noteId: string, resourceIds: string[]) {
const existingRows = await this.modelSelectAll('SELECT * FROM note_resources WHERE note_id = ?', [noteId]);
const notProcessedResourceIds = resourceIds.slice();
@ -100,7 +100,7 @@ export default class NoteResource extends BaseModel {
await this.db().transactionExecBatch(queries);
}
static async addOrphanedResources() {
public static async addOrphanedResources() {
const missingResources = await this.db().selectAll('SELECT id FROM resources WHERE id NOT IN (SELECT DISTINCT resource_id FROM note_resources)');
const queries = [];
for (let i = 0; i < missingResources.length; i++) {
@ -125,11 +125,11 @@ export default class NoteResource extends BaseModel {
await this.db().transactionExecBatch(queries);
}
static async remove(noteId: string) {
public static async remove(noteId: string) {
await this.db().exec({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE note_id = ?', params: [noteId] });
}
static async orphanResources(expiryDelay: number = null) {
public static async orphanResources(expiryDelay: number = null) {
if (expiryDelay === null) expiryDelay = 1000 * 60 * 60 * 24 * 10;
const cutOffTime = Date.now() - expiryDelay;
const output = await this.modelSelectAll(
@ -146,7 +146,7 @@ export default class NoteResource extends BaseModel {
return output.map((r: any) => r.resource_id);
}
static async deleteByResource(resourceId: string) {
public static async deleteByResource(resourceId: string) {
await this.db().exec('DELETE FROM note_resources WHERE resource_id = ?', [resourceId]);
}
}

View File

@ -2,20 +2,20 @@ import BaseItem from './BaseItem';
import BaseModel from '../BaseModel';
export default class NoteTag extends BaseItem {
static tableName() {
public static tableName() {
return 'note_tags';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_NOTE_TAG;
}
static async byNoteIds(noteIds: string[]) {
public static async byNoteIds(noteIds: string[]) {
if (!noteIds.length) return [];
return this.modelSelectAll(`SELECT * FROM note_tags WHERE note_id IN ("${noteIds.join('","')}")`);
}
static async tagIdsByNoteId(noteId: string) {
public static async tagIdsByNoteId(noteId: string) {
const rows = await this.db().selectAll('SELECT tag_id FROM note_tags WHERE note_id = ?', [noteId]);
const output = [];
for (let i = 0; i < rows.length; i++) {

View File

@ -29,15 +29,15 @@ export default class Resource extends BaseItem {
public static fsDriver_: any;
static tableName() {
public static tableName() {
return 'resources';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_RESOURCE;
}
static encryptionService() {
public static encryptionService() {
if (!this.encryptionService_) throw new Error('Resource.encryptionService_ is not set!!');
return this.encryptionService_;
}
@ -47,12 +47,12 @@ export default class Resource extends BaseItem {
return this.shareService_;
}
static isSupportedImageMimeType(type: string) {
public static isSupportedImageMimeType(type: string) {
const imageMimeTypes = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp'];
return imageMimeTypes.indexOf(type.toLowerCase()) >= 0;
}
static fetchStatuses(resourceIds: string[]): Promise<any[]> {
public static fetchStatuses(resourceIds: string[]): Promise<any[]> {
if (!resourceIds.length) return Promise.resolve([]);
return this.db().selectAll(`SELECT resource_id, fetch_status FROM resource_local_states WHERE resource_id IN ("${resourceIds.join('","')}")`);
}
@ -61,7 +61,7 @@ export default class Resource extends BaseItem {
return this.db().selectAllFields('SELECT id FROM resources WHERE is_shared = 1', [], 'id');
}
static errorFetchStatuses() {
public static errorFetchStatuses() {
return this.db().selectAll(`
SELECT title AS resource_title, resource_id, fetch_error
FROM resource_local_states
@ -70,7 +70,7 @@ export default class Resource extends BaseItem {
`, [Resource.FETCH_STATUS_ERROR]);
}
static needToBeFetched(resourceDownloadMode: string = null, limit: number = null) {
public static needToBeFetched(resourceDownloadMode: string = null, limit: number = null) {
const sql = ['SELECT * FROM resources WHERE encryption_applied = 0 AND id IN (SELECT resource_id FROM resource_local_states WHERE fetch_status = ?)'];
if (resourceDownloadMode !== 'always') {
sql.push('AND resources.id IN (SELECT resource_id FROM resources_to_download)');
@ -80,21 +80,21 @@ export default class Resource extends BaseItem {
return this.modelSelectAll(sql.join(' '), [Resource.FETCH_STATUS_IDLE]);
}
static async resetStartedFetchStatus() {
public static async resetStartedFetchStatus() {
return await this.db().exec('UPDATE resource_local_states SET fetch_status = ? WHERE fetch_status = ?', [Resource.FETCH_STATUS_IDLE, Resource.FETCH_STATUS_STARTED]);
}
static resetErrorStatus(resourceId: string) {
public static resetErrorStatus(resourceId: string) {
return this.db().exec('UPDATE resource_local_states SET fetch_status = ?, fetch_error = "" WHERE resource_id = ?', [Resource.FETCH_STATUS_IDLE, resourceId]);
}
static fsDriver() {
public static fsDriver() {
if (!Resource.fsDriver_) Resource.fsDriver_ = new FsDriverDummy();
return Resource.fsDriver_;
}
// DEPRECATED IN FAVOUR OF friendlySafeFilename()
static friendlyFilename(resource: ResourceEntity) {
public static friendlyFilename(resource: ResourceEntity) {
let output = safeFilename(resource.title); // Make sure not to allow spaces or any special characters as it's not supported in HTTP headers
if (!output) output = resource.id;
let extension = resource.file_extension;
@ -103,22 +103,22 @@ export default class Resource extends BaseItem {
return output + extension;
}
static baseDirectoryPath() {
public static baseDirectoryPath() {
return Setting.value('resourceDir');
}
static baseRelativeDirectoryPath() {
public static baseRelativeDirectoryPath() {
return Setting.value('resourceDirName');
}
static filename(resource: ResourceEntity, encryptedBlob = false) {
public static filename(resource: ResourceEntity, encryptedBlob = false) {
let extension = encryptedBlob ? 'crypted' : resource.file_extension;
if (!extension) extension = resource.mime ? mime.toFileExtension(resource.mime) : '';
extension = extension ? `.${extension}` : '';
return resource.id + extension;
}
static friendlySafeFilename(resource: ResourceEntity) {
public static friendlySafeFilename(resource: ResourceEntity) {
let ext = resource.file_extension;
if (!ext) ext = resource.mime ? mime.toFileExtension(resource.mime) : '';
const safeExt = ext ? pathUtils.safeFileExtension(ext).toLowerCase() : '';
@ -127,20 +127,20 @@ export default class Resource extends BaseItem {
return pathUtils.friendlySafeFilename(title) + (safeExt ? `.${safeExt}` : '');
}
static relativePath(resource: ResourceEntity, encryptedBlob = false) {
public static relativePath(resource: ResourceEntity, encryptedBlob = false) {
return `${Setting.value('resourceDirName')}/${this.filename(resource, encryptedBlob)}`;
}
static fullPath(resource: ResourceEntity, encryptedBlob = false) {
public static fullPath(resource: ResourceEntity, encryptedBlob = false) {
return `${Setting.value('resourceDir')}/${this.filename(resource, encryptedBlob)}`;
}
static async isReady(resource: ResourceEntity) {
public static async isReady(resource: ResourceEntity) {
const r = await this.readyStatus(resource);
return r === 'ok';
}
static async readyStatus(resource: ResourceEntity) {
public static async readyStatus(resource: ResourceEntity) {
const ls = await this.localState(resource);
if (!resource) return 'notFound';
if (ls.fetch_status !== Resource.FETCH_STATUS_DONE) return 'notDownloaded';
@ -148,13 +148,13 @@ export default class Resource extends BaseItem {
return 'ok';
}
static async requireIsReady(resource: ResourceEntity) {
public static async requireIsReady(resource: ResourceEntity) {
const readyStatus = await Resource.readyStatus(resource);
if (readyStatus !== 'ok') throw new Error(`Resource is not ready. Status: ${readyStatus}`);
}
// For resources, we need to decrypt the item (metadata) and the resource binary blob.
static async decrypt(item: ResourceEntity) {
public static async decrypt(item: ResourceEntity) {
// The item might already be decrypted but not the blob (for instance if it crashes while
// decrypting the blob or was otherwise interrupted).
const decryptedItem = item.encryption_cipher_text ? await super.decrypt(item) : Object.assign({}, item);
@ -230,7 +230,7 @@ export default class Resource extends BaseItem {
return { path: encryptedPath, resource: resourceCopy };
}
static markdownTag(resource: any) {
public static markdownTag(resource: any) {
let tagAlt = resource.alt ? resource.alt : resource.title;
if (!tagAlt) tagAlt = '';
const lines = [];
@ -246,48 +246,48 @@ export default class Resource extends BaseItem {
return lines.join('');
}
static internalUrl(resource: ResourceEntity) {
public static internalUrl(resource: ResourceEntity) {
return `:/${resource.id}`;
}
static pathToId(path: string) {
public static pathToId(path: string) {
return filename(path);
}
static async content(resource: ResourceEntity) {
public static async content(resource: ResourceEntity) {
return this.fsDriver().readFile(this.fullPath(resource), 'Buffer');
}
static isResourceUrl(url: string) {
public static isResourceUrl(url: string) {
return url && url.length === 34 && url[0] === ':' && url[1] === '/';
}
static urlToId(url: string) {
public static urlToId(url: string) {
if (!this.isResourceUrl(url)) throw new Error(`Not a valid resource URL: ${url}`);
return url.substr(2);
}
static async localState(resourceOrId: any) {
public static async localState(resourceOrId: any) {
return ResourceLocalState.byResourceId(typeof resourceOrId === 'object' ? resourceOrId.id : resourceOrId);
}
static async setLocalState(resourceOrId: any, state: ResourceLocalStateEntity) {
public static async setLocalState(resourceOrId: any, state: ResourceLocalStateEntity) {
const id = typeof resourceOrId === 'object' ? resourceOrId.id : resourceOrId;
await ResourceLocalState.save(Object.assign({}, state, { resource_id: id }));
}
static async needFileSizeSet() {
public static async needFileSizeSet() {
return this.modelSelectAll('SELECT * FROM resources WHERE `size` < 0 AND encryption_blob_encrypted = 0');
}
// Only set the `size` field and nothing else, not even the update_time
// This is because it's only necessary to do it once after migration 20
// and each client does it so there's no need to sync the resource.
static async setFileSizeOnly(resourceId: string, fileSize: number) {
public static async setFileSizeOnly(resourceId: string, fileSize: number) {
return this.db().exec('UPDATE resources set `size` = ? WHERE id = ?', [fileSize, resourceId]);
}
static async batchDelete(ids: string[], options: any = null) {
public static async batchDelete(ids: string[], options: any = null) {
// For resources, there's not really batch deleting since there's the file data to delete
// too, so each is processed one by one with the item being deleted last (since the db
// call is the less likely to fail).
@ -305,13 +305,13 @@ export default class Resource extends BaseItem {
await ResourceLocalState.batchDelete(ids);
}
static async markForDownload(resourceId: string) {
public static async markForDownload(resourceId: string) {
// Insert the row only if it's not already there
const t = Date.now();
await this.db().exec('INSERT INTO resources_to_download (resource_id, updated_time, created_time) SELECT ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM resources_to_download WHERE resource_id = ?)', [resourceId, t, t, resourceId]);
}
static async downloadedButEncryptedBlobCount(excludedIds: string[] = null) {
public static async downloadedButEncryptedBlobCount(excludedIds: string[] = null) {
let excludedSql = '';
if (excludedIds && excludedIds.length) {
excludedSql = `AND resource_id NOT IN ("${excludedIds.join('","')}")`;
@ -328,7 +328,7 @@ export default class Resource extends BaseItem {
return r ? r.total : 0;
}
static async downloadStatusCounts(status: number) {
public static async downloadStatusCounts(status: number) {
const r = await this.db().selectOne(`
SELECT count(*) as total
FROM resource_local_states
@ -338,7 +338,7 @@ export default class Resource extends BaseItem {
return r ? r.total : 0;
}
static async createdLocallyCount() {
public static async createdLocallyCount() {
const r = await this.db().selectOne(`
SELECT count(*) as total
FROM resources
@ -349,7 +349,7 @@ export default class Resource extends BaseItem {
return r ? r.total : 0;
}
static fetchStatusToLabel(status: number) {
public static fetchStatusToLabel(status: number) {
if (status === Resource.FETCH_STATUS_IDLE) return _('Not downloaded');
if (status === Resource.FETCH_STATUS_STARTED) return _('Downloading');
if (status === Resource.FETCH_STATUS_DONE) return _('Downloaded');
@ -357,7 +357,7 @@ export default class Resource extends BaseItem {
throw new Error(`Invalid status: ${status}`);
}
static async updateResourceBlobContent(resourceId: string, newBlobFilePath: string) {
public static async updateResourceBlobContent(resourceId: string, newBlobFilePath: string) {
const resource = await Resource.load(resourceId);
await this.requireIsReady(resource);
@ -370,7 +370,7 @@ export default class Resource extends BaseItem {
});
}
static async resourceBlobContent(resourceId: string, encoding = 'Buffer') {
public static async resourceBlobContent(resourceId: string, encoding = 'Buffer') {
const resource = await Resource.load(resourceId);
await this.requireIsReady(resource);
return await this.fsDriver().readFile(Resource.fullPath(resource), encoding);

View File

@ -3,15 +3,15 @@ import { ResourceLocalStateEntity } from '../services/database/types';
import Database from '../database';
export default class ResourceLocalState extends BaseModel {
static tableName() {
public static tableName() {
return 'resource_local_states';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_RESOURCE_LOCAL_STATE;
}
static async byResourceId(resourceId: string) {
public static async byResourceId(resourceId: string) {
if (!resourceId) throw new Error('Resource ID not provided'); // Sanity check
const result = await this.modelSelectOne('SELECT * FROM resource_local_states WHERE resource_id = ?', [resourceId]);
@ -26,13 +26,13 @@ export default class ResourceLocalState extends BaseModel {
return result;
}
static async save(o: ResourceLocalStateEntity) {
public static async save(o: ResourceLocalStateEntity) {
const queries = [{ sql: 'DELETE FROM resource_local_states WHERE resource_id = ?', params: [o.resource_id] }, Database.insertQuery(this.tableName(), o)];
return this.db().transactionExecBatch(queries);
}
static batchDelete(ids: string[], options: any = null) {
public static batchDelete(ids: string[], options: any = null) {
options = options ? Object.assign({}, options) : {};
options.idFieldName = 'resource_id';
return super.batchDelete(ids, options);

View File

@ -14,11 +14,11 @@ export interface ObjectPatch {
}
export default class Revision extends BaseItem {
static tableName() {
public static tableName() {
return 'revisions';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_REVISION;
}
@ -181,7 +181,7 @@ export default class Revision extends BaseItem {
};
}
static revisionPatchStatsText(rev: RevisionEntity) {
public static revisionPatchStatsText(rev: RevisionEntity) {
const titleStats = this.patchStats(rev.title_diff);
const bodyStats = this.patchStats(rev.body_diff);
const total = {
@ -195,28 +195,28 @@ export default class Revision extends BaseItem {
return output.join(', ');
}
static async countRevisions(itemType: ModelType, itemId: string) {
public static async countRevisions(itemType: ModelType, itemId: string) {
const r = await this.db().selectOne('SELECT count(*) as total FROM revisions WHERE item_type = ? AND item_id = ?', [itemType, itemId]);
return r ? r.total : 0;
}
static latestRevision(itemType: ModelType, itemId: string) {
public static latestRevision(itemType: ModelType, itemId: string) {
return this.modelSelectOne('SELECT * FROM revisions WHERE item_type = ? AND item_id = ? ORDER BY item_updated_time DESC LIMIT 1', [itemType, itemId]);
}
static allByType(itemType: ModelType, itemId: string) {
public static allByType(itemType: ModelType, itemId: string) {
return this.modelSelectAll('SELECT * FROM revisions WHERE item_type = ? AND item_id = ? ORDER BY item_updated_time ASC', [itemType, itemId]);
}
static async itemsWithRevisions(itemType: ModelType, itemIds: string[]) {
public static async itemsWithRevisions(itemType: ModelType, itemIds: string[]) {
if (!itemIds.length) return [];
const rows = await this.db().selectAll(`SELECT distinct item_id FROM revisions WHERE item_type = ? AND item_id IN ("${itemIds.join('","')}")`, [itemType]);
return rows.map((r: RevisionEntity) => r.item_id);
}
static async itemsWithNoRevisions(itemType: ModelType, itemIds: string[]) {
public static async itemsWithNoRevisions(itemType: ModelType, itemIds: string[]) {
const withRevs = await this.itemsWithRevisions(itemType, itemIds);
const output = [];
for (let i = 0; i < itemIds.length; i++) {
@ -225,7 +225,7 @@ export default class Revision extends BaseItem {
return ArrayUtils.unique(output);
}
static moveRevisionToTop(revision: RevisionEntity, revs: RevisionEntity[]) {
public static moveRevisionToTop(revision: RevisionEntity, revs: RevisionEntity[]) {
let targetIndex = -1;
for (let i = revs.length - 1; i >= 0; i--) {
const rev = revs[i];
@ -295,7 +295,7 @@ export default class Revision extends BaseItem {
return output;
}
static async deleteOldRevisions(ttl: number) {
public static async deleteOldRevisions(ttl: number) {
// When deleting old revisions, we need to make sure that the oldest surviving revision
// is a "merged" one (as opposed to a diff from a now deleted revision). So every time
// we deleted a revision, we need to find if there's a corresponding surviving revision
@ -342,7 +342,7 @@ export default class Revision extends BaseItem {
}
}
static async revisionExists(itemType: ModelType, itemId: string, updatedTime: number) {
public static async revisionExists(itemType: ModelType, itemId: string, updatedTime: number) {
const existingRev = await Revision.latestRevision(itemType, itemId);
return existingRev && existingRev.item_updated_time === updatedTime;
}

View File

@ -3,15 +3,15 @@
import BaseModel from '../BaseModel';
export default class Search extends BaseModel {
static tableName(): string {
public static tableName(): string {
throw new Error('Not using database');
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_SEARCH;
}
static keywords(query: string) {
public static keywords(query: string) {
let output: any = query.trim();
output = output.split(/[\s\t\n]+/);
output = output.filter((o: any) => !!o);

View File

@ -313,11 +313,11 @@ class Setting extends BaseModel {
private static rootFileHandler_: FileHandler = null;
private static settingFilename_: string = 'settings.json';
static tableName() {
public static tableName() {
return 'settings';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_SETTING;
}
@ -365,16 +365,16 @@ class Setting extends BaseModel {
return this.rootFileHandler_;
}
static keychainService() {
public static keychainService() {
if (!this.keychainService_) throw new Error('keychainService has not been set!!');
return this.keychainService_;
}
static setKeychainService(s: any) {
public static setKeychainService(s: any) {
this.keychainService_ = s;
}
static metadata(): SettingItems {
public static metadata(): SettingItems {
if (this.metadata_) return this.metadata_;
const platform = shim.platformName();
@ -1750,7 +1750,7 @@ class Setting extends BaseModel {
if (type < 0) throw new Error(`Invalid setting type: ${type}`);
}
static async registerSetting(key: string, metadataItem: SettingItem) {
public static async registerSetting(key: string, metadataItem: SettingItem) {
try {
if (metadataItem.isEnum && !metadataItem.options) throw new Error('The `options` property is required for enum types');
@ -1786,11 +1786,11 @@ class Setting extends BaseModel {
}
}
static async registerSection(name: string, source: SettingSectionSource, section: SettingSection) {
public static async registerSection(name: string, source: SettingSectionSource, section: SettingSection) {
this.customSections_[name] = { ...section, name: name, source: source };
}
static settingMetadata(key: string): SettingItem {
public static settingMetadata(key: string): SettingItem {
const metadata = this.metadata();
if (!(key in metadata)) throw new Error(`Unknown key: ${key}`);
const output = Object.assign({}, metadata[key]);
@ -1812,17 +1812,17 @@ class Setting extends BaseModel {
return !!this.cache_.find(d => d.key === key);
}
static keyDescription(key: string, appType: AppType = null) {
public static keyDescription(key: string, appType: AppType = null) {
const md = this.settingMetadata(key);
if (!md.description) return null;
return md.description(appType);
}
static isSecureKey(key: string) {
public static isSecureKey(key: string) {
return this.metadata()[key] && this.metadata()[key].secure === true;
}
static keys(publicOnly: boolean = false, appType: AppType = null, options: KeysOptions = null) {
public static keys(publicOnly: boolean = false, appType: AppType = null, options: KeysOptions = null) {
options = Object.assign({}, {
secureOnly: false,
}, options);
@ -1851,7 +1851,7 @@ class Setting extends BaseModel {
}
}
static isPublic(key: string) {
public static isPublic(key: string) {
return this.keys(true).indexOf(key) >= 0;
}
@ -1967,7 +1967,7 @@ class Setting extends BaseModel {
return md.storage || SettingStorage.Database;
}
static toPlainObject() {
public static toPlainObject() {
const keys = this.keys();
const keyToValues: any = {};
for (let i = 0; i < keys.length; i++) {
@ -1976,14 +1976,14 @@ class Setting extends BaseModel {
return keyToValues;
}
static dispatchUpdateAll() {
public static dispatchUpdateAll() {
this.dispatch({
type: 'SETTING_UPDATE_ALL',
settings: this.toPlainObject(),
});
}
static setConstant(key: string, value: any) {
public static setConstant(key: string, value: any) {
if (!(key in this.constants_)) throw new Error(`Unknown constant key: ${key}`);
(this.constants_ as any)[key] = value;
}
@ -2046,11 +2046,11 @@ class Setting extends BaseModel {
this.scheduleChangeEvent();
}
static incValue(key: string, inc: any) {
public static incValue(key: string, inc: any) {
return this.setValue(key, this.value(key) + inc);
}
static toggle(key: string) {
public static toggle(key: string) {
return this.setValue(key, !this.value(key));
}
@ -2065,27 +2065,27 @@ class Setting extends BaseModel {
return false;
}
static objectValue(settingKey: string, objectKey: string, defaultValue: any = null) {
public static objectValue(settingKey: string, objectKey: string, defaultValue: any = null) {
const o = this.value(settingKey);
if (!o || !(objectKey in o)) return defaultValue;
return o[objectKey];
}
static setObjectValue(settingKey: string, objectKey: string, value: any) {
public static setObjectValue(settingKey: string, objectKey: string, value: any) {
let o = this.value(settingKey);
if (typeof o !== 'object') o = {};
o[objectKey] = value;
this.setValue(settingKey, o);
}
static deleteObjectValue(settingKey: string, objectKey: string) {
public static deleteObjectValue(settingKey: string, objectKey: string) {
const o = this.value(settingKey);
if (typeof o !== 'object') return;
delete o[objectKey];
this.setValue(settingKey, o);
}
static async deleteKeychainPasswords() {
public static async deleteKeychainPasswords() {
const secureKeys = this.keys(false, null, { secureOnly: true });
for (const key of secureKeys) {
await this.keychainService().deletePassword(`setting.${key}`);
@ -2121,7 +2121,7 @@ class Setting extends BaseModel {
return output;
}
static valueToString(key: string, value: any) {
public static valueToString(key: string, value: any) {
const md = this.settingMetadata(key);
value = this.formatValue(key, value);
if (md.type === SettingItemType.Int) return value.toFixed(0);
@ -2133,12 +2133,12 @@ class Setting extends BaseModel {
throw new Error(`Unhandled value type: ${md.type}`);
}
static filterValue(key: string, value: any) {
public static filterValue(key: string, value: any) {
const md = this.settingMetadata(key);
return md.filter ? md.filter(value) : value;
}
static formatValue(key: string | SettingItemType, value: any) {
public static formatValue(key: string | SettingItemType, value: any) {
const type = typeof key === 'string' ? this.settingMetadata(key).type : key;
if (type === SettingItemType.Int) return !value ? 0 : Math.floor(Number(value));
@ -2175,7 +2175,7 @@ class Setting extends BaseModel {
throw new Error(`Unhandled value type: ${type}`);
}
static value(key: string) {
public static value(key: string) {
// Need to copy arrays and objects since in setValue(), the old value and new one is compared
// with strict equality and the value is updated only if changed. However if the caller acquire
// and object and change a key, the objects will be detected as equal. By returning a copy
@ -2212,12 +2212,12 @@ class Setting extends BaseModel {
return this.value(key);
}
static isEnum(key: string) {
public static isEnum(key: string) {
const md = this.settingMetadata(key);
return md.isEnum === true;
}
static enumOptionValues(key: string) {
public static enumOptionValues(key: string) {
const options = this.enumOptions(key);
const output = [];
for (const n in options) {
@ -2227,7 +2227,7 @@ class Setting extends BaseModel {
return output;
}
static enumOptionLabel(key: string, value: any) {
public static enumOptionLabel(key: string, value: any) {
const options = this.enumOptions(key);
for (const n in options) {
if (n === value) return options[n];
@ -2235,14 +2235,14 @@ class Setting extends BaseModel {
return '';
}
static enumOptions(key: string) {
public static enumOptions(key: string) {
const metadata = this.metadata();
if (!metadata[key]) throw new Error(`Unknown key: ${key}`);
if (!metadata[key].options) throw new Error(`No options for: ${key}`);
return metadata[key].options();
}
static enumOptionsDoc(key: string, templateString: string = null) {
public static enumOptionsDoc(key: string, templateString: string = null) {
if (templateString === null) templateString = '%s: %s';
const options = this.enumOptions(key);
const output = [];
@ -2253,7 +2253,7 @@ class Setting extends BaseModel {
return output.join(', ');
}
static isAllowedEnumOption(key: string, value: any) {
public static isAllowedEnumOption(key: string, value: any) {
const options = this.enumOptions(key);
return !!options[value];
}
@ -2262,7 +2262,7 @@ class Setting extends BaseModel {
// { sync.5.path: 'http://example', sync.5.username: 'testing' }
// and baseKey is 'sync.5', the function will return
// { path: 'http://example', username: 'testing' }
static subValues(baseKey: string, settings: any, options: any = null) {
public static subValues(baseKey: string, settings: any, options: any = null) {
const includeBaseKeyInName = !!options && !!options.includeBaseKeyInName;
const output: any = {};
@ -2358,7 +2358,7 @@ class Setting extends BaseModel {
logger.debug('Settings have been saved.');
}
static scheduleChangeEvent() {
public static scheduleChangeEvent() {
if (this.changeEventTimeoutId_) shim.clearTimeout(this.changeEventTimeoutId_);
this.changeEventTimeoutId_ = shim.setTimeout(() => {
@ -2366,7 +2366,7 @@ class Setting extends BaseModel {
}, 1000);
}
static cancelScheduleChangeEvent() {
public static cancelScheduleChangeEvent() {
if (this.changeEventTimeoutId_) shim.clearTimeout(this.changeEventTimeoutId_);
this.changeEventTimeoutId_ = null;
}
@ -2388,7 +2388,7 @@ class Setting extends BaseModel {
eventManager.emit('settingsChange', { keys });
}
static scheduleSave() {
public static scheduleSave() {
if (!Setting.autoSaveEnabled) return;
if (this.saveTimeoutId_) shim.clearTimeout(this.saveTimeoutId_);
@ -2402,12 +2402,12 @@ class Setting extends BaseModel {
}, 500);
}
static cancelScheduleSave() {
public static cancelScheduleSave() {
if (this.saveTimeoutId_) shim.clearTimeout(this.saveTimeoutId_);
this.saveTimeoutId_ = null;
}
static publicSettings(appType: AppType) {
public static publicSettings(appType: AppType) {
if (!appType) throw new Error('appType is required');
const metadata = this.metadata();
@ -2424,7 +2424,7 @@ class Setting extends BaseModel {
return output;
}
static typeToString(typeId: number) {
public static typeToString(typeId: number) {
if (typeId === SettingItemType.Int) return 'int';
if (typeId === SettingItemType.String) return 'string';
if (typeId === SettingItemType.Bool) return 'bool';
@ -2438,7 +2438,7 @@ class Setting extends BaseModel {
return SettingSectionSource.Default;
}
static groupMetadatasBySections(metadatas: SettingItem[]) {
public static groupMetadatasBySections(metadatas: SettingItem[]) {
const sections = [];
const generalSection: any = { name: 'general', metadatas: [] };
const nameToSections: any = {};
@ -2472,7 +2472,7 @@ class Setting extends BaseModel {
return sections;
}
static sectionNameToLabel(name: string) {
public static sectionNameToLabel(name: string) {
if (name === 'general') return _('General');
if (name === 'sync') return _('Synchronisation');
if (name === 'appearance') return _('Appearance');
@ -2491,7 +2491,7 @@ class Setting extends BaseModel {
return name;
}
static sectionDescription(name: string) {
public static sectionDescription(name: string) {
if (name === 'markdownPlugins') return _('These plugins enhance the Markdown renderer with additional features. Please note that, while these features might be useful, they are not standard Markdown and thus most of them will only work in Joplin. Additionally, some of them are *incompatible* with the WYSIWYG editor. If you open a note that uses one of these plugins in that editor, you will lose the plugin formatting. It is indicated below which plugins are compatible or not with the WYSIWYG editor.');
if (name === 'general') return _('Notes and settings are stored in: %s', toSystemSlashes(this.value('profileDir'), process.platform));
@ -2500,7 +2500,7 @@ class Setting extends BaseModel {
return '';
}
static sectionNameToIcon(name: string) {
public static sectionNameToIcon(name: string) {
if (name === 'general') return 'icon-general';
if (name === 'sync') return 'icon-sync';
if (name === 'appearance') return 'icon-appearance';
@ -2519,7 +2519,7 @@ class Setting extends BaseModel {
return 'fas fa-cog';
}
static appTypeToLabel(name: string) {
public static appTypeToLabel(name: string) {
// Not translated for now because only used on Welcome notes (which are not translated)
if (name === 'cli') return 'CLI';
return name[0].toUpperCase() + name.substr(1).toLowerCase();

View File

@ -3,11 +3,11 @@
import BaseModel from '../BaseModel';
export default class SmartFilter extends BaseModel {
static tableName(): string {
public static tableName(): string {
throw new Error('Not using database');
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_SMART_FILTER;
}
}

View File

@ -7,15 +7,15 @@ import Note from './Note';
import { _ } from '../locale';
export default class Tag extends BaseItem {
static tableName() {
public static tableName() {
return 'tags';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_TAG;
}
static async noteIds(tagId: string) {
public static async noteIds(tagId: string) {
const rows = await this.db().selectAll('SELECT note_id FROM note_tags WHERE tag_id = ?', [tagId]);
const output = [];
for (let i = 0; i < rows.length; i++) {
@ -24,7 +24,7 @@ export default class Tag extends BaseItem {
return output;
}
static async notes(tagId: string, options: any = null) {
public static async notes(tagId: string, options: any = null) {
if (options === null) options = {};
const noteIds = await this.noteIds(tagId);
@ -39,7 +39,7 @@ export default class Tag extends BaseItem {
}
// Untag all the notes and delete tag
static async untagAll(tagId: string) {
public static async untagAll(tagId: string) {
const noteTags = await NoteTag.modelSelectAll('SELECT id FROM note_tags WHERE tag_id = ?', [tagId]);
for (let i = 0; i < noteTags.length; i++) {
await NoteTag.delete(noteTags[i].id);
@ -48,7 +48,7 @@ export default class Tag extends BaseItem {
await Tag.delete(tagId);
}
static async delete(id: string, options: any = null) {
public static async delete(id: string, options: any = null) {
if (!options) options = {};
await super.delete(id, options);
@ -59,7 +59,7 @@ export default class Tag extends BaseItem {
});
}
static async addNote(tagId: string, noteId: string) {
public static async addNote(tagId: string, noteId: string) {
const hasIt = await this.hasNote(tagId, noteId);
if (hasIt) return;
@ -89,7 +89,7 @@ export default class Tag extends BaseItem {
return output;
}
static async removeNote(tagId: string, noteId: string) {
public static async removeNote(tagId: string, noteId: string) {
const noteTags = await NoteTag.modelSelectAll('SELECT id FROM note_tags WHERE tag_id = ? and note_id = ?', [tagId, noteId]);
for (let i = 0; i < noteTags.length; i++) {
await NoteTag.delete(noteTags[i].id);
@ -101,34 +101,34 @@ export default class Tag extends BaseItem {
});
}
static loadWithCount(tagId: string) {
public static loadWithCount(tagId: string) {
const sql = 'SELECT * FROM tags_with_note_count WHERE id = ?';
return this.modelSelectOne(sql, [tagId]);
}
static async hasNote(tagId: string, noteId: string) {
public static async hasNote(tagId: string, noteId: string) {
const r = await this.db().selectOne('SELECT note_id FROM note_tags WHERE tag_id = ? AND note_id = ? LIMIT 1', [tagId, noteId]);
return !!r;
}
static async allWithNotes() {
public static async allWithNotes() {
return await Tag.modelSelectAll('SELECT * FROM tags_with_note_count');
}
static async searchAllWithNotes(options: any) {
public static async searchAllWithNotes(options: any) {
if (!options) options = {};
if (!options.conditions) options.conditions = [];
options.conditions.push('id IN (SELECT distinct id FROM tags_with_note_count)');
return this.search(options);
}
static async tagsByNoteId(noteId: string) {
public static async tagsByNoteId(noteId: string) {
const tagIds = await NoteTag.tagIdsByNoteId(noteId);
if (!tagIds.length) return [];
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${tagIds.join('","')}")`);
}
static async commonTagsByNoteIds(noteIds: string[]) {
public static async commonTagsByNoteIds(noteIds: string[]) {
if (!noteIds || noteIds.length === 0) {
return [];
}
@ -143,17 +143,17 @@ export default class Tag extends BaseItem {
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${commonTagIds.join('","')}")`);
}
static async loadByTitle(title: string) {
public static async loadByTitle(title: string) {
return this.loadByField('title', title, { caseInsensitive: true });
}
static async addNoteTagByTitle(noteId: string, tagTitle: string) {
public static async addNoteTagByTitle(noteId: string, tagTitle: string) {
let tag = await this.loadByTitle(tagTitle);
if (!tag) tag = await Tag.save({ title: tagTitle }, { userSideValidation: true });
return await this.addNote(tag.id, noteId);
}
static async setNoteTagsByTitles(noteId: string, tagTitles: string[]) {
public static async setNoteTagsByTitles(noteId: string, tagTitles: string[]) {
const previousTags = await this.tagsByNoteId(noteId);
const addedTitles = [];
@ -173,7 +173,7 @@ export default class Tag extends BaseItem {
}
}
static async setNoteTagsByIds(noteId: string, tagIds: string[]) {
public static async setNoteTagsByIds(noteId: string, tagIds: string[]) {
const previousTags = await this.tagsByNoteId(noteId);
const addedIds = [];
@ -190,7 +190,7 @@ export default class Tag extends BaseItem {
}
}
static async save(o: TagEntity, options: any = null) {
public static async save(o: TagEntity, options: any = null) {
options = Object.assign({}, {
dispatchUpdateAction: true,
userSideValidation: false,

View File

@ -22,7 +22,7 @@ export default class OneDriveApi {
// apps are considered "public"), in which case the secret should not be sent to the API.
// In practice the React Native app is public, and the Node one is not because we
// use a local server for the OAuth dance.
constructor(clientId: string, clientSecret: string, isPublic: boolean) {
public constructor(clientId: string, clientSecret: string, isPublic: boolean) {
this.clientId_ = clientId;
this.clientSecret_ = clientSecret;
this.auth_ = null;
@ -33,57 +33,57 @@ export default class OneDriveApi {
};
}
isPublic() {
public isPublic() {
return this.isPublic_;
}
dispatch(eventName: string, param: any) {
public dispatch(eventName: string, param: any) {
const ls = this.listeners_[eventName];
for (let i = 0; i < ls.length; i++) {
ls[i](param);
}
}
on(eventName: string, callback: Function) {
public on(eventName: string, callback: Function) {
this.listeners_[eventName].push(callback);
}
tokenBaseUrl() {
public tokenBaseUrl() {
return 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
}
nativeClientRedirectUrl() {
public nativeClientRedirectUrl() {
return 'https://login.microsoftonline.com/common/oauth2/nativeclient';
}
auth(): any {
public auth(): any {
return this.auth_;
}
setAuth(auth: any) {
public setAuth(auth: any) {
this.auth_ = auth;
this.dispatch('authRefreshed', this.auth());
}
token() {
public token() {
return this.auth_ ? this.auth_.access_token : null;
}
clientId() {
public clientId() {
return this.clientId_;
}
clientSecret() {
public clientSecret() {
return this.clientSecret_;
}
async appDirectory() {
public async appDirectory() {
const driveId = this.accountProperties_.driveId;
const r = await this.execJson('GET', `/me/drives/${driveId}/special/approot`);
return `${r.parentReference.path}/${r.name}`;
}
authCodeUrl(redirectUri: string) {
public authCodeUrl(redirectUri: string) {
const query = {
client_id: this.clientId_,
scope: 'files.readwrite offline_access sites.readwrite.all',
@ -94,7 +94,7 @@ export default class OneDriveApi {
return `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?${stringify(query)}`;
}
async execTokenRequest(code: string, redirectUri: string) {
public async execTokenRequest(code: string, redirectUri: string) {
const body: any = {};
body['client_id'] = this.clientId();
if (!this.isPublic()) body['client_secret'] = this.clientSecret();
@ -126,7 +126,7 @@ export default class OneDriveApi {
}
}
oneDriveErrorResponseToError(errorResponse: any) {
public oneDriveErrorResponseToError(errorResponse: any) {
if (!errorResponse) return new Error('Undefined error');
if (errorResponse.error) {
@ -140,7 +140,7 @@ export default class OneDriveApi {
}
}
async uploadChunk(url: string, handle: any, buffer: any, options: any) {
public async uploadChunk(url: string, handle: any, buffer: any, options: any) {
options = Object.assign({}, options);
if (!options.method) { options.method = 'POST'; }
@ -162,7 +162,7 @@ export default class OneDriveApi {
return response;
}
async uploadBigFile(url: string, options: any) {
public async uploadBigFile(url: string, options: any) {
const response = await shim.fetch(url, {
method: 'POST',
headers: {
@ -227,7 +227,7 @@ export default class OneDriveApi {
}
}
async exec(method: string, path: string, query: any = null, data: any = null, options: any = null) {
public async exec(method: string, path: string, query: any = null, data: any = null, options: any = null) {
if (!path) throw new Error('Path is required');
method = method.toUpperCase();
@ -367,11 +367,11 @@ export default class OneDriveApi {
throw new Error(`Could not execute request after multiple attempts: ${method} ${url}`);
}
setAccountProperties(accountProperties: any) {
public setAccountProperties(accountProperties: any) {
this.accountProperties_ = accountProperties;
}
async execAccountPropertiesRequest() {
public async execAccountPropertiesRequest() {
try {
const response = await this.exec('GET', 'https://graph.microsoft.com/v1.0/me/drive');
@ -383,7 +383,7 @@ export default class OneDriveApi {
}
}
async execJson(method: string, path: string, query: any = null, data: any = null) {
public async execJson(method: string, path: string, query: any = null, data: any = null) {
const response = await this.exec(method, path, query, data);
const errorResponseText = await response.text();
try {
@ -396,13 +396,13 @@ export default class OneDriveApi {
}
}
async execText(method: string, path: string, query: any = null, data: any = null) {
public async execText(method: string, path: string, query: any = null, data: any = null) {
const response = await this.exec(method, path, query, data);
const output = await response.text();
return output;
}
async refreshAccessToken() {
public async refreshAccessToken() {
if (!this.auth_ || !this.auth_.refresh_token) {
this.setAuth(null);
throw new Error(_('Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.'));

View File

@ -17,7 +17,7 @@ class Registry {
private db_: any;
private isOnMobileData_ = false;
logger() {
public logger() {
if (!this.logger_) {
// console.warn('Calling logger before it is initialized');
return new Logger();
@ -26,35 +26,35 @@ class Registry {
return this.logger_;
}
setLogger(l: Logger) {
public setLogger(l: Logger) {
this.logger_ = l;
}
setShowErrorMessageBoxHandler(v: any) {
public setShowErrorMessageBoxHandler(v: any) {
this.showErrorMessageBoxHandler_ = v;
}
showErrorMessageBox(message: string) {
public showErrorMessageBox(message: string) {
if (!this.showErrorMessageBoxHandler_) return;
this.showErrorMessageBoxHandler_(message);
}
// If isOnMobileData is true, the doWifiConnectionCheck is not set
// and the sync.mobileWifiOnly setting is true it will cancel the sync.
setIsOnMobileData(isOnMobileData: boolean) {
public setIsOnMobileData(isOnMobileData: boolean) {
this.isOnMobileData_ = isOnMobileData;
}
resetSyncTarget(syncTargetId: number = null) {
public resetSyncTarget(syncTargetId: number = null) {
if (syncTargetId === null) syncTargetId = Setting.value('sync.target');
delete this.syncTargets_[syncTargetId];
}
syncTargetNextcloud() {
public syncTargetNextcloud() {
return this.syncTarget(SyncTargetRegistry.nameToId('nextcloud'));
}
syncTarget = (syncTargetId: number = null) => {
public syncTarget = (syncTargetId: number = null) => {
if (syncTargetId === null) syncTargetId = Setting.value('sync.target');
if (this.syncTargets_[syncTargetId]) return this.syncTargets_[syncTargetId];
@ -70,7 +70,7 @@ class Registry {
// This can be used when some data has been modified and we want to make
// sure it gets synced. So we wait for the current sync operation to
// finish (if one is running), then we trigger a sync just after.
waitForSyncFinishedThenSync = async () => {
public waitForSyncFinishedThenSync = async () => {
if (!Setting.value('sync.target')) {
this.logger().info('waitForSyncFinishedThenSync - cancelling because no sync target is selected.');
return;
@ -86,7 +86,7 @@ class Registry {
}
};
scheduleSync = async (delay: number = null, syncOptions: any = null, doWifiConnectionCheck: boolean = false) => {
public scheduleSync = async (delay: number = null, syncOptions: any = null, doWifiConnectionCheck: boolean = false) => {
this.schedSyncCalls_.push(true);
try {
@ -194,7 +194,7 @@ class Registry {
}
};
setupRecurrentSync() {
public setupRecurrentSync() {
this.setupRecurrentCalls_.push(true);
try {
@ -223,15 +223,15 @@ class Registry {
}
}
setDb = (v: any) => {
public setDb = (v: any) => {
this.db_ = v;
};
db() {
public db() {
return this.db_;
}
cancelTimers_() {
private cancelTimers_() {
if (this.recurrentSyncId_) {
shim.clearInterval(this.recurrentSyncId_);
this.recurrentSyncId_ = null;
@ -242,7 +242,7 @@ class Registry {
}
}
cancelTimers = async () => {
public cancelTimers = async () => {
this.logger().info('Cancelling sync timers');
this.cancelTimers_();

View File

@ -9,31 +9,31 @@ export default class AlarmService {
private static logger_: Logger;
// private static inAppNotificationHandler_:any;
static setDriver(v: any) {
public static setDriver(v: any) {
this.driver_ = v;
if (this.driver_.setService) this.driver_.setService(this);
}
static driver() {
public static driver() {
if (!this.driver_) throw new Error('AlarmService driver not set!');
return this.driver_;
}
static setLogger(v: Logger) {
public static setLogger(v: Logger) {
this.logger_ = v;
}
static logger() {
public static logger() {
return this.logger_;
}
static setInAppNotificationHandler(v: any) {
public static setInAppNotificationHandler(v: any) {
// this.inAppNotificationHandler_ = v;
if (this.driver_.setInAppNotificationHandler) this.driver_.setInAppNotificationHandler(v);
}
static async garbageCollect() {
public static async garbageCollect() {
this.logger().info('Garbage collecting alarms...');
// Delete alarms that have already been triggered
@ -50,7 +50,7 @@ export default 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: any, isDeleted: boolean = false) {
public static async updateNoteNotification(noteOrId: any, isDeleted: boolean = false) {
try {
let note = null;
let noteId = null;
@ -114,7 +114,7 @@ export default class AlarmService {
}
}
static async updateAllNotifications() {
public static async updateAllNotifications() {
this.logger().info('Updating all notifications...');
await this.garbageCollect();

View File

@ -14,29 +14,29 @@ export default class AlarmServiceDriverNode {
private notifications_: any = {};
private service_: any = null;
constructor(options: Options) {
public 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;
}
setService(s: any) {
public setService(s: any) {
this.service_ = s;
}
logger() {
public logger() {
return this.service_.logger();
}
hasPersistentNotifications() {
public hasPersistentNotifications() {
return false;
}
notificationIsSet(id: number) {
public notificationIsSet(id: number) {
return id in this.notifications_;
}
clearNotification(id: number) {
public clearNotification(id: number) {
if (!this.notificationIsSet(id)) return;
shim.clearTimeout(this.notifications_[id].timeoutId);
delete this.notifications_[id];
@ -126,7 +126,7 @@ export default class AlarmServiceDriverNode {
return 'granted';
}
async scheduleNotification(notification: Notification) {
public async scheduleNotification(notification: Notification) {
const now = Date.now();
const interval = notification.date.getTime() - now;
if (interval < 0) return;

View File

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

View File

@ -31,53 +31,53 @@ export default class DecryptionWorker {
private startCalls_: boolean[] = [];
private encryptionService_: EncryptionService = null;
constructor() {
public constructor() {
this.state_ = 'idle';
this.logger_ = new Logger();
this.eventEmitter_ = new EventEmitter();
}
setLogger(l: Logger) {
public setLogger(l: Logger) {
this.logger_ = l;
}
logger() {
public logger() {
return this.logger_;
}
on(eventName: string, callback: Function) {
public on(eventName: string, callback: Function) {
return this.eventEmitter_.on(eventName, callback);
}
off(eventName: string, callback: Function) {
public off(eventName: string, callback: Function) {
return this.eventEmitter_.removeListener(eventName, callback);
}
static instance() {
public static instance() {
if (DecryptionWorker.instance_) return DecryptionWorker.instance_;
DecryptionWorker.instance_ = new DecryptionWorker();
return DecryptionWorker.instance_;
}
setEncryptionService(v: any) {
public setEncryptionService(v: any) {
this.encryptionService_ = v;
}
setKvStore(v: KvStore) {
public setKvStore(v: KvStore) {
this.kvStore_ = v;
}
encryptionService() {
public encryptionService() {
if (!this.encryptionService_) throw new Error('DecryptionWorker.encryptionService_ is not set!!');
return this.encryptionService_;
}
kvStore() {
public kvStore() {
if (!this.kvStore_) throw new Error('DecryptionWorker.kvStore_ is not set!!');
return this.kvStore_;
}
async scheduleStart() {
public async scheduleStart() {
if (this.scheduleId_) return;
this.scheduleId_ = shim.setTimeout(() => {
@ -88,7 +88,7 @@ export default class DecryptionWorker {
}, 1000);
}
async decryptionDisabledItems() {
public async decryptionDisabledItems() {
let items = await this.kvStore().searchByPrefix('decrypt:');
items = items.filter(item => item.value > this.maxDecryptionAttempts_);
items = items.map(item => {
@ -101,15 +101,15 @@ export default class DecryptionWorker {
return items;
}
async clearDisabledItem(typeId: string, itemId: string) {
public async clearDisabledItem(typeId: string, itemId: string) {
await this.kvStore().deleteValue(`decrypt:${typeId}:${itemId}`);
}
async clearDisabledItems() {
public async clearDisabledItems() {
await this.kvStore().deleteByPrefix('decrypt:');
}
dispatchReport(report: any) {
public dispatchReport(report: any) {
const action = Object.assign({}, report);
action.type = 'DECRYPTION_WORKER_SET';
this.dispatch(action);
@ -301,7 +301,7 @@ export default class DecryptionWorker {
return output;
}
async destroy() {
public async destroy() {
this.eventEmitter_.removeAllListeners();
if (this.scheduleId_) {
shim.clearTimeout(this.scheduleId_);

View File

@ -55,27 +55,27 @@ export default class ExternalEditWatcher {
};
}
tempDir() {
public tempDir() {
return Setting.value('profileDir');
}
on(eventName: string, callback: Function) {
public on(eventName: string, callback: Function) {
return this.eventEmitter_.on(eventName, callback);
}
off(eventName: string, callback: Function) {
public off(eventName: string, callback: Function) {
return this.eventEmitter_.removeListener(eventName, callback);
}
setLogger(l: Logger) {
public setLogger(l: Logger) {
this.logger_ = l;
}
logger() {
public logger() {
return this.logger_;
}
watch(fileToWatch: string) {
public watch(fileToWatch: string) {
if (!this.chokidar_) return;
if (!this.watcher_) {
@ -164,11 +164,11 @@ export default class ExternalEditWatcher {
return this.watcher_;
}
noteIdToFilePath_(noteId: string) {
private noteIdToFilePath_(noteId: string) {
return `${this.tempDir()}/edit-${noteId}.md`;
}
noteFilePathToId_(path: string) {
private noteFilePathToId_(path: string) {
let id: any = toSystemSlashes(path, 'linux').split('/');
if (!id.length) throw new Error(`Invalid path: ${path}`);
id = id[id.length - 1];
@ -178,7 +178,7 @@ export default class ExternalEditWatcher {
return id[1];
}
watchedFiles() {
public watchedFiles() {
if (!this.watcher_) return [];
const output = [];
@ -196,7 +196,7 @@ export default class ExternalEditWatcher {
return output;
}
noteIsWatched(note: NoteEntity) {
public noteIsWatched(note: NoteEntity) {
if (!this.watcher_) return false;
const noteFilename = basename(this.noteIdToFilePath_(note.id));
@ -215,7 +215,7 @@ export default class ExternalEditWatcher {
return false;
}
async openAndWatch(note: NoteEntity) {
public async openAndWatch(note: NoteEntity) {
if (!note || !note.id) {
this.logger().warn('ExternalEditWatcher: Cannot open note: ', note);
return;
@ -235,7 +235,7 @@ export default class ExternalEditWatcher {
this.logger().info(`ExternalEditWatcher: Started watching ${filePath}`);
}
async stopWatching(noteId: string) {
public async stopWatching(noteId: string) {
if (!noteId) return;
const filePath = this.noteIdToFilePath_(noteId);
@ -248,7 +248,7 @@ export default class ExternalEditWatcher {
this.logger().info(`ExternalEditWatcher: Stopped watching ${filePath}`);
}
async stopWatchingAll() {
public async stopWatchingAll() {
const filePaths = this.watchedFiles();
for (let i = 0; i < filePaths.length; i++) {
await shim.fsDriver().remove(filePaths[i]);
@ -262,7 +262,7 @@ export default class ExternalEditWatcher {
});
}
async updateNoteFile(note: NoteEntity) {
public async updateNoteFile(note: NoteEntity) {
if (!this.noteIsWatched(note)) return;
if (!note || !note.id) {
@ -279,7 +279,7 @@ export default class ExternalEditWatcher {
await this.writeNoteToFile_(note);
}
async writeNoteToFile_(note: NoteEntity) {
private async writeNoteToFile_(note: NoteEntity) {
if (!note || !note.id) {
this.logger().warn('ExternalEditWatcher: Cannot update note file: ', note);
return null;

View File

@ -4,7 +4,7 @@ import ItemChange from '../models/ItemChange';
const dayMs = 86400000;
export default class ItemChangeUtils {
static async deleteProcessedChanges(itemMinTtl: number = dayMs * 90) {
public static async deleteProcessedChanges(itemMinTtl: number = dayMs * 90) {
const lastProcessedChangeIds = [
Setting.value('resourceService.lastProcessedChangeId'),
Setting.value('searchEngine.lastProcessedChangeId'),

View File

@ -5,18 +5,18 @@ export default class MigrationService extends BaseService {
private static instance_: MigrationService;
static instance() {
public static instance() {
if (this.instance_) return this.instance_;
this.instance_ = new MigrationService();
return this.instance_;
}
async runScript(num: number) {
public async runScript(num: number) {
const script = Migration.script(num);
await script.exec();
}
async run() {
public async run() {
const migrations = await Migration.migrationsToDo();
for (const migration of migrations) {

View File

@ -41,7 +41,7 @@ interface ReportItem {
}
export default class ReportService {
csvEscapeCell(cell: string) {
public csvEscapeCell(cell: string) {
cell = this.csvValueToString(cell);
const output = cell.replace(/"/, '""');
if (this.csvCellRequiresQuotes(cell, ',')) {
@ -50,26 +50,26 @@ export default class ReportService {
return output;
}
csvCellRequiresQuotes(cell: string, delimiter: string) {
public csvCellRequiresQuotes(cell: string, delimiter: string) {
if (cell.indexOf('\n') >= 0) return true;
if (cell.indexOf('"') >= 0) return true;
if (cell.indexOf(delimiter) >= 0) return true;
return false;
}
csvValueToString(v: string) {
public csvValueToString(v: string) {
if (v === undefined || v === null) return '';
return v.toString();
}
csvCreateLine(row: string[]) {
public csvCreateLine(row: string[]) {
for (let i = 0; i < row.length; i++) {
row[i] = this.csvEscapeCell(row[i]);
}
return row.join(',');
}
csvCreate(rows: any[]) {
public csvCreate(rows: any[]) {
const output = [];
for (let i = 0; i < rows.length; i++) {
output.push(this.csvCreateLine(rows[i]));
@ -77,7 +77,7 @@ export default class ReportService {
return output.join('\n');
}
async basicItemList(option: any = null) {
public async basicItemList(option: any = null) {
if (!option) option = {};
if (!option.format) option.format = 'array';
@ -100,7 +100,7 @@ export default class ReportService {
return option.format === 'csv' ? this.csvCreate(output) : output;
}
async syncStatus(syncTarget: number) {
public async syncStatus(syncTarget: number) {
const output: any = {
items: {},
total: {},
@ -162,7 +162,7 @@ export default class ReportService {
return section;
}
async status(syncTarget: number): Promise<ReportSection[]> {
public async status(syncTarget: number): Promise<ReportSection[]> {
const r = await this.syncStatus(syncTarget);
const sections: ReportSection[] = [];
let section: ReportSection = null;

View File

@ -36,7 +36,7 @@ export default class ResourceEditWatcher {
private tempDir_: string = '';
private openItem_: OpenItemFn;
constructor() {
public constructor() {
this.logger_ = new Logger();
this.dispatch = () => {};
this.watcher_ = null;
@ -44,13 +44,13 @@ export default class ResourceEditWatcher {
this.eventEmitter_ = new EventEmitter();
}
initialize(logger: any, dispatch: Function, openItem: OpenItemFn) {
public initialize(logger: any, dispatch: Function, openItem: OpenItemFn) {
this.logger_ = logger;
this.dispatch = dispatch;
this.openItem_ = openItem;
}
static instance() {
public static instance() {
if (this.instance_) return this.instance_;
this.instance_ = new ResourceEditWatcher();
return this.instance_;
@ -65,19 +65,19 @@ export default class ResourceEditWatcher {
return this.tempDir_;
}
logger() {
public logger() {
return this.logger_;
}
on(eventName: string, callback: Function) {
public on(eventName: string, callback: Function) {
return this.eventEmitter_.on(eventName, callback);
}
off(eventName: string, callback: Function) {
public off(eventName: string, callback: Function) {
return this.eventEmitter_.removeListener(eventName, callback);
}
externalApi() {
public externalApi() {
return {
openAndWatch: async ({ resourceId }: any) => {
return this.openAndWatch(resourceId);
@ -260,7 +260,7 @@ export default class ResourceEditWatcher {
this.openItem_(watchedItem.path);
}
async stopWatching(resourceId: string) {
public async stopWatching(resourceId: string) {
if (!resourceId) return;
const item = this.watchedItemByResourceId(resourceId);

View File

@ -24,43 +24,43 @@ export default class ResourceFetcher extends BaseService {
private scheduleQueueProcessIID_: any;
private scheduleAutoAddResourcesIID_: any;
constructor(fileApi: any = null) {
public constructor(fileApi: any = null) {
super();
this.setFileApi(fileApi);
}
static instance() {
public static instance() {
if (ResourceFetcher.instance_) return ResourceFetcher.instance_;
ResourceFetcher.instance_ = new ResourceFetcher();
return ResourceFetcher.instance_;
}
on(eventName: string, callback: Function) {
public on(eventName: string, callback: Function) {
return this.eventEmitter_.on(eventName, callback);
}
off(eventName: string, callback: Function) {
public off(eventName: string, callback: Function) {
return this.eventEmitter_.removeListener(eventName, callback);
}
setLogger(logger: Logger) {
public setLogger(logger: Logger) {
this.logger_ = logger;
}
logger() {
public logger() {
return this.logger_;
}
setFileApi(v: any) {
public setFileApi(v: any) {
if (v !== null && typeof v !== 'function') throw new Error(`fileApi must be a function that returns the API. Type is ${typeof v}`);
this.fileApi_ = v;
}
async fileApi() {
public async fileApi() {
return this.fileApi_();
}
queuedItemIndex_(resourceId: string) {
private queuedItemIndex_(resourceId: string) {
for (let i = 0; i < this.fetchingItems_.length; i++) {
const item = this.fetchingItems_[i];
if (item.id === resourceId) return i;
@ -68,7 +68,7 @@ export default class ResourceFetcher extends BaseService {
return -1;
}
updateReport() {
public updateReport() {
const fetchingCount = Object.keys(this.fetchingItems_).length;
this.dispatch({
type: 'RESOURCE_FETCHER_SET',
@ -77,7 +77,7 @@ export default class ResourceFetcher extends BaseService {
});
}
async markForDownload(resourceIds: string[]) {
public async markForDownload(resourceIds: string[]) {
if (!Array.isArray(resourceIds)) resourceIds = [resourceIds];
const fetchStatuses = await Resource.fetchStatuses(resourceIds);
@ -97,7 +97,7 @@ export default class ResourceFetcher extends BaseService {
}
}
queueDownload_(resourceId: string, priority: string = null) {
public queueDownload_(resourceId: string, priority: string = null) {
if (priority === null) priority = 'normal';
const index = this.queuedItemIndex_(resourceId);
@ -118,7 +118,7 @@ export default class ResourceFetcher extends BaseService {
return true;
}
async startDownload_(resourceId: string) {
private async startDownload_(resourceId: string) {
if (this.fetchingItems_[resourceId]) return;
this.fetchingItems_[resourceId] = true;
@ -195,7 +195,7 @@ export default class ResourceFetcher extends BaseService {
});
}
processQueue_() {
private processQueue_() {
while (Object.getOwnPropertyNames(this.fetchingItems_).length < this.maxDownloads_) {
if (!this.queue_.length) break;
const item = this.queue_.splice(0, 1)[0];
@ -207,7 +207,7 @@ export default class ResourceFetcher extends BaseService {
}
}
async waitForAllFinished() {
public async waitForAllFinished() {
return new Promise((resolve) => {
const iid = shim.setInterval(() => {
if (!this.updateReportIID_ &&
@ -223,7 +223,7 @@ export default class ResourceFetcher extends BaseService {
});
}
async autoAddResources(limit: number = null) {
public async autoAddResources(limit: number = null) {
this.autoAddResourcesCalls_.push(true);
try {
if (limit === null) limit = 10;
@ -251,12 +251,12 @@ export default class ResourceFetcher extends BaseService {
}
}
async start() {
public async start() {
await Resource.resetStartedFetchStatus();
void this.autoAddResources(10);
}
scheduleQueueProcess() {
public scheduleQueueProcess() {
if (this.scheduleQueueProcessIID_) {
shim.clearTimeout(this.scheduleQueueProcessIID_);
this.scheduleQueueProcessIID_ = null;
@ -268,7 +268,7 @@ export default class ResourceFetcher extends BaseService {
}, 100);
}
scheduleAutoAddResources() {
public scheduleAutoAddResources() {
if (this.scheduleAutoAddResourcesIID_) return;
this.scheduleAutoAddResourcesIID_ = shim.setTimeout(() => {
@ -277,12 +277,12 @@ export default class ResourceFetcher extends BaseService {
}, 1000);
}
async fetchAll() {
public async fetchAll() {
await Resource.resetStartedFetchStatus();
void this.autoAddResources(null);
}
async destroy() {
public async destroy() {
this.eventEmitter_.removeAllListeners();
if (this.scheduleQueueProcessIID_) {
shim.clearTimeout(this.scheduleQueueProcessIID_);

View File

@ -32,17 +32,17 @@ export default class RevisionService extends BaseService {
private isCollecting_ = false;
public isRunningInBackground_ = false;
static instance() {
public static instance() {
if (this.instance_) return this.instance_;
this.instance_ = new RevisionService();
return this.instance_;
}
oldNoteCutOffDate_() {
public oldNoteCutOffDate_() {
return Date.now() - Setting.value('revisionService.oldNoteInterval');
}
async isOldNote(noteId: string) {
public async isOldNote(noteId: string) {
if (noteId in this.isOldNotesCache_) return this.isOldNotesCache_[noteId];
const isOld = await Note.noteIsOlderThan(noteId, this.oldNoteCutOffDate_());
@ -50,7 +50,7 @@ export default class RevisionService extends BaseService {
return isOld;
}
noteMetadata_(note: NoteEntity) {
private noteMetadata_(note: NoteEntity) {
const excludedFields = ['type_', 'title', 'body', 'created_time', 'updated_time', 'encryption_applied', 'encryption_cipher_text', 'is_conflict'];
const md: any = {};
for (const k in note) {
@ -214,7 +214,7 @@ export default class RevisionService extends BaseService {
logger.info(`collectRevisions: Created revisions for ${doneNoteIds.length} notes`);
}
async deleteOldRevisions(ttl: number) {
public async deleteOldRevisions(ttl: number) {
return Revision.deleteOldRevisions(ttl);
}
@ -239,11 +239,11 @@ export default class RevisionService extends BaseService {
return output;
}
restoreFolderTitle() {
public restoreFolderTitle() {
return _('Restored Notes');
}
async restoreFolder() {
public async restoreFolder() {
let folder = await Folder.loadByTitle(this.restoreFolderTitle());
if (!folder) {
folder = await Folder.save({ title: this.restoreFolderTitle() });
@ -267,7 +267,7 @@ export default class RevisionService extends BaseService {
return _('The note "%s" has been successfully restored to the notebook "%s".', substrWithEllipsis(note.title, 0, 32), this.restoreFolderTitle());
}
async importRevisionNote(note: NoteEntity): Promise<NoteEntity> {
public async importRevisionNote(note: NoteEntity): Promise<NoteEntity> {
const toImport = Object.assign({}, note);
delete toImport.id;
delete toImport.updated_time;
@ -282,7 +282,7 @@ export default class RevisionService extends BaseService {
return Note.save(toImport);
}
async maintenance() {
public async maintenance() {
this.maintenanceCalls_.push(true);
try {
const startTime = Date.now();
@ -308,7 +308,7 @@ export default class RevisionService extends BaseService {
}
}
runInBackground(collectRevisionInterval: number = null) {
public runInBackground(collectRevisionInterval: number = null) {
if (this.isRunningInBackground_) return;
this.isRunningInBackground_ = true;
@ -325,7 +325,7 @@ export default class RevisionService extends BaseService {
}, collectRevisionInterval);
}
async cancelTimers() {
public async cancelTimers() {
if (this.maintenanceTimer1_) {
shim.clearTimeout(this.maintenanceTimer1_);
this.maintenanceTimer1_ = null;

View File

@ -6,22 +6,22 @@ class UndoQueue {
private inner_: any[] = [];
private size_: number = 20;
pop() {
public pop() {
return this.inner_.pop();
}
push(e: any) {
public push(e: any) {
this.inner_.push(e);
while (this.length > this.size_) {
this.inner_.splice(0, 1);
}
}
get length(): number {
public get length(): number {
return this.inner_.length;
}
at(index: number): any {
public at(index: number): any {
return this.inner_[index];
}
@ -35,31 +35,31 @@ export default class UndoRedoService {
private eventEmitter: any = new EventEmitter();
private isUndoing: boolean = false;
constructor() {
public constructor() {
this.push = this.push.bind(this);
}
on(eventName: string, callback: Function) {
public on(eventName: string, callback: Function) {
return this.eventEmitter.on(eventName, callback);
}
off(eventName: string, callback: Function) {
public off(eventName: string, callback: Function) {
return this.eventEmitter.removeListener(eventName, callback);
}
push(state: any) {
public push(state: any) {
this.undoStates.push(state);
this.redoStates = new UndoQueue();
this.eventEmitter.emit('stackChange');
}
schedulePush(state: any) {
public schedulePush(state: any) {
this.pushAsyncQueue.push(async () => {
this.push(state);
});
}
async undo(redoState: any) {
public async undo(redoState: any) {
if (this.isUndoing) return;
if (!this.canUndo) throw new Error('Nothing to undo');
this.isUndoing = true;
@ -71,7 +71,7 @@ export default class UndoRedoService {
return state;
}
async redo(undoState: any) {
public async redo(undoState: any) {
if (this.isUndoing) return;
if (!this.canRedo) throw new Error('Nothing to redo');
this.isUndoing = true;
@ -83,7 +83,7 @@ export default class UndoRedoService {
return state;
}
async reset() {
public async reset() {
this.undoStates = new UndoQueue();
this.redoStates = new UndoQueue();
this.isUndoing = false;
@ -92,11 +92,11 @@ export default class UndoRedoService {
return output;
}
get canUndo(): boolean {
public get canUndo(): boolean {
return !!this.undoStates.length;
}
get canRedo(): boolean {
public get canRedo(): boolean {
return !!this.redoStates.length;
}

View File

@ -62,7 +62,7 @@ export default class MenuUtils {
private menuItemCache_: MenuItemCache = {};
private menuItemPropsCache_: MenuItemPropsCache = {};
constructor(service: CommandService) {
public constructor(service: CommandService) {
this.service_ = service;
}

View File

@ -26,7 +26,7 @@ export default class ToolbarButtonUtils {
private service_: CommandService;
private toolbarButtonCache_: ToolbarButtonCache = {};
constructor(service: CommandService) {
public constructor(service: CommandService) {
this.service_ = service;
}

View File

@ -1103,7 +1103,7 @@ export class RawContextKey<T> extends ContextKeyDefinedExpr {
private readonly _defaultValue: T | undefined;
constructor(key: string, defaultValue: T | undefined) {
public constructor(key: string, defaultValue: T | undefined) {
super(key);
this._defaultValue = defaultValue;
}

View File

@ -79,7 +79,7 @@ export default class EncryptionService {
},
};
constructor() {
public constructor() {
// Note: 1 MB is very slow with Node and probably even worse on mobile.
//
// On mobile the time it takes to decrypt increases exponentially for some reason, so it's important
@ -117,23 +117,23 @@ export default class EncryptionService {
return this.defaultMasterKeyEncryptionMethod_;
}
loadedMasterKeysCount() {
public loadedMasterKeysCount() {
return Object.keys(this.decryptedMasterKeys_).length;
}
chunkSize() {
public chunkSize() {
return this.chunkSize_;
}
defaultEncryptionMethod() {
public defaultEncryptionMethod() {
return this.defaultEncryptionMethod_;
}
setActiveMasterKeyId(id: string) {
public setActiveMasterKeyId(id: string) {
setActiveMasterKeyId(id);
}
activeMasterKeyId() {
public activeMasterKeyId() {
const id = getActiveMasterKeyId();
if (!id) {
const error: any = new Error('No master key is defined as active. Check this: Either one or more master keys exist but no password was provided for any of them. Or no master key exist. Or master keys and password exist, but none was set as active.');
@ -162,11 +162,11 @@ export default class EncryptionService {
if (makeActive) this.setActiveMasterKeyId(model.id);
}
unloadMasterKey(model: MasterKeyEntity) {
public unloadMasterKey(model: MasterKeyEntity) {
delete this.decryptedMasterKeys_[model.id];
}
loadedMasterKey(id: string) {
public loadedMasterKey(id: string) {
if (!this.decryptedMasterKeys_[id]) {
const error: any = new Error(`Master key is not loaded: ${id}`);
error.code = 'masterKeyNotLoaded';
@ -176,22 +176,22 @@ export default class EncryptionService {
return this.decryptedMasterKeys_[id];
}
loadedMasterKeyIds() {
public loadedMasterKeyIds() {
return Object.keys(this.decryptedMasterKeys_);
}
fsDriver() {
public fsDriver() {
if (!EncryptionService.fsDriver_) throw new Error('EncryptionService.fsDriver_ not set!');
return EncryptionService.fsDriver_;
}
sha256(string: string) {
public sha256(string: string) {
const sjcl = shim.sjclModule;
const bitArray = sjcl.hash.sha256.hash(string);
return sjcl.codec.hex.fromBits(bitArray);
}
async generateApiToken() {
public async generateApiToken() {
return await this.randomHexString(64);
}
@ -390,7 +390,7 @@ export default class EncryptionService {
throw new Error(`Unknown encryption method: ${method}`);
}
async decrypt(method: EncryptionMethod, key: string, cipherText: string) {
public async decrypt(method: EncryptionMethod, key: string, cipherText: string) {
if (!method) throw new Error('Encryption method is required');
if (!key) throw new Error('Encryption key is required');
@ -411,7 +411,7 @@ export default class EncryptionService {
}
}
async encryptAbstract_(source: any, destination: any, options: EncryptOptions = null) {
private async encryptAbstract_(source: any, destination: any, options: EncryptOptions = null) {
options = Object.assign({}, {
encryptionMethod: this.defaultEncryptionMethod(),
}, options);
@ -447,7 +447,7 @@ export default class EncryptionService {
}
}
async decryptAbstract_(source: any, destination: any, options: EncryptOptions = null) {
private async decryptAbstract_(source: any, destination: any, options: EncryptOptions = null) {
if (!options) options = {};
const header: any = await this.decodeHeaderSource_(source);
@ -474,7 +474,7 @@ export default class EncryptionService {
}
}
stringReader_(string: string, sync = false) {
private stringReader_(string: string, sync = false) {
const reader = {
index: 0,
read: function(size: number) {
@ -487,7 +487,7 @@ export default class EncryptionService {
return reader;
}
stringWriter_() {
private stringWriter_() {
const output: any = {
data: [],
append: async function(data: any) {
@ -501,7 +501,7 @@ export default class EncryptionService {
return output;
}
async fileReader_(path: string, encoding: any) {
private async fileReader_(path: string, encoding: any) {
const handle = await this.fsDriver().open(path, 'r');
const reader = {
handle: handle,
@ -515,7 +515,7 @@ export default class EncryptionService {
return reader;
}
async fileWriter_(path: string, encoding: any) {
private async fileWriter_(path: string, encoding: any) {
return {
append: async (data: any) => {
return this.fsDriver().appendFile(path, data, encoding);
@ -538,7 +538,7 @@ export default class EncryptionService {
return destination.data.join('');
}
async encryptFile(srcPath: string, destPath: string, options: EncryptOptions = null) {
public async encryptFile(srcPath: string, destPath: string, options: EncryptOptions = null) {
let source = await this.fileReader_(srcPath, 'base64');
let destination = await this.fileWriter_(destPath, 'ascii');
@ -563,7 +563,7 @@ export default class EncryptionService {
await cleanUp();
}
async decryptFile(srcPath: string, destPath: string, options: EncryptOptions = null) {
public async decryptFile(srcPath: string, destPath: string, options: EncryptOptions = null) {
let source = await this.fileReader_(srcPath, 'ascii');
let destination = await this.fileWriter_(destPath, 'base64');
@ -588,13 +588,13 @@ export default class EncryptionService {
await cleanUp();
}
headerTemplate(version: number) {
public headerTemplate(version: number) {
const r = (this.headerTemplates_ as any)[version];
if (!r) throw new Error(`Unknown header version: ${version}`);
return r;
}
encodeHeader_(header: any) {
public encodeHeader_(header: any) {
// Sanity check
if (header.masterKeyId.length !== 32) throw new Error(`Invalid master key ID size: ${header.masterKeyId}`);
@ -605,12 +605,12 @@ export default class EncryptionService {
return `JED01${encryptionMetadata}`;
}
async decodeHeaderString(cipherText: any) {
public async decodeHeaderString(cipherText: any) {
const source = this.stringReader_(cipherText);
return this.decodeHeaderSource_(source);
}
async decodeHeaderSource_(source: any) {
private async decodeHeaderSource_(source: any) {
const identifier = await source.read(5);
if (!isValidHeaderIdentifier(identifier)) throw new JoplinError(`Invalid encryption identifier. Data is not actually encrypted? ID was: ${identifier}`, 'invalidIdentifier');
const mdSizeHex = await source.read(6);
@ -620,7 +620,7 @@ export default class EncryptionService {
return this.decodeHeaderBytes_(identifier + mdSizeHex + md);
}
decodeHeaderBytes_(headerHexaBytes: any) {
public decodeHeaderBytes_(headerHexaBytes: any) {
const reader: any = this.stringReader_(headerHexaBytes, true);
const identifier = reader.read(3);
const version = parseInt(reader.read(2), 16);
@ -652,18 +652,18 @@ export default class EncryptionService {
return output;
}
isValidEncryptionMethod(method: EncryptionMethod) {
public isValidEncryptionMethod(method: EncryptionMethod) {
return [EncryptionMethod.SJCL, EncryptionMethod.SJCL1a, EncryptionMethod.SJCL2, EncryptionMethod.SJCL3, EncryptionMethod.SJCL4].indexOf(method) >= 0;
}
async itemIsEncrypted(item: any) {
public async itemIsEncrypted(item: any) {
if (!item) throw new Error('No item');
const ItemClass = BaseItem.itemClass(item);
if (!ItemClass.encryptionSupported()) return false;
return item.encryption_applied && isValidHeaderIdentifier(item.encryption_cipher_text, true);
}
async fileIsEncrypted(path: string) {
public async fileIsEncrypted(path: string) {
const handle = await this.fsDriver().open(path, 'r');
const headerIdentifier = await this.fsDriver().readFileChunk(handle, 5, 'ascii');
await this.fsDriver().close(handle);

View File

@ -7,32 +7,32 @@ export default class InteropService_Exporter_Base {
private metadata_: any = {};
// @ts-ignore
async init(destDir: string, options: any = {}) {}
public async init(destDir: string, options: any = {}) {}
// @ts-ignore
async prepareForProcessingItemType(itemType: number, itemsToExport: any[]) {}
public async prepareForProcessingItemType(itemType: number, itemsToExport: any[]) {}
// @ts-ignore
async processItem(itemType: number, item: any) {}
public async processItem(itemType: number, item: any) {}
// @ts-ignore
async processResource(resource: any, filePath: string) {}
async close() {}
public async processResource(resource: any, filePath: string) {}
public async close() {}
setMetadata(md: any) {
public setMetadata(md: any) {
this.metadata_ = md;
}
metadata() {
public metadata() {
return this.metadata_;
}
updateContext(context: any) {
public updateContext(context: any) {
this.context_ = Object.assign({}, this.context_, context);
}
context() {
public context() {
return this.context_;
}
async temporaryDirectory_(createIt: boolean) {
protected async temporaryDirectory_(createIt: boolean) {
const md5 = require('md5');
const tempDir = `${Setting.value('tempDir')}/${md5(Math.random() + Date.now())}`;
if (createIt) await require('fs-extra').mkdirp(tempDir);

View File

@ -7,12 +7,12 @@ export default class InteropService_Exporter_Custom extends InteropService_Expor
private customContext_: ExportContext;
private module_: Module = null;
constructor(module: Module) {
public constructor(module: Module) {
super();
this.module_ = module;
}
async init(destPath: string, options: ExportOptions) {
public async init(destPath: string, options: ExportOptions) {
this.customContext_ = {
destPath: destPath,
options: options,
@ -21,15 +21,15 @@ export default class InteropService_Exporter_Custom extends InteropService_Expor
return this.module_.onInit(this.customContext_);
}
async processItem(itemType: number, item: any) {
public async processItem(itemType: number, item: any) {
return this.module_.onProcessItem(this.customContext_, itemType, item);
}
async processResource(resource: any, filePath: string) {
public async processResource(resource: any, filePath: string) {
return this.module_.onProcessResource(this.customContext_, resource, filePath);
}
async close() {
public async close() {
return this.module_.onClose(this.customContext_);
}
}

View File

@ -26,7 +26,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
private style_: any;
private packIntoSingleFile_: boolean = false;
async init(path: string, options: any = {}) {
public async init(path: string, options: any = {}) {
this.customCss_ = options.customCss ? options.customCss : '';
if (this.metadata().target === 'file') {
@ -48,7 +48,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
this.style_ = themeStyle(Setting.THEME_LIGHT);
}
async makeDirPath_(item: any, pathPart: string = null) {
private async makeDirPath_(item: any, pathPart: string = null) {
let output = '';
while (true) {
if (item.type_ === BaseModel.TYPE_FOLDER) {
@ -64,7 +64,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
}
}
async processNoteResources_(item: any) {
private async processNoteResources_(item: any) {
const target = this.metadata().target;
const linkedResourceIds = await Note.linkedResourceIds(item.body);
const relativePath = target === 'directory' ? rtrimSlashes(await this.makeDirPath_(item, '..')) : '';
@ -85,7 +85,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
return newBody;
}
async processItem(_itemType: number, item: any) {
public async processItem(_itemType: number, item: any) {
if ([BaseModel.TYPE_NOTE, BaseModel.TYPE_FOLDER].indexOf(item.type_) < 0) return;
let dirPath = '';
@ -150,7 +150,7 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte
}
}
async processResource(resource: any, filePath: string) {
public async processResource(resource: any, filePath: string) {
const destResourcePath = `${this.resourceDir_}/${basename(filePath)}`;
await shim.fsDriver().copy(filePath, destResourcePath);
this.resources_.push(resource);

View File

@ -14,7 +14,7 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
private resourceDir_: string;
private createdDirs_: string[];
async init(destDir: string) {
public async init(destDir: string) {
this.destDir_ = destDir;
this.resourceDir_ = destDir ? `${destDir}/_resources` : null;
this.createdDirs_ = [];
@ -23,7 +23,7 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
await shim.fsDriver().mkdir(this.resourceDir_);
}
async makeDirPath_(item: any, pathPart: string = null, findUniqueFilename: boolean = true) {
private async makeDirPath_(item: any, pathPart: string = null, findUniqueFilename: boolean = true) {
let output = '';
while (true) {
if (item.type_ === BaseModel.TYPE_FOLDER) {
@ -39,14 +39,14 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
}
}
async relaceLinkedItemIdsByRelativePaths_(item: any) {
private async relaceLinkedItemIdsByRelativePaths_(item: any) {
const relativePathToRoot = await this.makeDirPath_(item, '..');
const newBody = await this.replaceResourceIdsByRelativePaths_(item.body, relativePathToRoot);
return await this.replaceNoteIdsByRelativePaths_(newBody, relativePathToRoot);
}
async replaceResourceIdsByRelativePaths_(noteBody: string, relativePathToRoot: string) {
private async replaceResourceIdsByRelativePaths_(noteBody: string, relativePathToRoot: string) {
const linkedResourceIds = await Note.linkedResourceIds(noteBody);
const resourcePaths = this.context() && this.context().destResourcePaths ? this.context().destResourcePaths : {};
@ -56,7 +56,7 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
return await this.replaceItemIdsByRelativePaths_(noteBody, linkedResourceIds, resourcePaths, createRelativePath);
}
async replaceNoteIdsByRelativePaths_(noteBody: string, relativePathToRoot: string) {
private async replaceNoteIdsByRelativePaths_(noteBody: string, relativePathToRoot: string) {
const linkedNoteIds = await Note.linkedNoteIds(noteBody);
const notePaths = this.context() && this.context().notePaths ? this.context().notePaths : {};
@ -66,7 +66,7 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
return await this.replaceItemIdsByRelativePaths_(noteBody, linkedNoteIds, notePaths, createRelativePath);
}
async replaceItemIdsByRelativePaths_(noteBody: string, linkedItemIds: string[], paths: any, fn_createRelativePath: Function) {
private async replaceItemIdsByRelativePaths_(noteBody: string, linkedItemIds: string[], paths: any, fn_createRelativePath: Function) {
let newBody = noteBody;
for (let i = 0; i < linkedItemIds.length; i++) {
@ -78,7 +78,7 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
return newBody;
}
async prepareForProcessingItemType(itemType: number, itemsToExport: any[]) {
public async prepareForProcessingItemType(itemType: number, itemsToExport: any[]) {
if (itemType === BaseModel.TYPE_NOTE) {
// Create unique file path for the note
const context: any = {
@ -114,7 +114,7 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
return await Note.replaceResourceInternalToExternalLinks(await Note.serialize(modNote, ['body']));
}
async processItem(_itemType: number, item: any) {
public async processItem(_itemType: number, item: any) {
if ([BaseModel.TYPE_NOTE, BaseModel.TYPE_FOLDER].indexOf(item.type_) < 0) return;
if (item.type_ === BaseModel.TYPE_FOLDER) {
@ -150,7 +150,7 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
return fileName;
}
async processResource(resource: ResourceEntity, filePath: string) {
public async processResource(resource: ResourceEntity, filePath: string) {
const context = this.context();
if (!context.destResourcePaths) context.destResourcePaths = {};
@ -163,5 +163,5 @@ export default class InteropService_Exporter_Md extends InteropService_Exporter_
this.updateContext(context);
}
async close() {}
public async close() {}
}

View File

@ -8,7 +8,7 @@ export default class InteropService_Exporter_Raw extends InteropService_Exporter
private destDir_: string;
private resourceDir_: string;
async init(destDir: string) {
public async init(destDir: string) {
this.destDir_ = destDir;
this.resourceDir_ = destDir ? `${destDir}/resources` : null;
@ -16,17 +16,17 @@ export default class InteropService_Exporter_Raw extends InteropService_Exporter
await shim.fsDriver().mkdir(this.resourceDir_);
}
async processItem(itemType: number, item: any) {
public async processItem(itemType: number, item: any) {
const ItemClass = BaseItem.getClassByItemType(itemType);
const serialized = await ItemClass.serialize(item);
const filePath = `${this.destDir_}/${ItemClass.systemPath(item)}`;
await shim.fsDriver().writeFile(filePath, serialized, 'utf-8');
}
async processResource(_resource: any, filePath: string) {
public async processResource(_resource: any, filePath: string) {
const destResourcePath = `${this.resourceDir_}/${basename(filePath)}`;
await shim.fsDriver().copy(filePath, destResourcePath);
}
async close() {}
public async close() {}
}

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