You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Chore: Convert CLI app class to TS
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -1,242 +1,245 @@ | ||||
| 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 { | ||||
| const { splitCommandBatch } = require('@joplin/lib/string-utils'); | ||||
| class Application extends BaseApplication_1.default { | ||||
|     constructor() { | ||||
| 		super(); | ||||
|  | ||||
| 		this.showPromptString_ = true; | ||||
|         super(...arguments); | ||||
|         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; | ||||
|     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.TYPE_FOLDER; | ||||
|                 type = BaseModel_1.default.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); | ||||
|  | ||||
|     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(_('More than one item match "%s". Please narrow down your query.', pattern)); | ||||
|  | ||||
|                 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 { | ||||
|             } | ||||
|             else { | ||||
|                 return output.length ? output[0] : null; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| 	async loadItems(type, pattern, options = null) { | ||||
|     loadItems(type, pattern, options = null) { | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|             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); | ||||
|                 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.TYPE_FOLDER && (pattern === Folder.conflictFolderTitle() || pattern === Folder.conflictFolderId())) return [Folder.conflictFolder()]; | ||||
|  | ||||
| 		if (!options) options = {}; | ||||
|  | ||||
|             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.itemClass(type); | ||||
|  | ||||
| 		if (type === BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { | ||||
|             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(_('No notebook selected.')); | ||||
| 			return await Note.previews(parent.id, { titlePattern: pattern }); | ||||
| 		} else { | ||||
|                 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.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 (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); | ||||
|                 } | ||||
| 			if (item) return [item]; | ||||
|  | ||||
| 			item = await ItemClass.load(pattern); // Load by id | ||||
| 			if (item) return [item]; | ||||
|  | ||||
|                 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 await ItemClass.loadByPartialId(pattern); | ||||
|                     return yield ItemClass.loadByPartialId(pattern); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return []; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     setupCommand(cmd) { | ||||
| 		return setupCommand(cmd, t => this.stdout(t), () => this.store(), () => this.gui()); | ||||
|         return (0, setupCommand_1.default)(cmd, (t) => this.stdout(t), () => this.store(), () => this.gui()); | ||||
|     } | ||||
|  | ||||
|     stdout(text) { | ||||
|         return this.gui().stdout(text); | ||||
|     } | ||||
|  | ||||
| 	async exit(code = 0) { | ||||
| 		const doExit = async () => { | ||||
|     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(); | ||||
| 			await super.exit(code); | ||||
| 		}; | ||||
|  | ||||
|                 yield _super.exit.call(this, 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(); | ||||
|             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(); | ||||
|             } | ||||
|  | ||||
| 		await doExit(); | ||||
|             yield 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; | ||||
|  | ||||
|             (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; | ||||
|                 if (!cmd.enabled()) | ||||
|                     return; | ||||
|                 cmd = this.setupCommand(cmd); | ||||
|                 this.commands_[cmd.name()] = cmd; | ||||
|             }); | ||||
|  | ||||
|             this.allCommandsLoaded_ = true; | ||||
|         } | ||||
|  | ||||
|         if (uiType !== null) { | ||||
| 			const temp = []; | ||||
|             const temp = {}; | ||||
|             for (const n in this.commands_) { | ||||
| 				if (!this.commands_.hasOwnProperty(n)) continue; | ||||
|                 if (!this.commands_.hasOwnProperty(n)) | ||||
|                     continue; | ||||
|                 const c = this.commands_[n]; | ||||
| 				if (!c.supportsUi(uiType)) continue; | ||||
|                 if (!c.supportsUi(uiType)) | ||||
|                     continue; | ||||
|                 temp[n] = c; | ||||
|             } | ||||
|             return temp; | ||||
|         } | ||||
|  | ||||
|         return this.commands_; | ||||
|     } | ||||
|  | ||||
| 	async commandNames() { | ||||
| 		const metadata = await this.commandMetadata(); | ||||
|     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; | ||||
|                 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'); | ||||
|     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 { ...this.commandMetadata_ }; | ||||
|                 return Object.assign({}, this.commandMetadata_); | ||||
|             } | ||||
|  | ||||
|             const commands = this.commands(); | ||||
|  | ||||
|             output = {}; | ||||
|             for (const n in commands) { | ||||
| 			if (!commands.hasOwnProperty(n)) continue; | ||||
|                 if (!commands.hasOwnProperty(n)) | ||||
|                     continue; | ||||
|                 const cmd = commands[n]; | ||||
|                 output[n] = cmd.metadata(); | ||||
|             } | ||||
|  | ||||
| 		await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24); | ||||
|  | ||||
|             yield this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24); | ||||
|             this.commandMetadata_ = output; | ||||
| 		return { ...this.commandMetadata_ }; | ||||
|             return Object.assign({}, this.commandMetadata_); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     hasGui() { | ||||
|         return this.gui() && !this.gui().isDummy(); | ||||
|     } | ||||
|  | ||||
|     findCommandByName(name) { | ||||
| 		if (this.commands_[name]) return this.commands_[name]; | ||||
|  | ||||
|         if (this.commands_[name]) | ||||
|             return this.commands_[name]; | ||||
|         let CommandClass = null; | ||||
|         try { | ||||
|             CommandClass = require(`${__dirname}/command-${name}.js`); | ||||
| 		} catch (error) { | ||||
|         } | ||||
|         catch (error) { | ||||
|             if (error.message && error.message.indexOf('Cannot find module') >= 0) { | ||||
| 				const e = new Error(_('No such command: %s', name)); | ||||
|                 const e = new Error((0, locale_1._)('No such command: %s', name)); | ||||
|                 e.type = 'notFound'; | ||||
|                 throw e; | ||||
| 			} else { | ||||
|             } | ||||
|             else { | ||||
|                 throw error; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let cmd = new CommandClass(); | ||||
|         cmd = this.setupCommand(cmd); | ||||
|         this.commands_[name] = cmd; | ||||
|         return this.commands_[name]; | ||||
|     } | ||||
|  | ||||
|     dummyGui() { | ||||
|         return { | ||||
|             isDummy: () => { | ||||
| @@ -247,7 +250,7 @@ class Application extends BaseApplication { | ||||
|             }, | ||||
|             showConsole: () => { }, | ||||
|             maximizeConsole: () => { }, | ||||
| 			stdout: text => { | ||||
|             stdout: (text) => { | ||||
|                 // eslint-disable-next-line no-console | ||||
|                 console.info(text); | ||||
|             }, | ||||
| @@ -263,30 +266,33 @@ class Application extends BaseApplication { | ||||
|             termRestoreState: () => { }, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| 	async execCommand(argv) { | ||||
| 		if (!argv.length) return this.execCommand(['help']); | ||||
|     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(_('The command "%s" is only available in GUI mode', this.activeCommand_.name())); | ||||
|                 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); | ||||
| 			await this.activeCommand_.action(cmdArgs); | ||||
| 		} catch (error) { | ||||
|                 yield this.activeCommand_.action(cmdArgs); | ||||
|             } | ||||
|             catch (error) { | ||||
|                 outException = error; | ||||
|             } | ||||
|             this.activeCommand_ = null; | ||||
| 		if (outException) throw outException; | ||||
|             if (outException) | ||||
|                 throw outException; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     currentCommand() { | ||||
|         return this.activeCommand_; | ||||
|     } | ||||
|  | ||||
| 	async loadKeymaps() { | ||||
|     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' }, | ||||
| @@ -300,7 +306,7 @@ class Application extends BaseApplication { | ||||
|                 { 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: [' '], 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' }, | ||||
| @@ -311,39 +317,36 @@ class Application extends BaseApplication { | ||||
|                 { 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)) { | ||||
|             const filePath = `${Setting_1.default.value('profileDir')}/keymap.json`; | ||||
|             if (yield (0, fs_extra_1.pathExists)(filePath)) { | ||||
|                 try { | ||||
| 				let configString = await fs.readFile(filePath, 'utf-8'); | ||||
|                     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) { | ||||
|                 } | ||||
|                 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; | ||||
|                 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++) { | ||||
| @@ -355,115 +358,104 @@ class Application extends BaseApplication { | ||||
|                 }); | ||||
|                 output[i].keys = newKeys; | ||||
|             } | ||||
|  | ||||
|             return output; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| 	async commandList(argv) { | ||||
|     commandList(argv) { | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|             if (argv.length && argv[0] === 'batch') { | ||||
|                 const commands = []; | ||||
| 			const commandLines = splitCommandBatch(await fs.readFile(argv[1], 'utf-8')); | ||||
|  | ||||
|                 const commandLines = splitCommandBatch(yield (0, fs_extra_1.readFile)(argv[1], 'utf-8')); | ||||
|                 for (const commandLine of commandLines) { | ||||
| 				if (!commandLine.trim()) continue; | ||||
| 				const splitted = splitCommandString(commandLine.trim()); | ||||
|                     if (!commandLine.trim()) | ||||
|                         continue; | ||||
|                     const splitted = (0, utils_1.splitCommandString)(commandLine.trim()); | ||||
|                     commands.push(splitted); | ||||
|                 } | ||||
|                 return commands; | ||||
| 		} else { | ||||
|             } | ||||
|             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) { | ||||
|     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 = await super.start(argv, { keychainEnabled }); | ||||
|  | ||||
| 		cliUtils.setStdout(object => { | ||||
|             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_ = await Folder.load(Setting.value('activeFolderId')); | ||||
|  | ||||
| 			await this.applySettingsSideEffects(); | ||||
|  | ||||
|                 this.currentFolder_ = yield Folder_1.default.load(Setting_1.default.value('activeFolderId')); | ||||
|                 yield this.applySettingsSideEffects(); | ||||
|                 try { | ||||
| 				const commands = await this.commandList(argv); | ||||
|                     const commands = yield this.commandList(argv); | ||||
|                     for (const command of commands) { | ||||
| 					await this.execCommand(command); | ||||
|                         yield this.execCommand(command); | ||||
|                     } | ||||
| 			} catch (error) { | ||||
|                 } | ||||
|                 catch (error) { | ||||
|                     if (this.showStackTraces_) { | ||||
|                         console.error(error); | ||||
| 				} else { | ||||
|                     } | ||||
|                     else { | ||||
|                         // eslint-disable-next-line no-console | ||||
|                         console.info(error.message); | ||||
|                     } | ||||
|                     process.exit(1); | ||||
|                 } | ||||
|  | ||||
| 			await Setting.saveAll(); | ||||
|  | ||||
|                 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 { | ||||
|             } | ||||
|             else { | ||||
|                 // Otherwise open the GUI | ||||
| 			const keymap = await this.loadKeymaps(); | ||||
|  | ||||
|                 const keymap = yield 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(); | ||||
|  | ||||
|                 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.dispatchUpdateAll(); | ||||
|  | ||||
| 			await refreshFolders((action) => { this.store().dispatch(action); }); | ||||
|  | ||||
| 			const tags = await Tag.allWithNotes(); | ||||
|  | ||||
| 			ResourceService.runInBackground(); | ||||
|  | ||||
| 			RevisionService.instance().runInBackground(); | ||||
|  | ||||
|                 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.value('activeFolderId'), | ||||
|                     id: Setting_1.default.value('activeFolderId'), | ||||
|                 }); | ||||
|                 this.startRotatingLogMaintenance(Setting_1.default.value('profileDir')); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
| 			this.startRotatingLogMaintenance(Setting.value('profileDir')); | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| let application_ = null; | ||||
|  | ||||
| function app() { | ||||
| 	if (application_) return application_; | ||||
|     if (application_) | ||||
|         return application_; | ||||
|     application_ = new Application(); | ||||
|     return application_; | ||||
| } | ||||
|  | ||||
| module.exports = { app }; | ||||
| exports.default = app; | ||||
| //# sourceMappingURL=app.js.map | ||||
							
								
								
									
										472
									
								
								packages/app-cli/app/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										472
									
								
								packages/app-cli/app/app.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<string, any> = {}; | ||||
| 	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<string, any> = {}; | ||||
| 			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<any> { | ||||
| 		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<string, KeyMapItem> = {}; | ||||
|  | ||||
| 		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; | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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)); | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|  | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'); | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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'); | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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'; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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<any> = null; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user