From a8f6676fb3a3ea9ad27d0e0be258d45d0e4f8bae Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Sat, 20 Jan 2024 14:29:21 +0000 Subject: [PATCH] Chore: Convert CLI app class to TS --- .eslintignore | 1 + .gitignore | 1 + packages/app-cli/app/app.js | 918 +++++++++--------- packages/app-cli/app/app.ts | 472 +++++++++ packages/app-cli/app/autocompletion.js | 2 +- packages/app-cli/app/command-attach.ts | 2 +- packages/app-cli/app/command-cat.ts | 2 +- packages/app-cli/app/command-config.ts | 2 +- packages/app-cli/app/command-cp.ts | 2 +- packages/app-cli/app/command-done.ts | 5 +- packages/app-cli/app/command-edit.ts | 2 +- packages/app-cli/app/command-exit.ts | 2 +- .../app-cli/app/command-export-sync-status.ts | 2 +- packages/app-cli/app/command-export.ts | 2 +- packages/app-cli/app/command-geoloc.ts | 2 +- packages/app-cli/app/command-help.ts | 2 +- packages/app-cli/app/command-import.ts | 2 +- packages/app-cli/app/command-ls.ts | 2 +- packages/app-cli/app/command-mkbook.ts | 2 +- packages/app-cli/app/command-mknote.js | 2 +- packages/app-cli/app/command-mktodo.js | 2 +- packages/app-cli/app/command-mv.ts | 2 +- packages/app-cli/app/command-ren.ts | 2 +- packages/app-cli/app/command-rmbook.ts | 2 +- packages/app-cli/app/command-rmnote.ts | 2 +- packages/app-cli/app/command-set.ts | 2 +- packages/app-cli/app/command-status.js | 2 +- packages/app-cli/app/command-sync.ts | 2 +- packages/app-cli/app/command-tag.js | 2 +- packages/app-cli/app/command-todo.js | 2 +- packages/app-cli/app/command-use.ts | 2 +- packages/app-cli/app/main.js | 2 +- packages/app-cli/app/utils/testUtils.ts | 2 +- packages/lib/BaseApplication.ts | 2 +- 34 files changed, 961 insertions(+), 494 deletions(-) create mode 100644 packages/app-cli/app/app.ts diff --git a/.eslintignore b/.eslintignore index 8d804e416..cd2be4a55 100644 --- a/.eslintignore +++ b/.eslintignore @@ -86,6 +86,7 @@ packages/lib/countable/Countable.js # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD packages/app-cli/app/LinkSelector.js +packages/app-cli/app/app.js packages/app-cli/app/base-command.js packages/app-cli/app/command-apidoc.js packages/app-cli/app/command-attach.js diff --git a/.gitignore b/.gitignore index 34e1afc60..ca8193cc9 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,7 @@ docs/**/*.mustache # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD packages/app-cli/app/LinkSelector.js +packages/app-cli/app/app.js packages/app-cli/app/base-command.js packages/app-cli/app/command-apidoc.js packages/app-cli/app/command-attach.js diff --git a/packages/app-cli/app/app.js b/packages/app-cli/app/app.js index a74fe98ca..1d5dbb74c 100644 --- a/packages/app-cli/app/app.js +++ b/packages/app-cli/app/app.js @@ -1,469 +1,461 @@ -const BaseApplication = require('@joplin/lib/BaseApplication').default; -const { refreshFolders } = require('@joplin/lib/folders-screen-utils.js'); -const ResourceService = require('@joplin/lib/services/ResourceService').default; -const BaseModel = require('@joplin/lib/BaseModel').default; -const Folder = require('@joplin/lib/models/Folder').default; -const BaseItem = require('@joplin/lib/models/BaseItem').default; -const Note = require('@joplin/lib/models/Note').default; -const Tag = require('@joplin/lib/models/Tag').default; -const Setting = require('@joplin/lib/models/Setting').default; -const { reg } = require('@joplin/lib/registry.js'); -const { fileExtension } = require('@joplin/lib/path-utils'); -const { splitCommandString } = require('@joplin/utils'); -const { splitCommandBatch } = require('@joplin/lib/string-utils'); -const { _ } = require('@joplin/lib/locale'); -const fs = require('fs-extra'); +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const BaseApplication_1 = require("@joplin/lib/BaseApplication"); +const folders_screen_utils_js_1 = require("@joplin/lib/folders-screen-utils.js"); +const ResourceService_1 = require("@joplin/lib/services/ResourceService"); +const BaseModel_1 = require("@joplin/lib/BaseModel"); +const Folder_1 = require("@joplin/lib/models/Folder"); +const BaseItem_1 = require("@joplin/lib/models/BaseItem"); +const Note_1 = require("@joplin/lib/models/Note"); +const Tag_1 = require("@joplin/lib/models/Tag"); +const Setting_1 = require("@joplin/lib/models/Setting"); +const registry_js_1 = require("@joplin/lib/registry.js"); +const path_utils_1 = require("@joplin/lib/path-utils"); +const utils_1 = require("@joplin/utils"); +const locale_1 = require("@joplin/lib/locale"); +const fs_extra_1 = require("fs-extra"); +const RevisionService_1 = require("@joplin/lib/services/RevisionService"); +const shim_1 = require("@joplin/lib/shim"); +const setupCommand_1 = require("./setupCommand"); const { cliUtils } = require('./cli-utils.js'); const Cache = require('@joplin/lib/Cache'); -const RevisionService = require('@joplin/lib/services/RevisionService').default; -const shim = require('@joplin/lib/shim').default; -const setupCommand = require('./setupCommand').default; - -class Application extends BaseApplication { - constructor() { - super(); - - this.showPromptString_ = true; - this.commands_ = {}; - this.commandMetadata_ = null; - this.activeCommand_ = null; - this.allCommandsLoaded_ = false; - this.showStackTraces_ = false; - this.gui_ = null; - this.cache_ = new Cache(); - } - - gui() { - return this.gui_; - } - - commandStdoutMaxWidth() { - return this.gui().stdoutMaxWidth(); - } - - async guessTypeAndLoadItem(pattern, options = null) { - let type = BaseModel.TYPE_NOTE; - if (pattern.indexOf('/') === 0) { - type = BaseModel.TYPE_FOLDER; - pattern = pattern.substr(1); - } - return this.loadItem(type, pattern, options); - } - - async loadItem(type, pattern, options = null) { - const output = await this.loadItems(type, pattern, options); - - if (output.length > 1) { - // output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; }); - - // let answers = { 0: _('[Cancel]') }; - // for (let i = 0; i < output.length; i++) { - // answers[i + 1] = output[i].title; - // } - - // Not really useful with new UI? - throw new Error(_('More than one item match "%s". Please narrow down your query.', pattern)); - - // let msg = _('More than one item match "%s". Please select one:', pattern); - // const response = await cliUtils.promptMcq(msg, answers); - // if (!response) return null; - - // return output[response - 1]; - } else { - return output.length ? output[0] : null; - } - } - - async loadItems(type, pattern, options = null) { - if (type === 'folderOrNote') { - const folders = await this.loadItems(BaseModel.TYPE_FOLDER, pattern, options); - if (folders.length) return folders; - return await this.loadItems(BaseModel.TYPE_NOTE, pattern, options); - } - - pattern = pattern ? pattern.toString() : ''; - - if (type === BaseModel.TYPE_FOLDER && (pattern === Folder.conflictFolderTitle() || pattern === Folder.conflictFolderId())) return [Folder.conflictFolder()]; - - if (!options) options = {}; - - const parent = options.parent ? options.parent : app().currentFolder(); - const ItemClass = BaseItem.itemClass(type); - - if (type === BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { - // Handle it as pattern - if (!parent) throw new Error(_('No notebook selected.')); - return await Note.previews(parent.id, { titlePattern: pattern }); - } else { - // Single item - let item = null; - if (type === BaseModel.TYPE_NOTE) { - if (!parent) throw new Error(_('No notebook has been specified.')); - item = await ItemClass.loadFolderNoteByField(parent.id, 'title', pattern); - } else { - item = await ItemClass.loadByTitle(pattern); - } - if (item) return [item]; - - item = await ItemClass.load(pattern); // Load by id - if (item) return [item]; - - if (pattern.length >= 2) { - return await ItemClass.loadByPartialId(pattern); - } - } - - return []; - } - - setupCommand(cmd) { - return setupCommand(cmd, t => this.stdout(t), () => this.store(), () => this.gui()); - } - - stdout(text) { - return this.gui().stdout(text); - } - - async exit(code = 0) { - const doExit = async () => { - this.gui().exit(); - await super.exit(code); - }; - - // Give it a few seconds to cancel otherwise exit anyway - shim.setTimeout(async () => { - await doExit(); - }, 5000); - - if (await reg.syncTarget().syncStarted()) { - this.stdout(_('Cancelling background synchronisation... Please wait.')); - const sync = await reg.syncTarget().synchronizer(); - await sync.cancel(); - } - - await doExit(); - } - - commands(uiType = null) { - if (!this.allCommandsLoaded_) { - // eslint-disable-next-line github/array-foreach -- Old code before rule was applied - fs.readdirSync(__dirname).forEach(path => { - if (path.indexOf('command-') !== 0) return; - if (path.endsWith('.test.js')) return; - const ext = fileExtension(path); - if (ext !== 'js') return; - - const CommandClass = require(`./${path}`); - let cmd = new CommandClass(); - if (!cmd.enabled()) return; - cmd = this.setupCommand(cmd); - this.commands_[cmd.name()] = cmd; - }); - - this.allCommandsLoaded_ = true; - } - - if (uiType !== null) { - const temp = []; - for (const n in this.commands_) { - if (!this.commands_.hasOwnProperty(n)) continue; - const c = this.commands_[n]; - if (!c.supportsUi(uiType)) continue; - temp[n] = c; - } - return temp; - } - - return this.commands_; - } - - async commandNames() { - const metadata = await this.commandMetadata(); - const output = []; - for (const n in metadata) { - if (!metadata.hasOwnProperty(n)) continue; - output.push(n); - } - return output; - } - - async commandMetadata() { - if (this.commandMetadata_) return this.commandMetadata_; - - let output = await this.cache_.getItem('metadata'); - if (output) { - this.commandMetadata_ = output; - return { ...this.commandMetadata_ }; - } - - const commands = this.commands(); - - output = {}; - for (const n in commands) { - if (!commands.hasOwnProperty(n)) continue; - const cmd = commands[n]; - output[n] = cmd.metadata(); - } - - await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24); - - this.commandMetadata_ = output; - return { ...this.commandMetadata_ }; - } - - hasGui() { - return this.gui() && !this.gui().isDummy(); - } - - findCommandByName(name) { - if (this.commands_[name]) return this.commands_[name]; - - let CommandClass = null; - try { - CommandClass = require(`${__dirname}/command-${name}.js`); - } catch (error) { - if (error.message && error.message.indexOf('Cannot find module') >= 0) { - const e = new Error(_('No such command: %s', name)); - e.type = 'notFound'; - throw e; - } else { - throw error; - } - } - - let cmd = new CommandClass(); - cmd = this.setupCommand(cmd); - this.commands_[name] = cmd; - return this.commands_[name]; - } - - dummyGui() { - return { - isDummy: () => { - return true; - }, - prompt: (initialText = '', promptString = '', options = null) => { - return cliUtils.prompt(initialText, promptString, options); - }, - showConsole: () => {}, - maximizeConsole: () => {}, - stdout: text => { - // eslint-disable-next-line no-console - console.info(text); - }, - fullScreen: () => {}, - exit: () => {}, - showModalOverlay: () => {}, - hideModalOverlay: () => {}, - stdoutMaxWidth: () => { - return 100; - }, - forceRender: () => {}, - termSaveState: () => {}, - termRestoreState: () => {}, - }; - } - - async execCommand(argv) { - if (!argv.length) return this.execCommand(['help']); - // reg.logger().debug('execCommand()', argv); - const commandName = argv[0]; - this.activeCommand_ = this.findCommandByName(commandName); - - let outException = null; - try { - if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name())); - const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv); - await this.activeCommand_.action(cmdArgs); - } catch (error) { - outException = error; - } - this.activeCommand_ = null; - if (outException) throw outException; - } - - currentCommand() { - return this.activeCommand_; - } - - async loadKeymaps() { - const defaultKeyMap = [ - { keys: [':'], type: 'function', command: 'enter_command_line_mode' }, - { keys: ['TAB'], type: 'function', command: 'focus_next' }, - { keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' }, - { keys: ['UP'], type: 'function', command: 'move_up' }, - { keys: ['DOWN'], type: 'function', command: 'move_down' }, - { keys: ['PAGE_UP'], type: 'function', command: 'page_up' }, - { keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' }, - { keys: ['ENTER'], type: 'function', command: 'activate' }, - { keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' }, - { keys: ['n'], type: 'function', command: 'next_link' }, - { keys: ['b'], type: 'function', command: 'previous_link' }, - { keys: ['o'], type: 'function', command: 'open_link' }, - { keys: [' '], command: 'todo toggle $n' }, - { keys: ['tc'], type: 'function', command: 'toggle_console' }, - { keys: ['tm'], type: 'function', command: 'toggle_metadata' }, - { keys: ['ti'], type: 'function', command: 'toggle_ids' }, - { keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 }, - { keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 }, - { keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 }, - { keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 }, - { keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 }, - { keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 }, - ]; - - // Filter the keymap item by command so that items in keymap.json can override - // the default ones. - const itemsByCommand = {}; - - for (let i = 0; i < defaultKeyMap.length; i++) { - itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]; - } - - const filePath = `${Setting.value('profileDir')}/keymap.json`; - if (await fs.pathExists(filePath)) { - try { - let configString = await fs.readFile(filePath, 'utf-8'); - configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments - const keymap = JSON.parse(configString); - for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) { - const item = keymap[keymapIndex]; - itemsByCommand[item.command] = item; - } - } catch (error) { - let msg = error.message ? error.message : ''; - msg = `Could not load keymap ${filePath}\n${msg}`; - error.message = msg; - throw error; - } - } - - const output = []; - for (const n in itemsByCommand) { - if (!itemsByCommand.hasOwnProperty(n)) continue; - output.push(itemsByCommand[n]); - } - - // Map reserved shortcuts to their equivalent key - // https://github.com/cronvel/terminal-kit/issues/101 - for (let i = 0; i < output.length; i++) { - const newKeys = output[i].keys.map(k => { - k = k.replace(/CTRL_H/g, 'BACKSPACE'); - k = k.replace(/CTRL_I/g, 'TAB'); - k = k.replace(/CTRL_M/g, 'ENTER'); - return k; - }); - output[i].keys = newKeys; - } - - return output; - } - - async commandList(argv) { - if (argv.length && argv[0] === 'batch') { - const commands = []; - const commandLines = splitCommandBatch(await fs.readFile(argv[1], 'utf-8')); - - for (const commandLine of commandLines) { - if (!commandLine.trim()) continue; - const splitted = splitCommandString(commandLine.trim()); - commands.push(splitted); - } - return commands; - } else { - return [argv]; - } - } - - // We need this special case here because by the time the `version` command - // runs, the keychain has already been setup. - checkIfKeychainEnabled(argv) { - return argv.indexOf('version') < 0; - } - - async start(argv) { - const keychainEnabled = this.checkIfKeychainEnabled(argv); - - argv = await super.start(argv, { keychainEnabled }); - - cliUtils.setStdout(object => { - return this.stdout(object); - }); - - this.initRedux(); - - // If we have some arguments left at this point, it's a command - // so execute it. - if (argv.length) { - this.gui_ = this.dummyGui(); - - this.currentFolder_ = await Folder.load(Setting.value('activeFolderId')); - - await this.applySettingsSideEffects(); - - try { - const commands = await this.commandList(argv); - for (const command of commands) { - await this.execCommand(command); - } - } catch (error) { - if (this.showStackTraces_) { - console.error(error); - } else { - // eslint-disable-next-line no-console - console.info(error.message); - } - process.exit(1); - } - - await Setting.saveAll(); - - // Need to call exit() explicitly, otherwise Node wait for any timeout to complete - // https://stackoverflow.com/questions/18050095 - process.exit(0); - } else { - // Otherwise open the GUI - const keymap = await this.loadKeymaps(); - - const AppGui = require('./app-gui.js'); - this.gui_ = new AppGui(this, this.store(), keymap); - this.gui_.setLogger(this.logger()); - await this.gui_.start(); - - // Since the settings need to be loaded before the store is created, it will never - // receive the SETTING_UPDATE_ALL even, which mean state.settings will not be - // initialised. So we manually call dispatchUpdateAll() to force an update. - Setting.dispatchUpdateAll(); - - await refreshFolders((action) => { this.store().dispatch(action); }); - - const tags = await Tag.allWithNotes(); - - ResourceService.runInBackground(); - - RevisionService.instance().runInBackground(); - - this.dispatch({ - type: 'TAG_UPDATE_ALL', - items: tags, - }); - - this.store().dispatch({ - type: 'FOLDER_SELECT', - id: Setting.value('activeFolderId'), - }); - - this.startRotatingLogMaintenance(Setting.value('profileDir')); - } - } +const { splitCommandBatch } = require('@joplin/lib/string-utils'); +class Application extends BaseApplication_1.default { + constructor() { + super(...arguments); + this.commands_ = {}; + this.commandMetadata_ = null; + this.activeCommand_ = null; + this.allCommandsLoaded_ = false; + this.gui_ = null; + this.cache_ = new Cache(); + } + gui() { + return this.gui_; + } + commandStdoutMaxWidth() { + return this.gui().stdoutMaxWidth(); + } + guessTypeAndLoadItem(pattern, options = null) { + return __awaiter(this, void 0, void 0, function* () { + let type = BaseModel_1.default.TYPE_NOTE; + if (pattern.indexOf('/') === 0) { + type = BaseModel_1.default.TYPE_FOLDER; + pattern = pattern.substr(1); + } + return this.loadItem(type, pattern, options); + }); + } + loadItem(type, pattern, options = null) { + return __awaiter(this, void 0, void 0, function* () { + const output = yield this.loadItems(type, pattern, options); + if (output.length > 1) { + // output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; }); + // let answers = { 0: _('[Cancel]') }; + // for (let i = 0; i < output.length; i++) { + // answers[i + 1] = output[i].title; + // } + // Not really useful with new UI? + throw new Error((0, locale_1._)('More than one item match "%s". Please narrow down your query.', pattern)); + // let msg = _('More than one item match "%s". Please select one:', pattern); + // const response = await cliUtils.promptMcq(msg, answers); + // if (!response) return null; + // return output[response - 1]; + } + else { + return output.length ? output[0] : null; + } + }); + } + loadItems(type, pattern, options = null) { + return __awaiter(this, void 0, void 0, function* () { + if (type === 'folderOrNote') { + const folders = yield this.loadItems(BaseModel_1.default.TYPE_FOLDER, pattern, options); + if (folders.length) + return folders; + return yield this.loadItems(BaseModel_1.default.TYPE_NOTE, pattern, options); + } + pattern = pattern ? pattern.toString() : ''; + if (type === BaseModel_1.default.TYPE_FOLDER && (pattern === Folder_1.default.conflictFolderTitle() || pattern === Folder_1.default.conflictFolderId())) + return [Folder_1.default.conflictFolder()]; + if (!options) + options = {}; + const parent = options.parent ? options.parent : app().currentFolder(); + const ItemClass = BaseItem_1.default.itemClass(type); + if (type === BaseModel_1.default.TYPE_NOTE && pattern.indexOf('*') >= 0) { + // Handle it as pattern + if (!parent) + throw new Error((0, locale_1._)('No notebook selected.')); + return yield Note_1.default.previews(parent.id, { titlePattern: pattern }); + } + else { + // Single item + let item = null; + if (type === BaseModel_1.default.TYPE_NOTE) { + if (!parent) + throw new Error((0, locale_1._)('No notebook has been specified.')); + item = yield ItemClass.loadFolderNoteByField(parent.id, 'title', pattern); + } + else { + item = yield ItemClass.loadByTitle(pattern); + } + if (item) + return [item]; + item = yield ItemClass.load(pattern); // Load by id + if (item) + return [item]; + if (pattern.length >= 2) { + return yield ItemClass.loadByPartialId(pattern); + } + } + return []; + }); + } + setupCommand(cmd) { + return (0, setupCommand_1.default)(cmd, (t) => this.stdout(t), () => this.store(), () => this.gui()); + } + stdout(text) { + return this.gui().stdout(text); + } + exit(code = 0) { + const _super = Object.create(null, { + exit: { get: () => super.exit } + }); + return __awaiter(this, void 0, void 0, function* () { + const doExit = () => __awaiter(this, void 0, void 0, function* () { + this.gui().exit(); + yield _super.exit.call(this, code); + }); + // Give it a few seconds to cancel otherwise exit anyway + shim_1.default.setTimeout(() => __awaiter(this, void 0, void 0, function* () { + yield doExit(); + }), 5000); + if (yield registry_js_1.reg.syncTarget().syncStarted()) { + this.stdout((0, locale_1._)('Cancelling background synchronisation... Please wait.')); + const sync = yield registry_js_1.reg.syncTarget().synchronizer(); + yield sync.cancel(); + } + yield doExit(); + }); + } + commands(uiType = null) { + if (!this.allCommandsLoaded_) { + // eslint-disable-next-line github/array-foreach -- Old code before rule was applied + (0, fs_extra_1.readdirSync)(__dirname).forEach(path => { + if (path.indexOf('command-') !== 0) + return; + if (path.endsWith('.test.js')) + return; + const ext = (0, path_utils_1.fileExtension)(path); + if (ext !== 'js') + return; + const CommandClass = require(`./${path}`); + let cmd = new CommandClass(); + if (!cmd.enabled()) + return; + cmd = this.setupCommand(cmd); + this.commands_[cmd.name()] = cmd; + }); + this.allCommandsLoaded_ = true; + } + if (uiType !== null) { + const temp = {}; + for (const n in this.commands_) { + if (!this.commands_.hasOwnProperty(n)) + continue; + const c = this.commands_[n]; + if (!c.supportsUi(uiType)) + continue; + temp[n] = c; + } + return temp; + } + return this.commands_; + } + commandNames() { + return __awaiter(this, void 0, void 0, function* () { + const metadata = yield this.commandMetadata(); + const output = []; + for (const n in metadata) { + if (!metadata.hasOwnProperty(n)) + continue; + output.push(n); + } + return output; + }); + } + commandMetadata() { + return __awaiter(this, void 0, void 0, function* () { + if (this.commandMetadata_) + return this.commandMetadata_; + let output = yield this.cache_.getItem('metadata'); + if (output) { + this.commandMetadata_ = output; + return Object.assign({}, this.commandMetadata_); + } + const commands = this.commands(); + output = {}; + for (const n in commands) { + if (!commands.hasOwnProperty(n)) + continue; + const cmd = commands[n]; + output[n] = cmd.metadata(); + } + yield this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24); + this.commandMetadata_ = output; + return Object.assign({}, this.commandMetadata_); + }); + } + hasGui() { + return this.gui() && !this.gui().isDummy(); + } + findCommandByName(name) { + if (this.commands_[name]) + return this.commands_[name]; + let CommandClass = null; + try { + CommandClass = require(`${__dirname}/command-${name}.js`); + } + catch (error) { + if (error.message && error.message.indexOf('Cannot find module') >= 0) { + const e = new Error((0, locale_1._)('No such command: %s', name)); + e.type = 'notFound'; + throw e; + } + else { + throw error; + } + } + let cmd = new CommandClass(); + cmd = this.setupCommand(cmd); + this.commands_[name] = cmd; + return this.commands_[name]; + } + dummyGui() { + return { + isDummy: () => { + return true; + }, + prompt: (initialText = '', promptString = '', options = null) => { + return cliUtils.prompt(initialText, promptString, options); + }, + showConsole: () => { }, + maximizeConsole: () => { }, + stdout: (text) => { + // eslint-disable-next-line no-console + console.info(text); + }, + fullScreen: () => { }, + exit: () => { }, + showModalOverlay: () => { }, + hideModalOverlay: () => { }, + stdoutMaxWidth: () => { + return 100; + }, + forceRender: () => { }, + termSaveState: () => { }, + termRestoreState: () => { }, + }; + } + execCommand(argv) { + return __awaiter(this, void 0, void 0, function* () { + if (!argv.length) + return this.execCommand(['help']); + // reg.logger().debug('execCommand()', argv); + const commandName = argv[0]; + this.activeCommand_ = this.findCommandByName(commandName); + let outException = null; + try { + if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) + throw new Error((0, locale_1._)('The command "%s" is only available in GUI mode', this.activeCommand_.name())); + const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv); + yield this.activeCommand_.action(cmdArgs); + } + catch (error) { + outException = error; + } + this.activeCommand_ = null; + if (outException) + throw outException; + }); + } + currentCommand() { + return this.activeCommand_; + } + loadKeymaps() { + return __awaiter(this, void 0, void 0, function* () { + const defaultKeyMap = [ + { keys: [':'], type: 'function', command: 'enter_command_line_mode' }, + { keys: ['TAB'], type: 'function', command: 'focus_next' }, + { keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' }, + { keys: ['UP'], type: 'function', command: 'move_up' }, + { keys: ['DOWN'], type: 'function', command: 'move_down' }, + { keys: ['PAGE_UP'], type: 'function', command: 'page_up' }, + { keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' }, + { keys: ['ENTER'], type: 'function', command: 'activate' }, + { keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' }, + { keys: ['n'], type: 'function', command: 'next_link' }, + { keys: ['b'], type: 'function', command: 'previous_link' }, + { keys: ['o'], type: 'function', command: 'open_link' }, + { keys: [' '], type: 'prompt', command: 'todo toggle $n' }, + { keys: ['tc'], type: 'function', command: 'toggle_console' }, + { keys: ['tm'], type: 'function', command: 'toggle_metadata' }, + { keys: ['ti'], type: 'function', command: 'toggle_ids' }, + { keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 }, + { keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 }, + { keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 }, + { keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 }, + { keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 }, + { keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 }, + ]; + // Filter the keymap item by command so that items in keymap.json can override + // the default ones. + const itemsByCommand = {}; + for (let i = 0; i < defaultKeyMap.length; i++) { + itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]; + } + const filePath = `${Setting_1.default.value('profileDir')}/keymap.json`; + if (yield (0, fs_extra_1.pathExists)(filePath)) { + try { + let configString = yield (0, fs_extra_1.readFile)(filePath, 'utf-8'); + configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments + const keymap = JSON.parse(configString); + for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) { + const item = keymap[keymapIndex]; + itemsByCommand[item.command] = item; + } + } + catch (error) { + let msg = error.message ? error.message : ''; + msg = `Could not load keymap ${filePath}\n${msg}`; + error.message = msg; + throw error; + } + } + const output = []; + for (const n in itemsByCommand) { + if (!itemsByCommand.hasOwnProperty(n)) + continue; + output.push(itemsByCommand[n]); + } + // Map reserved shortcuts to their equivalent key + // https://github.com/cronvel/terminal-kit/issues/101 + for (let i = 0; i < output.length; i++) { + const newKeys = output[i].keys.map(k => { + k = k.replace(/CTRL_H/g, 'BACKSPACE'); + k = k.replace(/CTRL_I/g, 'TAB'); + k = k.replace(/CTRL_M/g, 'ENTER'); + return k; + }); + output[i].keys = newKeys; + } + return output; + }); + } + commandList(argv) { + return __awaiter(this, void 0, void 0, function* () { + if (argv.length && argv[0] === 'batch') { + const commands = []; + const commandLines = splitCommandBatch(yield (0, fs_extra_1.readFile)(argv[1], 'utf-8')); + for (const commandLine of commandLines) { + if (!commandLine.trim()) + continue; + const splitted = (0, utils_1.splitCommandString)(commandLine.trim()); + commands.push(splitted); + } + return commands; + } + else { + return [argv]; + } + }); + } + // We need this special case here because by the time the `version` command + // runs, the keychain has already been setup. + checkIfKeychainEnabled(argv) { + return argv.indexOf('version') < 0; + } + start(argv) { + const _super = Object.create(null, { + start: { get: () => super.start } + }); + return __awaiter(this, void 0, void 0, function* () { + const keychainEnabled = this.checkIfKeychainEnabled(argv); + argv = yield _super.start.call(this, argv, { keychainEnabled }); + cliUtils.setStdout((object) => { + return this.stdout(object); + }); + this.initRedux(); + // If we have some arguments left at this point, it's a command + // so execute it. + if (argv.length) { + this.gui_ = this.dummyGui(); + this.currentFolder_ = yield Folder_1.default.load(Setting_1.default.value('activeFolderId')); + yield this.applySettingsSideEffects(); + try { + const commands = yield this.commandList(argv); + for (const command of commands) { + yield this.execCommand(command); + } + } + catch (error) { + if (this.showStackTraces_) { + console.error(error); + } + else { + // eslint-disable-next-line no-console + console.info(error.message); + } + process.exit(1); + } + yield Setting_1.default.saveAll(); + // Need to call exit() explicitly, otherwise Node wait for any timeout to complete + // https://stackoverflow.com/questions/18050095 + process.exit(0); + } + else { + // Otherwise open the GUI + const keymap = yield this.loadKeymaps(); + const AppGui = require('./app-gui.js'); + this.gui_ = new AppGui(this, this.store(), keymap); + this.gui_.setLogger(this.logger()); + yield this.gui_.start(); + // Since the settings need to be loaded before the store is created, it will never + // receive the SETTING_UPDATE_ALL even, which mean state.settings will not be + // initialised. So we manually call dispatchUpdateAll() to force an update. + Setting_1.default.dispatchUpdateAll(); + yield (0, folders_screen_utils_js_1.refreshFolders)((action) => this.store().dispatch(action)); + const tags = yield Tag_1.default.allWithNotes(); + ResourceService_1.default.runInBackground(); + RevisionService_1.default.instance().runInBackground(); + this.dispatch({ + type: 'TAG_UPDATE_ALL', + items: tags, + }); + this.store().dispatch({ + type: 'FOLDER_SELECT', + id: Setting_1.default.value('activeFolderId'), + }); + this.startRotatingLogMaintenance(Setting_1.default.value('profileDir')); + } + }); + } } - let application_ = null; - function app() { - if (application_) return application_; - application_ = new Application(); - return application_; + if (application_) + return application_; + application_ = new Application(); + return application_; } - -module.exports = { app }; +exports.default = app; +//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/packages/app-cli/app/app.ts b/packages/app-cli/app/app.ts new file mode 100644 index 000000000..87640039d --- /dev/null +++ b/packages/app-cli/app/app.ts @@ -0,0 +1,472 @@ +import BaseApplication from '@joplin/lib/BaseApplication'; +import { refreshFolders } from '@joplin/lib/folders-screen-utils.js'; +import ResourceService from '@joplin/lib/services/ResourceService'; +import BaseModel, { ModelType } from '@joplin/lib/BaseModel'; +import Folder from '@joplin/lib/models/Folder'; +import BaseItem from '@joplin/lib/models/BaseItem'; +import Note from '@joplin/lib/models/Note'; +import Tag from '@joplin/lib/models/Tag'; +import Setting from '@joplin/lib/models/Setting'; +import { reg } from '@joplin/lib/registry.js'; +import { fileExtension } from '@joplin/lib/path-utils'; +import { splitCommandString } from '@joplin/utils'; +import { _ } from '@joplin/lib/locale'; +import { pathExists, readFile, readdirSync } from 'fs-extra'; +import RevisionService from '@joplin/lib/services/RevisionService'; +import shim from '@joplin/lib/shim'; +import setupCommand from './setupCommand'; +import { FolderEntity, NoteEntity } from '@joplin/lib/services/database/types'; +const { cliUtils } = require('./cli-utils.js'); +const Cache = require('@joplin/lib/Cache'); +const { splitCommandBatch } = require('@joplin/lib/string-utils'); + +class Application extends BaseApplication { + + private commands_: Record = {}; + private commandMetadata_: any = null; + private activeCommand_: any = null; + private allCommandsLoaded_ = false; + private gui_: any = null; + private cache_ = new Cache(); + + public gui() { + return this.gui_; + } + + public commandStdoutMaxWidth() { + return this.gui().stdoutMaxWidth(); + } + + public async guessTypeAndLoadItem(pattern: string, options: any = null) { + let type = BaseModel.TYPE_NOTE; + if (pattern.indexOf('/') === 0) { + type = BaseModel.TYPE_FOLDER; + pattern = pattern.substr(1); + } + return this.loadItem(type, pattern, options); + } + + public async loadItem(type: ModelType | 'folderOrNote', pattern: string, options: any = null) { + const output = await this.loadItems(type, pattern, options); + + if (output.length > 1) { + // output.sort((a, b) => { return a.user_updated_time < b.user_updated_time ? +1 : -1; }); + + // let answers = { 0: _('[Cancel]') }; + // for (let i = 0; i < output.length; i++) { + // answers[i + 1] = output[i].title; + // } + + // Not really useful with new UI? + throw new Error(_('More than one item match "%s". Please narrow down your query.', pattern)); + + // let msg = _('More than one item match "%s". Please select one:', pattern); + // const response = await cliUtils.promptMcq(msg, answers); + // if (!response) return null; + + // return output[response - 1]; + } else { + return output.length ? output[0] : null; + } + } + + public async loadItems(type: ModelType | 'folderOrNote', pattern: string, options: any = null): Promise<(FolderEntity | NoteEntity)[]> { + if (type === 'folderOrNote') { + const folders: FolderEntity[] = await this.loadItems(BaseModel.TYPE_FOLDER, pattern, options); + if (folders.length) return folders; + return await this.loadItems(BaseModel.TYPE_NOTE, pattern, options); + } + + pattern = pattern ? pattern.toString() : ''; + + if (type === BaseModel.TYPE_FOLDER && (pattern === Folder.conflictFolderTitle() || pattern === Folder.conflictFolderId())) return [Folder.conflictFolder()]; + + if (!options) options = {}; + + const parent = options.parent ? options.parent : app().currentFolder(); + const ItemClass = BaseItem.itemClass(type); + + if (type === BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { + // Handle it as pattern + if (!parent) throw new Error(_('No notebook selected.')); + return await Note.previews(parent.id, { titlePattern: pattern }); + } else { + // Single item + let item = null; + if (type === BaseModel.TYPE_NOTE) { + if (!parent) throw new Error(_('No notebook has been specified.')); + item = await ItemClass.loadFolderNoteByField(parent.id, 'title', pattern); + } else { + item = await ItemClass.loadByTitle(pattern); + } + if (item) return [item]; + + item = await ItemClass.load(pattern); // Load by id + if (item) return [item]; + + if (pattern.length >= 2) { + return await ItemClass.loadByPartialId(pattern); + } + } + + return []; + } + + public setupCommand(cmd: string) { + return setupCommand(cmd, (t: string) => this.stdout(t), () => this.store(), () => this.gui()); + } + + public stdout(text: string) { + return this.gui().stdout(text); + } + + public async exit(code = 0) { + const doExit = async () => { + this.gui().exit(); + await super.exit(code); + }; + + // Give it a few seconds to cancel otherwise exit anyway + shim.setTimeout(async () => { + await doExit(); + }, 5000); + + if (await reg.syncTarget().syncStarted()) { + this.stdout(_('Cancelling background synchronisation... Please wait.')); + const sync = await reg.syncTarget().synchronizer(); + await sync.cancel(); + } + + await doExit(); + } + + public commands(uiType: string = null) { + if (!this.allCommandsLoaded_) { + // eslint-disable-next-line github/array-foreach -- Old code before rule was applied + readdirSync(__dirname).forEach(path => { + if (path.indexOf('command-') !== 0) return; + if (path.endsWith('.test.js')) return; + const ext = fileExtension(path); + if (ext !== 'js') return; + + const CommandClass = require(`./${path}`); + let cmd = new CommandClass(); + if (!cmd.enabled()) return; + cmd = this.setupCommand(cmd); + this.commands_[cmd.name()] = cmd; + }); + + this.allCommandsLoaded_ = true; + } + + if (uiType !== null) { + const temp: Record = {}; + for (const n in this.commands_) { + if (!this.commands_.hasOwnProperty(n)) continue; + const c = this.commands_[n]; + if (!c.supportsUi(uiType)) continue; + temp[n] = c; + } + return temp; + } + + return this.commands_; + } + + public async commandNames() { + const metadata = await this.commandMetadata(); + const output = []; + for (const n in metadata) { + if (!metadata.hasOwnProperty(n)) continue; + output.push(n); + } + return output; + } + + public async commandMetadata() { + if (this.commandMetadata_) return this.commandMetadata_; + + let output = await this.cache_.getItem('metadata'); + if (output) { + this.commandMetadata_ = output; + return { ...this.commandMetadata_ }; + } + + const commands = this.commands(); + + output = {}; + for (const n in commands) { + if (!commands.hasOwnProperty(n)) continue; + const cmd = commands[n]; + output[n] = cmd.metadata(); + } + + await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24); + + this.commandMetadata_ = output; + return { ...this.commandMetadata_ }; + } + + public hasGui() { + return this.gui() && !this.gui().isDummy(); + } + + public findCommandByName(name: string) { + if (this.commands_[name]) return this.commands_[name]; + + let CommandClass = null; + try { + CommandClass = require(`${__dirname}/command-${name}.js`); + } catch (error) { + if (error.message && error.message.indexOf('Cannot find module') >= 0) { + const e: any = new Error(_('No such command: %s', name)); + e.type = 'notFound'; + throw e; + } else { + throw error; + } + } + + let cmd = new CommandClass(); + cmd = this.setupCommand(cmd); + this.commands_[name] = cmd; + return this.commands_[name]; + } + + public dummyGui() { + return { + isDummy: () => { + return true; + }, + prompt: (initialText = '', promptString = '', options: any = null) => { + return cliUtils.prompt(initialText, promptString, options); + }, + showConsole: () => {}, + maximizeConsole: () => {}, + stdout: (text: string) => { + // eslint-disable-next-line no-console + console.info(text); + }, + fullScreen: () => {}, + exit: () => {}, + showModalOverlay: () => {}, + hideModalOverlay: () => {}, + stdoutMaxWidth: () => { + return 100; + }, + forceRender: () => {}, + termSaveState: () => {}, + termRestoreState: () => {}, + }; + } + + public async execCommand(argv: string[]): Promise { + if (!argv.length) return this.execCommand(['help']); + // reg.logger().debug('execCommand()', argv); + const commandName = argv[0]; + this.activeCommand_ = this.findCommandByName(commandName); + + let outException = null; + try { + if (this.gui().isDummy() && !this.activeCommand_.supportsUi('cli')) throw new Error(_('The command "%s" is only available in GUI mode', this.activeCommand_.name())); + const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv); + await this.activeCommand_.action(cmdArgs); + } catch (error) { + outException = error; + } + this.activeCommand_ = null; + if (outException) throw outException; + } + + public currentCommand() { + return this.activeCommand_; + } + + public async loadKeymaps() { + interface KeyMapItem { + keys: string[]; + type: 'function' | 'prompt'; + command: string; + cursorPosition?: number; + } + + const defaultKeyMap: KeyMapItem[] = [ + { keys: [':'], type: 'function', command: 'enter_command_line_mode' }, + { keys: ['TAB'], type: 'function', command: 'focus_next' }, + { keys: ['SHIFT_TAB'], type: 'function', command: 'focus_previous' }, + { keys: ['UP'], type: 'function', command: 'move_up' }, + { keys: ['DOWN'], type: 'function', command: 'move_down' }, + { keys: ['PAGE_UP'], type: 'function', command: 'page_up' }, + { keys: ['PAGE_DOWN'], type: 'function', command: 'page_down' }, + { keys: ['ENTER'], type: 'function', command: 'activate' }, + { keys: ['DELETE', 'BACKSPACE'], type: 'function', command: 'delete' }, + { keys: ['n'], type: 'function', command: 'next_link' }, + { keys: ['b'], type: 'function', command: 'previous_link' }, + { keys: ['o'], type: 'function', command: 'open_link' }, + { keys: [' '], type: 'prompt', command: 'todo toggle $n' }, + { keys: ['tc'], type: 'function', command: 'toggle_console' }, + { keys: ['tm'], type: 'function', command: 'toggle_metadata' }, + { keys: ['ti'], type: 'function', command: 'toggle_ids' }, + { keys: ['/'], type: 'prompt', command: 'search ""', cursorPosition: -2 }, + { keys: ['mn'], type: 'prompt', command: 'mknote ""', cursorPosition: -2 }, + { keys: ['mt'], type: 'prompt', command: 'mktodo ""', cursorPosition: -2 }, + { keys: ['mb'], type: 'prompt', command: 'mkbook ""', cursorPosition: -2 }, + { keys: ['yn'], type: 'prompt', command: 'cp $n ""', cursorPosition: -2 }, + { keys: ['dn'], type: 'prompt', command: 'mv $n ""', cursorPosition: -2 }, + ]; + + // Filter the keymap item by command so that items in keymap.json can override + // the default ones. + const itemsByCommand: Record = {}; + + for (let i = 0; i < defaultKeyMap.length; i++) { + itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]; + } + + const filePath = `${Setting.value('profileDir')}/keymap.json`; + if (await pathExists(filePath)) { + try { + let configString = await readFile(filePath, 'utf-8'); + configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments + const keymap = JSON.parse(configString); + for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) { + const item = keymap[keymapIndex]; + itemsByCommand[item.command] = item; + } + } catch (error) { + let msg = error.message ? error.message : ''; + msg = `Could not load keymap ${filePath}\n${msg}`; + error.message = msg; + throw error; + } + } + + const output = []; + for (const n in itemsByCommand) { + if (!itemsByCommand.hasOwnProperty(n)) continue; + output.push(itemsByCommand[n]); + } + + // Map reserved shortcuts to their equivalent key + // https://github.com/cronvel/terminal-kit/issues/101 + for (let i = 0; i < output.length; i++) { + const newKeys = output[i].keys.map(k => { + k = k.replace(/CTRL_H/g, 'BACKSPACE'); + k = k.replace(/CTRL_I/g, 'TAB'); + k = k.replace(/CTRL_M/g, 'ENTER'); + return k; + }); + output[i].keys = newKeys; + } + + return output; + } + + public async commandList(argv: string[]) { + if (argv.length && argv[0] === 'batch') { + const commands = []; + const commandLines = splitCommandBatch(await readFile(argv[1], 'utf-8')); + + for (const commandLine of commandLines) { + if (!commandLine.trim()) continue; + const splitted = splitCommandString(commandLine.trim()); + commands.push(splitted); + } + return commands; + } else { + return [argv]; + } + } + + // We need this special case here because by the time the `version` command + // runs, the keychain has already been setup. + public checkIfKeychainEnabled(argv: string[]) { + return argv.indexOf('version') < 0; + } + + public async start(argv: string[]) { + const keychainEnabled = this.checkIfKeychainEnabled(argv); + + argv = await super.start(argv, { keychainEnabled }); + + cliUtils.setStdout((object: any) => { + return this.stdout(object); + }); + + this.initRedux(); + + // If we have some arguments left at this point, it's a command + // so execute it. + if (argv.length) { + this.gui_ = this.dummyGui(); + + this.currentFolder_ = await Folder.load(Setting.value('activeFolderId')); + + await this.applySettingsSideEffects(); + + try { + const commands = await this.commandList(argv); + for (const command of commands) { + await this.execCommand(command); + } + } catch (error) { + if (this.showStackTraces_) { + console.error(error); + } else { + // eslint-disable-next-line no-console + console.info(error.message); + } + process.exit(1); + } + + await Setting.saveAll(); + + // Need to call exit() explicitly, otherwise Node wait for any timeout to complete + // https://stackoverflow.com/questions/18050095 + process.exit(0); + } else { + // Otherwise open the GUI + const keymap = await this.loadKeymaps(); + + const AppGui = require('./app-gui.js'); + this.gui_ = new AppGui(this, this.store(), keymap); + this.gui_.setLogger(this.logger()); + await this.gui_.start(); + + // Since the settings need to be loaded before the store is created, it will never + // receive the SETTING_UPDATE_ALL even, which mean state.settings will not be + // initialised. So we manually call dispatchUpdateAll() to force an update. + Setting.dispatchUpdateAll(); + + await refreshFolders((action: any) => this.store().dispatch(action)); + + const tags = await Tag.allWithNotes(); + + ResourceService.runInBackground(); + + RevisionService.instance().runInBackground(); + + this.dispatch({ + type: 'TAG_UPDATE_ALL', + items: tags, + }); + + this.store().dispatch({ + type: 'FOLDER_SELECT', + id: Setting.value('activeFolderId'), + }); + + this.startRotatingLogMaintenance(Setting.value('profileDir')); + } + } +} + +let application_: Application = null; + +function app() { + if (application_) return application_; + application_ = new Application(); + return application_; +} + +export default app; diff --git a/packages/app-cli/app/autocompletion.js b/packages/app-cli/app/autocompletion.js index 5d36114a1..dda663943 100644 --- a/packages/app-cli/app/autocompletion.js +++ b/packages/app-cli/app/autocompletion.js @@ -1,4 +1,4 @@ -const { app } = require('./app.js'); +const app = require('./app').default; const Note = require('@joplin/lib/models/Note').default; const Folder = require('@joplin/lib/models/Folder').default; const Tag = require('@joplin/lib/models/Tag').default; diff --git a/packages/app-cli/app/command-attach.ts b/packages/app-cli/app/command-attach.ts index 56df50fcb..dee07b1fb 100644 --- a/packages/app-cli/app/command-attach.ts +++ b/packages/app-cli/app/command-attach.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import shim from '@joplin/lib/shim'; diff --git a/packages/app-cli/app/command-cat.ts b/packages/app-cli/app/command-cat.ts index bbb5a7400..1dfd409f5 100644 --- a/packages/app-cli/app/command-cat.ts +++ b/packages/app-cli/app/command-cat.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import BaseItem from '@joplin/lib/models/BaseItem'; diff --git a/packages/app-cli/app/command-config.ts b/packages/app-cli/app/command-config.ts index 87fa8118a..a698942fc 100644 --- a/packages/app-cli/app/command-config.ts +++ b/packages/app-cli/app/command-config.ts @@ -1,6 +1,6 @@ import BaseCommand from './base-command'; import { _, setLocale } from '@joplin/lib/locale'; -const { app } = require('./app.js'); +import app from './app'; import * as fs from 'fs-extra'; import Setting, { AppType } from '@joplin/lib/models/Setting'; import { ReadStream } from 'tty'; diff --git a/packages/app-cli/app/command-cp.ts b/packages/app-cli/app/command-cp.ts index 907279cbe..23df1bacf 100644 --- a/packages/app-cli/app/command-cp.ts +++ b/packages/app-cli/app/command-cp.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Note from '@joplin/lib/models/Note'; diff --git a/packages/app-cli/app/command-done.ts b/packages/app-cli/app/command-done.ts index dad2d7136..241ee2e62 100644 --- a/packages/app-cli/app/command-done.ts +++ b/packages/app-cli/app/command-done.ts @@ -1,9 +1,10 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Note from '@joplin/lib/models/Note'; import time from '@joplin/lib/time'; +import { NoteEntity } from '@joplin/lib/services/database/types'; class Command extends BaseCommand { public override usage() { @@ -15,7 +16,7 @@ class Command extends BaseCommand { } public static async handleAction(commandInstance: BaseCommand, args: any, isCompleted: boolean) { - const note = await app().loadItem(BaseModel.TYPE_NOTE, args.note); + const note: NoteEntity = await app().loadItem(BaseModel.TYPE_NOTE, args.note); commandInstance.encryptionCheck(note); if (!note) throw new Error(_('Cannot find "%s".', args.note)); if (!note.is_todo) throw new Error(_('Note is not a to-do: "%s"', args.note)); diff --git a/packages/app-cli/app/command-edit.ts b/packages/app-cli/app/command-edit.ts index eaa676f4a..dc32c8a92 100644 --- a/packages/app-cli/app/command-edit.ts +++ b/packages/app-cli/app/command-edit.ts @@ -2,7 +2,7 @@ import * as fs from 'fs-extra'; import BaseCommand from './base-command'; import { splitCommandString } from '@joplin/utils'; import uuid from '@joplin/lib/uuid'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import Note from '@joplin/lib/models/Note'; import Setting from '@joplin/lib/models/Setting'; diff --git a/packages/app-cli/app/command-exit.ts b/packages/app-cli/app/command-exit.ts index 3630513f1..7975d38b3 100644 --- a/packages/app-cli/app/command-exit.ts +++ b/packages/app-cli/app/command-exit.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; class Command extends BaseCommand { diff --git a/packages/app-cli/app/command-export-sync-status.ts b/packages/app-cli/app/command-export-sync-status.ts index 966dfd0a7..71e2386a7 100644 --- a/packages/app-cli/app/command-export-sync-status.ts +++ b/packages/app-cli/app/command-export-sync-status.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import Setting from '@joplin/lib/models/Setting'; import ReportService from '@joplin/lib/services/ReportService'; import * as fs from 'fs-extra'; diff --git a/packages/app-cli/app/command-export.ts b/packages/app-cli/app/command-export.ts index d7886dff1..74042b2d3 100644 --- a/packages/app-cli/app/command-export.ts +++ b/packages/app-cli/app/command-export.ts @@ -1,7 +1,7 @@ import BaseCommand from './base-command'; import InteropService from '@joplin/lib/services/interop/InteropService'; import BaseModel from '@joplin/lib/BaseModel'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import { ExportOptions } from '@joplin/lib/services/interop/types'; diff --git a/packages/app-cli/app/command-geoloc.ts b/packages/app-cli/app/command-geoloc.ts index 099dabe36..e45341d9d 100644 --- a/packages/app-cli/app/command-geoloc.ts +++ b/packages/app-cli/app/command-geoloc.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Note from '@joplin/lib/models/Note'; diff --git a/packages/app-cli/app/command-help.ts b/packages/app-cli/app/command-help.ts index ab1bd9b8a..2dd4e61fd 100644 --- a/packages/app-cli/app/command-help.ts +++ b/packages/app-cli/app/command-help.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; const { renderCommandHelp } = require('./help-utils.js'); import { _ } from '@joplin/lib/locale'; const { cliUtils } = require('./cli-utils.js'); diff --git a/packages/app-cli/app/command-import.ts b/packages/app-cli/app/command-import.ts index 47348ffec..12108374b 100644 --- a/packages/app-cli/app/command-import.ts +++ b/packages/app-cli/app/command-import.ts @@ -2,7 +2,7 @@ import BaseCommand from './base-command'; import InteropService from '@joplin/lib/services/interop/InteropService'; import BaseModel from '@joplin/lib/BaseModel'; const { cliUtils } = require('./cli-utils.js'); -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import { ImportOptions } from '@joplin/lib/services/interop/types'; import { unique } from '@joplin/lib/array'; diff --git a/packages/app-cli/app/command-ls.ts b/packages/app-cli/app/command-ls.ts index 6fa428675..c6d720c40 100644 --- a/packages/app-cli/app/command-ls.ts +++ b/packages/app-cli/app/command-ls.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Folder from '@joplin/lib/models/Folder'; diff --git a/packages/app-cli/app/command-mkbook.ts b/packages/app-cli/app/command-mkbook.ts index e23577cfd..118c54124 100644 --- a/packages/app-cli/app/command-mkbook.ts +++ b/packages/app-cli/app/command-mkbook.ts @@ -1,5 +1,5 @@ const BaseCommand = require('./base-command').default; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Folder from '@joplin/lib/models/Folder'; diff --git a/packages/app-cli/app/command-mknote.js b/packages/app-cli/app/command-mknote.js index 3b2a82289..993918307 100644 --- a/packages/app-cli/app/command-mknote.js +++ b/packages/app-cli/app/command-mknote.js @@ -1,5 +1,5 @@ const BaseCommand = require('./base-command').default; -const { app } = require('./app.js'); +const app = require('./app').default; const { _ } = require('@joplin/lib/locale'); const Note = require('@joplin/lib/models/Note').default; diff --git a/packages/app-cli/app/command-mktodo.js b/packages/app-cli/app/command-mktodo.js index 986d260d0..41651b443 100644 --- a/packages/app-cli/app/command-mktodo.js +++ b/packages/app-cli/app/command-mktodo.js @@ -1,5 +1,5 @@ const BaseCommand = require('./base-command').default; -const { app } = require('./app.js'); +const app = require('./app').default; const { _ } = require('@joplin/lib/locale'); const Note = require('@joplin/lib/models/Note').default; diff --git a/packages/app-cli/app/command-mv.ts b/packages/app-cli/app/command-mv.ts index adc52e90d..857ce5f59 100644 --- a/packages/app-cli/app/command-mv.ts +++ b/packages/app-cli/app/command-mv.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Folder from '@joplin/lib/models/Folder'; diff --git a/packages/app-cli/app/command-ren.ts b/packages/app-cli/app/command-ren.ts index 22af123af..ced1665f0 100644 --- a/packages/app-cli/app/command-ren.ts +++ b/packages/app-cli/app/command-ren.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Folder from '@joplin/lib/models/Folder'; diff --git a/packages/app-cli/app/command-rmbook.ts b/packages/app-cli/app/command-rmbook.ts index 346ba67c8..5120a0d5c 100644 --- a/packages/app-cli/app/command-rmbook.ts +++ b/packages/app-cli/app/command-rmbook.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import Folder from '@joplin/lib/models/Folder'; import BaseModel from '@joplin/lib/BaseModel'; diff --git a/packages/app-cli/app/command-rmnote.ts b/packages/app-cli/app/command-rmnote.ts index a671537bf..5a3011c23 100644 --- a/packages/app-cli/app/command-rmnote.ts +++ b/packages/app-cli/app/command-rmnote.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import Note from '@joplin/lib/models/Note'; import BaseModel from '@joplin/lib/BaseModel'; diff --git a/packages/app-cli/app/command-set.ts b/packages/app-cli/app/command-set.ts index 4ba8183ab..54b59ae77 100644 --- a/packages/app-cli/app/command-set.ts +++ b/packages/app-cli/app/command-set.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; import Database from '@joplin/lib/database'; diff --git a/packages/app-cli/app/command-status.js b/packages/app-cli/app/command-status.js index 877307dce..02915d892 100644 --- a/packages/app-cli/app/command-status.js +++ b/packages/app-cli/app/command-status.js @@ -1,5 +1,5 @@ const BaseCommand = require('./base-command').default; -const { app } = require('./app.js'); +const app = require('./app').default; const Setting = require('@joplin/lib/models/Setting').default; const { _ } = require('@joplin/lib/locale'); const ReportService = require('@joplin/lib/services/ReportService').default; diff --git a/packages/app-cli/app/command-sync.ts b/packages/app-cli/app/command-sync.ts index 4e8ecec64..468752aa0 100644 --- a/packages/app-cli/app/command-sync.ts +++ b/packages/app-cli/app/command-sync.ts @@ -7,7 +7,7 @@ import Synchronizer from '@joplin/lib/Synchronizer'; import { masterKeysWithoutPassword } from '@joplin/lib/services/e2ee/utils'; import { appTypeToLockType } from '@joplin/lib/services/synchronizer/LockHandler'; const BaseCommand = require('./base-command').default; -const { app } = require('./app.js'); +import app from './app'; const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js'); import { reg } from '@joplin/lib/registry'; const { cliUtils } = require('./cli-utils.js'); diff --git a/packages/app-cli/app/command-tag.js b/packages/app-cli/app/command-tag.js index 76513ad46..91d09a70a 100644 --- a/packages/app-cli/app/command-tag.js +++ b/packages/app-cli/app/command-tag.js @@ -1,5 +1,5 @@ const BaseCommand = require('./base-command').default; -const { app } = require('./app.js'); +const app = require('./app').default; const { _ } = require('@joplin/lib/locale'); const Tag = require('@joplin/lib/models/Tag').default; const BaseModel = require('@joplin/lib/BaseModel').default; diff --git a/packages/app-cli/app/command-todo.js b/packages/app-cli/app/command-todo.js index 18e7d32c1..327a090be 100644 --- a/packages/app-cli/app/command-todo.js +++ b/packages/app-cli/app/command-todo.js @@ -1,5 +1,5 @@ const BaseCommand = require('./base-command').default; -const { app } = require('./app.js'); +const app = require('./app').default; const { _ } = require('@joplin/lib/locale'); const BaseModel = require('@joplin/lib/BaseModel').default; const Note = require('@joplin/lib/models/Note').default; diff --git a/packages/app-cli/app/command-use.ts b/packages/app-cli/app/command-use.ts index 330ace04e..4dc272f8b 100644 --- a/packages/app-cli/app/command-use.ts +++ b/packages/app-cli/app/command-use.ts @@ -1,5 +1,5 @@ import BaseCommand from './base-command'; -const { app } = require('./app.js'); +import app from './app'; import { _ } from '@joplin/lib/locale'; import BaseModel from '@joplin/lib/BaseModel'; diff --git a/packages/app-cli/app/main.js b/packages/app-cli/app/main.js index 24f97fe72..28530476b 100644 --- a/packages/app-cli/app/main.js +++ b/packages/app-cli/app/main.js @@ -10,7 +10,7 @@ if (compareVersion(nodeVersion, '10.0.0') < 0) { process.exit(1); } -const { app } = require('./app.js'); +const app = require('./app').default; const Folder = require('@joplin/lib/models/Folder').default; const Resource = require('@joplin/lib/models/Resource').default; const BaseItem = require('@joplin/lib/models/BaseItem').default; diff --git a/packages/app-cli/app/utils/testUtils.ts b/packages/app-cli/app/utils/testUtils.ts index c128e8b75..fc524b808 100644 --- a/packages/app-cli/app/utils/testUtils.ts +++ b/packages/app-cli/app/utils/testUtils.ts @@ -1,4 +1,4 @@ -const { app } = require('../app'); +import app from '../app'; import Folder from '@joplin/lib/models/Folder'; import BaseCommand from '../base-command'; import setupCommand from '../setupCommand'; diff --git a/packages/lib/BaseApplication.ts b/packages/lib/BaseApplication.ts index 14e372380..82914db22 100644 --- a/packages/lib/BaseApplication.ts +++ b/packages/lib/BaseApplication.ts @@ -88,7 +88,7 @@ export default class BaseApplication { // Note: this is basically a cache of state.selectedFolderId. It should *only* // be derived from the state and not set directly since that would make the // state and UI out of sync. - private currentFolder_: any = null; + protected currentFolder_: any = null; protected store_: Store = null;