diff --git a/CliClient/app/ResourceServer.js b/CliClient/app/ResourceServer.js index 7e650d1e0..c7425d039 100644 --- a/CliClient/app/ResourceServer.js +++ b/CliClient/app/ResourceServer.js @@ -1,13 +1,14 @@ -const { _ } = require("lib/locale.js"); -const { Logger } = require("lib/logger.js"); -const Resource = require("lib/models/Resource.js"); -const { netUtils } = require("lib/net-utils.js"); +const { _ } = require('lib/locale.js'); +const { Logger } = require('lib/logger.js'); +const Resource = require('lib/models/Resource.js'); +const { netUtils } = require('lib/net-utils.js'); const http = require("http"); const urlParser = require("url"); -const enableServerDestroy = require("server-destroy"); +const enableServerDestroy = require('server-destroy'); class ResourceServer { + constructor() { this.server_ = null; this.logger_ = new Logger(); @@ -29,8 +30,8 @@ class ResourceServer { } baseUrl() { - if (!this.port_) return ""; - return "http://127.0.0.1:" + this.port_; + if (!this.port_) return ''; + return 'http://127.0.0.1:' + this.port_; } setLinkHandler(handler) { @@ -39,34 +40,35 @@ class ResourceServer { async start() { this.port_ = await netUtils.findAvailablePort([9167, 9267, 8167, 8267]); - if (!this.port_) { - this.logger().error("Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin"); + if (!this.port_) { + this.logger().error('Could not find available port to start resource server. Please report the error at https://github.com/laurent22/joplin'); return; } this.server_ = http.createServer(); - this.server_.on("request", async (request, response) => { - const writeResponse = message => { + this.server_.on('request', async (request, response) => { + + const writeResponse = (message) => { response.write(message); response.end(); - }; + } const url = urlParser.parse(request.url, true); - let resourceId = url.pathname.split("/"); + let resourceId = url.pathname.split('/'); if (resourceId.length < 2) { - writeResponse("Error: could not get resource ID from path name: " + url.pathname); + writeResponse('Error: could not get resource ID from path name: ' + url.pathname); return; } resourceId = resourceId[1]; - if (!this.linkHandler_) throw new Error("No link handler is defined"); + if (!this.linkHandler_) throw new Error('No link handler is defined'); try { const done = await this.linkHandler_(resourceId, response); - if (!done) throw new Error("Unhandled resource: " + resourceId); + if (!done) throw new Error('Unhandled resource: ' + resourceId); } catch (error) { - response.setHeader("Content-Type", "text/plain"); + response.setHeader('Content-Type', 'text/plain'); response.statusCode = 400; response.write(error.message); } @@ -74,8 +76,8 @@ class ResourceServer { response.end(); }); - this.server_.on("error", error => { - this.logger().error("Resource server:", error); + this.server_.on('error', (error) => { + this.logger().error('Resource server:', error); }); this.server_.listen(this.port_); @@ -89,6 +91,7 @@ class ResourceServer { if (this.server_) this.server_.destroy(); this.server_ = null; } + } -module.exports = ResourceServer; +module.exports = ResourceServer; \ No newline at end of file diff --git a/CliClient/app/app-gui.js b/CliClient/app/app-gui.js index b8e824e84..037cedddc 100644 --- a/CliClient/app/app-gui.js +++ b/CliClient/app/app-gui.js @@ -1,39 +1,40 @@ -const { Logger } = require("lib/logger.js"); -const Folder = require("lib/models/Folder.js"); -const Tag = require("lib/models/Tag.js"); -const BaseModel = require("lib/BaseModel.js"); -const Note = require("lib/models/Note.js"); -const Resource = require("lib/models/Resource.js"); -const { cliUtils } = require("./cli-utils.js"); -const { reducer, defaultState } = require("lib/reducer.js"); -const { splitCommandString } = require("lib/string-utils.js"); -const { reg } = require("lib/registry.js"); -const { _ } = require("lib/locale.js"); +const { Logger } = require('lib/logger.js'); +const Folder = require('lib/models/Folder.js'); +const Tag = require('lib/models/Tag.js'); +const BaseModel = require('lib/BaseModel.js'); +const Note = require('lib/models/Note.js'); +const Resource = require('lib/models/Resource.js'); +const { cliUtils } = require('./cli-utils.js'); +const { reducer, defaultState } = require('lib/reducer.js'); +const { splitCommandString } = require('lib/string-utils.js'); +const { reg } = require('lib/registry.js'); +const { _ } = require('lib/locale.js'); -const chalk = require("chalk"); -const tk = require("terminal-kit"); -const TermWrapper = require("tkwidgets/framework/TermWrapper.js"); -const Renderer = require("tkwidgets/framework/Renderer.js"); -const DecryptionWorker = require("lib/services/DecryptionWorker"); +const chalk = require('chalk'); +const tk = require('terminal-kit'); +const TermWrapper = require('tkwidgets/framework/TermWrapper.js'); +const Renderer = require('tkwidgets/framework/Renderer.js'); +const DecryptionWorker = require('lib/services/DecryptionWorker'); -const BaseWidget = require("tkwidgets/BaseWidget.js"); -const ListWidget = require("tkwidgets/ListWidget.js"); -const TextWidget = require("tkwidgets/TextWidget.js"); -const HLayoutWidget = require("tkwidgets/HLayoutWidget.js"); -const VLayoutWidget = require("tkwidgets/VLayoutWidget.js"); -const ReduxRootWidget = require("tkwidgets/ReduxRootWidget.js"); -const RootWidget = require("tkwidgets/RootWidget.js"); -const WindowWidget = require("tkwidgets/WindowWidget.js"); +const BaseWidget = require('tkwidgets/BaseWidget.js'); +const ListWidget = require('tkwidgets/ListWidget.js'); +const TextWidget = require('tkwidgets/TextWidget.js'); +const HLayoutWidget = require('tkwidgets/HLayoutWidget.js'); +const VLayoutWidget = require('tkwidgets/VLayoutWidget.js'); +const ReduxRootWidget = require('tkwidgets/ReduxRootWidget.js'); +const RootWidget = require('tkwidgets/RootWidget.js'); +const WindowWidget = require('tkwidgets/WindowWidget.js'); -const NoteWidget = require("./gui/NoteWidget.js"); -const ResourceServer = require("./ResourceServer.js"); -const NoteMetadataWidget = require("./gui/NoteMetadataWidget.js"); -const FolderListWidget = require("./gui/FolderListWidget.js"); -const NoteListWidget = require("./gui/NoteListWidget.js"); -const StatusBarWidget = require("./gui/StatusBarWidget.js"); -const ConsoleWidget = require("./gui/ConsoleWidget.js"); +const NoteWidget = require('./gui/NoteWidget.js'); +const ResourceServer = require('./ResourceServer.js'); +const NoteMetadataWidget = require('./gui/NoteMetadataWidget.js'); +const FolderListWidget = require('./gui/FolderListWidget.js'); +const NoteListWidget = require('./gui/NoteListWidget.js'); +const StatusBarWidget = require('./gui/StatusBarWidget.js'); +const ConsoleWidget = require('./gui/ConsoleWidget.js'); class AppGui { + constructor(app, store, keymap) { try { this.app_ = app; @@ -46,12 +47,12 @@ class AppGui { // Some keys are directly handled by the tkwidget framework // so they need to be remapped in a different way. this.tkWidgetKeys_ = { - focus_next: "TAB", - focus_previous: "SHIFT_TAB", - move_up: "UP", - move_down: "DOWN", - page_down: "PAGE_DOWN", - page_up: "PAGE_UP", + 'focus_next': 'TAB', + 'focus_previous': 'SHIFT_TAB', + 'move_up': 'UP', + 'move_down': 'DOWN', + 'page_down': 'PAGE_DOWN', + 'page_up': 'PAGE_UP', }; this.renderer_ = null; @@ -60,7 +61,7 @@ class AppGui { this.renderer_ = new Renderer(this.term(), this.rootWidget_); - this.app_.on("modelAction", async event => { + this.app_.on('modelAction', async (event) => { await this.handleModelAction(event.action); }); @@ -94,7 +95,7 @@ class AppGui { } async forceRender() { - this.widget("root").invalidate(); + this.widget('root').invalidate(); await this.renderer_.renderRoot(); } @@ -106,12 +107,12 @@ class AppGui { return this.term().restoreState(state); } - prompt(initialText = "", promptString = ":", options = null) { - return this.widget("statusBar").prompt(initialText, promptString, options); + prompt(initialText = '', promptString = ':', options = null) { + return this.widget('statusBar').prompt(initialText, promptString, options); } stdoutMaxWidth() { - return this.widget("console").innerWidth - 1; + return this.widget('console').innerWidth - 1; } isDummy() { @@ -120,7 +121,7 @@ class AppGui { buildUi() { this.rootWidget_ = new ReduxRootWidget(this.store_); - this.rootWidget_.name = "root"; + this.rootWidget_.name = 'root'; this.rootWidget_.autoShortcutsEnabled = false; const folderList = new FolderListWidget(); @@ -128,21 +129,21 @@ class AppGui { borderBottomWidth: 1, borderRightWidth: 1, }; - folderList.name = "folderList"; + folderList.name = 'folderList'; folderList.vStretch = true; - folderList.on("currentItemChange", async event => { + folderList.on('currentItemChange', async (event) => { const item = folderList.currentItem; - if (item === "-") { + if (item === '-') { let newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1); let nextItem = folderList.itemAt(newIndex); if (!nextItem) nextItem = folderList.itemAt(event.previousIndex); if (!nextItem) return; // Normally not possible - let actionType = "FOLDER_SELECT"; - if (nextItem.type_ === BaseModel.TYPE_TAG) actionType = "TAG_SELECT"; - if (nextItem.type_ === BaseModel.TYPE_SEARCH) actionType = "SEARCH_SELECT"; + let actionType = 'FOLDER_SELECT'; + if (nextItem.type_ === BaseModel.TYPE_TAG) actionType = 'TAG_SELECT'; + if (nextItem.type_ === BaseModel.TYPE_SEARCH) actionType = 'SEARCH_SELECT'; this.store_.dispatch({ type: actionType, @@ -150,22 +151,22 @@ class AppGui { }); } else if (item.type_ === Folder.modelType()) { this.store_.dispatch({ - type: "FOLDER_SELECT", + type: 'FOLDER_SELECT', id: item ? item.id : null, }); } else if (item.type_ === Tag.modelType()) { this.store_.dispatch({ - type: "TAG_SELECT", + type: 'TAG_SELECT', id: item ? item.id : null, }); } else if (item.type_ === BaseModel.TYPE_SEARCH) { this.store_.dispatch({ - type: "SEARCH_SELECT", + type: 'SEARCH_SELECT', id: item ? item.id : null, }); } }); - this.rootWidget_.connect(folderList, state => { + this.rootWidget_.connect(folderList, (state) => { return { selectedFolderId: state.selectedFolderId, selectedTagId: state.selectedTagId, @@ -178,21 +179,21 @@ class AppGui { }); const noteList = new NoteListWidget(); - noteList.name = "noteList"; + noteList.name = 'noteList'; noteList.vStretch = true; noteList.style = { borderBottomWidth: 1, borderLeftWidth: 1, borderRightWidth: 1, }; - noteList.on("currentItemChange", async () => { + noteList.on('currentItemChange', async () => { let note = noteList.currentItem; this.store_.dispatch({ - type: "NOTE_SELECT", + type: 'NOTE_SELECT', id: note ? note.id : null, }); }); - this.rootWidget_.connect(noteList, state => { + this.rootWidget_.connect(noteList, (state) => { return { selectedNoteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null, items: state.notes, @@ -201,12 +202,12 @@ class AppGui { const noteText = new NoteWidget(); noteText.hStretch = true; - noteText.name = "noteText"; + noteText.name = 'noteText'; noteText.style = { borderBottomWidth: 1, borderLeftWidth: 1, }; - this.rootWidget_.connect(noteText, state => { + this.rootWidget_.connect(noteText, (state) => { return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null, notes: state.notes, @@ -215,13 +216,13 @@ class AppGui { const noteMetadata = new NoteMetadataWidget(); noteMetadata.hStretch = true; - noteMetadata.name = "noteMetadata"; + noteMetadata.name = 'noteMetadata'; noteMetadata.style = { borderBottomWidth: 1, borderLeftWidth: 1, borderRightWidth: 1, }; - this.rootWidget_.connect(noteMetadata, state => { + this.rootWidget_.connect(noteMetadata, (state) => { return { noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null }; }); noteMetadata.hide(); @@ -237,58 +238,58 @@ class AppGui { statusBar.hStretch = true; const noteLayout = new VLayoutWidget(); - noteLayout.name = "noteLayout"; - noteLayout.addChild(noteText, { type: "stretch", factor: 1 }); - noteLayout.addChild(noteMetadata, { type: "stretch", factor: 1 }); + noteLayout.name = 'noteLayout'; + noteLayout.addChild(noteText, { type: 'stretch', factor: 1 }); + noteLayout.addChild(noteMetadata, { type: 'stretch', factor: 1 }); const hLayout = new HLayoutWidget(); - hLayout.name = "hLayout"; - hLayout.addChild(folderList, { type: "stretch", factor: 1 }); - hLayout.addChild(noteList, { type: "stretch", factor: 1 }); - hLayout.addChild(noteLayout, { type: "stretch", factor: 2 }); + hLayout.name = 'hLayout'; + hLayout.addChild(folderList, { type: 'stretch', factor: 1 }); + hLayout.addChild(noteList, { type: 'stretch', factor: 1 }); + hLayout.addChild(noteLayout, { type: 'stretch', factor: 2 }); const vLayout = new VLayoutWidget(); - vLayout.name = "vLayout"; - vLayout.addChild(hLayout, { type: "stretch", factor: 2 }); - vLayout.addChild(consoleWidget, { type: "stretch", factor: 1 }); - vLayout.addChild(statusBar, { type: "fixed", factor: 1 }); + vLayout.name = 'vLayout'; + vLayout.addChild(hLayout, { type: 'stretch', factor: 2 }); + vLayout.addChild(consoleWidget, { type: 'stretch', factor: 1 }); + vLayout.addChild(statusBar, { type: 'fixed', factor: 1 }); const win1 = new WindowWidget(); win1.addChild(vLayout); - win1.name = "mainWindow"; + win1.name = 'mainWindow'; this.rootWidget_.addChild(win1); } showModalOverlay(text) { - if (!this.widget("overlayWindow")) { + if (!this.widget('overlayWindow')) { const textWidget = new TextWidget(); textWidget.hStretch = true; textWidget.vStretch = true; - textWidget.text = "testing"; - textWidget.name = "overlayText"; + textWidget.text = 'testing'; + textWidget.name = 'overlayText'; const win = new WindowWidget(); - win.name = "overlayWindow"; + win.name = 'overlayWindow'; win.addChild(textWidget); this.rootWidget_.addChild(win); } - this.widget("overlayWindow").activate(); - this.widget("overlayText").text = text; + this.widget('overlayWindow').activate(); + this.widget('overlayText').text = text; } hideModalOverlay() { - if (this.widget("overlayWindow")) this.widget("overlayWindow").hide(); - this.widget("mainWindow").activate(); + if (this.widget('overlayWindow')) this.widget('overlayWindow').hide(); + this.widget('mainWindow').activate(); } addCommandToConsole(cmd) { if (!cmd) return; - const isConfigPassword = cmd.indexOf("config ") >= 0 && cmd.indexOf("password") >= 0; + const isConfigPassword = cmd.indexOf('config ') >= 0 && cmd.indexOf('password') >= 0; if (isConfigPassword) return; - this.stdout(chalk.cyan.bold("> " + cmd)); + this.stdout(chalk.cyan.bold('> ' + cmd)); } setupKeymap(keymap) { @@ -297,15 +298,15 @@ class AppGui { for (let i = 0; i < keymap.length; i++) { const item = Object.assign({}, keymap[i]); - if (!item.command) throw new Error("Missing command for keymap item: " + JSON.stringify(item)); + if (!item.command) throw new Error('Missing command for keymap item: ' + JSON.stringify(item)); - if (!("type" in item)) item.type = "exec"; + if (!('type' in item)) item.type = 'exec'; if (item.command in this.tkWidgetKeys_) { - item.type = "tkwidgets"; + item.type = 'tkwidgets'; } - item.canRunAlongOtherCommands = item.type === "function" && ["toggle_metadata", "toggle_console"].indexOf(item.command) >= 0; + item.canRunAlongOtherCommands = item.type === 'function' && ['toggle_metadata', 'toggle_console'].indexOf(item.command) >= 0; output.push(item); } @@ -318,7 +319,7 @@ class AppGui { } showConsole(doShow = true) { - this.widget("console").show(doShow); + this.widget('console').show(doShow); } hideConsole() { @@ -326,11 +327,11 @@ class AppGui { } consoleIsShown() { - return this.widget("console").shown; + return this.widget('console').shown; } maximizeConsole(doMaximize = true) { - const consoleWidget = this.widget("console"); + const consoleWidget = this.widget('console'); if (consoleWidget.isMaximized__ === undefined) { consoleWidget.isMaximized__ = false; @@ -339,13 +340,13 @@ class AppGui { if (consoleWidget.isMaximized__ === doMaximize) return; let constraints = { - type: "stretch", + type: 'stretch', factor: !doMaximize ? 1 : 4, }; consoleWidget.isMaximized__ = doMaximize; - this.widget("vLayout").setWidgetConstraints(consoleWidget, constraints); + this.widget('vLayout').setWidgetConstraints(consoleWidget, constraints); } minimizeConsole() { @@ -353,11 +354,11 @@ class AppGui { } consoleIsMaximized() { - return this.widget("console").isMaximized__ === true; + return this.widget('console').isMaximized__ === true; } showNoteMetadata(show = true) { - this.widget("noteMetadata").show(show); + this.widget('noteMetadata').show(show); } hideNoteMetadata() { @@ -365,11 +366,11 @@ class AppGui { } toggleNoteMetadata() { - this.showNoteMetadata(!this.widget("noteMetadata").shown); + this.showNoteMetadata(!this.widget('noteMetadata').shown); } widget(name) { - if (name === "root") return this.rootWidget_; + if (name === 'root') return this.rootWidget_; return this.rootWidget_.childByName(name); } @@ -402,10 +403,10 @@ class AppGui { } activeListItem() { - const widget = this.widget("mainWindow").focusedWidget; + const widget = this.widget('mainWindow').focusedWidget; if (!widget) return null; - - if (widget.name == "noteList" || widget.name == "folderList") { + + if (widget.name == 'noteList' || widget.name == 'folderList') { return widget.currentItem; } @@ -413,48 +414,54 @@ class AppGui { } async handleModelAction(action) { - this.logger().info("Action:", action); + this.logger().info('Action:', action); let state = Object.assign({}, defaultState); - state.notes = this.widget("noteList").items; + state.notes = this.widget('noteList').items; let newState = reducer(state, action); if (newState !== state) { - this.widget("noteList").items = newState.notes; + this.widget('noteList').items = newState.notes; } } async processFunctionCommand(cmd) { - if (cmd === "activate") { - const w = this.widget("mainWindow").focusedWidget; - if (w.name === "folderList") { - this.widget("noteList").focus(); - } else if (w.name === "noteList" || w.name === "noteText") { - this.processPromptCommand("edit $n"); + + if (cmd === 'activate') { + + const w = this.widget('mainWindow').focusedWidget; + if (w.name === 'folderList') { + this.widget('noteList').focus(); + } else if (w.name === 'noteList' || w.name === 'noteText') { + this.processPromptCommand('edit $n'); } - } else if (cmd === "delete") { - if (this.widget("folderList").hasFocus) { - const item = this.widget("folderList").selectedJoplinItem; + + } else if (cmd === 'delete') { + + if (this.widget('folderList').hasFocus) { + const item = this.widget('folderList').selectedJoplinItem; if (!item) return; if (item.type_ === BaseModel.TYPE_FOLDER) { - await this.processPromptCommand("rmbook " + item.id); + await this.processPromptCommand('rmbook ' + item.id); } else if (item.type_ === BaseModel.TYPE_TAG) { - this.stdout(_("To delete a tag, untag the associated notes.")); + this.stdout(_('To delete a tag, untag the associated notes.')); } else if (item.type_ === BaseModel.TYPE_SEARCH) { this.store().dispatch({ - type: "SEARCH_DELETE", + type: 'SEARCH_DELETE', id: item.id, }); } - } else if (this.widget("noteList").hasFocus) { - await this.processPromptCommand("rmnote $n"); + } else if (this.widget('noteList').hasFocus) { + await this.processPromptCommand('rmnote $n'); } else { - this.stdout(_("Please select the note or notebook to be deleted first.")); + this.stdout(_('Please select the note or notebook to be deleted first.')); } - } else if (cmd === "toggle_console") { + + } else if (cmd === 'toggle_console') { + if (!this.consoleIsShown()) { this.showConsole(); this.minimizeConsole(); @@ -465,15 +472,22 @@ class AppGui { this.maximizeConsole(); } } - } else if (cmd === "toggle_metadata") { + + } else if (cmd === 'toggle_metadata') { + this.toggleNoteMetadata(); - } else if (cmd === "enter_command_line_mode") { - const cmd = await this.widget("statusBar").prompt(); + + } else if (cmd === 'enter_command_line_mode') { + + const cmd = await this.widget('statusBar').prompt(); if (!cmd) return; this.addCommandToConsole(cmd); - await this.processPromptCommand(cmd); + await this.processPromptCommand(cmd); + } else { - throw new Error("Unknown command: " + cmd); + + throw new Error('Unknown command: ' + cmd); + } } @@ -482,21 +496,21 @@ class AppGui { cmd = cmd.trim(); if (!cmd.length) return; - this.logger().info("Got command: " + cmd); + this.logger().info('Got command: ' + cmd); - try { - let note = this.widget("noteList").currentItem; - let folder = this.widget("folderList").currentItem; + try { + let note = this.widget('noteList').currentItem; + let folder = this.widget('folderList').currentItem; let args = splitCommandString(cmd); for (let i = 0; i < args.length; i++) { - if (args[i] == "$n") { - args[i] = note ? note.id : ""; - } else if (args[i] == "$b") { - args[i] = folder ? folder.id : ""; - } else if (args[i] == "$c") { + if (args[i] == '$n') { + args[i] = note ? note.id : ''; + } else if (args[i] == '$b') { + args[i] = folder ? folder.id : ''; + } else if (args[i] == '$c') { const item = this.activeListItem(); - args[i] = item ? item.id : ""; + args[i] = item ? item.id : ''; } } @@ -505,40 +519,40 @@ class AppGui { this.stdout(error.message); } - this.widget("console").scrollBottom(); - + this.widget('console').scrollBottom(); + // Invalidate so that the screen is redrawn in case inputting a command has moved // the GUI up (in particular due to autocompletion), it's moved back to the right position. - this.widget("root").invalidate(); + this.widget('root').invalidate(); } async updateFolderList() { const folders = await Folder.all(); - this.widget("folderList").items = folders; + this.widget('folderList').items = folders; } async updateNoteList(folderId) { const fields = Note.previewFields(); - fields.splice(fields.indexOf("body"), 1); + fields.splice(fields.indexOf('body'), 1); const notes = folderId ? await Note.previews(folderId, { fields: fields }) : []; - this.widget("noteList").items = notes; + this.widget('noteList').items = notes; } async updateNoteText(note) { - const text = note ? note.body : ""; - this.widget("noteText").text = text; + const text = note ? note.body : ''; + this.widget('noteText').text = text; } // Any key after which a shortcut is not possible. isSpecialKey(name) { - return [":", "ENTER", "DOWN", "UP", "LEFT", "RIGHT", "DELETE", "BACKSPACE", "ESCAPE", "TAB", "SHIFT_TAB", "PAGE_UP", "PAGE_DOWN"].indexOf(name) >= 0; + return [':', 'ENTER', 'DOWN', 'UP', 'LEFT', 'RIGHT', 'DELETE', 'BACKSPACE', 'ESCAPE', 'TAB', 'SHIFT_TAB', 'PAGE_UP', 'PAGE_DOWN'].indexOf(name) >= 0; } fullScreen(enable = true) { if (enable) { this.term().fullscreen(); this.term().hideCursor(); - this.widget("root").invalidate(); + this.widget('root').invalidate(); } else { this.term().fullscreen(false); this.term().showCursor(); @@ -548,10 +562,10 @@ class AppGui { stdout(text) { if (text === null || text === undefined) return; - let lines = text.split("\n"); + let lines = text.split('\n'); for (let i = 0; i < lines.length; i++) { - const v = typeof lines[i] === "object" ? JSON.stringify(lines[i]) : lines[i]; - this.widget("console").addLine(v); + const v = typeof lines[i] === 'object' ? JSON.stringify(lines[i]) : lines[i]; + this.widget('console').addLine(v); } this.updateStatusBarMessage(); @@ -563,40 +577,40 @@ class AppGui { } updateStatusBarMessage() { - const consoleWidget = this.widget("console"); + const consoleWidget = this.widget('console'); - let msg = ""; + let msg = ''; const text = consoleWidget.lastLine; const cmd = this.app().currentCommand(); if (cmd) { msg += cmd.name(); - if (cmd.cancellable()) msg += " [Press Ctrl+C to cancel]"; - msg += ": "; + if (cmd.cancellable()) msg += ' [Press Ctrl+C to cancel]'; + msg += ': '; } if (text && text.length) { msg += text; } - if (msg !== "") this.widget("statusBar").setItemAt(0, msg); + if (msg !== '') this.widget('statusBar').setItemAt(0, msg); } async setupResourceServer() { const linkStyle = chalk.blue.underline; - const noteTextWidget = this.widget("noteText"); - const resourceIdRegex = /^:\/[a-f0-9]+$/i; + const noteTextWidget = this.widget('noteText'); + const resourceIdRegex = /^:\/[a-f0-9]+$/i const noteLinks = {}; const hasProtocol = function(s, protocols) { if (!s) return false; s = s.trim().toLowerCase(); for (let i = 0; i < protocols.length; i++) { - if (s.indexOf(protocols[i] + "://") === 0) return true; + if (s.indexOf(protocols[i] + '://') === 0) return true; } return false; - }; + } // By default, before the server is started, only the regular // URLs appear in blue. @@ -606,7 +620,7 @@ class AppGui { if (resourceIdRegex.test(url)) { return url; - } else if (hasProtocol(url, ["http", "https"])) { + } else if (hasProtocol(url, ['http', 'https'])) { return linkStyle(url); } else { return url; @@ -619,16 +633,16 @@ class AppGui { this.resourceServer_.setLinkHandler(async (path, response) => { const link = noteLinks[path]; - if (link.type === "url") { - response.writeHead(302, { Location: link.url }); + if (link.type === 'url') { + response.writeHead(302, { 'Location': link.url }); return true; } - if (link.type === "resource") { + if (link.type === 'resource') { const resourceId = link.id; let resource = await Resource.load(resourceId); - if (!resource) throw new Error("No resource with ID " + resourceId); // Should be nearly impossible - if (resource.mime) response.setHeader("Content-Type", resource.mime); + if (!resource) throw new Error('No resource with ID ' + resourceId); // Should be nearly impossible + if (resource.mime) response.setHeader('Content-Type', resource.mime); response.write(await Resource.content(resource)); return true; } @@ -645,21 +659,21 @@ class AppGui { if (resourceIdRegex.test(url)) { noteLinks[index] = { - type: "resource", + type: 'resource', id: url.substr(2), - }; - } else if (hasProtocol(url, ["http", "https", "file", "ftp"])) { + }; + } else if (hasProtocol(url, ['http', 'https', 'file', 'ftp'])) { noteLinks[index] = { - type: "url", + type: 'url', url: url, }; - } else if (url.indexOf("#") === 0) { - return ""; // Anchors aren't supported for now + } else if (url.indexOf('#') === 0) { + return ''; // Anchors aren't supported for now } else { return url; } - return linkStyle(this.resourceServer_.baseUrl() + "/" + index); + return linkStyle(this.resourceServer_.baseUrl() + '/' + index); }, }; } @@ -674,16 +688,17 @@ class AppGui { this.renderer_.start(); - const statusBar = this.widget("statusBar"); + const statusBar = this.widget('statusBar'); term.grabInput(); - term.on("key", async (name, matches, data) => { + term.on('key', async (name, matches, data) => { + // ------------------------------------------------------------------------- // Handle special shortcuts // ------------------------------------------------------------------------- - if (name === "CTRL_D") { + if (name === 'CTRL_D') { const cmd = this.app().currentCommand(); if (cmd && cmd.cancellable() && !this.commandCancelCalled_) { @@ -696,13 +711,13 @@ class AppGui { return; } - if (name === "CTRL_C") { + if (name === 'CTRL_C' ) { const cmd = this.app().currentCommand(); if (!cmd || !cmd.cancellable() || this.commandCancelCalled_) { this.stdout(_('Press Ctrl+D or type "exit" to exit the application')); } else { this.commandCancelCalled_ = true; - await cmd.cancel(); + await cmd.cancel() this.commandCancelCalled_ = false; } return; @@ -711,8 +726,8 @@ class AppGui { // ------------------------------------------------------------------------- // Build up current shortcut // ------------------------------------------------------------------------- - - const now = new Date().getTime(); + + const now = (new Date()).getTime(); if (now - this.lastShortcutKeyTime_ > 800 || this.isSpecialKey(name)) { this.currentShortcutKeys_ = [name]; @@ -732,7 +747,7 @@ class AppGui { // Process shortcut and execute associated command // ------------------------------------------------------------------------- - const shortcutKey = this.currentShortcutKeys_.join(""); + const shortcutKey = this.currentShortcutKeys_.join(''); let keymapItem = this.keymapItemByKey(shortcutKey); // If this command is an alias to another command, resolve to the actual command @@ -742,25 +757,25 @@ class AppGui { if (statusBar.promptActive) processShortcutKeys = false; if (processShortcutKeys) { - this.logger().info("Shortcut:", shortcutKey, keymapItem); + this.logger().info('Shortcut:', shortcutKey, keymapItem); this.currentShortcutKeys_ = []; - if (keymapItem.type === "function") { + if (keymapItem.type === 'function') { this.processFunctionCommand(keymapItem.command); - } else if (keymapItem.type === "prompt") { + } else if (keymapItem.type === 'prompt') { let promptOptions = {}; - if ("cursorPosition" in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition; - const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : "", null, promptOptions); + if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition; + const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions); this.addCommandToConsole(commandString); await this.processPromptCommand(commandString); - } else if (keymapItem.type === "exec") { + } else if (keymapItem.type === 'exec') { this.stdout(keymapItem.command); await this.processPromptCommand(keymapItem.command); - } else if (keymapItem.type === "tkwidgets") { - this.widget("root").handleKey(this.tkWidgetKeys_[keymapItem.command]); + } else if (keymapItem.type === 'tkwidgets') { + this.widget('root').handleKey(this.tkWidgetKeys_[keymapItem.command]); } else { - throw new Error("Unknown command type: " + JSON.stringify(keymapItem)); + throw new Error('Unknown command type: ' + JSON.stringify(keymapItem)); } } @@ -774,12 +789,13 @@ class AppGui { console.error(error); } - process.on("unhandledRejection", (reason, p) => { + process.on('unhandledRejection', (reason, p) => { this.fullScreen(false); - console.error("Unhandled promise rejection", p, "reason:", reason); + console.error('Unhandled promise rejection', p, 'reason:', reason); process.exit(1); }); } + } AppGui.INPUT_MODE_NORMAL = 1; diff --git a/CliClient/app/app.js b/CliClient/app/app.js index 38459577c..605892cd3 100644 --- a/CliClient/app/app.js +++ b/CliClient/app/app.js @@ -1,29 +1,30 @@ -const { BaseApplication } = require("lib/BaseApplication"); -const { createStore, applyMiddleware } = require("redux"); -const { reducer, defaultState } = require("lib/reducer.js"); -const { JoplinDatabase } = require("lib/joplin-database.js"); -const { Database } = require("lib/database.js"); -const { FoldersScreenUtils } = require("lib/folders-screen-utils.js"); -const { DatabaseDriverNode } = require("lib/database-driver-node.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const Note = require("lib/models/Note.js"); -const Tag = require("lib/models/Tag.js"); -const Setting = require("lib/models/Setting.js"); -const { Logger } = require("lib/logger.js"); -const { sprintf } = require("sprintf-js"); -const { reg } = require("lib/registry.js"); -const { fileExtension } = require("lib/path-utils.js"); -const { shim } = require("lib/shim.js"); -const { _, setLocale, defaultLocale, closestSupportedLocale } = require("lib/locale.js"); -const os = require("os"); -const fs = require("fs-extra"); -const { cliUtils } = require("./cli-utils.js"); -const EventEmitter = require("events"); -const Cache = require("lib/Cache"); +const { BaseApplication } = require('lib/BaseApplication'); +const { createStore, applyMiddleware } = require('redux'); +const { reducer, defaultState } = require('lib/reducer.js'); +const { JoplinDatabase } = require('lib/joplin-database.js'); +const { Database } = require('lib/database.js'); +const { FoldersScreenUtils } = require('lib/folders-screen-utils.js'); +const { DatabaseDriverNode } = require('lib/database-driver-node.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); +const Setting = require('lib/models/Setting.js'); +const { Logger } = require('lib/logger.js'); +const { sprintf } = require('sprintf-js'); +const { reg } = require('lib/registry.js'); +const { fileExtension } = require('lib/path-utils.js'); +const { shim } = require('lib/shim.js'); +const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/locale.js'); +const os = require('os'); +const fs = require('fs-extra'); +const { cliUtils } = require('./cli-utils.js'); +const EventEmitter = require('events'); +const Cache = require('lib/Cache'); class Application extends BaseApplication { + constructor() { super(); @@ -47,7 +48,7 @@ class Application extends BaseApplication { async guessTypeAndLoadItem(pattern, options = null) { let type = BaseModel.TYPE_NOTE; - if (pattern.indexOf("/") === 0) { + if (pattern.indexOf('/') === 0) { type = BaseModel.TYPE_FOLDER; pattern = pattern.substr(1); } @@ -79,13 +80,13 @@ class Application extends BaseApplication { } async loadItems(type, pattern, options = null) { - if (type === "folderOrNote") { + 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() : ""; + pattern = pattern ? pattern.toString() : ''; if (type == BaseModel.TYPE_FOLDER && (pattern == Folder.conflictFolderTitle() || pattern == Folder.conflictFolderId())) return [Folder.conflictFolder()]; @@ -94,16 +95,14 @@ class Application extends BaseApplication { 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.")); + 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 + } 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); + if (!parent) throw new Error(_('No notebook has been specified.')); + item = await ItemClass.loadFolderNoteByField(parent.id, 'title', pattern); } else { item = await ItemClass.loadByTitle(pattern); } @@ -125,34 +124,34 @@ class Application extends BaseApplication { } setupCommand(cmd) { - cmd.setStdout(text => { + cmd.setStdout((text) => { return this.stdout(text); }); - cmd.setDispatcher(action => { + cmd.setDispatcher((action) => { if (this.store()) { return this.store().dispatch(action); } else { - return action => {}; + return (action) => {}; } }); cmd.setPrompt(async (message, options) => { if (!options) options = {}; - if (!options.type) options.type = "boolean"; - if (!options.booleanAnswerDefault) options.booleanAnswerDefault = "y"; - if (!options.answers) options.answers = options.booleanAnswerDefault === "y" ? [_("Y"), _("n")] : [_("N"), _("y")]; + if (!options.type) options.type = 'boolean'; + if (!options.booleanAnswerDefault) options.booleanAnswerDefault = 'y'; + if (!options.answers) options.answers = options.booleanAnswerDefault === 'y' ? [_('Y'), _('n')] : [_('N'), _('y')]; - if (options.type == "boolean") { - message += " (" + options.answers.join("/") + ")"; + if (options.type == 'boolean') { + message += ' (' + options.answers.join('/') + ')'; } - let answer = await this.gui().prompt("", message + " ", options); + let answer = await this.gui().prompt('', message + ' ', options); - if (options.type === "boolean") { + if (options.type === 'boolean') { if (answer === null) return false; // Pressed ESCAPE if (!answer) answer = options.answers[0]; - let positiveIndex = options.booleanAnswerDefault == "y" ? 0 : 1; + let positiveIndex = options.booleanAnswerDefault == 'y' ? 0 : 1; return answer.toLowerCase() === options.answers[positiveIndex].toLowerCase(); } else { return answer; @@ -174,7 +173,7 @@ class Application extends BaseApplication { }, 5000); if (await reg.syncTarget().syncStarted()) { - this.stdout(_("Cancelling background synchronisation... Please wait.")); + this.stdout(_('Cancelling background synchronisation... Please wait.')); const sync = await reg.syncTarget().synchronizer(); await sync.cancel(); } @@ -184,12 +183,12 @@ class Application extends BaseApplication { commands(uiType = null) { if (!this.allCommandsLoaded_) { - fs.readdirSync(__dirname).forEach(path => { - if (path.indexOf("command-") !== 0) return; - const ext = fileExtension(path); - if (ext != "js") return; + fs.readdirSync(__dirname).forEach((path) => { + if (path.indexOf('command-') !== 0) return; + const ext = fileExtension(path) + if (ext != 'js') return; - let CommandClass = require("./" + path); + let CommandClass = require('./' + path); let cmd = new CommandClass(); if (!cmd.enabled()) return; cmd = this.setupCommand(cmd); @@ -226,7 +225,7 @@ class Application extends BaseApplication { async commandMetadata() { if (this.commandMetadata_) return this.commandMetadata_; - let output = await this.cache_.getItem("metadata"); + let output = await this.cache_.getItem('metadata'); if (output) { this.commandMetadata_ = output; return Object.assign({}, this.commandMetadata_); @@ -241,7 +240,7 @@ class Application extends BaseApplication { output[n] = cmd.metadata(); } - await this.cache_.setItem("metadata", output, 1000 * 60 * 60 * 24); + await this.cache_.setItem('metadata', output, 1000 * 60 * 60 * 24); this.commandMetadata_ = output; return Object.assign({}, this.commandMetadata_); @@ -256,11 +255,11 @@ class Application extends BaseApplication { let CommandClass = null; try { - CommandClass = require(__dirname + "/command-" + name + ".js"); + CommandClass = require(__dirname + '/command-' + name + '.js'); } catch (error) { - if (error.message && error.message.indexOf("Cannot find module") >= 0) { - let e = new Error(_("No such command: %s", name)); - e.type = "notFound"; + if (error.message && error.message.indexOf('Cannot find module') >= 0) { + let e = new Error(_('No such command: %s', name)); + e.type = 'notFound'; throw e; } else { throw error; @@ -275,39 +274,31 @@ class Application extends BaseApplication { dummyGui() { return { - isDummy: () => { - return true; - }, - prompt: (initialText = "", promptString = "", options = null) => { - return cliUtils.prompt(initialText, promptString, options); - }, + isDummy: () => { return true; }, + prompt: (initialText = '', promptString = '', options = null) => { return cliUtils.prompt(initialText, promptString, options); }, showConsole: () => {}, maximizeConsole: () => {}, - stdout: text => { - console.info(text); - }, - fullScreen: (b = true) => {}, + stdout: (text) => { console.info(text); }, + fullScreen: (b=true) => {}, exit: () => {}, - showModalOverlay: text => {}, + showModalOverlay: (text) => {}, hideModalOverlay: () => {}, - stdoutMaxWidth: () => { - return 100; - }, + stdoutMaxWidth: () => { return 100; }, forceRender: () => {}, termSaveState: () => {}, - termRestoreState: state => {}, + termRestoreState: (state) => {}, }; } async execCommand(argv) { - if (!argv.length) return this.execCommand(["help"]); - reg.logger().info("execCommand()", argv); + if (!argv.length) return this.execCommand(['help']); + reg.logger().info('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(_('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) { @@ -323,24 +314,24 @@ class Application extends BaseApplication { 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: [" "], command: "todo toggle $n" }, - { keys: ["tc"], type: "function", command: "toggle_console" }, - { keys: ["tm"], type: "function", command: "toggle_metadata" }, - { 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 }, + { "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": [" "], "command": "todo toggle $n" }, + { "keys": ["tc"], "type": "function", "command": "toggle_console" }, + { "keys": ["tm"], "type": "function", "command": "toggle_metadata" }, + { "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 @@ -348,22 +339,22 @@ class Application extends BaseApplication { const itemsByCommand = {}; for (let i = 0; i < defaultKeyMap.length; i++) { - itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i]; + itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i] } - const filePath = Setting.value("profileDir") + "/keymap.json"; + 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 + 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; + let msg = error.message ? error.message : ''; + msg = 'Could not load keymap ' + filePath + '\n' + msg; error.message = msg; throw error; } @@ -381,7 +372,7 @@ class Application extends BaseApplication { async start(argv) { argv = await super.start(argv); - cliUtils.setStdout(object => { + cliUtils.setStdout((object) => { return this.stdout(object); }); @@ -390,7 +381,7 @@ class Application extends BaseApplication { if (argv.length) { this.gui_ = this.dummyGui(); - this.currentFolder_ = await Folder.load(Setting.value("activeFolderId")); + this.currentFolder_ = await Folder.load(Setting.value('activeFolderId')); try { await this.execCommand(argv); @@ -402,13 +393,12 @@ class Application extends BaseApplication { } process.exit(1); } - } else { - // Otherwise open the GUI + } else { // Otherwise open the GUI this.initRedux(); const keymap = await this.loadKeymaps(); - const AppGui = require("./app-gui.js"); + const AppGui = require('./app-gui.js'); this.gui_ = new AppGui(this, this.store(), keymap); this.gui_.setLogger(this.logger_); await this.gui_.start(); @@ -423,16 +413,17 @@ class Application extends BaseApplication { const tags = await Tag.allWithNotes(); this.dispatch({ - type: "TAG_UPDATE_ALL", + type: 'TAG_UPDATE_ALL', items: tags, }); this.store().dispatch({ - type: "FOLDER_SELECT", - id: Setting.value("activeFolderId"), + type: 'FOLDER_SELECT', + id: Setting.value('activeFolderId'), }); } } + } let application_ = null; @@ -443,4 +434,4 @@ function app() { return application_; } -module.exports = { app }; +module.exports = { app }; \ No newline at end of file diff --git a/CliClient/app/autocompletion.js b/CliClient/app/autocompletion.js index b72201cde..ade05f5c1 100644 --- a/CliClient/app/autocompletion.js +++ b/CliClient/app/autocompletion.js @@ -1,10 +1,10 @@ -var { app } = require("./app.js"); -var Note = require("lib/models/Note.js"); -var Folder = require("lib/models/Folder.js"); -var Tag = require("lib/models/Tag.js"); -var { cliUtils } = require("./cli-utils.js"); -var yargParser = require("yargs-parser"); -var fs = require("fs-extra"); +var { app } = require('./app.js'); +var Note = require('lib/models/Note.js'); +var Folder = require('lib/models/Folder.js'); +var Tag = require('lib/models/Tag.js'); +var { cliUtils } = require('./cli-utils.js'); +var yargParser = require('yargs-parser'); +var fs = require('fs-extra'); async function handleAutocompletionPromise(line) { // Auto-complete the command name @@ -14,11 +14,11 @@ async function handleAutocompletionPromise(line) { //should look for commmands it could be if (words.length == 1) { if (names.indexOf(words[0]) === -1) { - let x = names.filter(n => n.indexOf(words[0]) === 0); + let x = names.filter((n) => n.indexOf(words[0]) === 0); if (x.length === 1) { - return x[0] + " "; + return x[0] + ' '; } - return x.length > 0 ? x.map(a => a + " ") : line; + return x.length > 0 ? x.map((a) => a + ' ') : line; } else { return line; } @@ -31,14 +31,14 @@ async function handleAutocompletionPromise(line) { return line; } //complete an option - let next = words.length > 1 ? words[words.length - 1] : ""; + let next = words.length > 1 ? words[words.length - 1] : ''; let l = []; - if (next[0] === "-") { - for (let i = 0; i < metadata.options.length; i++) { - const options = metadata.options[i][0].split(" "); - //if there are multiple options then they will be seperated by comma and + if (next[0] === '-') { + for (let i = 0; i toCommandLine(a)); - ret.prefix = toCommandLine(words.slice(0, -1)) + " "; + let ret = l.map(a=>toCommandLine(a)); + ret.prefix = toCommandLine(words.slice(0, -1)) + ' '; return ret; } //Complete an argument //Determine the number of positional arguments by counting the number of //words that don't start with a - less one for the command name - const positionalArgs = words.filter(a => a.indexOf("-") !== 0).length - 1; + const positionalArgs = words.filter((a)=>a.indexOf('-') !== 0).length - 1; - let cmdUsage = yargParser(metadata.usage)["_"]; + let cmdUsage = yargParser(metadata.usage)['_']; cmdUsage.splice(0, 1); if (cmdUsage.length >= positionalArgs) { + let argName = cmdUsage[positionalArgs - 1]; argName = cliUtils.parseCommandArg(argName).name; const currentFolder = app().currentFolder(); - if (argName == "note" || argName == "note-pattern") { - const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + "*" }) : []; - l.push(...notes.map(n => n.title)); + if (argName == 'note' || argName == 'note-pattern') { + const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : []; + l.push(...notes.map((n) => n.title)); } - if (argName == "notebook") { - const folders = await Folder.search({ titlePattern: next + "*" }); - l.push(...folders.map(n => n.title)); + if (argName == 'notebook') { + const folders = await Folder.search({ titlePattern: next + '*' }); + l.push(...folders.map((n) => n.title)); } - if (argName == "item") { - const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + "*" }) : []; - const folders = await Folder.search({ titlePattern: next + "*" }); - l.push(...notes.map(n => n.title), folders.map(n => n.title)); + if (argName == 'item') { + const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: next + '*' }) : []; + const folders = await Folder.search({ titlePattern: next + '*' }); + l.push(...notes.map((n) => n.title), folders.map((n) => n.title)); } - if (argName == "tag") { - let tags = await Tag.search({ titlePattern: next + "*" }); - l.push(...tags.map(n => n.title)); + if (argName == 'tag') { + let tags = await Tag.search({ titlePattern: next + '*' }); + l.push(...tags.map((n) => n.title)); } - if (argName == "file") { - let files = await fs.readdir("."); + if (argName == 'file') { + let files = await fs.readdir('.'); l.push(...files); } - if (argName == "tag-command") { - let c = filterList(["add", "remove", "list"], next); + if (argName == 'tag-command') { + let c = filterList(['add', 'remove', 'list'], next); l.push(...c); } - if (argName == "todo-command") { - let c = filterList(["toggle", "clear"], next); + if (argName == 'todo-command') { + let c = filterList(['toggle', 'clear'], next); l.push(...c); } } if (l.length === 1) { return toCommandLine([...words.slice(0, -1), l[0]]); } else if (l.length > 1) { - let ret = l.map(a => toCommandLine(a)); - ret.prefix = toCommandLine(words.slice(0, -1)) + " "; + let ret = l.map(a=>toCommandLine(a)); + ret.prefix = toCommandLine(words.slice(0, -1)) + ' '; return ret; } return line; + } function handleAutocompletion(str, callback) { handleAutocompletionPromise(str).then(function(res) { @@ -125,35 +127,33 @@ function handleAutocompletion(str, callback) { } function toCommandLine(args) { if (Array.isArray(args)) { - return args - .map(function(a) { - if (a.indexOf('"') !== -1 || a.indexOf(" ") !== -1) { - return "'" + a + "'"; - } else if (a.indexOf("'") !== -1) { - return '"' + a + '"'; - } else { - return a; - } - }) - .join(" "); + return args.map(function(a) { + if(a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) { + return "'" + a + "'"; + } else if (a.indexOf("'") !== -1) { + return '"' + a + '"'; + } else { + return a; + } + }).join(' '); } else { - if (args.indexOf('"') !== -1 || args.indexOf(" ") !== -1) { + if(args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) { return "'" + args + "' "; } else if (args.indexOf("'") !== -1) { return '"' + args + '" '; } else { - return args + " "; + return args + ' '; } } } function getArguments(line) { let inSingleQuotes = false; let inDoubleQuotes = false; - let currentWord = ""; + let currentWord = ''; let parsed = []; - for (let i = 0; i < line.length; i++) { - if (line[i] === '"') { - if (inDoubleQuotes) { + for(let i = 0; i { - if (path.indexOf("command-") !== 0) return; - const ext = fileExtension(path); - if (ext != "js") return; + fs.readdirSync(__dirname).forEach((path) => { + if (path.indexOf('command-') !== 0) return; + const ext = fileExtension(path) + if (ext != 'js') return; - let CommandClass = require("./" + path); + let CommandClass = require('./' + path); let cmd = new CommandClass(); if (!cmd.enabled()) return; if (cmd.hidden()) return; @@ -75,26 +75,24 @@ function getOptionColWidth(options) { function getHeader() { let output = []; - output.push("NAME"); - output.push(""); - output.push(wrap("joplin - a note taking and to-do app with synchronisation capabilities"), INDENT); + output.push('NAME'); + output.push(''); + output.push(wrap('joplin - a note taking and to-do app with synchronisation capabilities'), INDENT); - output.push(""); + output.push(''); - output.push("DESCRIPTION"); - output.push(""); + output.push('DESCRIPTION'); + output.push(''); let description = []; - description.push("Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks."); - description.push("The notes are searchable, can be copied, tagged and modified with your own text editor."); + description.push('Joplin is a note taking and to-do application, which can handle a large number of notes organised into notebooks.'); + description.push('The notes are searchable, can be copied, tagged and modified with your own text editor.'); description.push("\n\n"); - description.push("The notes can be synchronised with various target including the file system (for example with a network directory) or with Microsoft OneDrive."); + description.push('The notes can be synchronised with various target including the file system (for example with a network directory) or with Microsoft OneDrive.'); description.push("\n\n"); - description.push( - "Notes exported from Evenotes via .enex files can be imported into Joplin, including the formatted content, resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.)." - ); + description.push('Notes exported from Evenotes via .enex files can be imported into Joplin, including the formatted content, resources (images, attachments, etc.) and complete metadata (geolocation, updated time, created time, etc.).'); - output.push(wrap(description.join(""), INDENT)); + output.push(wrap(description.join(''), INDENT)); return output.join("\n"); } @@ -102,17 +100,17 @@ function getHeader() { function getFooter() { let output = []; - output.push("WEBSITE"); - output.push(""); - output.push(INDENT + "http://joplin.cozic.net"); + output.push('WEBSITE'); + output.push(''); + output.push(INDENT + 'http://joplin.cozic.net'); - output.push(""); + output.push(''); - output.push("LICENSE"); - output.push(""); - let filePath = rootDir + "/LICENSE_" + languageCode(); - if (!fs.existsSync(filePath)) filePath = rootDir + "/LICENSE"; - const licenseText = fs.readFileSync(filePath, "utf8"); + output.push('LICENSE'); + output.push(''); + let filePath = rootDir + '/LICENSE_' + languageCode(); + if (!fs.existsSync(filePath)) filePath = rootDir + '/LICENSE'; + const licenseText = fs.readFileSync(filePath, 'utf8'); output.push(wrap(licenseText, INDENT)); return output.join("\n"); @@ -133,9 +131,9 @@ async function main() { const commandsText = commandBlocks.join("\n\n"); const footerText = getFooter(); - console.info(headerText + "\n\n" + "USAGE" + "\n\n" + commandsText + "\n\n" + footerText); + console.info(headerText + "\n\n" + 'USAGE' + "\n\n" + commandsText + "\n\n" + footerText); } -main().catch(error => { +main().catch((error) => { console.error(error); -}); +}); \ No newline at end of file diff --git a/CliClient/app/cli-integration-tests.js b/CliClient/app/cli-integration-tests.js index 90fdd1cdb..b38510af0 100644 --- a/CliClient/app/cli-integration-tests.js +++ b/CliClient/app/cli-integration-tests.js @@ -1,30 +1,30 @@ -"use strict"; +"use strict" -const fs = require("fs-extra"); -const { Logger } = require("lib/logger.js"); -const { dirname } = require("lib/path-utils.js"); -const { DatabaseDriverNode } = require("lib/database-driver-node.js"); -const { JoplinDatabase } = require("lib/joplin-database.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const Setting = require("lib/models/Setting.js"); -const { sprintf } = require("sprintf-js"); -const exec = require("child_process").exec; +const fs = require('fs-extra'); +const { Logger } = require('lib/logger.js'); +const { dirname } = require('lib/path-utils.js'); +const { DatabaseDriverNode } = require('lib/database-driver-node.js'); +const { JoplinDatabase } = require('lib/joplin-database.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const Setting = require('lib/models/Setting.js'); +const { sprintf } = require('sprintf-js'); +const exec = require('child_process').exec -process.on("unhandledRejection", (reason, p) => { - console.error("Unhandled promise rejection", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.error('Unhandled promise rejection', p, 'reason:', reason); }); -const baseDir = dirname(__dirname) + "/tests/cli-integration"; -const joplinAppPath = __dirname + "/main.js"; +const baseDir = dirname(__dirname) + '/tests/cli-integration'; +const joplinAppPath = __dirname + '/main.js'; const logger = new Logger(); -logger.addTarget("console"); +logger.addTarget('console'); logger.setLevel(Logger.LEVEL_ERROR); const dbLogger = new Logger(); -dbLogger.addTarget("console"); +dbLogger.addTarget('console'); dbLogger.setLevel(Logger.LEVEL_INFO); const db = new JoplinDatabase(new DatabaseDriverNode()); @@ -32,17 +32,17 @@ db.setLogger(dbLogger); function createClient(id) { return { - id: id, - profileDir: baseDir + "/client" + id, + 'id': id, + 'profileDir': baseDir + '/client' + id, }; } const client = createClient(1); function execCommand(client, command, options = {}) { - let exePath = "node " + joplinAppPath; - let cmd = exePath + " --update-geolocation-disabled --env dev --profile " + client.profileDir + " " + command; - logger.info(client.id + ": " + command); + let exePath = 'node ' + joplinAppPath; + let cmd = exePath + ' --update-geolocation-disabled --env dev --profile ' + client.profileDir + ' ' + command; + logger.info(client.id + ': ' + command); return new Promise((resolve, reject) => { exec(cmd, (error, stdout, stderr) => { @@ -58,58 +58,65 @@ function execCommand(client, command, options = {}) { function assertTrue(v) { if (!v) throw new Error(sprintf('Expected "true", got "%s"."', v)); - process.stdout.write("."); + process.stdout.write('.'); } function assertFalse(v) { if (v) throw new Error(sprintf('Expected "false", got "%s"."', v)); - process.stdout.write("."); + process.stdout.write('.'); } function assertEquals(expected, real) { if (expected !== real) throw new Error(sprintf('Expecting "%s", got "%s"', expected, real)); - process.stdout.write("."); + process.stdout.write('.'); } async function clearDatabase() { - await db.transactionExecBatch(["DELETE FROM folders", "DELETE FROM notes", "DELETE FROM tags", "DELETE FROM note_tags", "DELETE FROM resources", "DELETE FROM deleted_items"]); + await db.transactionExecBatch([ + 'DELETE FROM folders', + 'DELETE FROM notes', + 'DELETE FROM tags', + 'DELETE FROM note_tags', + 'DELETE FROM resources', + 'DELETE FROM deleted_items', + ]); } const testUnits = {}; testUnits.testFolders = async () => { - await execCommand(client, "mkbook nb1"); + await execCommand(client, 'mkbook nb1'); let folders = await Folder.all(); assertEquals(1, folders.length); - assertEquals("nb1", folders[0].title); + assertEquals('nb1', folders[0].title); - await execCommand(client, "mkbook nb1"); + await execCommand(client, 'mkbook nb1'); folders = await Folder.all(); assertEquals(1, folders.length); - assertEquals("nb1", folders[0].title); + assertEquals('nb1', folders[0].title); - await execCommand(client, "rm -r -f nb1"); + await execCommand(client, 'rm -r -f nb1'); folders = await Folder.all(); assertEquals(0, folders.length); -}; +} testUnits.testNotes = async () => { - await execCommand(client, "mkbook nb1"); - await execCommand(client, "mknote n1"); + await execCommand(client, 'mkbook nb1'); + await execCommand(client, 'mknote n1'); let notes = await Note.all(); assertEquals(1, notes.length); - assertEquals("n1", notes[0].title); + assertEquals('n1', notes[0].title); - await execCommand(client, "rm -f n1"); + await execCommand(client, 'rm -f n1'); notes = await Note.all(); assertEquals(0, notes.length); - await execCommand(client, "mknote n1"); - await execCommand(client, "mknote n2"); + await execCommand(client, 'mknote n1'); + await execCommand(client, 'mknote n2'); notes = await Note.all(); assertEquals(2, notes.length); @@ -123,86 +130,86 @@ testUnits.testNotes = async () => { notes = await Note.all(); assertEquals(0, notes.length); -}; +} testUnits.testCat = async () => { - await execCommand(client, "mkbook nb1"); - await execCommand(client, "mknote mynote"); + await execCommand(client, 'mkbook nb1'); + await execCommand(client, 'mknote mynote'); - let folder = await Folder.loadByTitle("nb1"); - let note = await Note.loadFolderNoteByField(folder.id, "title", "mynote"); + let folder = await Folder.loadByTitle('nb1'); + let note = await Note.loadFolderNoteByField(folder.id, 'title', 'mynote'); - let r = await execCommand(client, "cat mynote"); - assertTrue(r.indexOf("mynote") >= 0); + let r = await execCommand(client, 'cat mynote'); + assertTrue(r.indexOf('mynote') >= 0); assertFalse(r.indexOf(note.id) >= 0); - r = await execCommand(client, "cat -v mynote"); + r = await execCommand(client, 'cat -v mynote'); assertTrue(r.indexOf(note.id) >= 0); -}; +} testUnits.testConfig = async () => { - await execCommand(client, "config editor vim"); + await execCommand(client, 'config editor vim'); await Setting.load(); - assertEquals("vim", Setting.value("editor")); + assertEquals('vim', Setting.value('editor')); - await execCommand(client, "config editor subl"); + await execCommand(client, 'config editor subl'); await Setting.load(); - assertEquals("subl", Setting.value("editor")); + assertEquals('subl', Setting.value('editor')); - let r = await execCommand(client, "config"); - assertTrue(r.indexOf("editor") >= 0); - assertTrue(r.indexOf("subl") >= 0); -}; + let r = await execCommand(client, 'config'); + assertTrue(r.indexOf('editor') >= 0); + assertTrue(r.indexOf('subl') >= 0); +} testUnits.testCp = async () => { - await execCommand(client, "mkbook nb2"); - await execCommand(client, "mkbook nb1"); - await execCommand(client, "mknote n1"); + await execCommand(client, 'mkbook nb2'); + await execCommand(client, 'mkbook nb1'); + await execCommand(client, 'mknote n1'); - await execCommand(client, "cp n1"); + await execCommand(client, 'cp n1'); - let f1 = await Folder.loadByTitle("nb1"); - let f2 = await Folder.loadByTitle("nb2"); + let f1 = await Folder.loadByTitle('nb1'); + let f2 = await Folder.loadByTitle('nb2'); let notes = await Note.previews(f1.id); assertEquals(2, notes.length); - await execCommand(client, "cp n1 nb2"); + await execCommand(client, 'cp n1 nb2'); let notesF1 = await Note.previews(f1.id); assertEquals(2, notesF1.length); notes = await Note.previews(f2.id); assertEquals(1, notes.length); assertEquals(notesF1[0].title, notes[0].title); -}; +} testUnits.testLs = async () => { - await execCommand(client, "mkbook nb1"); - await execCommand(client, "mknote note1"); - await execCommand(client, "mknote note2"); - let r = await execCommand(client, "ls"); + await execCommand(client, 'mkbook nb1'); + await execCommand(client, 'mknote note1'); + await execCommand(client, 'mknote note2'); + let r = await execCommand(client, 'ls'); - assertTrue(r.indexOf("note1") >= 0); - assertTrue(r.indexOf("note2") >= 0); -}; + assertTrue(r.indexOf('note1') >= 0); + assertTrue(r.indexOf('note2') >= 0); +} testUnits.testMv = async () => { - await execCommand(client, "mkbook nb2"); - await execCommand(client, "mkbook nb1"); - await execCommand(client, "mknote n1"); - await execCommand(client, "mv n1 nb2"); + await execCommand(client, 'mkbook nb2'); + await execCommand(client, 'mkbook nb1'); + await execCommand(client, 'mknote n1'); + await execCommand(client, 'mv n1 nb2'); - let f1 = await Folder.loadByTitle("nb1"); - let f2 = await Folder.loadByTitle("nb2"); + let f1 = await Folder.loadByTitle('nb1'); + let f2 = await Folder.loadByTitle('nb2'); let notes1 = await Note.previews(f1.id); let notes2 = await Note.previews(f2.id); assertEquals(0, notes1.length); assertEquals(1, notes2.length); - await execCommand(client, "mknote note1"); - await execCommand(client, "mknote note2"); - await execCommand(client, "mknote note3"); - await execCommand(client, "mknote blabla"); + await execCommand(client, 'mknote note1'); + await execCommand(client, 'mknote note2'); + await execCommand(client, 'mknote note3'); + await execCommand(client, 'mknote blabla'); await execCommand(client, "mv 'note*' nb2"); notes1 = await Note.previews(f1.id); @@ -210,19 +217,19 @@ testUnits.testMv = async () => { assertEquals(1, notes1.length); assertEquals(4, notes2.length); -}; +} async function main(argv) { await fs.remove(baseDir); - logger.info(await execCommand(client, "version")); + logger.info(await execCommand(client, 'version')); - await db.open({ name: client.profileDir + "/database.sqlite" }); + await db.open({ name: client.profileDir + '/database.sqlite' }); BaseModel.db_ = db; await Setting.load(); - let onlyThisTest = "testMv"; - onlyThisTest = ""; + let onlyThisTest = 'testMv'; + onlyThisTest = ''; for (let n in testUnits) { if (!testUnits.hasOwnProperty(n)) continue; @@ -230,13 +237,13 @@ async function main(argv) { await clearDatabase(); let testName = n.substr(4).toLowerCase(); - process.stdout.write(testName + ": "); + process.stdout.write(testName + ': '); await testUnits[n](); - console.info(""); + console.info(''); } } -main(process.argv).catch(error => { - console.info(""); +main(process.argv).catch((error) => { + console.info(''); logger.error(error); -}); +}); \ No newline at end of file diff --git a/CliClient/app/cli-utils.js b/CliClient/app/cli-utils.js index e97e96091..895549b72 100644 --- a/CliClient/app/cli-utils.js +++ b/CliClient/app/cli-utils.js @@ -1,12 +1,12 @@ -const yargParser = require("yargs-parser"); -const { _ } = require("lib/locale.js"); -const { time } = require("lib/time-utils.js"); -const stringPadding = require("string-padding"); +const yargParser = require('yargs-parser'); +const { _ } = require('lib/locale.js'); +const { time } = require('lib/time-utils.js'); +const stringPadding = require('string-padding'); const cliUtils = {}; cliUtils.printArray = function(logFunction, rows, headers = null) { - if (!rows.length) return ""; + if (!rows.length) return ''; const ALIGN_LEFT = 0; const ALIGN_RIGHT = 1; @@ -16,11 +16,11 @@ cliUtils.printArray = function(logFunction, rows, headers = null) { for (let i = 0; i < rows.length; i++) { let row = rows[i]; - + for (let j = 0; j < row.length; j++) { let item = row[j]; let width = item ? item.toString().length : 0; - let align = typeof item == "number" ? ALIGN_RIGHT : ALIGN_LEFT; + let align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT; if (!colWidths[j] || colWidths[j] < width) colWidths[j] = width; if (colAligns.length <= j) colAligns[j] = align; } @@ -33,46 +33,46 @@ cliUtils.printArray = function(logFunction, rows, headers = null) { let item = rows[row][col]; let width = colWidths[col]; let dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT; - line.push(stringPadding(item, width, " ", dir)); + line.push(stringPadding(item, width, ' ', dir)); } - logFunction(line.join(" ")); + logFunction(line.join(' ')); } -}; +} cliUtils.parseFlags = function(flags) { let output = {}; - flags = flags.split(","); + flags = flags.split(','); for (let i = 0; i < flags.length; i++) { let f = flags[i].trim(); - if (f.substr(0, 2) == "--") { - f = f.split(" "); + if (f.substr(0, 2) == '--') { + f = f.split(' '); output.long = f[0].substr(2).trim(); if (f.length == 2) { output.arg = cliUtils.parseCommandArg(f[1].trim()); } - } else if (f.substr(0, 1) == "-") { + } else if (f.substr(0, 1) == '-') { output.short = f.substr(1); } } return output; -}; +} cliUtils.parseCommandArg = function(arg) { - if (arg.length <= 2) throw new Error("Invalid command arg: " + arg); + if (arg.length <= 2) throw new Error('Invalid command arg: ' + arg); const c1 = arg[0]; const c2 = arg[arg.length - 1]; const name = arg.substr(1, arg.length - 2); - if (c1 == "<" && c2 == ">") { + if (c1 == '<' && c2 == '>') { return { required: true, name: name }; - } else if (c1 == "[" && c2 == "]") { + } else if (c1 == '[' && c2 == ']') { return { required: false, name: name }; } else { - throw new Error("Invalid command arg: " + arg); + throw new Error('Invalid command arg: ' + arg); } -}; +} cliUtils.makeCommandArgs = function(cmd, argv) { let cmdUsage = cmd.usage(); @@ -83,7 +83,7 @@ cliUtils.makeCommandArgs = function(cmd, argv) { let booleanFlags = []; let aliases = {}; for (let i = 0; i < options.length; i++) { - if (options[i].length != 2) throw new Error("Invalid options: " + options[i]); + if (options[i].length != 2) throw new Error('Invalid options: ' + options[i]); let flags = options[i][0]; let text = options[i][1]; @@ -102,96 +102,97 @@ cliUtils.makeCommandArgs = function(cmd, argv) { let args = yargParser(argv, { boolean: booleanFlags, alias: aliases, - string: ["_"], + string: ['_'], }); - for (let i = 1; i < cmdUsage["_"].length; i++) { - const a = cliUtils.parseCommandArg(cmdUsage["_"][i]); - if (a.required && !args["_"][i]) throw new Error(_("Missing required argument: %s", a.name)); + for (let i = 1; i < cmdUsage['_'].length; i++) { + const a = cliUtils.parseCommandArg(cmdUsage['_'][i]); + if (a.required && !args['_'][i]) throw new Error(_('Missing required argument: %s', a.name)); if (i >= a.length) { output[a.name] = null; } else { - output[a.name] = args["_"][i]; + output[a.name] = args['_'][i]; } } let argOptions = {}; for (let key in args) { if (!args.hasOwnProperty(key)) continue; - if (key == "_") continue; + if (key == '_') continue; argOptions[key] = args[key]; } output.options = argOptions; return output; -}; +} cliUtils.promptMcq = function(message, answers) { - const readline = require("readline"); + const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, - output: process.stdout, + output: process.stdout }); message += "\n\n"; for (let n in answers) { if (!answers.hasOwnProperty(n)) continue; - message += _("%s: %s", n, answers[n]) + "\n"; + message += _('%s: %s', n, answers[n]) + "\n"; } message += "\n"; - message += _("Your choice: "); + message += _('Your choice: '); return new Promise((resolve, reject) => { - rl.question(message, answer => { + rl.question(message, (answer) => { rl.close(); if (!(answer in answers)) { - reject(new Error(_("Invalid answer: %s", answer))); + reject(new Error(_('Invalid answer: %s', answer))); return; } resolve(answer); }); }); -}; +} cliUtils.promptConfirm = function(message, answers = null) { - if (!answers) answers = [_("Y"), _("n")]; - const readline = require("readline"); + if (!answers) answers = [_('Y'), _('n')]; + const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, - output: process.stdout, + output: process.stdout }); - message += " (" + answers.join("/") + ")"; + message += ' (' + answers.join('/') + ')'; return new Promise((resolve, reject) => { - rl.question(message + " ", answer => { + rl.question(message + ' ', (answer) => { const ok = !answer || answer.toLowerCase() == answers[0].toLowerCase(); rl.close(); resolve(ok); }); }); -}; +} // Note: initialText is there to have the same signature as statusBar.prompt() so that // it can be a drop-in replacement, however initialText is not used (and cannot be // with readline.question?). -cliUtils.prompt = function(initialText = "", promptString = ":", options = null) { +cliUtils.prompt = function(initialText = '', promptString = ':', options = null) { if (!options) options = {}; - const readline = require("readline"); - const Writable = require("stream").Writable; + const readline = require('readline'); + const Writable = require('stream').Writable; const mutableStdout = new Writable({ write: function(chunk, encoding, callback) { - if (!this.muted) process.stdout.write(chunk, encoding); + if (!this.muted) + process.stdout.write(chunk, encoding); callback(); - }, + } }); const rl = readline.createInterface({ @@ -203,15 +204,15 @@ cliUtils.prompt = function(initialText = "", promptString = ":", options = null) return new Promise((resolve, reject) => { mutableStdout.muted = false; - rl.question(promptString, answer => { + rl.question(promptString, (answer) => { rl.close(); - if (!!options.secure) this.stdout_(""); + if (!!options.secure) this.stdout_(''); resolve(answer); }); mutableStdout.muted = !!options.secure; }); -}; +} let redrawStarted_ = false; let redrawLastLog_ = null; @@ -219,7 +220,7 @@ let redrawLastUpdateTime_ = 0; cliUtils.setStdout = function(v) { this.stdout_ = v; -}; +} cliUtils.redraw = function(s) { const now = time.unixMs(); @@ -232,8 +233,8 @@ cliUtils.redraw = function(s) { redrawLastLog_ = s; } - redrawStarted_ = true; -}; + redrawStarted_ = true; +} cliUtils.redrawDone = function() { if (!redrawStarted_) return; @@ -244,6 +245,6 @@ cliUtils.redrawDone = function() { redrawLastLog_ = null; redrawStarted_ = false; -}; +} -module.exports = { cliUtils }; +module.exports = { cliUtils }; \ No newline at end of file diff --git a/CliClient/app/command-attach.js b/CliClient/app/command-attach.js index b9c1f4b84..0b22ef4cd 100644 --- a/CliClient/app/command-attach.js +++ b/CliClient/app/command-attach.js @@ -1,30 +1,32 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const { shim } = require("lib/shim.js"); -const fs = require("fs-extra"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const { shim } = require('lib/shim.js'); +const fs = require('fs-extra'); class Command extends BaseCommand { + usage() { - return "attach "; + return 'attach '; } description() { - return _("Attaches the given file to the note."); + return _('Attaches the given file to the note.'); } async action(args) { - let title = args["note"]; + let title = args['note']; let note = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); this.encryptionCheck(note); if (!note) throw new Error(_('Cannot find "%s".', title)); - const localFilePath = args["file"]; + const localFilePath = args['file']; await shim.attachFileToNote(note, localFilePath); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-cat.js b/CliClient/app/command-cat.js index cb16f2ed7..04237b681 100644 --- a/CliClient/app/command-cat.js +++ b/CliClient/app/command-cat.js @@ -1,25 +1,28 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); class Command extends BaseCommand { + usage() { - return "cat "; + return 'cat '; } description() { - return _("Displays the given note."); + return _('Displays the given note.'); } options() { - return [["-v, --verbose", _("Displays the complete information about note.")]]; + return [ + ['-v, --verbose', _('Displays the complete information about note.')], + ]; } async action(args) { - let title = args["note"]; + let title = args['note']; let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); if (!item) throw new Error(_('Cannot find "%s".', title)); @@ -27,13 +30,10 @@ class Command extends BaseCommand { const content = args.options.verbose ? await Note.serialize(item) : await Note.serializeForEdit(item); this.stdout(content); - app() - .gui() - .showConsole(); - app() - .gui() - .maximizeConsole(); + app().gui().showConsole(); + app().gui().maximizeConsole(); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-config.js b/CliClient/app/command-config.js index d321f78b1..b790d0045 100644 --- a/CliClient/app/command-config.js +++ b/CliClient/app/command-config.js @@ -1,11 +1,12 @@ -const { BaseCommand } = require("./base-command.js"); -const { _, setLocale } = require("lib/locale.js"); -const { app } = require("./app.js"); -const Setting = require("lib/models/Setting.js"); +const { BaseCommand } = require('./base-command.js'); +const { _, setLocale } = require('lib/locale.js'); +const { app } = require('./app.js'); +const Setting = require('lib/models/Setting.js'); class Command extends BaseCommand { + usage() { - return "config [name] [value]"; + return 'config [name] [value]'; } description() { @@ -13,62 +14,57 @@ class Command extends BaseCommand { } options() { - return [["-v, --verbose", _("Also displays unset and hidden config variables.")]]; + return [ + ['-v, --verbose', _('Also displays unset and hidden config variables.')], + ]; } async action(args) { const verbose = args.options.verbose; - const renderKeyValue = name => { + const renderKeyValue = (name) => { const md = Setting.settingMetadata(name); let value = Setting.value(name); - if (typeof value === "object" || Array.isArray(value)) value = JSON.stringify(value); - if (md.secure) value = "********"; + if (typeof value === 'object' || Array.isArray(value)) value = JSON.stringify(value); + if (md.secure) value = '********'; if (Setting.isEnum(name)) { - return _("%s = %s (%s)", name, value, Setting.enumOptionsDoc(name)); + return _('%s = %s (%s)', name, value, Setting.enumOptionsDoc(name)); } else { - return _("%s = %s", name, value); + return _('%s = %s', name, value); } - }; + } if (!args.name && !args.value) { - let keys = Setting.keys(!verbose, "cli"); + let keys = Setting.keys(!verbose, 'cli'); keys.sort(); for (let i = 0; i < keys.length; i++) { const value = Setting.value(keys[i]); if (!verbose && !value) continue; this.stdout(renderKeyValue(keys[i])); } - app() - .gui() - .showConsole(); - app() - .gui() - .maximizeConsole(); + app().gui().showConsole(); + app().gui().maximizeConsole(); return; } if (args.name && !args.value) { this.stdout(renderKeyValue(args.name)); - app() - .gui() - .showConsole(); - app() - .gui() - .maximizeConsole(); + app().gui().showConsole(); + app().gui().maximizeConsole(); return; } Setting.setValue(args.name, args.value); - if (args.name == "locale") { - setLocale(Setting.value("locale")); + if (args.name == 'locale') { + setLocale(Setting.value('locale')); app().onLocaleChanged(); } await Setting.saveAll(); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-cp.js b/CliClient/app/command-cp.js index 87b600560..05d2938e0 100644 --- a/CliClient/app/command-cp.js +++ b/CliClient/app/command-cp.js @@ -1,37 +1,39 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); class Command extends BaseCommand { + usage() { - return "cp [notebook]"; + return 'cp [notebook]'; } description() { - return _("Duplicates the notes matching to [notebook]. If no notebook is specified the note is duplicated in the current notebook."); + return _('Duplicates the notes matching to [notebook]. If no notebook is specified the note is duplicated in the current notebook.'); } async action(args) { let folder = null; - if (args["notebook"]) { - folder = await app().loadItem(BaseModel.TYPE_FOLDER, args["notebook"]); + if (args['notebook']) { + folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']); } else { folder = app().currentFolder(); } - if (!folder) throw new Error(_('Cannot find "%s".', args["notebook"])); + if (!folder) throw new Error(_('Cannot find "%s".', args['notebook'])); - const notes = await app().loadItems(BaseModel.TYPE_NOTE, args["note"]); - if (!notes.length) throw new Error(_('Cannot find "%s".', args["note"])); + const notes = await app().loadItems(BaseModel.TYPE_NOTE, args['note']); + if (!notes.length) throw new Error(_('Cannot find "%s".', args['note'])); for (let i = 0; i < notes.length; i++) { const newNote = await Note.copyToFolder(notes[i].id, folder.id); Note.updateGeolocation(newNote.id); } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-done.js b/CliClient/app/command-done.js index 708d36d25..b05f3f55b 100644 --- a/CliClient/app/command-done.js +++ b/CliClient/app/command-done.js @@ -1,18 +1,19 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const { time } = require("lib/time-utils.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const { time } = require('lib/time-utils.js'); class Command extends BaseCommand { + usage() { - return "done "; + return 'done '; } description() { - return _("Marks a to-do as done."); + return _('Marks a to-do as done.'); } static async handleAction(commandInstance, args, isCompleted) { @@ -34,6 +35,7 @@ class Command extends BaseCommand { async action(args) { await Command.handleAction(this, args, true); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-dump.js b/CliClient/app/command-dump.js index 3da46da8d..0672c2632 100644 --- a/CliClient/app/command-dump.js +++ b/CliClient/app/command-dump.js @@ -1,17 +1,18 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const Tag = require("lib/models/Tag.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); class Command extends BaseCommand { + usage() { - return "dump"; + return 'dump'; } description() { - return "Dumps the complete database as JSON."; + return 'Dumps the complete database as JSON.'; } hidden() { @@ -34,9 +35,10 @@ class Command extends BaseCommand { } items = items.concat(tags); - + this.stdout(JSON.stringify(items)); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-e2ee.js b/CliClient/app/command-e2ee.js index 106ad08cf..2313d2ae5 100644 --- a/CliClient/app/command-e2ee.js +++ b/CliClient/app/command-e2ee.js @@ -1,26 +1,27 @@ -const { BaseCommand } = require("./base-command.js"); -const { _ } = require("lib/locale.js"); -const { cliUtils } = require("./cli-utils.js"); -const EncryptionService = require("lib/services/EncryptionService"); -const DecryptionWorker = require("lib/services/DecryptionWorker"); -const MasterKey = require("lib/models/MasterKey"); -const BaseItem = require("lib/models/BaseItem"); -const Setting = require("lib/models/Setting.js"); +const { BaseCommand } = require('./base-command.js'); +const { _ } = require('lib/locale.js'); +const { cliUtils } = require('./cli-utils.js'); +const EncryptionService = require('lib/services/EncryptionService'); +const DecryptionWorker = require('lib/services/DecryptionWorker'); +const MasterKey = require('lib/models/MasterKey'); +const BaseItem = require('lib/models/BaseItem'); +const Setting = require('lib/models/Setting.js'); class Command extends BaseCommand { + usage() { - return "e2ee [path]"; + return 'e2ee [path]'; } description() { - return _("Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`."); + return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`.'); } options() { return [ // This is here mostly for testing - shouldn't be used - ["-p, --password ", "Use this password as master password (For security reasons, it is not recommended to use this option)."], - ["-v, --verbose", "More verbose output for the `target-status` command"], + ['-p, --password ', 'Use this password as master password (For security reasons, it is not recommended to use this option).'], + ['-v, --verbose', 'More verbose output for the `target-status` command'], ]; } @@ -29,10 +30,10 @@ class Command extends BaseCommand { const options = args.options; - if (args.command === "enable") { - const password = options.password ? options.password.toString() : await this.prompt(_("Enter master password:"), { type: "string", secure: true }); + if (args.command === 'enable') { + const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true }); if (!password) { - this.stdout(_("Operation cancelled")); + this.stdout(_('Operation cancelled')); return; } @@ -40,27 +41,27 @@ class Command extends BaseCommand { return; } - if (args.command === "disable") { + if (args.command === 'disable') { await EncryptionService.instance().disableEncryption(); return; } - if (args.command === "decrypt") { - this.stdout(_("Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.")); + if (args.command === 'decrypt') { + this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.')); while (true) { try { await DecryptionWorker.instance().start(); break; } catch (error) { - if (error.code === "masterKeyNotLoaded") { + if (error.code === 'masterKeyNotLoaded') { const masterKeyId = error.masterKeyId; - const password = await this.prompt(_("Enter master password:"), { type: "string", secure: true }); + const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true }); if (!password) { - this.stdout(_("Operation cancelled")); + this.stdout(_('Operation cancelled')); return; } - Setting.setObjectKey("encryption.passwordCache", masterKeyId, password); + Setting.setObjectKey('encryption.passwordCache', masterKeyId, password); await EncryptionService.instance().loadMasterKeysFromSettings(); continue; } @@ -69,31 +70,31 @@ class Command extends BaseCommand { } } - this.stdout(_("Completed decryption.")); + this.stdout(_('Completed decryption.')); return; } - if (args.command === "status") { - this.stdout(_("Encryption is: %s", Setting.value("encryption.enabled") ? _("Enabled") : _("Disabled"))); + if (args.command === 'status') { + this.stdout(_('Encryption is: %s', Setting.value('encryption.enabled') ? _('Enabled') : _('Disabled'))); return; } - if (args.command === "target-status") { - const fs = require("fs-extra"); - const pathUtils = require("lib/path-utils.js"); - const fsDriver = new (require("lib/fs-driver-node.js")).FsDriverNode(); + if (args.command === 'target-status') { + const fs = require('fs-extra'); + const pathUtils = require('lib/path-utils.js'); + const fsDriver = new (require('lib/fs-driver-node.js').FsDriverNode)(); const targetPath = args.path; - if (!targetPath) throw new Error("Please specify the sync target path."); + if (!targetPath) throw new Error('Please specify the sync target path.'); const dirPaths = function(targetPath) { let paths = []; - fs.readdirSync(targetPath).forEach(path => { + fs.readdirSync(targetPath).forEach((path) => { paths.push(path); }); return paths; - }; + } let itemCount = 0; let resourceCount = 0; @@ -108,17 +109,17 @@ class Command extends BaseCommand { for (let i = 0; i < paths.length; i++) { const path = paths[i]; - const fullPath = targetPath + "/" + path; + const fullPath = targetPath + '/' + path; const stat = await fs.stat(fullPath); // this.stdout(fullPath); - if (path === ".resource") { + if (path === '.resource') { let resourcePaths = dirPaths(fullPath); for (let j = 0; j < resourcePaths.length; j++) { const resourcePath = resourcePaths[j]; resourceCount++; - const fullResourcePath = fullPath + "/" + resourcePath; + const fullResourcePath = fullPath + '/' + resourcePath; const isEncrypted = await EncryptionService.instance().fileIsEncrypted(fullResourcePath); if (isEncrypted) { encryptedResourceCount++; @@ -130,7 +131,7 @@ class Command extends BaseCommand { } else if (stat.isDirectory()) { continue; } else { - const content = await fs.readFile(fullPath, "utf8"); + const content = await fs.readFile(fullPath, 'utf8'); const item = await BaseItem.unserialize(content); const ItemClass = BaseItem.itemClass(item); @@ -152,22 +153,22 @@ class Command extends BaseCommand { } } - this.stdout("Encrypted items: " + encryptedItemCount + "/" + itemCount); - this.stdout("Encrypted resources: " + encryptedResourceCount + "/" + resourceCount); - this.stdout("Other items (never encrypted): " + otherItemCount); + this.stdout('Encrypted items: ' + encryptedItemCount + '/' + itemCount); + this.stdout('Encrypted resources: ' + encryptedResourceCount + '/' + resourceCount); + this.stdout('Other items (never encrypted): ' + otherItemCount); if (options.verbose) { - this.stdout(""); - this.stdout("# Encrypted paths"); - this.stdout(""); + this.stdout(''); + this.stdout('# Encrypted paths'); + this.stdout(''); for (let i = 0; i < encryptedPaths.length; i++) { const path = encryptedPaths[i]; this.stdout(path); } - this.stdout(""); - this.stdout("# Decrypted paths"); - this.stdout(""); + this.stdout(''); + this.stdout('# Decrypted paths'); + this.stdout(''); for (let i = 0; i < decryptedPaths.length; i++) { const path = decryptedPaths[i]; this.stdout(path); @@ -177,6 +178,7 @@ class Command extends BaseCommand { return; } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-edit.js b/CliClient/app/command-edit.js index 781297828..9fb39ec45 100644 --- a/CliClient/app/command-edit.js +++ b/CliClient/app/command-edit.js @@ -1,22 +1,23 @@ -const fs = require("fs-extra"); -const { BaseCommand } = require("./base-command.js"); -const { uuid } = require("lib/uuid.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const Setting = require("lib/models/Setting.js"); -const BaseModel = require("lib/BaseModel.js"); -const { cliUtils } = require("./cli-utils.js"); -const { time } = require("lib/time-utils.js"); +const fs = require('fs-extra'); +const { BaseCommand } = require('./base-command.js'); +const { uuid } = require('lib/uuid.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const Setting = require('lib/models/Setting.js'); +const BaseModel = require('lib/BaseModel.js'); +const { cliUtils } = require('./cli-utils.js'); +const { time } = require('lib/time-utils.js'); class Command extends BaseCommand { + usage() { - return "edit "; + return 'edit '; } description() { - return _("Edit note."); + return _('Edit note.'); } async action(args) { @@ -25,22 +26,22 @@ class Command extends BaseCommand { const onFinishedEditing = async () => { if (tempFilePath) fs.removeSync(tempFilePath); - }; + } const textEditorPath = () => { - if (Setting.value("editor")) return Setting.value("editor"); + if (Setting.value('editor')) return Setting.value('editor'); if (process.env.EDITOR) return process.env.EDITOR; - throw new Error(_("No text editor is defined. Please set it using `config editor `")); - }; + throw new Error(_('No text editor is defined. Please set it using `config editor `')); + } - try { + try { // ------------------------------------------------------------------------- // Load note or create it if it doesn't exist // ------------------------------------------------------------------------- - let title = args["note"]; + let title = args['note']; - if (!app().currentFolder()) throw new Error(_("No active notebook.")); + if (!app().currentFolder()) throw new Error(_('No active notebook.')); let note = await app().loadItem(BaseModel.TYPE_NOTE, title); this.encryptionCheck(note); @@ -57,14 +58,14 @@ class Command extends BaseCommand { // ------------------------------------------------------------------------- let editorPath = textEditorPath(); - let editorArgs = editorPath.split(" "); + let editorArgs = editorPath.split(' '); editorPath = editorArgs[0]; editorArgs = editorArgs.splice(1); const originalContent = await Note.serializeForEdit(note); - tempFilePath = Setting.value("tempDir") + "/" + uuid.create() + ".md"; + tempFilePath = Setting.value('tempDir') + '/' + uuid.create() + '.md'; editorArgs.push(tempFilePath); await fs.writeFile(tempFilePath, originalContent); @@ -73,56 +74,46 @@ class Command extends BaseCommand { // Start editing the file // ------------------------------------------------------------------------- - this.logger().info("Disabling fullscreen..."); + this.logger().info('Disabling fullscreen...'); - app() - .gui() - .showModalOverlay(_("Starting to edit note. Close the editor to get back to the prompt.")); - await app() - .gui() - .forceRender(); - const termState = app() - .gui() - .termSaveState(); + app().gui().showModalOverlay(_('Starting to edit note. Close the editor to get back to the prompt.')); + await app().gui().forceRender(); + const termState = app().gui().termSaveState(); - const spawnSync = require("child_process").spawnSync; - const result = spawnSync(editorPath, editorArgs, { stdio: "inherit" }); + const spawnSync = require('child_process').spawnSync; + const result = spawnSync(editorPath, editorArgs, { stdio: 'inherit' }); - if (result.error) this.stdout(_("Error opening note in editor: %s", result.error.message)); + if (result.error) this.stdout(_('Error opening note in editor: %s', result.error.message)); - app() - .gui() - .termRestoreState(termState); - app() - .gui() - .hideModalOverlay(); - app() - .gui() - .forceRender(); + app().gui().termRestoreState(termState); + app().gui().hideModalOverlay(); + app().gui().forceRender(); // ------------------------------------------------------------------------- // Save the note and clean up // ------------------------------------------------------------------------- - const updatedContent = await fs.readFile(tempFilePath, "utf8"); + const updatedContent = await fs.readFile(tempFilePath, 'utf8'); if (updatedContent !== originalContent) { let updatedNote = await Note.unserializeForEdit(updatedContent); updatedNote.id = note.id; await Note.save(updatedNote); - this.stdout(_("Note has been saved.")); + this.stdout(_('Note has been saved.')); } this.dispatch({ - type: "NOTE_SELECT", + type: 'NOTE_SELECT', id: note.id, }); await onFinishedEditing(); - } catch (error) { + + } catch(error) { await onFinishedEditing(); throw error; } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-exit.js b/CliClient/app/command-exit.js index 20a15bdbd..32be81aff 100644 --- a/CliClient/app/command-exit.js +++ b/CliClient/app/command-exit.js @@ -1,23 +1,25 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); class Command extends BaseCommand { + usage() { - return "exit"; + return 'exit'; } description() { - return _("Exits the application."); + return _('Exits the application.'); } compatibleUis() { - return ["gui"]; + return ['gui']; } async action(args) { await app().exit(); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-export-sync-status.js b/CliClient/app/command-export-sync-status.js index 578a6f66e..5b900c414 100644 --- a/CliClient/app/command-export-sync-status.js +++ b/CliClient/app/command-export-sync-status.js @@ -1,18 +1,19 @@ -const { BaseCommand } = require("./base-command.js"); -const { Database } = require("lib/database.js"); -const { app } = require("./app.js"); -const Setting = require("lib/models/Setting.js"); -const { _ } = require("lib/locale.js"); -const { ReportService } = require("lib/services/report.js"); -const fs = require("fs-extra"); +const { BaseCommand } = require('./base-command.js'); +const { Database } = require('lib/database.js'); +const { app } = require('./app.js'); +const Setting = require('lib/models/Setting.js'); +const { _ } = require('lib/locale.js'); +const { ReportService } = require('lib/services/report.js'); +const fs = require('fs-extra'); class Command extends BaseCommand { + usage() { - return "export-sync-status"; + return 'export-sync-status'; } description() { - return "Export sync status"; + return 'Export sync status'; } hidden() { @@ -21,18 +22,15 @@ class Command extends BaseCommand { async action(args) { const service = new ReportService(); - const csv = await service.basicItemList({ format: "csv" }); - const filePath = Setting.value("profileDir") + "/syncReport-" + new Date().getTime() + ".csv"; + const csv = await service.basicItemList({ format: 'csv' }); + const filePath = Setting.value('profileDir') + '/syncReport-' + (new Date()).getTime() + '.csv'; await fs.writeFileSync(filePath, csv); - this.stdout("Sync status exported to " + filePath); + this.stdout('Sync status exported to ' + filePath); - app() - .gui() - .showConsole(); - app() - .gui() - .maximizeConsole(); + app().gui().showConsole(); + app().gui().maximizeConsole(); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-export.js b/CliClient/app/command-export.js index c92e0de42..624bce752 100644 --- a/CliClient/app/command-export.js +++ b/CliClient/app/command-export.js @@ -1,56 +1,61 @@ -const { BaseCommand } = require("./base-command.js"); -const InteropService = require("lib/services/InteropService.js"); -const BaseModel = require("lib/BaseModel.js"); -const Note = require("lib/models/Note.js"); -const { reg } = require("lib/registry.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const fs = require("fs-extra"); +const { BaseCommand } = require('./base-command.js'); +const InteropService = require('lib/services/InteropService.js'); +const BaseModel = require('lib/BaseModel.js'); +const Note = require('lib/models/Note.js'); +const { reg } = require('lib/registry.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const fs = require('fs-extra'); class Command extends BaseCommand { + usage() { - return "export "; + return 'export '; } description() { - return _("Exports Joplin data to the given path. By default, it will export the complete database including notebooks, notes, tags and resources."); + return _('Exports Joplin data to the given path. By default, it will export the complete database including notebooks, notes, tags and resources.'); } options() { const service = new InteropService(); - const formats = service - .modules() - .filter(m => m.type === "exporter") - .map(m => m.format + (m.description ? " (" + m.description + ")" : "")); + const formats = service.modules() + .filter(m => m.type === 'exporter') + .map(m => m.format + (m.description ? ' (' + m.description + ')' : '')); return [ - ["--format ", _("Destination format: %s", formats.join(", "))], - ["--note ", _("Exports only the given note.")], - ["--notebook ", _("Exports only the given notebook.")], + ['--format ', _('Destination format: %s', formats.join(', '))], + ['--note ', _('Exports only the given note.')], + ['--notebook ', _('Exports only the given notebook.')], ]; } - + async action(args) { let exportOptions = {}; exportOptions.path = args.path; - exportOptions.format = args.options.format ? args.options.format : "jex"; + exportOptions.format = args.options.format ? args.options.format : 'jex'; if (args.options.note) { + const notes = await app().loadItems(BaseModel.TYPE_NOTE, args.options.note, { parent: app().currentFolder() }); if (!notes.length) throw new Error(_('Cannot find "%s".', args.options.note)); - exportOptions.sourceNoteIds = notes.map(n => n.id); + exportOptions.sourceNoteIds = notes.map((n) => n.id); + } else if (args.options.notebook) { + const folders = await app().loadItems(BaseModel.TYPE_FOLDER, args.options.notebook); if (!folders.length) throw new Error(_('Cannot find "%s".', args.options.notebook)); - exportOptions.sourceFolderIds = folders.map(n => n.id); + exportOptions.sourceFolderIds = folders.map((n) => n.id); + } const service = new InteropService(); const result = await service.export(exportOptions); - result.warnings.map(w => this.stdout(w)); + result.warnings.map((w) => this.stdout(w)); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-geoloc.js b/CliClient/app/command-geoloc.js index 015c4f54d..eddb3bf69 100644 --- a/CliClient/app/command-geoloc.js +++ b/CliClient/app/command-geoloc.js @@ -1,31 +1,31 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); class Command extends BaseCommand { + usage() { - return "geoloc "; + return 'geoloc '; } description() { - return _("Displays a geolocation URL for the note."); + return _('Displays a geolocation URL for the note.'); } async action(args) { - let title = args["note"]; + let title = args['note']; let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() }); if (!item) throw new Error(_('Cannot find "%s".', title)); const url = Note.geolocationUrl(item); this.stdout(url); - app() - .gui() - .showConsole(); + app().gui().showConsole(); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-help.js b/CliClient/app/command-help.js index faa2566b7..d743b08bc 100644 --- a/CliClient/app/command-help.js +++ b/CliClient/app/command-help.js @@ -1,19 +1,20 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { renderCommandHelp } = require("./help-utils.js"); -const { Database } = require("lib/database.js"); -const Setting = require("lib/models/Setting.js"); -const { wrap } = require("lib/string-utils.js"); -const { _ } = require("lib/locale.js"); -const { cliUtils } = require("./cli-utils.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { renderCommandHelp } = require('./help-utils.js'); +const { Database } = require('lib/database.js'); +const Setting = require('lib/models/Setting.js'); +const { wrap } = require('lib/string-utils.js'); +const { _ } = require('lib/locale.js'); +const { cliUtils } = require('./cli-utils.js'); class Command extends BaseCommand { + usage() { - return "help [command]"; + return 'help [command]'; } description() { - return _("Displays usage information."); + return _('Displays usage information.'); } allCommands() { @@ -27,7 +28,7 @@ class Command extends BaseCommand { output.push(command); } - output.sort((a, b) => (a.name() < b.name() ? -1 : +1)); + output.sort((a, b) => a.name() < b.name() ? -1 : +1); return output; } @@ -35,69 +36,56 @@ class Command extends BaseCommand { async action(args) { const stdoutWidth = app().commandStdoutMaxWidth(); - if (args.command === "shortcuts" || args.command === "keymap") { - this.stdout(_("For information on how to customise the shortcuts please visit %s", "http://joplin.cozic.net/terminal/#shortcuts")); - this.stdout(""); + if (args.command === 'shortcuts' || args.command === 'keymap') { + this.stdout(_('For information on how to customise the shortcuts please visit %s', 'http://joplin.cozic.net/terminal/#shortcuts')); + this.stdout(''); - if ( - app() - .gui() - .isDummy() - ) { - throw new Error(_("Shortcuts are not available in CLI mode.")); + if (app().gui().isDummy()) { + throw new Error(_('Shortcuts are not available in CLI mode.')); } - const keymap = app() - .gui() - .keymap(); + const keymap = app().gui().keymap(); let rows = []; for (let i = 0; i < keymap.length; i++) { const item = keymap[i]; - const keys = item.keys.map(k => (k === " " ? "(SPACE)" : k)); - rows.push([keys.join(", "), item.command]); + const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k); + rows.push([keys.join(', '), item.command]); } cliUtils.printArray(this.stdout.bind(this), rows); - } else if (args.command === "all") { + } else if (args.command === 'all') { const commands = this.allCommands(); - const output = commands.map(c => renderCommandHelp(c)); - this.stdout(output.join("\n\n")); + const output = commands.map((c) => renderCommandHelp(c)); + this.stdout(output.join('\n\n')); } else if (args.command) { - const command = app().findCommandByName(args["command"]); + const command = app().findCommandByName(args['command']); if (!command) throw new Error(_('Cannot find "%s".', args.command)); this.stdout(renderCommandHelp(command, stdoutWidth)); } else { - const commandNames = this.allCommands().map(a => a.name()); + const commandNames = this.allCommands().map((a) => a.name()); - this.stdout(_("Type `help [command]` for more information about a command; or type `help all` for the complete usage information.")); - this.stdout(""); - this.stdout(_("The possible commands are:")); - this.stdout(""); - this.stdout(commandNames.join(", ")); - this.stdout(""); - this.stdout( - _( - "In any command, a note or notebook can be refered to by title or ID, or using the shortcuts `$n` or `$b` for, respectively, the currently selected note or notebook. `$c` can be used to refer to the currently selected item." - ) - ); - this.stdout(""); - this.stdout(_("To move from one pane to another, press Tab or Shift+Tab.")); - this.stdout(_("Use the arrows and page up/down to scroll the lists and text areas (including this console).")); + this.stdout(_('Type `help [command]` for more information about a command; or type `help all` for the complete usage information.')); + this.stdout(''); + this.stdout(_('The possible commands are:')); + this.stdout(''); + this.stdout(commandNames.join(', ')); + this.stdout(''); + this.stdout(_('In any command, a note or notebook can be refered to by title or ID, or using the shortcuts `$n` or `$b` for, respectively, the currently selected note or notebook. `$c` can be used to refer to the currently selected item.')); + this.stdout(''); + this.stdout(_('To move from one pane to another, press Tab or Shift+Tab.')); + this.stdout(_('Use the arrows and page up/down to scroll the lists and text areas (including this console).')); this.stdout(_('To maximise/minimise the console, press "TC".')); this.stdout(_('To enter command line mode, press ":"')); - this.stdout(_("To exit command line mode, press ESCAPE")); - this.stdout(_("For the list of keyboard shortcuts and config options, type `help keymap`")); + this.stdout(_('To exit command line mode, press ESCAPE')); + this.stdout(_('For the list of keyboard shortcuts and config options, type `help keymap`')); } - app() - .gui() - .showConsole(); - app() - .gui() - .maximizeConsole(); + app().gui().showConsole(); + app().gui().maximizeConsole(); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-import.js b/CliClient/app/command-import.js index d39cdda1d..0f44aef62 100644 --- a/CliClient/app/command-import.js +++ b/CliClient/app/command-import.js @@ -1,34 +1,35 @@ -const { BaseCommand } = require("./base-command.js"); -const InteropService = require("lib/services/InteropService.js"); -const BaseModel = require("lib/BaseModel.js"); -const Note = require("lib/models/Note.js"); -const { filename, basename, fileExtension } = require("lib/path-utils.js"); -const { importEnex } = require("lib/import-enex"); -const { cliUtils } = require("./cli-utils.js"); -const { reg } = require("lib/registry.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const fs = require("fs-extra"); +const { BaseCommand } = require('./base-command.js'); +const InteropService = require('lib/services/InteropService.js'); +const BaseModel = require('lib/BaseModel.js'); +const Note = require('lib/models/Note.js'); +const { filename, basename, fileExtension } = require('lib/path-utils.js'); +const { importEnex } = require('lib/import-enex'); +const { cliUtils } = require('./cli-utils.js'); +const { reg } = require('lib/registry.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const fs = require('fs-extra'); class Command extends BaseCommand { + usage() { - return "import [notebook]"; + return 'import [notebook]'; } description() { - return _("Imports data into Joplin."); + return _('Imports data into Joplin.'); } options() { const service = new InteropService(); - const formats = service - .modules() - .filter(m => m.type === "importer") - .map(m => m.format); + const formats = service.modules().filter(m => m.type === 'importer').map(m => m.format); - return [["--format ", _("Source format: %s", ["auto"].concat(formats).join(", "))], ["-f, --force", _("Do not ask for confirmation.")]]; + return [ + ['--format ', _('Source format: %s', (['auto'].concat(formats)).join(', '))], + ['-f, --force', _('Do not ask for confirmation.')], + ]; } - + async action(args) { let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args.notebook); @@ -36,40 +37,39 @@ class Command extends BaseCommand { const importOptions = {}; importOptions.path = args.path; - importOptions.format = args.options.format ? args.options.format : "auto"; + importOptions.format = args.options.format ? args.options.format : 'auto'; importOptions.destinationFolderId = folder ? folder.id : null; - let lastProgress = ""; + let lastProgress = ''; // onProgress/onError supported by Enex import only - importOptions.onProgress = progressState => { + importOptions.onProgress = (progressState) => { let line = []; - line.push(_("Found: %d.", progressState.loaded)); - line.push(_("Created: %d.", progressState.created)); - if (progressState.updated) line.push(_("Updated: %d.", progressState.updated)); - if (progressState.skipped) line.push(_("Skipped: %d.", progressState.skipped)); - if (progressState.resourcesCreated) line.push(_("Resources: %d.", progressState.resourcesCreated)); - if (progressState.notesTagged) line.push(_("Tagged: %d.", progressState.notesTagged)); - lastProgress = line.join(" "); + line.push(_('Found: %d.', progressState.loaded)); + line.push(_('Created: %d.', progressState.created)); + if (progressState.updated) line.push(_('Updated: %d.', progressState.updated)); + if (progressState.skipped) line.push(_('Skipped: %d.', progressState.skipped)); + if (progressState.resourcesCreated) line.push(_('Resources: %d.', progressState.resourcesCreated)); + if (progressState.notesTagged) line.push(_('Tagged: %d.', progressState.notesTagged)); + lastProgress = line.join(' '); cliUtils.redraw(lastProgress); }; - importOptions.onError = error => { + importOptions.onError = (error) => { let s = error.trace ? error.trace : error.toString(); this.stdout(s); }; - app() - .gui() - .showConsole(); - this.stdout(_("Importing notes...")); + app().gui().showConsole(); + this.stdout(_('Importing notes...')); const service = new InteropService(); const result = await service.import(importOptions); - result.warnings.map(w => this.stdout(w)); + result.warnings.map((w) => this.stdout(w)); cliUtils.redrawDone(); - if (lastProgress) this.stdout(_("The notes have been imported: %s", lastProgress)); + if (lastProgress) this.stdout(_('The notes have been imported: %s', lastProgress)); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-ls.js b/CliClient/app/command-ls.js index f925977f3..0f6525915 100644 --- a/CliClient/app/command-ls.js +++ b/CliClient/app/command-ls.js @@ -1,45 +1,41 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Setting = require("lib/models/Setting.js"); -const Note = require("lib/models/Note.js"); -const { sprintf } = require("sprintf-js"); -const { time } = require("lib/time-utils.js"); -const { cliUtils } = require("./cli-utils.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Setting = require('lib/models/Setting.js'); +const Note = require('lib/models/Note.js'); +const { sprintf } = require('sprintf-js'); +const { time } = require('lib/time-utils.js'); +const { cliUtils } = require('./cli-utils.js'); class Command extends BaseCommand { + usage() { - return "ls [note-pattern]"; + return 'ls [note-pattern]'; } description() { - return _("Displays the notes in the current notebook. Use `ls /` to display the list of notebooks."); + return _('Displays the notes in the current notebook. Use `ls /` to display the list of notebooks.'); } enabled() { return false; } - + options() { return [ - ["-n, --limit ", _("Displays only the first top notes.")], - ["-s, --sort ", _("Sorts the item by (eg. title, updated_time, created_time).")], - ["-r, --reverse", _("Reverses the sorting order.")], - [ - "-t, --type ", - _( - "Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos." - ), - ], - ["-f, --format ", _('Either "text" or "json"')], - ["-l, --long", _("Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE")], + ['-n, --limit ', _('Displays only the first top notes.')], + ['-s, --sort ', _('Sorts the item by (eg. title, updated_time, created_time).')], + ['-r, --reverse', _('Reverses the sorting order.')], + ['-t, --type ', _('Displays only the items of the specific type(s). Can be `n` for notes, `t` for to-dos, or `nt` for notes and to-dos (eg. `-tt` would display only the to-dos, while `-ttd` would display notes and to-dos.')], + ['-f, --format ', _('Either "text" or "json"')], + ['-l, --long', _('Use long list format. Format is ID, NOTE_COUNT (for notebook), DATE, TODO_CHECKED (for to-dos), TITLE')], ]; } async action(args) { - let pattern = args["note-pattern"]; + let pattern = args['note-pattern']; let items = []; let options = args.options; @@ -47,30 +43,30 @@ class Command extends BaseCommand { if (options.limit) queryOptions.limit = options.limit; if (options.sort) { queryOptions.orderBy = options.sort; - queryOptions.orderByDir = "ASC"; + queryOptions.orderByDir = 'ASC'; } - if (options.reverse === true) queryOptions.orderByDir = queryOptions.orderByDir == "ASC" ? "DESC" : "ASC"; + if (options.reverse === true) queryOptions.orderByDir = queryOptions.orderByDir == 'ASC' ? 'DESC' : 'ASC'; queryOptions.caseInsensitive = true; if (options.type) { queryOptions.itemTypes = []; - if (options.type.indexOf("n") >= 0) queryOptions.itemTypes.push("note"); - if (options.type.indexOf("t") >= 0) queryOptions.itemTypes.push("todo"); + if (options.type.indexOf('n') >= 0) queryOptions.itemTypes.push('note'); + if (options.type.indexOf('t') >= 0) queryOptions.itemTypes.push('todo'); } if (pattern) queryOptions.titlePattern = pattern; - queryOptions.uncompletedTodosOnTop = Setting.value("uncompletedTodosOnTop"); + queryOptions.uncompletedTodosOnTop = Setting.value('uncompletedTodosOnTop'); let modelType = null; - if (pattern == "/" || !app().currentFolder()) { + if (pattern == '/' || !app().currentFolder()) { queryOptions.includeConflictFolder = true; items = await Folder.all(queryOptions); modelType = Folder.modelType(); } else { - if (!app().currentFolder()) throw new Error(_("Please select a notebook first.")); + if (!app().currentFolder()) throw new Error(_('Please select a notebook first.')); items = await Note.previews(app().currentFolder().id, queryOptions); modelType = Note.modelType(); } - if (options.format && options.format == "json") { + if (options.format && options.format == 'json') { this.stdout(JSON.stringify(items)); } else { let hasTodos = false; @@ -102,16 +98,16 @@ class Command extends BaseCommand { let title = item.title; if (!shortIdShown && (seenTitles.indexOf(item.title) >= 0 || !item.title)) { - title += " (" + BaseModel.shortId(item.id) + ")"; + title += ' (' + BaseModel.shortId(item.id) + ')'; } else { seenTitles.push(item.title); } if (hasTodos) { if (item.is_todo) { - row.push(sprintf("[%s]", !!item.todo_completed ? "X" : " ")); + row.push(sprintf('[%s]', !!item.todo_completed ? 'X' : ' ')); } else { - row.push(" "); + row.push(' '); } } @@ -122,7 +118,9 @@ class Command extends BaseCommand { cliUtils.printArray(this.stdout.bind(this), rows); } + } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-mkbook.js b/CliClient/app/command-mkbook.js index 2aa247bdb..cc668b49a 100644 --- a/CliClient/app/command-mkbook.js +++ b/CliClient/app/command-mkbook.js @@ -1,22 +1,24 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const Folder = require("lib/models/Folder.js"); -const { reg } = require("lib/registry.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const Folder = require('lib/models/Folder.js'); +const { reg } = require('lib/registry.js'); class Command extends BaseCommand { + usage() { - return "mkbook "; + return 'mkbook '; } description() { - return _("Creates a new notebook."); + return _('Creates a new notebook.'); } async action(args) { - let folder = await Folder.save({ title: args["new-notebook"] }, { userSideValidation: true }); + let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true }); app().switchCurrentFolder(folder); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-mknote.js b/CliClient/app/command-mknote.js index 89028a92b..79f4c19a7 100644 --- a/CliClient/app/command-mknote.js +++ b/CliClient/app/command-mknote.js @@ -1,22 +1,23 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const Note = require("lib/models/Note.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const Note = require('lib/models/Note.js'); class Command extends BaseCommand { + usage() { - return "mknote "; + return 'mknote '; } description() { - return _("Creates a new note."); + return _('Creates a new note.'); } async action(args) { - if (!app().currentFolder()) throw new Error(_("Notes can only be created within a notebook.")); + if (!app().currentFolder()) throw new Error(_('Notes can only be created within a notebook.')); let note = { - title: args["new-note"], + title: args['new-note'], parent_id: app().currentFolder().id, }; @@ -25,6 +26,7 @@ class Command extends BaseCommand { app().switchCurrentFolder(app().currentFolder()); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-mktodo.js b/CliClient/app/command-mktodo.js index 7a442756f..d96fb5275 100644 --- a/CliClient/app/command-mktodo.js +++ b/CliClient/app/command-mktodo.js @@ -1,22 +1,23 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const Note = require("lib/models/Note.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const Note = require('lib/models/Note.js'); class Command extends BaseCommand { + usage() { - return "mktodo "; + return 'mktodo '; } description() { - return _("Creates a new to-do."); + return _('Creates a new to-do.'); } async action(args) { - if (!app().currentFolder()) throw new Error(_("Notes can only be created within a notebook.")); + if (!app().currentFolder()) throw new Error(_('Notes can only be created within a notebook.')); let note = { - title: args["new-todo"], + title: args['new-todo'], parent_id: app().currentFolder().id, is_todo: 1, }; @@ -26,6 +27,7 @@ class Command extends BaseCommand { app().switchCurrentFolder(app().currentFolder()); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-mv.js b/CliClient/app/command-mv.js index 734dcfd65..729bab96e 100644 --- a/CliClient/app/command-mv.js +++ b/CliClient/app/command-mv.js @@ -1,24 +1,25 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); class Command extends BaseCommand { + usage() { - return "mv [notebook]"; + return 'mv [notebook]'; } description() { - return _("Moves the notes matching to [notebook]."); + return _('Moves the notes matching to [notebook].'); } async action(args) { - const pattern = args["note"]; - const destination = args["notebook"]; - - const folder = await Folder.loadByField("title", destination); + const pattern = args['note']; + const destination = args['notebook']; + + const folder = await Folder.loadByField('title', destination); if (!folder) throw new Error(_('Cannot find "%s".', destination)); const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern); @@ -28,6 +29,7 @@ class Command extends BaseCommand { await Note.moveToFolder(notes[i].id, folder.id); } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-ren.js b/CliClient/app/command-ren.js index 3ed99c1d0..45b60671c 100644 --- a/CliClient/app/command-ren.js +++ b/CliClient/app/command-ren.js @@ -1,24 +1,25 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); class Command extends BaseCommand { + usage() { - return "ren "; + return 'ren '; } description() { - return _("Renames the given (note or notebook) to ."); + return _('Renames the given (note or notebook) to .'); } async action(args) { - const pattern = args["item"]; - const name = args["name"]; + const pattern = args['item']; + const name = args['name']; - const item = await app().loadItem("folderOrNote", pattern); + const item = await app().loadItem('folderOrNote', pattern); this.encryptionCheck(item); if (!item) throw new Error(_('Cannot find "%s".', pattern)); @@ -34,6 +35,7 @@ class Command extends BaseCommand { await Note.save(newItem); } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-rmbook.js b/CliClient/app/command-rmbook.js index 1058586e6..6e9c48474 100644 --- a/CliClient/app/command-rmbook.js +++ b/CliClient/app/command-rmbook.js @@ -1,36 +1,40 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const BaseModel = require("lib/BaseModel.js"); -const { cliUtils } = require("./cli-utils.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const BaseModel = require('lib/BaseModel.js'); +const { cliUtils } = require('./cli-utils.js'); class Command extends BaseCommand { + usage() { - return "rmbook "; + return 'rmbook '; } description() { - return _("Deletes the given notebook."); + return _('Deletes the given notebook.'); } options() { - return [["-f, --force", _("Deletes the notebook without asking for confirmation.")]]; + return [ + ['-f, --force', _('Deletes the notebook without asking for confirmation.')], + ]; } async action(args) { - const pattern = args["notebook"]; + const pattern = args['notebook']; const force = args.options && args.options.force === true; const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern); if (!folder) throw new Error(_('Cannot find "%s".', pattern)); - const ok = force ? true : await this.prompt(_("Delete notebook? All notes within this notebook will also be deleted."), { booleanAnswerDefault: "n" }); + const ok = force ? true : await this.prompt(_('Delete notebook? All notes within this notebook will also be deleted.'), { booleanAnswerDefault: 'n' }); if (!ok) return; await Folder.delete(folder.id); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-rmnote.js b/CliClient/app/command-rmnote.js index 136ffbb36..8e1ecea2f 100644 --- a/CliClient/app/command-rmnote.js +++ b/CliClient/app/command-rmnote.js @@ -1,37 +1,41 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const BaseModel = require("lib/BaseModel.js"); -const { cliUtils } = require("./cli-utils.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const BaseModel = require('lib/BaseModel.js'); +const { cliUtils } = require('./cli-utils.js'); class Command extends BaseCommand { + usage() { - return "rmnote "; + return 'rmnote '; } description() { - return _("Deletes the notes matching ."); + return _('Deletes the notes matching .'); } options() { - return [["-f, --force", _("Deletes the notes without asking for confirmation.")]]; + return [ + ['-f, --force', _('Deletes the notes without asking for confirmation.')], + ]; } async action(args) { - const pattern = args["note-pattern"]; + const pattern = args['note-pattern']; const force = args.options && args.options.force === true; const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern); if (!notes.length) throw new Error(_('Cannot find "%s".', pattern)); - const ok = force ? true : await this.prompt(notes.length > 1 ? _("%d notes match this pattern. Delete them?", notes.length) : _("Delete note?"), { booleanAnswerDefault: "n" }); + const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' }); if (!ok) return; - let ids = notes.map(n => n.id); + let ids = notes.map((n) => n.id); await Note.batchDelete(ids); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-search.js b/CliClient/app/command-search.js index 43142f4ec..3ae14ea2c 100644 --- a/CliClient/app/command-search.js +++ b/CliClient/app/command-search.js @@ -1,29 +1,30 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const { sprintf } = require("sprintf-js"); -const { time } = require("lib/time-utils.js"); -const { uuid } = require("lib/uuid.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const { sprintf } = require('sprintf-js'); +const { time } = require('lib/time-utils.js'); +const { uuid } = require('lib/uuid.js'); class Command extends BaseCommand { + usage() { - return "search [notebook]"; + return 'search [notebook]'; } description() { - return _("Searches for the given in all the notes."); + return _('Searches for the given in all the notes.'); } compatibleUis() { - return ["gui"]; + return ['gui']; } async action(args) { - let pattern = args["pattern"]; - let folderTitle = args["notebook"]; + let pattern = args['pattern']; + let folderTitle = args['notebook']; let folder = null; if (folderTitle) { @@ -34,18 +35,18 @@ class Command extends BaseCommand { const searchId = uuid.create(); this.dispatch({ - type: "SEARCH_ADD", + type: 'SEARCH_ADD', search: { id: searchId, title: pattern, query_pattern: pattern, - query_folder_id: folder ? folder.id : "", + query_folder_id: folder ? folder.id : '', type_: BaseModel.TYPE_SEARCH, }, }); this.dispatch({ - type: "SEARCH_SELECT", + type: 'SEARCH_SELECT', id: searchId, }); @@ -78,6 +79,7 @@ class Command extends BaseCommand { // this.stdout(line); // } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-set.js b/CliClient/app/command-set.js index 4904d59b9..3a01ae5b4 100644 --- a/CliClient/app/command-set.js +++ b/CliClient/app/command-set.js @@ -1,15 +1,16 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const { Database } = require("lib/database.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const BaseItem = require("lib/models/BaseItem.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const { Database } = require('lib/database.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const BaseItem = require('lib/models/BaseItem.js'); class Command extends BaseCommand { + usage() { - return "set [value]"; + return 'set [value]'; } description() { @@ -17,18 +18,18 @@ class Command extends BaseCommand { const s = []; for (let i = 0; i < fields.length; i++) { const f = fields[i]; - if (f.name === "id") continue; - s.push(f.name + " (" + Database.enumName("fieldType", f.type) + ")"); + if (f.name === 'id') continue; + s.push(f.name + ' (' + Database.enumName('fieldType', f.type) + ')'); } - return _("Sets the property of the given to the given [value]. Possible properties are:\n\n%s", s.join(", ")); + return _('Sets the property of the given to the given [value]. Possible properties are:\n\n%s', s.join(', ')); } async action(args) { - let title = args["note"]; - let propName = args["name"]; - let propValue = args["value"]; - if (!propValue) propValue = ""; + let title = args['note']; + let propName = args['name']; + let propValue = args['value']; + if (!propValue) propValue = ''; let notes = await app().loadItems(BaseModel.TYPE_NOTE, title); if (!notes.length) throw new Error(_('Cannot find "%s".', title)); @@ -44,6 +45,7 @@ class Command extends BaseCommand { await Note.save(newNote); } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-status.js b/CliClient/app/command-status.js index 126a76910..0bda410bf 100644 --- a/CliClient/app/command-status.js +++ b/CliClient/app/command-status.js @@ -1,30 +1,31 @@ -const { BaseCommand } = require("./base-command.js"); -const { Database } = require("lib/database.js"); -const { app } = require("./app.js"); -const Setting = require("lib/models/Setting.js"); -const { _ } = require("lib/locale.js"); -const { ReportService } = require("lib/services/report.js"); +const { BaseCommand } = require('./base-command.js'); +const { Database } = require('lib/database.js'); +const { app } = require('./app.js'); +const Setting = require('lib/models/Setting.js'); +const { _ } = require('lib/locale.js'); +const { ReportService } = require('lib/services/report.js'); class Command extends BaseCommand { + usage() { - return "status"; + return 'status'; } description() { - return _("Displays summary about the notes and notebooks."); + return _('Displays summary about the notes and notebooks.'); } async action(args) { let service = new ReportService(); - let report = await service.status(Setting.value("sync.target")); + let report = await service.status(Setting.value('sync.target')); for (let i = 0; i < report.length; i++) { let section = report[i]; - if (i > 0) this.stdout(""); + if (i > 0) this.stdout(''); - this.stdout("# " + section.title); - this.stdout(""); + this.stdout('# ' + section.title); + this.stdout(''); for (let n in section.body) { if (!section.body.hasOwnProperty(n)) continue; @@ -33,13 +34,10 @@ class Command extends BaseCommand { } } - app() - .gui() - .showConsole(); - app() - .gui() - .maximizeConsole(); + app().gui().showConsole(); + app().gui().maximizeConsole(); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-sync.js b/CliClient/app/command-sync.js index 6183053c6..e31d18f2c 100644 --- a/CliClient/app/command-sync.js +++ b/CliClient/app/command-sync.js @@ -1,18 +1,19 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const { OneDriveApiNodeUtils } = require("./onedrive-api-node-utils.js"); -const Setting = require("lib/models/Setting.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const { Synchronizer } = require("lib/synchronizer.js"); -const { reg } = require("lib/registry.js"); -const { cliUtils } = require("./cli-utils.js"); -const md5 = require("md5"); -const locker = require("proper-lockfile"); -const fs = require("fs-extra"); -const SyncTargetRegistry = require("lib/SyncTargetRegistry"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const { OneDriveApiNodeUtils } = require('./onedrive-api-node-utils.js'); +const Setting = require('lib/models/Setting.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const { reg } = require('lib/registry.js'); +const { cliUtils } = require('./cli-utils.js'); +const md5 = require('md5'); +const locker = require('proper-lockfile'); +const fs = require('fs-extra'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry'); class Command extends BaseCommand { + constructor() { super(); this.syncTargetId_ = null; @@ -21,15 +22,17 @@ class Command extends BaseCommand { } usage() { - return "sync"; + return 'sync'; } description() { - return _("Synchronises with remote storage."); + return _('Synchronises with remote storage.'); } options() { - return [["--target ", _("Sync to provided target (defaults to sync.target config value)")]]; + return [ + ['--target ', _('Sync to provided target (defaults to sync.target config value)')], + ]; } static lockFile(filePath) { @@ -62,26 +65,23 @@ class Command extends BaseCommand { const syncTarget = reg.syncTarget(this.syncTargetId_); const syncTargetMd = SyncTargetRegistry.idToMetadata(this.syncTargetId_); - if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { - // OneDrive + if (this.syncTargetId_ === 3 || this.syncTargetId_ === 4) { // OneDrive this.oneDriveApiUtils_ = new OneDriveApiNodeUtils(syncTarget.api()); const auth = await this.oneDriveApiUtils_.oauthDance({ - log: (...s) => { - return this.stdout(...s); - }, + log: (...s) => { return this.stdout(...s); } }); this.oneDriveApiUtils_ = null; - - Setting.setValue("sync." + this.syncTargetId_ + ".auth", auth ? JSON.stringify(auth) : null); + + Setting.setValue('sync.' + this.syncTargetId_ + '.auth', auth ? JSON.stringify(auth) : null); if (!auth) { - this.stdout(_("Authentication was not completed (did not receive an authentication token).")); + this.stdout(_('Authentication was not completed (did not receive an authentication token).')); return false; } return true; } - this.stdout(_("Not authentified with %s. Please provide any missing credentials.", syncTarget.label())); + this.stdout(_('Not authentified with %s. Please provide any missing credentials.', syncTarget.label())); return false; } @@ -100,15 +100,15 @@ class Command extends BaseCommand { this.releaseLockFn_ = null; // Lock is unique per profile/database - const lockFilePath = require("os").tmpdir() + "/synclock_" + md5(escape(Setting.value("profileDir"))); // https://github.com/pvorb/node-md5/issues/41 - if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, "synclock"); + const lockFilePath = require('os').tmpdir() + '/synclock_' + md5(escape(Setting.value('profileDir'))); // https://github.com/pvorb/node-md5/issues/41 + if (!await fs.pathExists(lockFilePath)) await fs.writeFile(lockFilePath, 'synclock'); try { - if (await Command.isLocked(lockFilePath)) throw new Error(_("Synchronisation is already in progress.")); + if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.')); this.releaseLockFn_ = await Command.lockFile(lockFilePath); } catch (error) { - if (error.code == "ELOCKED") { + if (error.code == 'ELOCKED') { const msg = _('Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at "%s" and resume the operation.', error.file); this.stdout(msg); return; @@ -125,43 +125,39 @@ class Command extends BaseCommand { }; try { - this.syncTargetId_ = Setting.value("sync.target"); + this.syncTargetId_ = Setting.value('sync.target'); if (args.options.target) this.syncTargetId_ = args.options.target; const syncTarget = reg.syncTarget(this.syncTargetId_); if (!syncTarget.isAuthenticated()) { - app() - .gui() - .showConsole(); - app() - .gui() - .maximizeConsole(); + app().gui().showConsole(); + app().gui().maximizeConsole(); const authDone = await this.doAuth(); if (!authDone) return cleanUp(); } - + const sync = await syncTarget.synchronizer(); let options = { - onProgress: report => { + onProgress: (report) => { let lines = Synchronizer.reportToLines(report); - if (lines.length) cliUtils.redraw(lines.join(" ")); + if (lines.length) cliUtils.redraw(lines.join(' ')); }, - onMessage: msg => { + onMessage: (msg) => { cliUtils.redrawDone(); this.stdout(msg); }, }; - this.stdout(_("Synchronisation target: %s (%s)", Setting.enumOptionLabel("sync.target", this.syncTargetId_), this.syncTargetId_)); + this.stdout(_('Synchronisation target: %s (%s)', Setting.enumOptionLabel('sync.target', this.syncTargetId_), this.syncTargetId_)); - if (!sync) throw new Error(_("Cannot initialize synchroniser.")); + if (!sync) throw new Error(_('Cannot initialize synchroniser.')); - this.stdout(_("Starting synchronisation...")); + this.stdout(_('Starting synchronisation...')); - const contextKey = "sync." + this.syncTargetId_ + ".context"; + const contextKey = 'sync.' + this.syncTargetId_ + '.context'; let context = Setting.value(contextKey); context = context ? JSON.parse(context) : {}; @@ -171,7 +167,7 @@ class Command extends BaseCommand { let newContext = await sync.start(options); Setting.setValue(contextKey, JSON.stringify(newContext)); } catch (error) { - if (error.code == "alreadyStarted") { + if (error.code == 'alreadyStarted') { this.stdout(error.message); } else { throw error; @@ -193,11 +189,11 @@ class Command extends BaseCommand { return; } - const syncTargetId = this.syncTargetId_ ? this.syncTargetId_ : Setting.value("sync.target"); + const syncTargetId = this.syncTargetId_ ? this.syncTargetId_ : Setting.value('sync.target'); cliUtils.redrawDone(); - this.stdout(_("Cancelling... Please wait.")); + this.stdout(_('Cancelling... Please wait.')); const syncTarget = reg.syncTarget(syncTargetId); @@ -215,6 +211,7 @@ class Command extends BaseCommand { cancellable() { return true; } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-tag.js b/CliClient/app/command-tag.js index 95ec2e1db..f2a38943c 100644 --- a/CliClient/app/command-tag.js +++ b/CliClient/app/command-tag.js @@ -1,20 +1,19 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const Tag = require("lib/models/Tag.js"); -const BaseModel = require("lib/BaseModel.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const Tag = require('lib/models/Tag.js'); +const BaseModel = require('lib/BaseModel.js'); class Command extends BaseCommand { + usage() { - return "tag [tag] [note]"; + return 'tag [tag] [note]'; } description() { - return _( - ' can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags.' - ); + return _(' can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags.'); } - + async action(args) { let tag = null; if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag); @@ -23,38 +22,35 @@ class Command extends BaseCommand { notes = await app().loadItems(BaseModel.TYPE_NOTE, args.note); } - const command = args["tag-command"]; + const command = args['tag-command']; - if (command == "remove" && !tag) throw new Error(_('Cannot find "%s".', args.tag)); + if (command == 'remove' && !tag) throw new Error(_('Cannot find "%s".', args.tag)); - if (command == "add") { + if (command == 'add') { if (!notes.length) throw new Error(_('Cannot find "%s".', args.note)); if (!tag) tag = await Tag.save({ title: args.tag }, { userSideValidation: true }); for (let i = 0; i < notes.length; i++) { await Tag.addNote(tag.id, notes[i].id); } - } else if (command == "remove") { + } else if (command == 'remove') { if (!tag) throw new Error(_('Cannot find "%s".', args.tag)); if (!notes.length) throw new Error(_('Cannot find "%s".', args.note)); for (let i = 0; i < notes.length; i++) { await Tag.removeNote(tag.id, notes[i].id); } - } else if (command == "list") { + } else if (command == 'list') { if (tag) { let notes = await Tag.notes(tag.id); - notes.map(note => { - this.stdout(note.title); - }); + notes.map((note) => { this.stdout(note.title); }); } else { let tags = await Tag.all(); - tags.map(tag => { - this.stdout(tag.title); - }); + tags.map((tag) => { this.stdout(tag.title); }); } } else { throw new Error(_('Invalid command: "%s"', command)); } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-todo.js b/CliClient/app/command-todo.js index 6821c2b15..1f83e2cda 100644 --- a/CliClient/app/command-todo.js +++ b/CliClient/app/command-todo.js @@ -1,25 +1,24 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const { time } = require("lib/time-utils.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const { time } = require('lib/time-utils.js'); class Command extends BaseCommand { + usage() { - return "todo "; + return 'todo '; } description() { - return _( - ' can either be "toggle" or "clear". Use "toggle" to toggle the given to-do between completed and uncompleted state (If the target is a regular note it will be converted to a to-do). Use "clear" to convert the to-do back to a regular note.' - ); + return _(' can either be "toggle" or "clear". Use "toggle" to toggle the given to-do between completed and uncompleted state (If the target is a regular note it will be converted to a to-do). Use "clear" to convert the to-do back to a regular note.'); } async action(args) { - const action = args["todo-command"]; - const pattern = args["note-pattern"]; + const action = args['todo-command']; + const pattern = args['note-pattern']; const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern); if (!notes.length) throw new Error(_('Cannot find "%s".', pattern)); @@ -32,19 +31,20 @@ class Command extends BaseCommand { id: note.id, }; - if (action == "toggle") { + if (action == 'toggle') { if (!note.is_todo) { toSave = Note.toggleIsTodo(note); } else { toSave.todo_completed = note.todo_completed ? 0 : time.unixMs(); } - } else if (action == "clear") { + } else if (action == 'clear') { toSave.is_todo = 0; - } + } await Note.save(toSave); } } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-undone.js b/CliClient/app/command-undone.js index 29d96ee4e..3373c0ee3 100644 --- a/CliClient/app/command-undone.js +++ b/CliClient/app/command-undone.js @@ -1,25 +1,27 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const { time } = require("lib/time-utils.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const { time } = require('lib/time-utils.js'); -const CommandDone = require("./command-done.js"); +const CommandDone = require('./command-done.js'); class Command extends BaseCommand { + usage() { - return "undone "; + return 'undone '; } description() { - return _("Marks a to-do as non-completed."); + return _('Marks a to-do as non-completed.'); } async action(args) { await CommandDone.handleAction(this, args, false); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-use.js b/CliClient/app/command-use.js index 01b41b876..c25a635c2 100644 --- a/CliClient/app/command-use.js +++ b/CliClient/app/command-use.js @@ -1,16 +1,17 @@ -const { BaseCommand } = require("./base-command.js"); -const { app } = require("./app.js"); -const { _ } = require("lib/locale.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); +const { BaseCommand } = require('./base-command.js'); +const { app } = require('./app.js'); +const { _ } = require('lib/locale.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); class Command extends BaseCommand { + usage() { - return "use "; + return 'use '; } description() { - return _("Switches to [notebook] - all further operations will happen within this notebook."); + return _('Switches to [notebook] - all further operations will happen within this notebook.'); } autocomplete() { @@ -18,14 +19,15 @@ class Command extends BaseCommand { } compatibleUis() { - return ["cli"]; + return ['cli']; } async action(args) { - let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args["notebook"]); - if (!folder) throw new Error(_('Cannot find "%s".', args["notebook"])); + let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']); + if (!folder) throw new Error(_('Cannot find "%s".', args['notebook'])); app().switchCurrentFolder(folder); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/command-version.js b/CliClient/app/command-version.js index 4012b07d7..9a3603b24 100644 --- a/CliClient/app/command-version.js +++ b/CliClient/app/command-version.js @@ -1,20 +1,22 @@ -const { BaseCommand } = require("./base-command.js"); -const Setting = require("lib/models/Setting.js"); -const { _ } = require("lib/locale.js"); +const { BaseCommand } = require('./base-command.js'); +const Setting = require('lib/models/Setting.js'); +const { _ } = require('lib/locale.js'); class Command extends BaseCommand { + usage() { - return "version"; + return 'version'; } description() { - return _("Displays version information"); + return _('Displays version information'); } async action(args) { - const p = require("./package.json"); - this.stdout(_("%s %s (%s)", p.name, p.version, Setting.value("env"))); + const p = require('./package.json'); + this.stdout(_('%s %s (%s)', p.name, p.version, Setting.value('env'))); } + } -module.exports = Command; +module.exports = Command; \ No newline at end of file diff --git a/CliClient/app/fuzzing.js b/CliClient/app/fuzzing.js index 468d2c1c5..f28fb0c06 100644 --- a/CliClient/app/fuzzing.js +++ b/CliClient/app/fuzzing.js @@ -1,17 +1,17 @@ -"use strict"; +"use strict" -const { time } = require("lib/time-utils.js"); -const { Logger } = require("lib/logger.js"); -const Resource = require("lib/models/Resource.js"); -const { dirname } = require("lib/path-utils.js"); -const { FsDriverNode } = require("./fs-driver-node.js"); -const lodash = require("lodash"); -const exec = require("child_process").exec; -const fs = require("fs-extra"); +const { time } = require('lib/time-utils.js'); +const { Logger } = require('lib/logger.js'); +const Resource = require('lib/models/Resource.js'); +const { dirname } = require('lib/path-utils.js'); +const { FsDriverNode } = require('./fs-driver-node.js'); +const lodash = require('lodash'); +const exec = require('child_process').exec +const fs = require('fs-extra'); -const baseDir = dirname(__dirname) + "/tests/fuzzing"; -const syncDir = baseDir + "/sync"; -const joplinAppPath = __dirname + "/main.js"; +const baseDir = dirname(__dirname) + '/tests/fuzzing'; +const syncDir = baseDir + '/sync'; +const joplinAppPath = __dirname + '/main.js'; let syncDurations = []; const fsDriver = new FsDriverNode(); @@ -19,17 +19,17 @@ Logger.fsDriver_ = fsDriver; Resource.fsDriver_ = fsDriver; const logger = new Logger(); -logger.addTarget("console"); +logger.addTarget('console'); logger.setLevel(Logger.LEVEL_DEBUG); -process.on("unhandledRejection", (reason, p) => { - console.error("Unhandled promise rejection", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.error('Unhandled promise rejection', p, 'reason:', reason); }); function createClient(id) { return { - id: id, - profileDir: baseDir + "/client" + id, + 'id': id, + 'profileDir': baseDir + '/client' + id, }; } @@ -39,11 +39,7 @@ async function createClients() { for (let clientId = 0; clientId < 2; clientId++) { let client = createClient(clientId); promises.push(fs.remove(client.profileDir)); - promises.push( - execCommand(client, "config sync.target 2").then(() => { - return execCommand(client, "config sync.2.path " + syncDir); - }) - ); + promises.push(execCommand(client, 'config sync.target 2').then(() => { return execCommand(client, 'config sync.2.path ' + syncDir); })); output.push(client); } @@ -58,2025 +54,24 @@ function randomElement(array) { } function randomWord() { - const words = [ - "belief", - "scandalous", - "flawless", - "wrestle", - "sort", - "moldy", - "carve", - "incompetent", - "cruel", - "awful", - "fang", - "holistic", - "makeshift", - "synonymous", - "questionable", - "soft", - "drop", - "boot", - "whimsical", - "stir", - "idea", - "adhesive", - "present", - "hilarious", - "unusual", - "divergent", - "probable", - "depend", - "suck", - "belong", - "advise", - "straight", - "encouraging", - "wing", - "clam", - "serve", - "fill", - "nostalgic", - "dysfunctional", - "aggressive", - "floor", - "baby", - "grease", - "sisters", - "print", - "switch", - "control", - "victorious", - "cracker", - "dream", - "wistful", - "adaptable", - "reminiscent", - "inquisitive", - "pushy", - "unaccountable", - "receive", - "guttural", - "two", - "protect", - "skin", - "unbiased", - "plastic", - "loutish", - "zip", - "used", - "divide", - "communicate", - "dear", - "muddled", - "dinosaurs", - "grip", - "trees", - "well-off", - "calendar", - "chickens", - "irate", - "deranged", - "trip", - "stream", - "white", - "poison", - "attack", - "obtain", - "theory", - "laborer", - "omniscient", - "brake", - "maniacal", - "curvy", - "smoke", - "babies", - "punch", - "hammer", - "toothbrush", - "same", - "crown", - "jagged", - "peep", - "difficult", - "reject", - "merciful", - "useless", - "doctor", - "mix", - "wicked", - "plant", - "quickest", - "roll", - "suffer", - "curly", - "brother", - "frighten", - "cold", - "tremendous", - "move", - "knot", - "lame", - "imaginary", - "capricious", - "raspy", - "aunt", - "loving", - "wink", - "wooden", - "hop", - "free", - "drab", - "fire", - "instrument", - "border", - "frame", - "silent", - "glue", - "decorate", - "distance", - "powerful", - "pig", - "admit", - "fix", - "pour", - "flesh", - "profuse", - "skinny", - "learn", - "filthy", - "dress", - "bloody", - "produce", - "innocent", - "meaty", - "pray", - "slimy", - "sun", - "kindhearted", - "dime", - "exclusive", - "boast", - "neat", - "ruthless", - "recess", - "grieving", - "daily", - "hateful", - "ignorant", - "fence", - "spring", - "slim", - "education", - "overflow", - "plastic", - "gaping", - "chew", - "detect", - "right", - "lunch", - "gainful", - "argue", - "cloistered", - "horses", - "orange", - "shame", - "bitter", - "able", - "sail", - "magical", - "exist", - "force", - "wheel", - "best", - "suit", - "spurious", - "partner", - "request", - "dog", - "gusty", - "money", - "gaze", - "lonely", - "company", - "pale", - "tempt", - "rat", - "flame", - "wobble", - "superficial", - "stop", - "protective", - "stare", - "tongue", - "heal", - "railway", - "idiotic", - "roll", - "puffy", - "turn", - "meeting", - "new", - "frightening", - "sophisticated", - "poke", - "elderly", - "room", - "stimulating", - "increase", - "moor", - "secret", - "lean", - "occur", - "country", - "damp", - "evanescent", - "alluring", - "oafish", - "join", - "thundering", - "cars", - "awesome", - "advice", - "unruly", - "ray", - "wind", - "anxious", - "fly", - "hammer", - "adventurous", - "shop", - "cook", - "trucks", - "nonchalant", - "addition", - "base", - "abashed", - "excuse", - "giants", - "dramatic", - "piquant", - "coach", - "possess", - "poor", - "finger", - "wide-eyed", - "aquatic", - "welcome", - "instruct", - "expert", - "evasive", - "hug", - "cute", - "return", - "mice", - "damage", - "turkey", - "quiet", - "bewildered", - "tidy", - "pointless", - "outrageous", - "medical", - "foolish", - "curve", - "grandiose", - "gullible", - "hapless", - "gleaming", - "third", - "grin", - "pipe", - "egg", - "act", - "physical", - "eager", - "side", - "milk", - "tearful", - "fertile", - "average", - "glamorous", - "strange", - "yak", - "terrific", - "thin", - "near", - "snails", - "flowery", - "authority", - "fish", - "curious", - "perpetual", - "healthy", - "health", - "match", - "fade", - "chemical", - "economic", - "drawer", - "avoid", - "lying", - "minister", - "lick", - "powder", - "decay", - "desire", - "furry", - "faint", - "beam", - "sordid", - "fax", - "tail", - "bawdy", - "cherry", - "letter", - "clover", - "ladybug", - "teeth", - "behavior", - "black", - "amazing", - "pink", - "waste", - "island", - "forgetful", - "needless", - "lock", - "waves", - "boundary", - "receipt", - "handy", - "religion", - "hypnotic", - "aftermath", - "explain", - "sense", - "mundane", - "rambunctious", - "second", - "preserve", - "alarm", - "dusty", - "event", - "blow", - "weigh", - "value", - "glorious", - "jail", - "sigh", - "cemetery", - "serious", - "yummy", - "cattle", - "understood", - "limit", - "alert", - "fear", - "lucky", - "tested", - "surround", - "dolls", - "pleasant", - "disillusioned", - "discover", - "tray", - "night", - "seemly", - "liquid", - "worry", - "pen", - "bent", - "gruesome", - "war", - "teeny-tiny", - "common", - "judge", - "symptomatic", - "bed", - "trot", - "unequaled", - "flowers", - "friends", - "damaged", - "peel", - "skip", - "show", - "twist", - "worthless", - "brush", - "look", - "behave", - "imperfect", - "week", - "petite", - "direction", - "soda", - "lively", - "coal", - "coil", - "release", - "berserk", - "books", - "impossible", - "replace", - "cough", - "chunky", - "torpid", - "discreet", - "material", - "bomb", - "soothe", - "crack", - "hope", - "license", - "frightened", - "breathe", - "maddening", - "calculator", - "committee", - "paltry", - "green", - "subsequent", - "arrest", - "gigantic", - "tasty", - "metal", - "willing", - "man", - "stem", - "nonstop", - "route", - "impulse", - "government", - "comfortable", - "include", - "literate", - "multiply", - "test", - "vast", - "exercise", - "addicted", - "agreeable", - "lace", - "toes", - "young", - "water", - "end", - "wash", - "glossy", - "round", - "staking", - "sink", - "open", - "spot", - "trip", - "fierce", - "robust", - "pastoral", - "drown", - "dress", - "machine", - "calculating", - "holiday", - "crabby", - "disgusting", - "plan", - "sleet", - "sleepy", - "typical", - "borrow", - "possible", - "curtain", - "airplane", - "industry", - "nut", - "rough", - "wacky", - "rock", - "enormous", - "uninterested", - "sugar", - "rake", - "consist", - "wrist", - "basket", - "chop", - "wet", - "street", - "known", - "settle", - "bless", - "cluttered", - "wild", - "expand", - "angle", - "snake", - "yawn", - "hate", - "flood", - "rabid", - "spiteful", - "anger", - "market", - "bizarre", - "force", - "majestic", - "scissors", - "beg", - "rifle", - "foregoing", - "cactus", - "funny", - "eggnog", - "wish", - "high-pitched", - "drop", - "camp", - "scarf", - "car", - "groan", - "wonderful", - "wealthy", - "cup", - "lock", - "available", - "previous", - "jam", - "political", - "vacation", - "three", - "desk", - "fry", - "aspiring", - "productive", - "clear", - "bored", - "flashy", - "plug", - "precede", - "abhorrent", - "muddle", - "flimsy", - "paste", - "need", - "reward", - "frail", - "obnoxious", - "creature", - "whip", - "unbecoming", - "lake", - "unused", - "chin", - "tour", - "zephyr", - "experience", - "building", - "scrub", - "correct", - "hover", - "panicky", - "scorch", - "diligent", - "hulking", - "ubiquitous", - "tedious", - "aberrant", - "file", - "accidental", - "mist", - "blue-eyed", - "trite", - "nondescript", - "cows", - "wait", - "test", - "snotty", - "amuck", - "jump", - "lackadaisical", - "grey", - "tawdry", - "strong", - "land", - "kind", - "star", - "ludicrous", - "stupid", - "telling", - "use", - "bruise", - "whirl", - "cream", - "harsh", - "aboriginal", - "substantial", - "brawny", - "tease", - "pollution", - "weather", - "degree", - "dry", - "film", - "obey", - "closed", - "dependent", - "want", - "undesirable", - "stamp", - "relax", - "foot", - "obscene", - "successful", - "wriggle", - "drain", - "greasy", - "escape", - "cross", - "odd", - "boring", - "absorbed", - "houses", - "suppose", - "suit", - "moon", - "ceaseless", - "explode", - "clap", - "pop", - "courageous", - "miss", - "notebook", - "delirious", - "form", - "pretty", - "sock", - "grotesque", - "noxious", - "record", - "stop", - "saw", - "thing", - "dislike", - "cloth", - "six", - "jar", - "unnatural", - "spiffy", - "itchy", - "secretary", - "move", - "certain", - "unkempt", - "sassy", - "queue", - "shrug", - "crow", - "heavenly", - "desert", - "screw", - "vessel", - "mug", - "encourage", - "icy", - "enthusiastic", - "throat", - "whistle", - "ignore", - "miniature", - "squeak", - "scarecrow", - "fluttering", - "hang", - "icicle", - "lie", - "juicy", - "empty", - "baseball", - "various", - "promise", - "abortive", - "descriptive", - "high", - "spy", - "faded", - "talk", - "air", - "messup", - "decorous", - "sneaky", - "mark", - "sack", - "ultra", - "chivalrous", - "lethal", - "expect", - "disgusted", - "reaction", - "fireman", - "private", - "ritzy", - "manage", - "actor", - "rely", - "uppity", - "thread", - "bat", - "space", - "underwear", - "blood", - "nine", - "maid", - "shelf", - "hanging", - "shop", - "prick", - "wound", - "sloppy", - "offer", - "increase", - "clear", - "slap", - "rude", - "poised", - "wretched", - "cause", - "quince", - "tame", - "remarkable", - "abject", - "sail", - "guide", - "subdued", - "spiky", - "debonair", - "chicken", - "tired", - "hum", - "land", - "scared", - "splendid", - "guess", - "cast", - "rub", - "magnificent", - "ants", - "overwrought", - "interfere", - "gorgeous", - "office", - "trade", - "sniff", - "melted", - "bore", - "point", - "pet", - "purple", - "brake", - "flavor", - "toe", - "prickly", - "zinc", - "homely", - "modern", - "kindly", - "whisper", - "bare", - "annoyed", - "glass", - "noisy", - "null", - "thoughtless", - "skirt", - "dock", - "rings", - "mind", - "neck", - "macho", - "wave", - "history", - "play", - "road", - "profit", - "word", - "opposite", - "dreary", - "governor", - "horse", - "trust", - "elbow", - "kiss", - "crayon", - "stitch", - "excited", - "needy", - "arrange", - "easy", - "alcoholic", - "safe", - "lumpy", - "monkey", - "smile", - "capable", - "untidy", - "extra-small", - "memory", - "selective", - "reproduce", - "old-fashioned", - "overrated", - "texture", - "knit", - "downtown", - "risk", - "pot", - "sofa", - "righteous", - "wren", - "pull", - "carry", - "aboard", - "listen", - "classy", - "thank", - "shocking", - "condition", - "root", - "attempt", - "swim", - "frog", - "hurt", - "army", - "title", - "handsomely", - "town", - "guiltless", - "thaw", - "spell", - "selfish", - "disturbed", - "tramp", - "girls", - "utopian", - "noiseless", - "trail", - "bashful", - "business", - "rhetorical", - "snail", - "sign", - "plausible", - "left", - "design", - "tall", - "violent", - "wasteful", - "beautiful", - "breezy", - "tap", - "murder", - "talented", - "needle", - "creator", - "imagine", - "flippant", - "dead", - "bone", - "coherent", - "relation", - "aromatic", - "mountainous", - "face", - "ask", - "picture", - "pedal", - "colour", - "obese", - "group", - "top", - "bubble", - "pinch", - "optimal", - "school", - "bathe", - "flagrant", - "check", - "deliver", - "pass", - "tan", - "crate", - "hose", - "debt", - "faulty", - "longing", - "hollow", - "invincible", - "afford", - "lovely", - "ticket", - "changeable", - "subtract", - "fumbling", - "responsible", - "confused", - "woman", - "touch", - "watch", - "zesty", - "library", - "jail", - "wrap", - "terrify", - "brick", - "popcorn", - "cooperative", - "peck", - "pocket", - "property", - "buzz", - "tiresome", - "digestion", - "exciting", - "nation", - "juvenile", - "shade", - "copper", - "wanting", - "deer", - "waste", - "man", - "join", - "spotty", - "amused", - "mountain", - "waggish", - "bushes", - "tense", - "river", - "heartbreaking", - "help", - "mine", - "narrow", - "smash", - "scrawny", - "tame", - "rain", - "playground", - "airport", - "astonishing", - "level", - "befitting", - "animal", - "heat", - "painful", - "cellar", - "ski", - "sedate", - "knowing", - "vigorous", - "change", - "eight", - "ship", - "work", - "strip", - "robin", - "tank", - "challenge", - "vacuous", - "representative", - "regret", - "tightfisted", - "erratic", - "club", - "imported", - "therapeutic", - "rainstorm", - "luxuriant", - "relieved", - "day", - "system", - "apologise", - "male", - "prepare", - "malicious", - "naive", - "whistle", - "curl", - "hobbies", - "trousers", - "stereotyped", - "dad", - "endurable", - "grass", - "hot", - "bomb", - "morning", - "guide", - "keen", - "plot", - "accept", - "disastrous", - "macabre", - "year", - "spicy", - "absorbing", - "sticks", - "efficient", - "drain", - "warm", - "rice", - "utter", - "fact", - "marked", - "ratty", - "chalk", - "towering", - "treat", - "nest", - "annoy", - "jealous", - "stamp", - "effect", - "cautious", - "jelly", - "feigned", - "gabby", - "corn", - "volleyball", - "pan", - "psychedelic", - "fairies", - "silent", - "zonked", - "bump", - "trouble", - "mass", - "queen", - "things", - "bury", - "sister", - "quiet", - "colossal", - "puncture", - "four", - "attend", - "love", - "wiry", - "vegetable", - "destruction", - "note", - "pies", - "resolute", - "load", - "fancy", - "tacky", - "periodic", - "abandoned", - "vivacious", - "blush", - "wrathful", - "miscreant", - "call", - "striped", - "wiggly", - "supreme", - "hand", - "impolite", - "rule", - "deserted", - "concern", - "cover", - "harbor", - "waiting", - "soggy", - "psychotic", - "ancient", - "sponge", - "domineering", - "elegant", - "impartial", - "unlock", - "abrasive", - "count", - "flight", - "neighborly", - "roof", - "bulb", - "auspicious", - "automatic", - "magic", - "sign", - "amusing", - "orange", - "branch", - "sulky", - "attack", - "fetch", - "number", - "jellyfish", - "start", - "alike", - "touch", - "sour", - "wary", - "minor", - "punish", - "connect", - "protest", - "pie", - "kaput", - "doubtful", - "friendly", - "simplistic", - "smart", - "vanish", - "applaud", - "jumbled", - "ready", - "yell", - "support", - "squash", - "raise", - "parallel", - "super", - "jazzy", - "crush", - "apathetic", - "water", - "food", - "thrill", - "permit", - "heady", - "last", - "mine", - "signal", - "smoke", - "preach", - "x-ray", - "name", - "birth", - "minute", - "steel", - "bedroom", - "female", - "acrid", - "riddle", - "attractive", - "earth", - "crack", - "muscle", - "alive", - "guarded", - "sweater", - "donkey", - "doubt", - "lettuce", - "magenta", - "live", - "farm", - "glib", - "bow", - "fascinated", - "friend", - "practise", - "remember", - "bleach", - "hungry", - "voiceless", - "pin", - "sparkling", - "report", - "arm", - "sad", - "shaggy", - "parcel", - "wail", - "flash", - "territory", - "functional", - "wise", - "screeching", - "appliance", - "future", - "appear", - "team", - "rabbit", - "porter", - "paint", - "flat", - "amusement", - "ocean", - "head", - "geese", - "wash", - "embarrassed", - "tub", - "boundless", - "freezing", - "mushy", - "surprise", - "temporary", - "marble", - "recondite", - "telephone", - "zipper", - "pine", - "reign", - "pump", - "tangy", - "reply", - "toys", - "purpose", - "songs", - "form", - "delicious", - "wood", - "horn", - "nutty", - "fruit", - "lumber", - "potato", - "cheat", - "cloudy", - "visit", - "reduce", - "destroy", - "deafening", - "full", - "warlike", - "mitten", - "cover", - "earthy", - "seashore", - "yarn", - "tenuous", - "pause", - "boil", - "dogs", - "tough", - "knife", - "shy", - "naughty", - "existence", - "fire", - "eminent", - "remove", - "juice", - "sleep", - "voyage", - "balance", - "unsightly", - "plough", - "ill-fated", - "pumped", - "motionless", - "allow", - "trade", - "warm", - "toad", - "wave", - "wall", - "pigs", - "circle", - "rejoice", - "ear", - "drink", - "found", - "taboo", - "object", - "old", - "temper", - "plant", - "public", - "picayune", - "blot", - "delight", - "carpenter", - "dispensable", - "tire", - "cow", - "furniture", - "rightful", - "mute", - "gentle", - "gifted", - "ragged", - "stiff", - "retire", - "compare", - "sable", - "hole", - "judicious", - "chilly", - "sparkle", - "futuristic", - "love", - "bubble", - "travel", - "name", - "numberless", - "succeed", - "acoustic", - "lowly", - "society", - "injure", - "agree", - "reason", - "party", - "wool", - "careful", - "hook", - "bell", - "ball", - "attach", - "scream", - "development", - "happy", - "appreciate", - "disagree", - "request", - "march", - "rampant", - "scrape", - "sack", - "hair", - "measure", - "owe", - "grubby", - "vein", - "boy", - "punishment", - "smoggy", - "wry", - "immense", - "shoe", - "pack", - "brash", - "cave", - "sincere", - "adorable", - "fantastic", - "attraction", - "racial", - "jittery", - "defiant", - "honey", - "paper", - "weight", - "bee", - "blind", - "birthday", - "toothsome", - "trick", - "guard", - "fog", - "handle", - "dirty", - "salt", - "rinse", - "nippy", - "observe", - "suggestion", - "weak", - "instinctive", - "frequent", - "detail", - "verse", - "quirky", - "scattered", - "toy", - "aware", - "distribution", - "repulsive", - "draconian", - "bucket", - "harm", - "radiate", - "bang", - "shrill", - "living", - "rhythm", - "obsequious", - "drum", - "inject", - "skate", - "beds", - "smash", - "order", - "stitch", - "ground", - "nosy", - "kick", - "dusty", - "home", - "rot", - "frame", - "jam", - "sky", - "soap", - "rescue", - "energetic", - "grape", - "massive", - "deeply", - "dazzling", - "park", - "pull", - "number", - "abundant", - "barbarous", - "drag", - "ajar", - "close", - "moan", - "haircut", - "shade", - "married", - "cats", - "thirsty", - "dirt", - "vagabond", - "fearful", - "squealing", - "squalid", - "zebra", - "murky", - "sheet", - "fat", - "follow", - "bikes", - "unpack", - "materialistic", - "surprise", - "arch", - "selection", - "acoustics", - "helpless", - "thoughtful", - "cry", - "quarrelsome", - "arrogant", - "illegal", - "sudden", - "elite", - "tomatoes", - "spoil", - "flower", - "shivering", - "front", - "caption", - "volcano", - "ugliest", - "ambitious", - "pickle", - "interrupt", - "nervous", - "approve", - "messy", - "dust", - "oceanic", - "brass", - "tremble", - "fine", - "nerve", - "lunchroom", - "hard", - "engine", - "erect", - "flower", - "cynical", - "irritating", - "tight", - "cobweb", - "gray", - "invention", - "snatch", - "account", - "sharp", - "spooky", - "squeamish", - "eatable", - "share", - "need", - "moaning", - "suspect", - "rush", - "rural", - "false", - "float", - "bite", - "careless", - "sidewalk", - "cowardly", - "stroke", - "educated", - "ugly", - "type", - "wandering", - "bolt", - "mint", - "fit", - "large", - "extra-large", - "defeated", - "kitty", - "tacit", - "abiding", - "grandfather", - "trains", - "lamp", - "habitual", - "fast", - "offbeat", - "accurate", - "many", - "fortunate", - "lyrical", - "charge", - "illustrious", - "transport", - "wakeful", - "cable", - "ordinary", - "string", - "question", - "train", - "fancy", - "kick", - "enchanting", - "jobless", - "ahead", - "comparison", - "loose", - "dance", - "add", - "wonder", - "stale", - "earn", - "reflective", - "bright", - "true", - "statuesque", - "amount", - "matter", - "repair", - "care", - "ruin", - "terrible", - "elastic", - "spiders", - "craven", - "lamentable", - "decision", - "swing", - "connection", - "gaudy", - "knowledge", - "cheap", - "lazy", - "step", - "dinner", - "rod", - "agreement", - "comb", - "mean", - "past", - "knotty", - "busy", - "quicksand", - "match", - "early", - "long", - "onerous", - "ambiguous", - "worried", - "spade", - "happen", - "crook", - "dapper", - "grate", - "announce", - "plate", - "haunt", - "friction", - "actually", - "chance", - "example", - "rapid", - "zealous", - "necessary", - "ink", - "mere", - "shock", - "huge", - "jaded", - "spill", - "store", - "fuzzy", - "table", - "bottle", - "halting", - "spark", - "end", - "remain", - "transport", - "seat", - "leg", - "long-term", - "clip", - "grumpy", - "shake", - "walk", - "try", - "action", - "soup", - "short", - "hurry", - "square", - "belligerent", - "thankful", - "beginner", - "small", - "bumpy", - "silly", - "badge", - "marvelous", - "wealth", - "open", - "unequal", - "scatter", - "pest", - "fool", - "step", - "groovy", - "childlike", - "door", - "bouncy", - "believe", - "incredible", - "box", - "unhealthy", - "swanky", - "abrupt", - "depressed", - "flaky", - "famous", - "detailed", - "regret", - "envious", - "natural", - "apparel", - "spare", - "mark", - "ten", - "power", - "glistening", - "arrive", - "animated", - "slip", - "heap", - "shaky", - "unfasten", - "contain", - "inexpensive", - "introduce", - "shallow", - "rule", - "gather", - "pump", - "humorous", - "acceptable", - "womanly", - "giddy", - "silk", - "yoke", - "straw", - "invite", - "one", - "red", - "growth", - "unadvised", - "measly", - "flap", - "puzzled", - "regular", - "painstaking", - "little", - "plain", - "tumble", - "rest", - "fabulous", - "melt", - "label", - "truculent", - "internal", - "passenger", - "zippy", - "bright", - "earsplitting", - "tooth", - "veil", - "grip", - "square", - "stuff", - "gate", - "level", - "stone", - "observation", - "time", - "workable", - "bird", - "realise", - "spotted", - "coast", - "quiver", - "rebel", - "entertain", - "rotten", - "loss", - "collect", - "meal", - "satisfy", - "military", - "bake", - "cagey", - "redundant", - "treatment", - "knock", - "blink", - "scale", - "board", - "fair", - "nebulous", - "sip", - "homeless", - "place", - "complain", - "joke", - "bat", - "winter", - "choke", - "frantic", - "chubby", - "highfalutin", - "troubled", - "whole", - "rose", - "delightful", - "loaf", - "afraid", - "sturdy", - "class", - "cultured", - "yielding", - "broken", - "kittens", - "absurd", - "discovery", - "next", - "disarm", - "dangerous", - "lively", - "reflect", - "chief", - "teeny", - "pencil", - "ban", - "grade", - "size", - "dashing", - "thought", - "breath", - "empty", - "hellish", - "shock", - "sea", - "weary", - "payment", - "limping", - "premium", - "grateful", - "somber", - "tax", - "coach", - "vulgar", - "stretch", - "tow", - "branch", - "insurance", - "yam", - "stormy", - "wish", - "snow", - "cute", - "milky", - "battle", - "far", - "roasted", - "slip", - "adamant", - "awake", - "employ", - "tangible", - "tickle", - "jog", - "hysterical", - "meddle", - "parsimonious", - "judge", - "educate", - "respect", - "sound", - "oven", - "gratis", - "station", - "train", - "purring", - "steady", - "carriage", - "humdrum", - "voracious", - "jolly", - "brainy", - "proud", - "elfin", - "cure", - "motion", - "record", - "quizzical", - "pail", - "bike", - "faithful", - "approval", - "vague", - "fall", - "store", - "normal", - "rock", - "bear", - "bounce", - "giant", - "satisfying", - "crooked", - "lopsided", - "vest", - "separate", - "sneeze", - "teaching", - "general", - "meat", - "festive", - "historical", - "line", - "north", - "tip", - "son", - "damaging", - "nimble", - "broad", - "list", - "confuse", - "first", - "deserve", - "steep", - "last", - "rich", - "oval", - "thick", - "glow", - "great", - "clammy", - "cheer", - "untidy", - "scientific", - "noise", - "stomach", - "undress", - "big", - "bite-sized", - "enter", - "cake", - "aloof", - "abnormal", - "month", - "grab", - "well-groomed", - "silver", - "art", - "berry", - "giraffe", - "complete", - "billowy", - "thumb", - "squeal", - "crib", - "discussion", - "even", - "stretch", - "mellow", - "angry", - "grouchy", - "absent", - "snow", - "middle", - "stingy", - "mourn", - "deep", - "honorable", - "nice", - "verdant", - "reach", - "lavish", - "sin", - "interest", - "whine", - "tug", - "vengeful", - "rhyme", - "stay", - "upset", - "hesitant", - "tent", - "wire", - "gold", - "momentous", - "yellow", - "cap", - "delicate", - "youthful", - "twig", - "burly", - "devilish", - "chess", - "wide", - "misty", - "useful", - "memorise", - "madly", - "plants", - "spectacular", - "accessible", - "collar", - "truck", - "harmony", - "uncovered", - "beef", - "low", - "channel", - "abusive", - "analyse", - "observant", - "snobbish", - "duck", - "excellent", - "intend", - "wreck", - "testy", - "care", - "shoes", - "charming", - "demonic", - "can", - "wipe", - "acidic", - "watch", - "decisive", - "brave", - "greet", - "imminent", - "influence", - "oranges", - "seal", - "eggs", - "knowledgeable", - "ashamed", - "shiny", - "inconclusive", - "remind", - "house", - "solid", - "quixotic", - "describe", - "support", - ]; + const words = ['belief','scandalous','flawless','wrestle','sort','moldy','carve','incompetent','cruel','awful','fang','holistic','makeshift','synonymous','questionable','soft','drop','boot','whimsical','stir','idea','adhesive','present','hilarious','unusual','divergent','probable','depend','suck','belong','advise','straight','encouraging','wing','clam','serve','fill','nostalgic','dysfunctional','aggressive','floor','baby','grease','sisters','print','switch','control','victorious','cracker','dream','wistful','adaptable','reminiscent','inquisitive','pushy','unaccountable','receive','guttural','two','protect','skin','unbiased','plastic','loutish','zip','used','divide','communicate','dear','muddled','dinosaurs','grip','trees','well-off','calendar','chickens','irate','deranged','trip','stream','white','poison','attack','obtain','theory','laborer','omniscient','brake','maniacal','curvy','smoke','babies','punch','hammer','toothbrush','same','crown','jagged','peep','difficult','reject','merciful','useless','doctor','mix','wicked','plant','quickest','roll','suffer','curly','brother','frighten','cold','tremendous','move','knot','lame','imaginary','capricious','raspy','aunt','loving','wink','wooden','hop','free','drab','fire','instrument','border','frame','silent','glue','decorate','distance','powerful','pig','admit','fix','pour','flesh','profuse','skinny','learn','filthy','dress','bloody','produce','innocent','meaty','pray','slimy','sun','kindhearted','dime','exclusive','boast','neat','ruthless','recess','grieving','daily','hateful','ignorant','fence','spring','slim','education','overflow','plastic','gaping','chew','detect','right','lunch','gainful','argue','cloistered','horses','orange','shame','bitter','able','sail','magical','exist','force','wheel','best','suit','spurious','partner','request','dog','gusty','money','gaze','lonely','company','pale','tempt','rat','flame','wobble','superficial','stop','protective','stare','tongue','heal','railway','idiotic','roll','puffy','turn','meeting','new','frightening','sophisticated','poke','elderly','room','stimulating','increase','moor','secret','lean','occur','country','damp','evanescent','alluring','oafish','join','thundering','cars','awesome','advice','unruly','ray','wind','anxious','fly','hammer','adventurous','shop','cook','trucks','nonchalant','addition','base','abashed','excuse','giants','dramatic','piquant','coach','possess','poor','finger','wide-eyed','aquatic','welcome','instruct','expert','evasive','hug','cute','return','mice','damage','turkey','quiet','bewildered','tidy','pointless','outrageous','medical','foolish','curve','grandiose','gullible','hapless','gleaming','third','grin','pipe','egg','act','physical','eager','side','milk','tearful','fertile','average','glamorous','strange','yak','terrific','thin','near','snails','flowery','authority','fish','curious','perpetual','healthy','health','match','fade','chemical','economic','drawer','avoid','lying','minister','lick','powder','decay','desire','furry','faint','beam','sordid','fax','tail','bawdy','cherry','letter','clover','ladybug','teeth','behavior','black','amazing','pink','waste','island','forgetful','needless','lock','waves','boundary','receipt','handy','religion','hypnotic','aftermath','explain','sense','mundane','rambunctious','second','preserve','alarm','dusty','event','blow','weigh','value','glorious','jail','sigh','cemetery','serious','yummy','cattle','understood','limit','alert','fear','lucky','tested','surround','dolls','pleasant','disillusioned','discover','tray','night','seemly','liquid','worry','pen','bent','gruesome','war','teeny-tiny','common','judge','symptomatic','bed','trot','unequaled','flowers','friends','damaged','peel','skip','show','twist','worthless','brush','look','behave','imperfect','week','petite','direction','soda','lively','coal','coil','release','berserk','books','impossible','replace','cough','chunky','torpid','discreet','material','bomb','soothe','crack','hope','license','frightened','breathe','maddening','calculator','committee','paltry','green','subsequent','arrest','gigantic','tasty','metal','willing','man','stem','nonstop','route','impulse','government','comfortable','include','literate','multiply','test','vast','exercise','addicted','agreeable','lace','toes','young','water','end','wash','glossy','round','staking','sink','open','spot','trip','fierce','robust','pastoral','drown','dress','machine','calculating','holiday','crabby','disgusting','plan','sleet','sleepy','typical','borrow','possible','curtain','airplane','industry','nut','rough','wacky','rock','enormous','uninterested','sugar','rake','consist','wrist','basket','chop','wet','street','known','settle','bless','cluttered','wild','expand','angle','snake','yawn','hate','flood','rabid','spiteful','anger','market','bizarre','force','majestic','scissors','beg','rifle','foregoing','cactus','funny','eggnog','wish','high-pitched','drop','camp','scarf','car','groan','wonderful','wealthy','cup','lock','available','previous','jam','political','vacation','three','desk','fry','aspiring','productive','clear','bored','flashy','plug','precede','abhorrent','muddle','flimsy','paste','need','reward','frail','obnoxious','creature','whip','unbecoming','lake','unused','chin','tour','zephyr','experience','building','scrub','correct','hover','panicky','scorch','diligent','hulking','ubiquitous','tedious','aberrant','file','accidental','mist','blue-eyed','trite','nondescript','cows','wait','test','snotty','amuck','jump','lackadaisical','grey','tawdry','strong','land','kind','star','ludicrous','stupid','telling','use','bruise','whirl','cream','harsh','aboriginal','substantial','brawny','tease','pollution','weather','degree','dry','film','obey','closed','dependent','want','undesirable','stamp','relax','foot','obscene','successful','wriggle','drain','greasy','escape','cross','odd','boring','absorbed','houses','suppose','suit','moon','ceaseless','explode','clap','pop','courageous','miss','notebook','delirious','form','pretty','sock','grotesque','noxious','record','stop','saw','thing','dislike','cloth','six','jar','unnatural','spiffy','itchy','secretary','move','certain','unkempt','sassy','queue','shrug','crow','heavenly','desert','screw','vessel','mug','encourage','icy','enthusiastic','throat','whistle','ignore','miniature','squeak','scarecrow','fluttering','hang','icicle','lie','juicy','empty','baseball','various','promise','abortive','descriptive','high','spy','faded','talk','air','messup','decorous','sneaky','mark','sack','ultra','chivalrous','lethal','expect','disgusted','reaction','fireman','private','ritzy','manage','actor','rely','uppity','thread','bat','space','underwear','blood','nine','maid','shelf','hanging','shop','prick','wound','sloppy','offer','increase','clear','slap','rude','poised','wretched','cause','quince','tame','remarkable','abject','sail','guide','subdued','spiky','debonair','chicken','tired','hum','land','scared','splendid','guess','cast','rub','magnificent','ants','overwrought','interfere','gorgeous','office','trade','sniff','melted','bore','point','pet','purple','brake','flavor','toe','prickly','zinc','homely','modern','kindly','whisper','bare','annoyed','glass','noisy','null','thoughtless','skirt','dock','rings','mind','neck','macho','wave','history','play','road','profit','word','opposite','dreary','governor','horse','trust','elbow','kiss','crayon','stitch','excited','needy','arrange','easy','alcoholic','safe','lumpy','monkey','smile','capable','untidy','extra-small','memory','selective','reproduce','old-fashioned','overrated','texture','knit','downtown','risk','pot','sofa','righteous','wren','pull','carry','aboard','listen','classy','thank','shocking','condition','root','attempt','swim','frog','hurt','army','title','handsomely','town','guiltless','thaw','spell','selfish','disturbed','tramp','girls','utopian','noiseless','trail','bashful','business','rhetorical','snail','sign','plausible','left','design','tall','violent','wasteful','beautiful','breezy','tap','murder','talented','needle','creator','imagine','flippant','dead','bone','coherent','relation','aromatic','mountainous','face','ask','picture','pedal','colour','obese','group','top','bubble','pinch','optimal','school','bathe','flagrant','check','deliver','pass','tan','crate','hose','debt','faulty','longing','hollow','invincible','afford','lovely','ticket','changeable','subtract','fumbling','responsible','confused','woman','touch','watch','zesty','library','jail','wrap','terrify','brick','popcorn','cooperative','peck','pocket','property','buzz','tiresome','digestion','exciting','nation','juvenile','shade','copper','wanting','deer','waste','man','join','spotty','amused','mountain','waggish','bushes','tense','river','heartbreaking','help','mine','narrow','smash','scrawny','tame','rain','playground','airport','astonishing','level','befitting','animal','heat','painful','cellar','ski','sedate','knowing','vigorous','change','eight','ship','work','strip','robin','tank','challenge','vacuous','representative','regret','tightfisted','erratic','club','imported','therapeutic','rainstorm','luxuriant','relieved','day','system','apologise','male','prepare','malicious','naive','whistle','curl','hobbies','trousers','stereotyped','dad','endurable','grass','hot','bomb','morning','guide','keen','plot','accept','disastrous','macabre','year','spicy','absorbing','sticks','efficient','drain','warm','rice','utter','fact','marked','ratty','chalk','towering','treat','nest','annoy','jealous','stamp','effect','cautious','jelly','feigned','gabby','corn','volleyball','pan','psychedelic','fairies','silent','zonked','bump','trouble','mass','queen','things','bury','sister','quiet','colossal','puncture','four','attend','love','wiry','vegetable','destruction','note','pies','resolute','load','fancy','tacky','periodic','abandoned','vivacious','blush','wrathful','miscreant','call','striped','wiggly','supreme','hand','impolite','rule','deserted','concern','cover','harbor','waiting','soggy','psychotic','ancient','sponge','domineering','elegant','impartial','unlock','abrasive','count','flight','neighborly','roof','bulb','auspicious','automatic','magic','sign','amusing','orange','branch','sulky','attack','fetch','number','jellyfish','start','alike','touch','sour','wary','minor','punish','connect','protest','pie','kaput','doubtful','friendly','simplistic','smart','vanish','applaud','jumbled','ready','yell','support','squash','raise','parallel','super','jazzy','crush','apathetic','water','food','thrill','permit','heady','last','mine','signal','smoke','preach','x-ray','name','birth','minute','steel','bedroom','female','acrid','riddle','attractive','earth','crack','muscle','alive','guarded','sweater','donkey','doubt','lettuce','magenta','live','farm','glib','bow','fascinated','friend','practise','remember','bleach','hungry','voiceless','pin','sparkling','report','arm','sad','shaggy','parcel','wail','flash','territory','functional','wise','screeching','appliance','future','appear','team','rabbit','porter','paint','flat','amusement','ocean','head','geese','wash','embarrassed','tub','boundless','freezing','mushy','surprise','temporary','marble','recondite','telephone','zipper','pine','reign','pump','tangy','reply','toys','purpose','songs','form','delicious','wood','horn','nutty','fruit','lumber','potato','cheat','cloudy','visit','reduce','destroy','deafening','full','warlike','mitten','cover','earthy','seashore','yarn','tenuous','pause','boil','dogs','tough','knife','shy','naughty','existence','fire','eminent','remove','juice','sleep','voyage','balance','unsightly','plough','ill-fated','pumped','motionless','allow','trade','warm','toad','wave','wall','pigs','circle','rejoice','ear','drink','found','taboo','object','old','temper','plant','public','picayune','blot','delight','carpenter','dispensable','tire','cow','furniture','rightful','mute','gentle','gifted','ragged','stiff','retire','compare','sable','hole','judicious','chilly','sparkle','futuristic','love','bubble','travel','name','numberless','succeed','acoustic','lowly','society','injure','agree','reason','party','wool','careful','hook','bell','ball','attach','scream','development','happy','appreciate','disagree','request','march','rampant','scrape','sack','hair','measure','owe','grubby','vein','boy','punishment','smoggy','wry','immense','shoe','pack','brash','cave','sincere','adorable','fantastic','attraction','racial','jittery','defiant','honey','paper','weight','bee','blind','birthday','toothsome','trick','guard','fog','handle','dirty','salt','rinse','nippy','observe','suggestion','weak','instinctive','frequent','detail','verse','quirky','scattered','toy','aware','distribution','repulsive','draconian','bucket','harm','radiate','bang','shrill','living','rhythm','obsequious','drum','inject','skate','beds','smash','order','stitch','ground','nosy','kick','dusty','home','rot','frame','jam','sky','soap','rescue','energetic','grape','massive','deeply','dazzling','park','pull','number','abundant','barbarous','drag','ajar','close','moan','haircut','shade','married','cats','thirsty','dirt','vagabond','fearful','squealing','squalid','zebra','murky','sheet','fat','follow','bikes','unpack','materialistic','surprise','arch','selection','acoustics','helpless','thoughtful','cry','quarrelsome','arrogant','illegal','sudden','elite','tomatoes','spoil','flower','shivering','front','caption','volcano','ugliest','ambitious','pickle','interrupt','nervous','approve','messy','dust','oceanic','brass','tremble','fine','nerve','lunchroom','hard','engine','erect','flower','cynical','irritating','tight','cobweb','gray','invention','snatch','account','sharp','spooky','squeamish','eatable','share','need','moaning','suspect','rush','rural','false','float','bite','careless','sidewalk','cowardly','stroke','educated','ugly','type','wandering','bolt','mint','fit','large','extra-large','defeated','kitty','tacit','abiding','grandfather','trains','lamp','habitual','fast','offbeat','accurate','many','fortunate','lyrical','charge','illustrious','transport','wakeful','cable','ordinary','string','question','train','fancy','kick','enchanting','jobless','ahead','comparison','loose','dance','add','wonder','stale','earn','reflective','bright','true','statuesque','amount','matter','repair','care','ruin','terrible','elastic','spiders','craven','lamentable','decision','swing','connection','gaudy','knowledge','cheap','lazy','step','dinner','rod','agreement','comb','mean','past','knotty','busy','quicksand','match','early','long','onerous','ambiguous','worried','spade','happen','crook','dapper','grate','announce','plate','haunt','friction','actually','chance','example','rapid','zealous','necessary','ink','mere','shock','huge','jaded','spill','store','fuzzy','table','bottle','halting','spark','end','remain','transport','seat','leg','long-term','clip','grumpy','shake','walk','try','action','soup','short','hurry','square','belligerent','thankful','beginner','small','bumpy','silly','badge','marvelous','wealth','open','unequal','scatter','pest','fool','step','groovy','childlike','door','bouncy','believe','incredible','box','unhealthy','swanky','abrupt','depressed','flaky','famous','detailed','regret','envious','natural','apparel','spare','mark','ten','power','glistening','arrive','animated','slip','heap','shaky','unfasten','contain','inexpensive','introduce','shallow','rule','gather','pump','humorous','acceptable','womanly','giddy','silk','yoke','straw','invite','one','red','growth','unadvised','measly','flap','puzzled','regular','painstaking','little','plain','tumble','rest','fabulous','melt','label','truculent','internal','passenger','zippy','bright','earsplitting','tooth','veil','grip','square','stuff','gate','level','stone','observation','time','workable','bird','realise','spotted','coast','quiver','rebel','entertain','rotten','loss','collect','meal','satisfy','military','bake','cagey','redundant','treatment','knock','blink','scale','board','fair','nebulous','sip','homeless','place','complain','joke','bat','winter','choke','frantic','chubby','highfalutin','troubled','whole','rose','delightful','loaf','afraid','sturdy','class','cultured','yielding','broken','kittens','absurd','discovery','next','disarm','dangerous','lively','reflect','chief','teeny','pencil','ban','grade','size','dashing','thought','breath','empty','hellish','shock','sea','weary','payment','limping','premium','grateful','somber','tax','coach','vulgar','stretch','tow','branch','insurance','yam','stormy','wish','snow','cute','milky','battle','far','roasted','slip','adamant','awake','employ','tangible','tickle','jog','hysterical','meddle','parsimonious','judge','educate','respect','sound','oven','gratis','station','train','purring','steady','carriage','humdrum','voracious','jolly','brainy','proud','elfin','cure','motion','record','quizzical','pail','bike','faithful','approval','vague','fall','store','normal','rock','bear','bounce','giant','satisfying','crooked','lopsided','vest','separate','sneeze','teaching','general','meat','festive','historical','line','north','tip','son','damaging','nimble','broad','list','confuse','first','deserve','steep','last','rich','oval','thick','glow','great','clammy','cheer','untidy','scientific','noise','stomach','undress','big','bite-sized','enter','cake','aloof','abnormal','month','grab','well-groomed','silver','art','berry','giraffe','complete','billowy','thumb','squeal','crib','discussion','even','stretch','mellow','angry','grouchy','absent','snow','middle','stingy','mourn','deep','honorable','nice','verdant','reach','lavish','sin','interest','whine','tug','vengeful','rhyme','stay','upset','hesitant','tent','wire','gold','momentous','yellow','cap','delicate','youthful','twig','burly','devilish','chess','wide','misty','useful','memorise','madly','plants','spectacular','accessible','collar','truck','harmony','uncovered','beef','low','channel','abusive','analyse','observant','snobbish','duck','excellent','intend','wreck','testy','care','shoes','charming','demonic','can','wipe','acidic','watch','decisive','brave','greet','imminent','influence','oranges','seal','eggs','knowledgeable','ashamed','shiny','inconclusive','remind','house','solid','quixotic','describe','support']; return randomElement(words); } function execCommand(client, command, options = {}) { - let exePath = "node " + joplinAppPath; - let cmd = exePath + " --update-geolocation-disabled --env dev --log-level debug --profile " + client.profileDir + " " + command; - logger.info(client.id + ": " + command); + let exePath = 'node ' + joplinAppPath; + let cmd = exePath + ' --update-geolocation-disabled --env dev --log-level debug --profile ' + client.profileDir + ' ' + command; + logger.info(client.id + ': ' + command); if (options.killAfter) { - logger.info("Kill after: " + options.killAfter); + logger.info('Kill after: ' + options.killAfter); } return new Promise((resolve, reject) => { let childProcess = exec(cmd, (error, stdout, stderr) => { if (error) { - if (error.signal == "SIGTERM") { - resolve("Process was killed"); + if (error.signal == 'SIGTERM') { + resolve('Process was killed'); } else { logger.error(stderr); reject(error); @@ -2088,7 +83,7 @@ function execCommand(client, command, options = {}) { if (options.killAfter) { setTimeout(() => { - logger.info("Sending kill signal..."); + logger.info('Sending kill signal...'); childProcess.kill(); }, options.killAfter); } @@ -2096,11 +91,11 @@ function execCommand(client, command, options = {}) { } async function clientItems(client) { - let itemsJson = await execCommand(client, "dump"); + let itemsJson = await execCommand(client, 'dump'); try { return JSON.parse(itemsJson); } catch (error) { - throw new Error("Cannot parse JSON: " + itemsJson); + throw new Error('Cannot parse JSON: ' + itemsJson); } } @@ -2126,66 +121,50 @@ function randomNote(items) { async function execRandomCommand(client) { let possibleCommands = [ - ["mkbook {word}", 40], // CREATE FOLDER - ["mknote {word}", 70], // CREATE NOTE - [ - async () => { - // DELETE RANDOM ITEM - let items = await clientItems(client); - let item = randomElement(items); - if (!item) return; + ['mkbook {word}', 40], // CREATE FOLDER + ['mknote {word}', 70], // CREATE NOTE + [async () => { // DELETE RANDOM ITEM + let items = await clientItems(client); + let item = randomElement(items); + if (!item) return; - if (item.type_ == 1) { - return execCommand(client, "rm -f " + item.id); - } else if (item.type_ == 2) { - return execCommand(client, "rm -r -f " + item.id); - } else if (item.type_ == 5) { - // tag - } else { - throw new Error("Unknown type: " + item.type_); + if (item.type_ == 1) { + return execCommand(client, 'rm -f ' + item.id); + } else if (item.type_ == 2) { + return execCommand(client, 'rm -r -f ' + item.id); + } else if (item.type_ == 5) { + // tag + } else { + throw new Error('Unknown type: ' + item.type_); + } + }, 30], + [async () => { // SYNC + let avgSyncDuration = averageSyncDuration(); + let options = {}; + if (!isNaN(avgSyncDuration)) { + if (Math.random() >= 0.5) { + options.killAfter = avgSyncDuration * Math.random(); } - }, - 30, - ], - [ - async () => { - // SYNC - let avgSyncDuration = averageSyncDuration(); - let options = {}; - if (!isNaN(avgSyncDuration)) { - if (Math.random() >= 0.5) { - options.killAfter = avgSyncDuration * Math.random(); - } - } - return execCommand(client, "sync --random-failures", options); - }, - 30, - ], - [ - async () => { - // UPDATE RANDOM ITEM - let items = await clientItems(client); - let item = randomNote(items); - if (!item) return; + } + return execCommand(client, 'sync --random-failures', options); + }, 30], + [async () => { // UPDATE RANDOM ITEM + let items = await clientItems(client); + let item = randomNote(items); + if (!item) return; - return execCommand(client, "set " + item.id + ' title "' + randomWord() + '"'); - }, - 50, - ], - [ - async () => { - // ADD TAG - let items = await clientItems(client); - let note = randomNote(items); - if (!note) return; + return execCommand(client, 'set ' + item.id + ' title "' + randomWord() + '"'); + }, 50], + [async () => { // ADD TAG + let items = await clientItems(client); + let note = randomNote(items); + if (!note) return; - let tag = randomTag(items); - let tagTitle = !tag || Math.random() >= 0.9 ? "tag-" + randomWord() : tag.title; - - return execCommand(client, "tag add " + tagTitle + " " + note.id); - }, - 50, - ], + let tag = randomTag(items); + let tagTitle = !tag || Math.random() >= 0.9 ? 'tag-' + randomWord() : tag.title; + + return execCommand(client, 'tag add ' + tagTitle + ' ' + note.id); + }, 50], ]; let cmd = null; @@ -2197,10 +176,10 @@ async function execRandomCommand(client) { cmd = cmd[0]; - if (typeof cmd === "function") { + if (typeof cmd === 'function') { return cmd(); } else { - cmd = cmd.replace("{word}", randomWord()); + cmd = cmd.replace('{word}', randomWord()); return execCommand(client, cmd); } } @@ -2211,12 +190,12 @@ function averageSyncDuration() { function randomNextCheckTime() { let output = time.unixMs() + 1000 + Math.random() * 1000 * 120; - logger.info("Next sync check: " + time.unixMsToIso(output) + " (" + Math.round((output - time.unixMs()) / 1000) + " sec.)"); + logger.info('Next sync check: ' + time.unixMsToIso(output) + ' (' + (Math.round((output - time.unixMs()) / 1000)) + ' sec.)'); return output; } function findItem(items, itemId) { - for (let i = 0; i < items.length; i++) { + for (let i = 0; i < items.length; i++) { if (items[i].id == itemId) return items[i]; } return null; @@ -2229,7 +208,7 @@ function compareItems(item1, item2) { let p1 = item1[n]; let p2 = item2[n]; - if (n == "notes_") { + if (n == 'notes_') { p1.sort(); p2.sort(); if (JSON.stringify(p1) !== JSON.stringify(p2)) { @@ -2265,7 +244,10 @@ function findMissingItems_(items1, items2) { } function findMissingItems(items1, items2) { - return [findMissingItems_(items1, items2), findMissingItems_(items2, items1)]; + return [ + findMissingItems_(items1, items2), + findMissingItems_(items2, items1), + ]; } async function compareClientItems(clientItems) { @@ -2274,11 +256,11 @@ async function compareClientItems(clientItems) { let items = clientItems[i]; itemCounts.push(items.length); } - logger.info("Item count: " + itemCounts.join(", ")); - + logger.info('Item count: ' + itemCounts.join(', ')); + let missingItems = findMissingItems(clientItems[0], clientItems[1]); if (missingItems[0].length || missingItems[1].length) { - logger.error("Items are different"); + logger.error('Items are different'); logger.error(missingItems); process.exit(1); } @@ -2290,7 +272,7 @@ async function compareClientItems(clientItems) { for (let clientId = 1; clientId < clientItems.length; clientId++) { let item2 = findItem(clientItems[clientId], item1.id); if (!item2) { - logger.error("Item not found on client " + clientId + ":"); + logger.error('Item not found on client ' + clientId + ':'); logger.error(item1); process.exit(1); } @@ -2306,7 +288,7 @@ async function compareClientItems(clientItems) { } if (differences.length) { - logger.error("Found differences between items:"); + logger.error('Found differences between items:'); logger.error(differences); process.exit(1); } @@ -2314,7 +296,7 @@ async function compareClientItems(clientItems) { async function main(argv) { await fs.remove(syncDir); - + let clients = await createClients(); let activeCommandCounts = []; let clientId = 0; @@ -2328,27 +310,25 @@ async function main(argv) { clients[clientId].activeCommandCount++; - execRandomCommand(clients[clientId]) - .catch(error => { - logger.info("Client " + clientId + ":"); - logger.error(error); - }) - .then(r => { - if (r) { - logger.info("Client " + clientId + ":\n" + r.trim()); - } - clients[clientId].activeCommandCount--; - }); + execRandomCommand(clients[clientId]).catch((error) => { + logger.info('Client ' + clientId + ':'); + logger.error(error); + }).then((r) => { + if (r) { + logger.info('Client ' + clientId + ":\n" + r.trim()) + } + clients[clientId].activeCommandCount--; + }); } let nextSyncCheckTime = randomNextCheckTime(); - let state = "commands"; + let state = 'commands'; setInterval(async () => { - if (state == "waitForSyncCheck") return; + if (state == 'waitForSyncCheck') return; - if (state == "syncCheck") { - state = "waitForSyncCheck"; + if (state == 'syncCheck') { + state = 'waitForSyncCheck'; let clientItems = []; // Up to 3 sync operations must be performed by each clients in order for them // to be perfectly in sync - in order for each items to send their changes @@ -2358,11 +338,11 @@ async function main(argv) { for (let loopCount = 0; loopCount < 3; loopCount++) { for (let i = 0; i < clients.length; i++) { let beforeTime = time.unixMs(); - await execCommand(clients[i], "sync"); + await execCommand(clients[i], 'sync'); syncDurations.push(time.unixMs() - beforeTime); if (syncDurations.length > 20) syncDurations.splice(0, 1); if (loopCount === 2) { - let dump = await execCommand(clients[i], "dump"); + let dump = await execCommand(clients[i], 'dump'); clientItems[i] = JSON.parse(dump); } } @@ -2371,22 +351,22 @@ async function main(argv) { await compareClientItems(clientItems); nextSyncCheckTime = randomNextCheckTime(); - state = "commands"; + state = 'commands'; return; } - if (state == "waitForClients") { + if (state == 'waitForClients') { for (let i = 0; i < clients.length; i++) { if (clients[i].activeCommandCount > 0) return; } - state = "syncCheck"; + state = 'syncCheck'; return; } - if (state == "commands") { + if (state == 'commands') { if (nextSyncCheckTime <= time.unixMs()) { - state = "waitForClients"; + state = 'waitForClients'; return; } @@ -2397,6 +377,6 @@ async function main(argv) { }, 100); } -main(process.argv).catch(error => { +main(process.argv).catch((error) => { logger.error(error); -}); +}); \ No newline at end of file diff --git a/CliClient/app/gui/ConsoleWidget.js b/CliClient/app/gui/ConsoleWidget.js index 02f5b2800..e6bc30bd1 100644 --- a/CliClient/app/gui/ConsoleWidget.js +++ b/CliClient/app/gui/ConsoleWidget.js @@ -1,6 +1,7 @@ -const TextWidget = require("tkwidgets/TextWidget.js"); +const TextWidget = require('tkwidgets/TextWidget.js'); class ConsoleWidget extends TextWidget { + constructor() { super(); this.lines_ = []; @@ -11,11 +12,11 @@ class ConsoleWidget extends TextWidget { } get name() { - return "console"; + return 'console'; } get lastLine() { - return this.lines_.length ? this.lines_[this.lines_.length - 1] : ""; + return this.lines_.length ? this.lines_[this.lines_.length-1] : ''; } addLine(line) { @@ -45,6 +46,7 @@ class ConsoleWidget extends TextWidget { super.render(); } + } -module.exports = ConsoleWidget; +module.exports = ConsoleWidget; \ No newline at end of file diff --git a/CliClient/app/gui/FolderListWidget.js b/CliClient/app/gui/FolderListWidget.js index 7c4897b21..4532d1a7c 100644 --- a/CliClient/app/gui/FolderListWidget.js +++ b/CliClient/app/gui/FolderListWidget.js @@ -1,10 +1,11 @@ -const Folder = require("lib/models/Folder.js"); -const Tag = require("lib/models/Tag.js"); -const BaseModel = require("lib/BaseModel.js"); -const ListWidget = require("tkwidgets/ListWidget.js"); -const _ = require("lib/locale.js")._; +const Folder = require('lib/models/Folder.js'); +const Tag = require('lib/models/Tag.js'); +const BaseModel = require('lib/BaseModel.js'); +const ListWidget = require('tkwidgets/ListWidget.js'); +const _ = require('lib/locale.js')._; class FolderListWidget extends ListWidget { + constructor() { super(); @@ -14,26 +15,26 @@ class FolderListWidget extends ListWidget { this.selectedFolderId_ = null; this.selectedTagId_ = null; this.selectedSearchId_ = null; - this.notesParentType_ = "Folder"; + this.notesParentType_ = 'Folder'; this.updateIndexFromSelectedFolderId_ = false; this.updateItems_ = false; - this.itemRenderer = item => { + this.itemRenderer = (item) => { let output = []; - if (item === "-") { - output.push("-".repeat(this.innerWidth)); + if (item === '-') { + output.push('-'.repeat(this.innerWidth)); } else if (item.type_ === Folder.modelType()) { output.push(Folder.displayTitle(item)); } else if (item.type_ === Tag.modelType()) { - output.push("[" + Folder.displayTitle(item) + "]"); + output.push('[' + Folder.displayTitle(item) + ']'); } else if (item.type_ === BaseModel.TYPE_SEARCH) { - output.push(_("Search:")); + output.push(_('Search:')); output.push(item.title); - } + } // if (item && item.id) output.push(item.id.substr(0, 5)); - - return output.join(" "); + + return output.join(' '); }; } @@ -43,7 +44,7 @@ class FolderListWidget extends ListWidget { set selectedFolderId(v) { this.selectedFolderId_ = v; - this.updateIndexFromSelectedItemId(); + this.updateIndexFromSelectedItemId() this.invalidate(); } @@ -53,7 +54,7 @@ class FolderListWidget extends ListWidget { set selectedSearchId(v) { this.selectedSearchId_ = v; - this.updateIndexFromSelectedItemId(); + this.updateIndexFromSelectedItemId() this.invalidate(); } @@ -63,7 +64,7 @@ class FolderListWidget extends ListWidget { set selectedTagId(v) { this.selectedTagId_ = v; - this.updateIndexFromSelectedItemId(); + this.updateIndexFromSelectedItemId() this.invalidate(); } @@ -74,7 +75,7 @@ class FolderListWidget extends ListWidget { set notesParentType(v) { //if (this.notesParentType_ === v) return; this.notesParentType_ = v; - this.updateIndexFromSelectedItemId(); + this.updateIndexFromSelectedItemId() this.invalidate(); } @@ -85,7 +86,7 @@ class FolderListWidget extends ListWidget { set searches(v) { this.searches_ = v; this.updateItems_ = true; - this.updateIndexFromSelectedItemId(); + this.updateIndexFromSelectedItemId() this.invalidate(); } @@ -96,7 +97,7 @@ class FolderListWidget extends ListWidget { set tags(v) { this.tags_ = v; this.updateItems_ = true; - this.updateIndexFromSelectedItemId(); + this.updateIndexFromSelectedItemId() this.invalidate(); } @@ -107,32 +108,32 @@ class FolderListWidget extends ListWidget { set folders(v) { this.folders_ = v; this.updateItems_ = true; - this.updateIndexFromSelectedItemId(); + this.updateIndexFromSelectedItemId() this.invalidate(); } - + render() { if (this.updateItems_) { - this.logger().debug("Rebuilding items...", this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId); + this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId); const wasSelectedItemId = this.selectedJoplinItemId; const previousParentType = this.notesParentType; let newItems = this.folders.slice(); if (this.tags.length) { - if (newItems.length) newItems.push("-"); + if (newItems.length) newItems.push('-'); newItems = newItems.concat(this.tags); } if (this.searches.length) { - if (newItems.length) newItems.push("-"); + if (newItems.length) newItems.push('-'); newItems = newItems.concat(this.searches); } this.items = newItems; this.notesParentType = previousParentType; - this.updateIndexFromSelectedItemId(wasSelectedItemId); + this.updateIndexFromSelectedItemId(wasSelectedItemId) this.updateItems_ = false; } @@ -140,24 +141,25 @@ class FolderListWidget extends ListWidget { } get selectedJoplinItemId() { - if (!this.notesParentType) return ""; - if (this.notesParentType === "Folder") return this.selectedFolderId; - if (this.notesParentType === "Tag") return this.selectedTagId; - if (this.notesParentType === "Search") return this.selectedSearchId; - throw new Error("Unknown parent type: " + this.notesParentType); + if (!this.notesParentType) return ''; + if (this.notesParentType === 'Folder') return this.selectedFolderId; + if (this.notesParentType === 'Tag') return this.selectedTagId; + if (this.notesParentType === 'Search') return this.selectedSearchId; + throw new Error('Unknown parent type: ' + this.notesParentType); } get selectedJoplinItem() { const id = this.selectedJoplinItemId; - const index = this.itemIndexByKey("id", id); + const index = this.itemIndexByKey('id', id); return this.itemAt(index); } updateIndexFromSelectedItemId(itemId = null) { if (itemId === null) itemId = this.selectedJoplinItemId; - const index = this.itemIndexByKey("id", itemId); + const index = this.itemIndexByKey('id', itemId); this.currentIndex = index >= 0 ? index : 0; } + } -module.exports = FolderListWidget; +module.exports = FolderListWidget; \ No newline at end of file diff --git a/CliClient/app/gui/NoteListWidget.js b/CliClient/app/gui/NoteListWidget.js index 0aa7dbb4c..30d44df44 100644 --- a/CliClient/app/gui/NoteListWidget.js +++ b/CliClient/app/gui/NoteListWidget.js @@ -1,17 +1,18 @@ -const Note = require("lib/models/Note.js"); -const ListWidget = require("tkwidgets/ListWidget.js"); +const Note = require('lib/models/Note.js'); +const ListWidget = require('tkwidgets/ListWidget.js'); class NoteListWidget extends ListWidget { + constructor() { super(); this.selectedNoteId_ = 0; this.updateIndexFromSelectedNoteId_ = false; - this.itemRenderer = note => { + this.itemRenderer = (note) => { let label = Note.displayTitle(note); // + ' ' + note.id; if (note.is_todo) { - label = "[" + (note.todo_completed ? "X" : " ") + "] " + label; + label = '[' + (note.todo_completed ? 'X' : ' ') + '] ' + label; } return label; }; @@ -24,13 +25,14 @@ class NoteListWidget extends ListWidget { render() { if (this.updateIndexFromSelectedNoteId_) { - const index = this.itemIndexByKey("id", this.selectedNoteId_); + const index = this.itemIndexByKey('id', this.selectedNoteId_); this.currentIndex = index >= 0 ? index : 0; this.updateIndexFromSelectedNoteId_ = false; } super.render(); } + } -module.exports = NoteListWidget; +module.exports = NoteListWidget; \ No newline at end of file diff --git a/CliClient/app/gui/NoteMetadataWidget.js b/CliClient/app/gui/NoteMetadataWidget.js index 9eb5207f9..ff68e189d 100644 --- a/CliClient/app/gui/NoteMetadataWidget.js +++ b/CliClient/app/gui/NoteMetadataWidget.js @@ -1,7 +1,8 @@ -const Note = require("lib/models/Note.js"); -const TextWidget = require("tkwidgets/TextWidget.js"); +const Note = require('lib/models/Note.js'); +const TextWidget = require('tkwidgets/TextWidget.js'); class NoteMetadataWidget extends TextWidget { + constructor() { super(); this.noteId_ = 0; @@ -26,9 +27,10 @@ class NoteMetadataWidget extends TextWidget { if (!this.note_ && this.noteId_) { this.note_ = await Note.load(this.noteId_); - this.text = this.note_ ? await Note.minimalSerializeForDisplay(this.note_) : ""; + this.text = this.note_ ? await Note.minimalSerializeForDisplay(this.note_) : ''; } } + } -module.exports = NoteMetadataWidget; +module.exports = NoteMetadataWidget; \ No newline at end of file diff --git a/CliClient/app/gui/NoteWidget.js b/CliClient/app/gui/NoteWidget.js index 7f5b9de31..d0a29538e 100644 --- a/CliClient/app/gui/NoteWidget.js +++ b/CliClient/app/gui/NoteWidget.js @@ -1,8 +1,9 @@ -const Note = require("lib/models/Note.js"); -const TextWidget = require("tkwidgets/TextWidget.js"); -const { _ } = require("lib/locale.js"); +const Note = require('lib/models/Note.js'); +const TextWidget = require('tkwidgets/TextWidget.js'); +const { _ } = require('lib/locale.js'); class NoteWidget extends TextWidget { + constructor() { super(); this.noteId_ = 0; @@ -33,9 +34,7 @@ class NoteWidget extends TextWidget { } welcomeText() { - return _( - "Welcome to Joplin!\n\nType `:help shortcuts` for the list of keyboard shortcuts, or just `:help` for usage information.\n\nFor example, to create a notebook press `mb`; to create a note press `mn`." - ); + return _('Welcome to Joplin!\n\nType `:help shortcuts` for the list of keyboard shortcuts, or just `:help` for usage information.\n\nFor example, to create a notebook press `mb`; to create a note press `mn`.'); } reloadNote() { @@ -43,25 +42,24 @@ class NoteWidget extends TextWidget { this.text = this.welcomeText(); this.scrollTop = 0; } else if (this.noteId_) { - this.doAsync("loadNote", async () => { + this.doAsync('loadNote', async () => { this.note_ = await Note.load(this.noteId_); - + if (this.note_ && this.note_.encryption_applied) { - this.text = _( - "One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon." - ); + this.text = _('One or more items are currently encrypted and you may need to supply a master password. To do so please type `e2ee decrypt`. If you have already supplied the password, the encrypted items are being decrypted in the background and will be available soon.'); } else { - this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : ""; + this.text = this.note_ ? this.note_.title + "\n\n" + this.note_.body : ''; } if (this.lastLoadedNoteId_ !== this.noteId_) this.scrollTop = 0; this.lastLoadedNoteId_ = this.noteId_; }); } else { - this.text = ""; + this.text = ''; this.scrollTop = 0; } } + } -module.exports = NoteWidget; +module.exports = NoteWidget; \ No newline at end of file diff --git a/CliClient/app/gui/StatusBarWidget.js b/CliClient/app/gui/StatusBarWidget.js index 4e3d2270a..ae911da77 100644 --- a/CliClient/app/gui/StatusBarWidget.js +++ b/CliClient/app/gui/StatusBarWidget.js @@ -1,10 +1,11 @@ -const BaseWidget = require("tkwidgets/BaseWidget.js"); -const chalk = require("chalk"); -const termutils = require("tkwidgets/framework/termutils.js"); -const stripAnsi = require("strip-ansi"); -const { handleAutocompletion } = require("../autocompletion.js"); +const BaseWidget = require('tkwidgets/BaseWidget.js'); +const chalk = require('chalk'); +const termutils = require('tkwidgets/framework/termutils.js'); +const stripAnsi = require('strip-ansi'); +const { handleAutocompletion } = require('../autocompletion.js'); class StatusBarWidget extends BaseWidget { + constructor() { super(); @@ -15,7 +16,7 @@ class StatusBarWidget extends BaseWidget { } get name() { - return "statusBar"; + return 'statusBar'; } get canHaveFocus() { @@ -27,9 +28,9 @@ class StatusBarWidget extends BaseWidget { this.invalidate(); } - async prompt(initialText = "", promptString = null, options = null) { - if (this.promptState_) throw new Error("Another prompt already active"); - if (promptString === null) promptString = ":"; + async prompt(initialText = '', promptString = null, options = null) { + if (this.promptState_) throw new Error('Another prompt already active'); + if (promptString === null) promptString = ':'; if (options === null) options = {}; this.root.globalDisableKeyboard(this); @@ -40,8 +41,8 @@ class StatusBarWidget extends BaseWidget { promptString: stripAnsi(promptString), }; - if ("cursorPosition" in options) this.promptState_.cursorPosition = options.cursorPosition; - if ("secure" in options) this.promptState_.secure = options.secure; + if ('cursorPosition' in options) this.promptState_.cursorPosition = options.cursorPosition; + if ('secure' in options) this.promptState_.secure = options.secure; this.promptState_.promise = new Promise((resolve, reject) => { this.promptState_.resolve = resolve; @@ -74,7 +75,7 @@ class StatusBarWidget extends BaseWidget { super.render(); const doSaveCursor = !this.promptActive; - + if (doSaveCursor) this.term.saveCursor(); this.innerClear(); @@ -86,13 +87,14 @@ class StatusBarWidget extends BaseWidget { //const textStyle = this.promptActive ? (s) => s : chalk.bgBlueBright.white; //const textStyle = (s) => s; - const textStyle = this.promptActive ? s => s : chalk.gray; + const textStyle = this.promptActive ? (s) => s : chalk.gray; - this.term.drawHLine(this.absoluteInnerX, this.absoluteInnerY, this.innerWidth, textStyle(" ")); + this.term.drawHLine(this.absoluteInnerX, this.absoluteInnerY, this.innerWidth, textStyle(' ')); this.term.moveTo(this.absoluteInnerX, this.absoluteInnerY); if (this.promptActive) { + this.term.write(textStyle(this.promptState_.promptString)); if (this.inputEventEmitter_) { @@ -111,11 +113,11 @@ class StatusBarWidget extends BaseWidget { history: this.history, default: this.promptState_.initialText, autoComplete: handleAutocompletion, - autoCompleteHint: true, - autoCompleteMenu: true, + autoCompleteHint : true, + autoCompleteMenu : true, }; - if ("cursorPosition" in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition; + if ('cursorPosition' in this.promptState_) options.cursorPosition = this.promptState_.cursorPosition; if (isSecurePrompt) options.echoChar = true; this.inputEventEmitter_ = this.term.inputField(options, (error, input) => { @@ -123,7 +125,7 @@ class StatusBarWidget extends BaseWidget { const resolveFn = this.promptState_.resolve; if (error) { - this.logger().error("StatusBar: inputField error:", error); + this.logger().error('StatusBar: inputField error:', error); } else { if (input === undefined) { // User cancel @@ -131,7 +133,7 @@ class StatusBarWidget extends BaseWidget { resolveResult = input ? input.trim() : input; // Add the command to history but only if it's longer than one character. // Below that it's usually an answer like "y"/"n", etc. - const isConfigPassword = input.indexOf("config ") >= 0 && input.indexOf("password") >= 0; + const isConfigPassword = input.indexOf('config ') >= 0 && input.indexOf('password') >= 0; if (!isSecurePrompt && input && input.length > 1 && !isConfigPassword) this.history_.push(input); } } @@ -151,15 +153,19 @@ class StatusBarWidget extends BaseWidget { // Only callback once everything has been cleaned up and reset resolveFn(resolveResult); }); + } else { + for (let i = 0; i < this.items_.length; i++) { const s = this.items_[i].substr(0, this.innerWidth - 1); this.term.write(textStyle(s)); } + } if (doSaveCursor) this.term.restoreCursor(); } + } module.exports = StatusBarWidget; diff --git a/CliClient/app/help-utils.js b/CliClient/app/help-utils.js index 43ca1fe91..a56359fc7 100644 --- a/CliClient/app/help-utils.js +++ b/CliClient/app/help-utils.js @@ -1,12 +1,12 @@ -const fs = require("fs-extra"); -const { wrap } = require("lib/string-utils.js"); -const Setting = require("lib/models/Setting.js"); -const { fileExtension, basename, dirname } = require("lib/path-utils.js"); -const { _, setLocale, languageCode } = require("lib/locale.js"); +const fs = require('fs-extra'); +const { wrap } = require('lib/string-utils.js'); +const Setting = require('lib/models/Setting.js'); +const { fileExtension, basename, dirname } = require('lib/path-utils.js'); +const { _, setLocale, languageCode } = require('lib/locale.js'); const rootDir = dirname(dirname(__dirname)); const MAX_WIDTH = 78; -const INDENT = " "; +const INDENT = ' '; function renderTwoColumnData(options, baseIndent, width) { let output = []; @@ -15,8 +15,8 @@ function renderTwoColumnData(options, baseIndent, width) { for (let i = 0; i < options.length; i++) { let option = options[i]; const flag = option[0]; - const indent = baseIndent + INDENT + " ".repeat(optionColWidth + 2); - + const indent = baseIndent + INDENT + ' '.repeat(optionColWidth + 2); + let r = wrap(option[1], indent, width); r = r.substr(flag.length + (baseIndent + INDENT).length); r = baseIndent + INDENT + flag + r; @@ -29,61 +29,61 @@ function renderTwoColumnData(options, baseIndent, width) { function renderCommandHelp(cmd, width = null) { if (width === null) width = MAX_WIDTH; - const baseIndent = ""; + const baseIndent = ''; let output = []; output.push(baseIndent + cmd.usage()); - output.push(""); + output.push(''); output.push(wrap(cmd.description(), baseIndent + INDENT, width)); const optionString = renderTwoColumnData(cmd.options(), baseIndent, width); if (optionString) { - output.push(""); + output.push(''); output.push(optionString); } - if (cmd.name() === "config") { - const renderMetadata = md => { + if (cmd.name() === 'config') { + const renderMetadata = (md) => { let desc = []; if (md.label) { let label = md.label(); - if (label.length && label[label.length - 1] !== ".") label += "."; + if (label.length && label[label.length - 1] !== '.') label += '.'; desc.push(label); } - const description = Setting.keyDescription(md.key, "cli"); + const description = Setting.keyDescription(md.key, 'cli'); if (description) desc.push(description); - desc.push(_("Type: %s.", md.isEnum ? _("Enum") : Setting.typeToString(md.type))); - if (md.isEnum) desc.push(_("Possible values: %s.", Setting.enumOptionsDoc(md.key, "%s (%s)"))); + desc.push(_('Type: %s.', md.isEnum ? _('Enum') : Setting.typeToString(md.type))); + if (md.isEnum) desc.push(_('Possible values: %s.', Setting.enumOptionsDoc(md.key, '%s (%s)'))); let defaultString = null; - if ("value" in md) { + if ('value' in md) { if (md.type === Setting.TYPE_STRING) { defaultString = md.value ? '"' + md.value + '"' : null; } else if (md.type === Setting.TYPE_INT) { defaultString = (md.value ? md.value : 0).toString(); } else if (md.type === Setting.TYPE_BOOL) { - defaultString = md.value === true ? "true" : "false"; + defaultString = (md.value === true ? 'true' : 'false'); } } - - if (defaultString !== null) desc.push(_("Default: %s", defaultString)); + + if (defaultString !== null) desc.push(_('Default: %s', defaultString)); return [md.key, desc.join("\n")]; }; - output.push(""); - output.push(_("Possible keys/values:")); - output.push(""); + output.push(''); + output.push(_('Possible keys/values:')); + output.push(''); let keysValues = []; - const keys = Setting.keys(true, "cli"); + const keys = Setting.keys(true, 'cli'); for (let i = 0; i < keys.length; i++) { - if (keysValues.length) keysValues.push(["", ""]); + if (keysValues.length) keysValues.push(['','']); const md = Setting.settingMetadata(keys[i]); if (!md.label) continue; keysValues.push(renderMetadata(md)); @@ -91,7 +91,7 @@ function renderCommandHelp(cmd, width = null) { output.push(renderTwoColumnData(keysValues, baseIndent, width)); } - + return output.join("\n"); } @@ -104,4 +104,4 @@ function getOptionColWidth(options) { return output; } -module.exports = { renderCommandHelp }; +module.exports = { renderCommandHelp }; \ No newline at end of file diff --git a/CliClient/app/main.js b/CliClient/app/main.js index c2092d03f..958314e7d 100644 --- a/CliClient/app/main.js +++ b/CliClient/app/main.js @@ -1,30 +1,30 @@ #!/usr/bin/env node // Make it possible to require("/lib/...") without specifying full path -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const compareVersion = require("compare-version"); -const nodeVersion = process && process.versions && process.versions.node ? process.versions.node : "0.0.0"; -if (compareVersion(nodeVersion, "8.0.0") < 0) { - console.error("Joplin requires Node 8+. Detected version " + nodeVersion); +const compareVersion = require('compare-version'); +const nodeVersion = process && process.versions && process.versions.node ? process.versions.node : '0.0.0'; +if (compareVersion(nodeVersion, '8.0.0') < 0) { + console.error('Joplin requires Node 8+. Detected version ' + nodeVersion); process.exit(1); } -const { app } = require("./app.js"); -const Folder = require("lib/models/Folder.js"); -const Resource = require("lib/models/Resource.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const Note = require("lib/models/Note.js"); -const Tag = require("lib/models/Tag.js"); -const NoteTag = require("lib/models/NoteTag.js"); -const MasterKey = require("lib/models/MasterKey"); -const Setting = require("lib/models/Setting.js"); -const { Logger } = require("lib/logger.js"); -const { FsDriverNode } = require("lib/fs-driver-node.js"); -const { shimInit } = require("lib/shim-init-node.js"); -const { _ } = require("lib/locale.js"); -const { FileApiDriverLocal } = require("lib/file-api-driver-local.js"); -const EncryptionService = require("lib/services/EncryptionService"); +const { app } = require('./app.js'); +const Folder = require('lib/models/Folder.js'); +const Resource = require('lib/models/Resource.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); +const NoteTag = require('lib/models/NoteTag.js'); +const MasterKey = require('lib/models/MasterKey'); +const Setting = require('lib/models/Setting.js'); +const { Logger } = require('lib/logger.js'); +const { FsDriverNode } = require('lib/fs-driver-node.js'); +const { shimInit } = require('lib/shim-init-node.js'); +const { _ } = require('lib/locale.js'); +const { FileApiDriverLocal } = require('lib/file-api-driver-local.js'); +const EncryptionService = require('lib/services/EncryptionService'); const fsDriver = new FsDriverNode(); Logger.fsDriver_ = fsDriver; @@ -34,15 +34,15 @@ FileApiDriverLocal.fsDriver_ = fsDriver; // That's not good, but it's to avoid circular dependency issues // in the BaseItem class. -BaseItem.loadClass("Note", Note); -BaseItem.loadClass("Folder", Folder); -BaseItem.loadClass("Resource", Resource); -BaseItem.loadClass("Tag", Tag); -BaseItem.loadClass("NoteTag", NoteTag); -BaseItem.loadClass("MasterKey", MasterKey); +BaseItem.loadClass('Note', Note); +BaseItem.loadClass('Folder', Folder); +BaseItem.loadClass('Resource', Resource); +BaseItem.loadClass('Tag', Tag); +BaseItem.loadClass('NoteTag', NoteTag); +BaseItem.loadClass('MasterKey', MasterKey); -Setting.setConstant("appId", "net.cozic.joplin-cli"); -Setting.setConstant("appType", "cli"); +Setting.setConstant('appId', 'net.cozic.joplin-cli'); +Setting.setConstant('appType', 'cli'); shimInit(); @@ -51,21 +51,25 @@ const application = app(); if (process.platform === "win32") { var rl = require("readline").createInterface({ input: process.stdin, - output: process.stdout, + output: process.stdout }); - rl.on("SIGINT", function() { + rl.on("SIGINT", function () { process.emit("SIGINT"); }); } -process.stdout.on("error", function(err) { +process.stdout.on('error', function( err ) { // https://stackoverflow.com/questions/12329816/error-write-epipe-when-piping-node-output-to-head#15884508 if (err.code == "EPIPE") { process.exit(0); } }); + + + + // async function main() { // const InteropService = require('lib/services/InteropService'); // const service = new InteropService(); @@ -75,14 +79,21 @@ process.stdout.on("error", function(err) { // main().catch((error) => { console.error(error); }); -application.start(process.argv).catch(error => { - if (error.code == "flagError") { + + + + + + + +application.start(process.argv).catch((error) => { + if (error.code == 'flagError') { console.error(error.message); - console.error(_("Type `joplin help` for usage information.")); + console.error(_('Type `joplin help` for usage information.')); } else { - console.error(_("Fatal error:")); + console.error(_('Fatal error:')); console.error(error); } process.exit(1); -}); +}); \ No newline at end of file diff --git a/CliClient/app/onedrive-api-node-utils.js b/CliClient/app/onedrive-api-node-utils.js index e054a69a3..b9ba88183 100644 --- a/CliClient/app/onedrive-api-node-utils.js +++ b/CliClient/app/onedrive-api-node-utils.js @@ -1,12 +1,13 @@ -const { _ } = require("lib/locale.js"); -const { netUtils } = require("lib/net-utils.js"); +const { _ } = require('lib/locale.js'); +const { netUtils } = require('lib/net-utils.js'); const http = require("http"); const urlParser = require("url"); -const FormData = require("form-data"); -const enableServerDestroy = require("server-destroy"); +const FormData = require('form-data'); +const enableServerDestroy = require('server-destroy'); class OneDriveApiNodeUtils { + constructor(api) { this.api_ = api; this.oauthServer_ = null; @@ -43,19 +44,19 @@ class OneDriveApiNodeUtils { this.api().setAuth(null); const port = await netUtils.findAvailablePort(this.possibleOAuthDancePorts(), 0); - if (!port) throw new Error(_("All potential ports are in use - please report the issue at %s", "https://github.com/laurent22/joplin")); + if (!port) throw new Error(_('All potential ports are in use - please report the issue at %s', 'https://github.com/laurent22/joplin')); - let authCodeUrl = this.api().authCodeUrl("http://localhost:" + port); + let authCodeUrl = this.api().authCodeUrl('http://localhost:' + port); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this.oauthServer_ = http.createServer(); let errorMessage = null; - this.oauthServer_.on("request", (request, response) => { + this.oauthServer_.on('request', (request, response) => { const url = urlParser.parse(request.url, true); - if (url.pathname === "/auth") { - response.writeHead(302, { Location: authCodeUrl }); + if (url.pathname === '/auth') { + response.writeHead(302, { 'Location': authCodeUrl }); response.end(); return; } @@ -63,10 +64,10 @@ class OneDriveApiNodeUtils { const query = url.query; const writeResponse = (code, message) => { - response.writeHead(code, { "Content-Type": "text/html" }); + response.writeHead(code, {"Content-Type": "text/html"}); response.write(this.makePage(message)); response.end(); - }; + } // After the response has been received, don't destroy the server right // away or the browser might display a connection reset error (even @@ -76,27 +77,24 @@ class OneDriveApiNodeUtils { this.oauthServer_.destroy(); this.oauthServer_ = null; }, 1000); - }; + } if (!query.code) return writeResponse(400, '"code" query parameter is missing'); - this.api() - .execTokenRequest(query.code, "http://localhost:" + port.toString()) - .then(() => { - writeResponse(200, _("The application has been authorised - you may now close this browser tab.")); - targetConsole.log(""); - targetConsole.log(_("The application has been successfully authorised.")); - waitAndDestroy(); - }) - .catch(error => { - writeResponse(400, error.message); - targetConsole.log(""); - targetConsole.log(error.message); - waitAndDestroy(); - }); + this.api().execTokenRequest(query.code, 'http://localhost:' + port.toString()).then(() => { + writeResponse(200, _('The application has been authorised - you may now close this browser tab.')); + targetConsole.log(''); + targetConsole.log(_('The application has been successfully authorised.')); + waitAndDestroy(); + }).catch((error) => { + writeResponse(400, error.message); + targetConsole.log(''); + targetConsole.log(error.message); + waitAndDestroy(); + }); }); - this.oauthServer_.on("close", () => { + this.oauthServer_.on('close', () => { if (errorMessage) { reject(new Error(errorMessage)); } else { @@ -113,15 +111,12 @@ class OneDriveApiNodeUtils { // doesn't get cut in terminals (especially those that don't handle multi // lines URLs). - targetConsole.log( - _( - 'Please open the following URL in your browser to authenticate the application. The application will create a directory in "Apps/Joplin" and will only read and write files in this directory. It will have no access to any files outside this directory nor to any other personal data. No data will be shared with any third party.' - ) - ); - targetConsole.log(""); - targetConsole.log("http://127.0.0.1:" + port + "/auth"); + targetConsole.log(_('Please open the following URL in your browser to authenticate the application. The application will create a directory in "Apps/Joplin" and will only read and write files in this directory. It will have no access to any files outside this directory nor to any other personal data. No data will be shared with any third party.')); + targetConsole.log(''); + targetConsole.log('http://127.0.0.1:' + port + '/auth'); }); } + } -module.exports = { OneDriveApiNodeUtils }; +module.exports = { OneDriveApiNodeUtils }; \ No newline at end of file diff --git a/CliClient/tests/ArrayUtils.js b/CliClient/tests/ArrayUtils.js index bb5bceb9a..bef63a8b8 100644 --- a/CliClient/tests/ArrayUtils.js +++ b/CliClient/tests/ArrayUtils.js @@ -1,58 +1,47 @@ -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const { time } = require("lib/time-utils.js"); -const { - fileContentEqual, - setupDatabase, - setupDatabaseAndSynchronizer, - db, - synchronizer, - fileApi, - sleep, - clearDatabase, - switchClient, - syncTargetId, - objectsEqual, - checkThrowAsync, -} = require("test-utils.js"); -const ArrayUtils = require("lib/ArrayUtils.js"); +const { time } = require('lib/time-utils.js'); +const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); +const ArrayUtils = require('lib/ArrayUtils.js'); -process.on("unhandledRejection", (reason, p) => { - console.log("Unhandled Rejection at: Promise", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); }); -describe("ArrayUtils", function() { - beforeEach(async done => { +describe('ArrayUtils', function() { + + beforeEach(async (done) => { done(); }); - it("should remove array elements", async done => { - let a = ["un", "deux", "trois"]; - a = ArrayUtils.removeElement(a, "deux"); + it('should remove array elements', async (done) => { + let a = ['un', 'deux', 'trois']; + a = ArrayUtils.removeElement(a, 'deux'); - expect(a[0]).toBe("un"); - expect(a[1]).toBe("trois"); + expect(a[0]).toBe('un'); + expect(a[1]).toBe('trois'); expect(a.length).toBe(2); - a = ["un", "deux", "trois"]; - a = ArrayUtils.removeElement(a, "not in there"); + a = ['un', 'deux', 'trois']; + a = ArrayUtils.removeElement(a, 'not in there'); expect(a.length).toBe(3); done(); }); - it("should find items using binary search", async done => { - let items = ["aaa", "ccc", "bbb"]; - expect(ArrayUtils.binarySearch(items, "bbb")).toBe(-1); // Array not sorted! + it('should find items using binary search', async (done) => { + let items = ['aaa', 'ccc', 'bbb']; + expect(ArrayUtils.binarySearch(items, 'bbb')).toBe(-1); // Array not sorted! items.sort(); - expect(ArrayUtils.binarySearch(items, "bbb")).toBe(1); - expect(ArrayUtils.binarySearch(items, "ccc")).toBe(2); - expect(ArrayUtils.binarySearch(items, "oops")).toBe(-1); - expect(ArrayUtils.binarySearch(items, "aaa")).toBe(0); + expect(ArrayUtils.binarySearch(items, 'bbb')).toBe(1); + expect(ArrayUtils.binarySearch(items, 'ccc')).toBe(2); + expect(ArrayUtils.binarySearch(items, 'oops')).toBe(-1); + expect(ArrayUtils.binarySearch(items, 'aaa')).toBe(0); items = []; - expect(ArrayUtils.binarySearch(items, "aaa")).toBe(-1); + expect(ArrayUtils.binarySearch(items, 'aaa')).toBe(-1); done(); }); -}); + +}); \ No newline at end of file diff --git a/CliClient/tests/encryption.js b/CliClient/tests/encryption.js index 326d60806..63ca4a417 100644 --- a/CliClient/tests/encryption.js +++ b/CliClient/tests/encryption.js @@ -1,54 +1,42 @@ -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const { time } = require("lib/time-utils.js"); -const { - fileContentEqual, - setupDatabase, - setupDatabaseAndSynchronizer, - db, - synchronizer, - fileApi, - sleep, - clearDatabase, - switchClient, - syncTargetId, - objectsEqual, - checkThrowAsync, -} = require("test-utils.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const Tag = require("lib/models/Tag.js"); -const { Database } = require("lib/database.js"); -const Setting = require("lib/models/Setting.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const BaseModel = require("lib/BaseModel.js"); -const MasterKey = require("lib/models/MasterKey"); -const SyncTargetRegistry = require("lib/SyncTargetRegistry.js"); -const EncryptionService = require("lib/services/EncryptionService.js"); +const { time } = require('lib/time-utils.js'); +const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); +const { Database } = require('lib/database.js'); +const Setting = require('lib/models/Setting.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const BaseModel = require('lib/BaseModel.js'); +const MasterKey = require('lib/models/MasterKey'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); +const EncryptionService = require('lib/services/EncryptionService.js'); -process.on("unhandledRejection", (reason, p) => { - console.log("Unhandled Rejection at: Promise", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); }); jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; // The first test is slow because the database needs to be built let service = null; -describe("Encryption", function() { - beforeEach(async done => { +describe('Encryption', function() { + + beforeEach(async (done) => { await setupDatabaseAndSynchronizer(1); //await setupDatabaseAndSynchronizer(2); //await switchClient(1); - service = new EncryptionService(); + service = new EncryptionService(); BaseItem.encryptionService_ = service; - Setting.setValue("encryption.enabled", true); + Setting.setValue('encryption.enabled', true); done(); }); - it("should encode and decode header", async done => { + it('should encode and decode header', async (done) => { const header = { encryptionMethod: EncryptionService.METHOD_SJCL, - masterKeyId: "01234568abcdefgh01234568abcdefgh", + masterKeyId: '01234568abcdefgh01234568abcdefgh', }; const encodedHeader = service.encodeHeader_(header); @@ -60,40 +48,40 @@ describe("Encryption", function() { done(); }); - it("should generate and decrypt a master key", async done => { - const masterKey = await service.generateMasterKey("123456"); + it('should generate and decrypt a master key', async (done) => { + const masterKey = await service.generateMasterKey('123456'); expect(!!masterKey.checksum).toBe(true); expect(!!masterKey.content).toBe(true); let hasThrown = false; try { - await service.decryptMasterKey(masterKey, "wrongpassword"); + await service.decryptMasterKey(masterKey, 'wrongpassword'); } catch (error) { hasThrown = true; } expect(hasThrown).toBe(true); - const decryptedMasterKey = await service.decryptMasterKey(masterKey, "123456"); + const decryptedMasterKey = await service.decryptMasterKey(masterKey, '123456'); expect(decryptedMasterKey.length).toBe(512); done(); }); - it("should encrypt and decrypt with a master key", async done => { - let masterKey = await service.generateMasterKey("123456"); + it('should encrypt and decrypt with a master key', async (done) => { + let masterKey = await service.generateMasterKey('123456'); masterKey = await MasterKey.save(masterKey); - await service.loadMasterKey(masterKey, "123456", true); + await service.loadMasterKey(masterKey, '123456', true); - const cipherText = await service.encryptString("some secret"); + const cipherText = await service.encryptString('some secret'); const plainText = await service.decryptString(cipherText); - expect(plainText).toBe("some secret"); + expect(plainText).toBe('some secret'); // Test that a long string, that is going to be split into multiple chunks, encrypt // and decrypt properly too. - let veryLongSecret = ""; + let veryLongSecret = ''; for (let i = 0; i < service.chunkSize() * 3; i++) veryLongSecret += Math.floor(Math.random() * 9); const cipherText2 = await service.encryptString(veryLongSecret); @@ -104,13 +92,13 @@ describe("Encryption", function() { done(); }); - it("should fail to decrypt if master key not present", async done => { - let masterKey = await service.generateMasterKey("123456"); + it('should fail to decrypt if master key not present', async (done) => { + let masterKey = await service.generateMasterKey('123456'); masterKey = await MasterKey.save(masterKey); - await service.loadMasterKey(masterKey, "123456", true); + await service.loadMasterKey(masterKey, '123456', true); - const cipherText = await service.encryptString("some secret"); + const cipherText = await service.encryptString('some secret'); await service.unloadMasterKey(masterKey); @@ -121,13 +109,14 @@ describe("Encryption", function() { done(); }); - it("should fail to decrypt if data tampered with", async done => { - let masterKey = await service.generateMasterKey("123456"); + + it('should fail to decrypt if data tampered with', async (done) => { + let masterKey = await service.generateMasterKey('123456'); masterKey = await MasterKey.save(masterKey); - await service.loadMasterKey(masterKey, "123456", true); + await service.loadMasterKey(masterKey, '123456', true); - let cipherText = await service.encryptString("some secret"); + let cipherText = await service.encryptString('some secret'); cipherText += "ABCDEFGHIJ"; let hasThrown = await checkThrowAsync(async () => await service.decryptString(cipherText)); @@ -137,13 +126,13 @@ describe("Encryption", function() { done(); }); - it("should encrypt and decrypt notes and folders", async done => { - let masterKey = await service.generateMasterKey("123456"); + it('should encrypt and decrypt notes and folders', async (done) => { + let masterKey = await service.generateMasterKey('123456'); masterKey = await MasterKey.save(masterKey); - await service.loadMasterKey(masterKey, "123456", true); + await service.loadMasterKey(masterKey, '123456', true); - let folder = await Folder.save({ title: "folder" }); - let note = await Note.save({ title: "encrypted note", body: "something", parent_id: folder.id }); + let folder = await Folder.save({ title: 'folder' }); + let note = await Note.save({ title: 'encrypted note', body: 'something', parent_id: folder.id }); let serialized = await Note.serializeForSync(note); let deserialized = Note.filter(await Note.unserialize(serialized)); @@ -170,14 +159,14 @@ describe("Encryption", function() { done(); }); - it("should encrypt and decrypt files", async done => { - let masterKey = await service.generateMasterKey("123456"); + it('should encrypt and decrypt files', async (done) => { + let masterKey = await service.generateMasterKey('123456'); masterKey = await MasterKey.save(masterKey); - await service.loadMasterKey(masterKey, "123456", true); + await service.loadMasterKey(masterKey, '123456', true); - const sourcePath = __dirname + "/../tests/support/photo.jpg"; - const encryptedPath = __dirname + "/data/photo.crypted"; - const decryptedPath = __dirname + "/data/photo.jpg"; + const sourcePath = __dirname + '/../tests/support/photo.jpg'; + const encryptedPath = __dirname + '/data/photo.crypted'; + const decryptedPath = __dirname + '/data/photo.jpg'; await service.encryptFile(sourcePath, encryptedPath); await service.decryptFile(encryptedPath, decryptedPath); @@ -187,4 +176,5 @@ describe("Encryption", function() { done(); }); -}); + +}); \ No newline at end of file diff --git a/CliClient/tests/models_Setting.js b/CliClient/tests/models_Setting.js index 03aedf7e3..53cce0439 100644 --- a/CliClient/tests/models_Setting.js +++ b/CliClient/tests/models_Setting.js @@ -1,47 +1,32 @@ -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const { time } = require("lib/time-utils.js"); -const { - asyncTest, - fileContentEqual, - setupDatabase, - setupDatabaseAndSynchronizer, - db, - synchronizer, - fileApi, - sleep, - clearDatabase, - switchClient, - syncTargetId, - objectsEqual, - checkThrowAsync, -} = require("test-utils.js"); -const Setting = require("lib/models/Setting.js"); +const { time } = require('lib/time-utils.js'); +const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); +const Setting = require('lib/models/Setting.js'); -process.on("unhandledRejection", (reason, p) => { - console.log("Unhandled Rejection at: Promise", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); }); -describe("models_Setting", function() { - beforeEach(async done => { +describe('models_Setting', function() { + + beforeEach(async (done) => { done(); }); - it( - "should return only sub-values", - asyncTest(async () => { - const settings = { - "sync.5.path": "http://example.com", - "sync.5.username": "testing", - }; + it('should return only sub-values', asyncTest(async () => { + const settings = { + 'sync.5.path': 'http://example.com', + 'sync.5.username': 'testing', + } - let output = Setting.subValues("sync.5", settings); - expect(output["path"]).toBe("http://example.com"); - expect(output["username"]).toBe("testing"); + let output = Setting.subValues('sync.5', settings); + expect(output['path']).toBe('http://example.com'); + expect(output['username']).toBe('testing'); - output = Setting.subValues("sync.4", settings); - expect("path" in output).toBe(false); - expect("username" in output).toBe(false); - }) - ); -}); + output = Setting.subValues('sync.4', settings); + expect('path' in output).toBe(false); + expect('username' in output).toBe(false); + })); + +}); \ No newline at end of file diff --git a/CliClient/tests/services_InteropService.js b/CliClient/tests/services_InteropService.js index 32b53ed9a..2c0582e57 100644 --- a/CliClient/tests/services_InteropService.js +++ b/CliClient/tests/services_InteropService.js @@ -1,49 +1,36 @@ -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const { time } = require("lib/time-utils.js"); -const { - asyncTest, - fileContentEqual, - setupDatabase, - setupDatabaseAndSynchronizer, - db, - synchronizer, - fileApi, - sleep, - clearDatabase, - switchClient, - syncTargetId, - objectsEqual, - checkThrowAsync, -} = require("test-utils.js"); -const InteropService = require("lib/services/InteropService.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const Tag = require("lib/models/Tag.js"); -const NoteTag = require("lib/models/NoteTag.js"); -const Resource = require("lib/models/Resource.js"); -const fs = require("fs-extra"); -const ArrayUtils = require("lib/ArrayUtils"); -const ObjectUtils = require("lib/ObjectUtils"); -const { shim } = require("lib/shim.js"); +const { time } = require('lib/time-utils.js'); +const { asyncTest, fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js'); +const InteropService = require('lib/services/InteropService.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); +const NoteTag = require('lib/models/NoteTag.js'); +const Resource = require('lib/models/Resource.js'); +const fs = require('fs-extra'); +const ArrayUtils = require('lib/ArrayUtils'); +const ObjectUtils = require('lib/ObjectUtils'); +const { shim } = require('lib/shim.js'); -process.on("unhandledRejection", (reason, p) => { - console.log("Unhandled Rejection at: Promise", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); }); function exportDir() { - return __dirname + "/export"; + return __dirname + '/export'; } function fieldsEqual(model1, model2, fieldNames) { for (let i = 0; i < fieldNames.length; i++) { const f = fieldNames[i]; - expect(model1[f]).toBe(model2[f], "For key " + f); + expect(model1[f]).toBe(model2[f], 'For key ' + f); } } -describe("services_InteropService", function() { - beforeEach(async done => { +describe('services_InteropService', function() { + + beforeEach(async (done) => { await setupDatabaseAndSynchronizer(1); await switchClient(1); @@ -53,233 +40,213 @@ describe("services_InteropService", function() { done(); }); - it( - "should export and import folders", - asyncTest(async () => { - const service = new InteropService(); - let folder1 = await Folder.save({ title: "folder1" }); - folder1 = await Folder.load(folder1.id); - const filePath = exportDir() + "/test.jex"; + it('should export and import folders', asyncTest(async () => { + const service = new InteropService(); + let folder1 = await Folder.save({ title: "folder1" }); + folder1 = await Folder.load(folder1.id); + const filePath = exportDir() + '/test.jex'; - await service.export({ path: filePath }); + await service.export({ path: filePath }); - await Folder.delete(folder1.id); + await Folder.delete(folder1.id); - await service.import({ path: filePath }); + await service.import({ path: filePath }); - // Check that a new folder, with a new ID, has been created + // Check that a new folder, with a new ID, has been created - expect(await Folder.count()).toBe(1); - let folder2 = (await Folder.all())[0]; - expect(folder2.id).not.toBe(folder1.id); - expect(folder2.title).toBe(folder1.title); + expect(await Folder.count()).toBe(1); + let folder2 = (await Folder.all())[0]; + expect(folder2.id).not.toBe(folder1.id); + expect(folder2.title).toBe(folder1.title); - await service.import({ path: filePath }); + await service.import({ path: filePath }); - // As there was already a folder with the same title, check that the new one has been renamed + // As there was already a folder with the same title, check that the new one has been renamed - await Folder.delete(folder2.id); - let folder3 = (await Folder.all())[0]; - expect(await Folder.count()).toBe(1); - expect(folder3.title).not.toBe(folder2.title); + await Folder.delete(folder2.id); + let folder3 = (await Folder.all())[0]; + expect(await Folder.count()).toBe(1); + expect(folder3.title).not.toBe(folder2.title); - let fieldNames = Folder.fieldNames(); - fieldNames = ArrayUtils.removeElement(fieldNames, "id"); - fieldNames = ArrayUtils.removeElement(fieldNames, "title"); + let fieldNames = Folder.fieldNames(); + fieldNames = ArrayUtils.removeElement(fieldNames, 'id'); + fieldNames = ArrayUtils.removeElement(fieldNames, 'title'); - fieldsEqual(folder3, folder1, fieldNames); - }) - ); + fieldsEqual(folder3, folder1, fieldNames); + })); - it( - "should export and import folders and notes", - asyncTest(async () => { - const service = new InteropService(); - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - note1 = await Note.load(note1.id); - const filePath = exportDir() + "/test.jex"; + it('should export and import folders and notes', asyncTest(async () => { + const service = new InteropService(); + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + note1 = await Note.load(note1.id); + const filePath = exportDir() + '/test.jex'; - await service.export({ path: filePath }); + await service.export({ path: filePath }); - await Folder.delete(folder1.id); - await Note.delete(note1.id); + await Folder.delete(folder1.id); + await Note.delete(note1.id); - await service.import({ path: filePath }); + await service.import({ path: filePath }); - expect(await Note.count()).toBe(1); - let note2 = (await Note.all())[0]; - let folder2 = (await Folder.all())[0]; + expect(await Note.count()).toBe(1); + let note2 = (await Note.all())[0]; + let folder2 = (await Folder.all())[0]; - expect(note1.parent_id).not.toBe(note2.parent_id); - expect(note1.id).not.toBe(note2.id); - expect(note2.parent_id).toBe(folder2.id); + expect(note1.parent_id).not.toBe(note2.parent_id); + expect(note1.id).not.toBe(note2.id); + expect(note2.parent_id).toBe(folder2.id); - let fieldNames = Note.fieldNames(); - fieldNames = ArrayUtils.removeElement(fieldNames, "id"); - fieldNames = ArrayUtils.removeElement(fieldNames, "parent_id"); + let fieldNames = Note.fieldNames(); + fieldNames = ArrayUtils.removeElement(fieldNames, 'id'); + fieldNames = ArrayUtils.removeElement(fieldNames, 'parent_id'); - fieldsEqual(note1, note2, fieldNames); + fieldsEqual(note1, note2, fieldNames); - await service.import({ path: filePath }); + await service.import({ path: filePath }); - note2 = (await Note.all())[0]; - let note3 = (await Note.all())[1]; + note2 = (await Note.all())[0]; + let note3 = (await Note.all())[1]; - expect(note2.id).not.toBe(note3.id); - expect(note2.parent_id).not.toBe(note3.parent_id); + expect(note2.id).not.toBe(note3.id); + expect(note2.parent_id).not.toBe(note3.parent_id); - fieldsEqual(note2, note3, fieldNames); - }) - ); + fieldsEqual(note2, note3, fieldNames); + })); - it( - "should export and import notes to specific folder", - asyncTest(async () => { - const service = new InteropService(); - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - note1 = await Note.load(note1.id); - const filePath = exportDir() + "/test.jex"; + it('should export and import notes to specific folder', asyncTest(async () => { + const service = new InteropService(); + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + note1 = await Note.load(note1.id); + const filePath = exportDir() + '/test.jex'; - await service.export({ path: filePath }); + await service.export({ path: filePath }); - await Note.delete(note1.id); + await Note.delete(note1.id); - await service.import({ path: filePath, destinationFolderId: folder1.id }); + await service.import({ path: filePath, destinationFolderId: folder1.id }); - expect(await Note.count()).toBe(1); - expect(await Folder.count()).toBe(1); + expect(await Note.count()).toBe(1); + expect(await Folder.count()).toBe(1); - expect(await checkThrowAsync(async () => await service.import({ path: filePath, destinationFolderId: "oops" }))).toBe(true); - }) - ); + expect(await checkThrowAsync(async () => await service.import({ path: filePath, destinationFolderId: 'oops' }))).toBe(true); + })); - it( - "should export and import tags", - asyncTest(async () => { - const service = new InteropService(); - const filePath = exportDir() + "/test.jex"; - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - let tag1 = await Tag.save({ title: "mon tag" }); - tag1 = await Tag.load(tag1.id); - await Tag.addNote(tag1.id, note1.id); + it('should export and import tags', asyncTest(async () => { + const service = new InteropService(); + const filePath = exportDir() + '/test.jex'; + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + let tag1 = await Tag.save({ title: 'mon tag' }); + tag1 = await Tag.load(tag1.id); + await Tag.addNote(tag1.id, note1.id); - await service.export({ path: filePath }); + await service.export({ path: filePath }); - await Folder.delete(folder1.id); - await Note.delete(note1.id); - await Tag.delete(tag1.id); + await Folder.delete(folder1.id); + await Note.delete(note1.id); + await Tag.delete(tag1.id); - await service.import({ path: filePath }); + await service.import({ path: filePath }); - expect(await Tag.count()).toBe(1); - let tag2 = (await Tag.all())[0]; - let note2 = (await Note.all())[0]; - expect(tag1.id).not.toBe(tag2.id); + expect(await Tag.count()).toBe(1); + let tag2 = (await Tag.all())[0]; + let note2 = (await Note.all())[0]; + expect(tag1.id).not.toBe(tag2.id); - let fieldNames = Note.fieldNames(); - fieldNames = ArrayUtils.removeElement(fieldNames, "id"); - fieldsEqual(tag1, tag2, fieldNames); + let fieldNames = Note.fieldNames(); + fieldNames = ArrayUtils.removeElement(fieldNames, 'id'); + fieldsEqual(tag1, tag2, fieldNames); - let noteIds = await Tag.noteIds(tag2.id); - expect(noteIds.length).toBe(1); - expect(noteIds[0]).toBe(note2.id); + let noteIds = await Tag.noteIds(tag2.id); + expect(noteIds.length).toBe(1); + expect(noteIds[0]).toBe(note2.id); - await service.import({ path: filePath }); + await service.import({ path: filePath }); - // If importing again, no new tag should be created as one with - // the same name already existed. The newly imported note should - // however go under that already existing tag. - expect(await Tag.count()).toBe(1); - noteIds = await Tag.noteIds(tag2.id); - expect(noteIds.length).toBe(2); - }) - ); + // If importing again, no new tag should be created as one with + // the same name already existed. The newly imported note should + // however go under that already existing tag. + expect(await Tag.count()).toBe(1); + noteIds = await Tag.noteIds(tag2.id); + expect(noteIds.length).toBe(2); + })); - it( - "should export and import resources", - asyncTest(async () => { - const service = new InteropService(); - const filePath = exportDir() + "/test.jex"; - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - await shim.attachFileToNote(note1, __dirname + "/../tests/support/photo.jpg"); - note1 = await Note.load(note1.id); - let resourceIds = Note.linkedResourceIds(note1.body); - let resource1 = await Resource.load(resourceIds[0]); + it('should export and import resources', asyncTest(async () => { + const service = new InteropService(); + const filePath = exportDir() + '/test.jex'; + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg'); + note1 = await Note.load(note1.id); + let resourceIds = Note.linkedResourceIds(note1.body); + let resource1 = await Resource.load(resourceIds[0]); - await service.export({ path: filePath }); + await service.export({ path: filePath }); + + await Note.delete(note1.id); - await Note.delete(note1.id); + await service.import({ path: filePath }); - await service.import({ path: filePath }); + expect(await Resource.count()).toBe(2); - expect(await Resource.count()).toBe(2); + let note2 = (await Note.all())[0]; + expect(note2.body).not.toBe(note1.body); + resourceIds = Note.linkedResourceIds(note2.body); + expect(resourceIds.length).toBe(1); + let resource2 = await Resource.load(resourceIds[0]); + expect(resource2.id).not.toBe(resource1.id); - let note2 = (await Note.all())[0]; - expect(note2.body).not.toBe(note1.body); - resourceIds = Note.linkedResourceIds(note2.body); - expect(resourceIds.length).toBe(1); - let resource2 = await Resource.load(resourceIds[0]); - expect(resource2.id).not.toBe(resource1.id); + let fieldNames = Note.fieldNames(); + fieldNames = ArrayUtils.removeElement(fieldNames, 'id'); + fieldsEqual(resource1, resource2, fieldNames); - let fieldNames = Note.fieldNames(); - fieldNames = ArrayUtils.removeElement(fieldNames, "id"); - fieldsEqual(resource1, resource2, fieldNames); + const resourcePath1 = Resource.fullPath(resource1); + const resourcePath2 = Resource.fullPath(resource2); - const resourcePath1 = Resource.fullPath(resource1); - const resourcePath2 = Resource.fullPath(resource2); + expect(resourcePath1).not.toBe(resourcePath2); + expect(fileContentEqual(resourcePath1, resourcePath2)).toBe(true); + })); - expect(resourcePath1).not.toBe(resourcePath2); - expect(fileContentEqual(resourcePath1, resourcePath2)).toBe(true); - }) - ); + it('should export and import single notes', asyncTest(async () => { + const service = new InteropService(); + const filePath = exportDir() + '/test.jex'; + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); - it( - "should export and import single notes", - asyncTest(async () => { - const service = new InteropService(); - const filePath = exportDir() + "/test.jex"; - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); + await service.export({ path: filePath, sourceNoteIds: [note1.id] }); + + await Note.delete(note1.id); + await Folder.delete(folder1.id); - await service.export({ path: filePath, sourceNoteIds: [note1.id] }); + await service.import({ path: filePath }); - await Note.delete(note1.id); - await Folder.delete(folder1.id); + expect(await Note.count()).toBe(1); + expect(await Folder.count()).toBe(1); - await service.import({ path: filePath }); + let folder2 = (await Folder.all())[0]; + expect(folder2.title).toBe('test'); + })); - expect(await Note.count()).toBe(1); - expect(await Folder.count()).toBe(1); + it('should export and import single folders', asyncTest(async () => { + const service = new InteropService(); + const filePath = exportDir() + '/test.jex'; + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); - let folder2 = (await Folder.all())[0]; - expect(folder2.title).toBe("test"); - }) - ); + await service.export({ path: filePath, sourceFolderIds: [folder1.id] }); + + await Note.delete(note1.id); + await Folder.delete(folder1.id); - it( - "should export and import single folders", - asyncTest(async () => { - const service = new InteropService(); - const filePath = exportDir() + "/test.jex"; - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); + await service.import({ path: filePath }); - await service.export({ path: filePath, sourceFolderIds: [folder1.id] }); + expect(await Note.count()).toBe(1); + expect(await Folder.count()).toBe(1); - await Note.delete(note1.id); - await Folder.delete(folder1.id); + let folder2 = (await Folder.all())[0]; + expect(folder2.title).toBe('folder1'); + })); - await service.import({ path: filePath }); - - expect(await Note.count()).toBe(1); - expect(await Folder.count()).toBe(1); - - let folder2 = (await Folder.all())[0]; - expect(folder2.title).toBe("folder1"); - }) - ); -}); +}); \ No newline at end of file diff --git a/CliClient/tests/synchronizer.js b/CliClient/tests/synchronizer.js index 2f4703089..b99a72d55 100644 --- a/CliClient/tests/synchronizer.js +++ b/CliClient/tests/synchronizer.js @@ -1,38 +1,22 @@ -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const { time } = require("lib/time-utils.js"); -const { - setupDatabase, - setupDatabaseAndSynchronizer, - db, - synchronizer, - fileApi, - sleep, - clearDatabase, - switchClient, - syncTargetId, - encryptionService, - loadEncryptionMasterKey, - fileContentEqual, - decryptionWorker, - checkThrowAsync, - asyncTest, -} = require("test-utils.js"); -const { shim } = require("lib/shim.js"); -const fs = require("fs-extra"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const Resource = require("lib/models/Resource.js"); -const Tag = require("lib/models/Tag.js"); -const { Database } = require("lib/database.js"); -const Setting = require("lib/models/Setting.js"); -const MasterKey = require("lib/models/MasterKey"); -const BaseItem = require("lib/models/BaseItem.js"); -const BaseModel = require("lib/BaseModel.js"); -const SyncTargetRegistry = require("lib/SyncTargetRegistry.js"); +const { time } = require('lib/time-utils.js'); +const { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, checkThrowAsync, asyncTest } = require('test-utils.js'); +const { shim } = require('lib/shim.js'); +const fs = require('fs-extra'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const Resource = require('lib/models/Resource.js'); +const Tag = require('lib/models/Tag.js'); +const { Database } = require('lib/database.js'); +const Setting = require('lib/models/Setting.js'); +const MasterKey = require('lib/models/MasterKey'); +const BaseItem = require('lib/models/BaseItem.js'); +const BaseModel = require('lib/BaseModel.js'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); -process.on("unhandledRejection", (reason, p) => { - console.log("Unhandled Rejection at: Promise", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); }); jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000 + 30000; // The first test is slow because the database needs to be built @@ -62,15 +46,15 @@ async function allSyncTargetItemsEncrypted() { totalCount++; if (remoteContent.type_ === BaseModel.TYPE_RESOURCE) { - const content = await fileApi().get(".resource/" + remoteContent.id); + const content = await fileApi().get('.resource/' + remoteContent.id); totalCount++; - if (content.substr(0, 5) === "JED01") output = encryptedCount++; + if (content.substr(0, 5) === 'JED01') output = encryptedCount++; } if (!!remoteContent.encryption_applied) encryptedCount++; } - if (!totalCount) throw new Error("No encryptable item on sync target"); + if (!totalCount) throw new Error('No encryptable item on sync target'); return totalCount === encryptedCount; } @@ -111,8 +95,9 @@ async function localItemsSameAsRemote(locals, expect) { let insideBeforeEach = false; -describe("Synchronizer", function() { - beforeEach(async done => { +describe('Synchronizer', function() { + + beforeEach(async (done) => { insideBeforeEach = true; await setupDatabaseAndSynchronizer(1); @@ -123,496 +108,450 @@ describe("Synchronizer", function() { insideBeforeEach = false; }); - it( - "should create remote items", - asyncTest(async () => { - let folder = await Folder.save({ title: "folder1" }); - await Note.save({ title: "un", parent_id: folder.id }); + it('should create remote items', asyncTest(async () => { + let folder = await Folder.save({ title: "folder1" }); + await Note.save({ title: "un", parent_id: folder.id }); - let all = await allItems(); + let all = await allItems(); + await localItemsSameAsRemote(all, expect); + })); - await synchronizer().start(); + it("should update remote items", asyncTest(async () => { + let folder = await Folder.save({ title: "folder1" }); + let note = await Note.save({ title: "un", parent_id: folder.id }); + await synchronizer().start(); - await localItemsSameAsRemote(all, expect); - }) - ); + await localItemsSameAsRemote(all, expect); + })); - it( - "should update remote items", - asyncTest(async () => { - let folder = await Folder.save({ title: "folder1" }); - let note = await Note.save({ title: "un", parent_id: folder.id }); - await synchronizer().start(); + it('should update remote item', asyncTest(async () => { + let folder = await Folder.save({ title: "folder1" }); + let note = await Note.save({ title: "un", parent_id: folder.id }); + await synchronizer().start(); - await Note.save({ title: "un UPDATE", id: note.id }); + await Note.save({ title: "un UPDATE", id: note.id }); - let all = await allItems(); - await synchronizer().start(); - - await localItemsSameAsRemote(all, expect); - }) - ); + let all = await allItems(); + await synchronizer().start(); - it( - "should create local items", - asyncTest(async () => { - let folder = await Folder.save({ title: "folder1" }); - await Note.save({ title: "un", parent_id: folder.id }); - await synchronizer().start(); + await localItemsSameAsRemote(all, expect); + })); - await switchClient(2); + it('should create local items', asyncTest(async () => { + let folder = await Folder.save({ title: "folder1" }); + await Note.save({ title: "un", parent_id: folder.id }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - let all = await allItems(); + await synchronizer().start(); - await localItemsSameAsRemote(all, expect); - }) - ); + let all = await allItems(); - it( - "should update local items", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - await synchronizer().start(); + await localItemsSameAsRemote(all, expect); + })); - await switchClient(2); + it('should update local items', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - let note2 = await Note.load(note1.id); - note2.title = "Updated on client 2"; - await Note.save(note2); - note2 = await Note.load(note2.id); + await sleep(0.1); - await synchronizer().start(); + let note2 = await Note.load(note1.id); + note2.title = "Updated on client 2"; + await Note.save(note2); + note2 = await Note.load(note2.id); - await switchClient(1); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(1); - let all = await allItems(); + await synchronizer().start(); - await localItemsSameAsRemote(all, expect); - }) - ); + let all = await allItems(); - it( - "should resolve note conflicts", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - await synchronizer().start(); + await localItemsSameAsRemote(all, expect); + })); - await switchClient(2); + it('should resolve note conflicts', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + await synchronizer().start(); - await synchronizer().start(); - let note2 = await Note.load(note1.id); - note2.title = "Updated on client 2"; - await Note.save(note2); - note2 = await Note.load(note2.id); - await synchronizer().start(); + await switchClient(2); - await switchClient(1); + await synchronizer().start(); + let note2 = await Note.load(note1.id); + note2.title = "Updated on client 2"; + await Note.save(note2); + note2 = await Note.load(note2.id); + await synchronizer().start(); - let note2conf = await Note.load(note1.id); - note2conf.title = "Updated on client 1"; - await Note.save(note2conf); - note2conf = await Note.load(note1.id); - await synchronizer().start(); - let conflictedNotes = await Note.conflictedNotes(); - expect(conflictedNotes.length).toBe(1); + await switchClient(1); - // Other than the id (since the conflicted note is a duplicate), and the is_conflict property - // the conflicted and original note must be the same in every way, to make sure no data has been lost. - let conflictedNote = conflictedNotes[0]; - expect(conflictedNote.id == note2conf.id).toBe(false); - for (let n in conflictedNote) { - if (!conflictedNote.hasOwnProperty(n)) continue; - if (n == "id" || n == "is_conflict") continue; - expect(conflictedNote[n]).toBe(note2conf[n], "Property: " + n); - } + let note2conf = await Note.load(note1.id); + note2conf.title = "Updated on client 1"; + await Note.save(note2conf); + note2conf = await Note.load(note1.id); + await synchronizer().start(); + let conflictedNotes = await Note.conflictedNotes(); + expect(conflictedNotes.length).toBe(1); - let noteUpdatedFromRemote = await Note.load(note1.id); - for (let n in noteUpdatedFromRemote) { - if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue; - expect(noteUpdatedFromRemote[n]).toBe(note2[n], "Property: " + n); - } - }) - ); + // Other than the id (since the conflicted note is a duplicate), and the is_conflict property + // the conflicted and original note must be the same in every way, to make sure no data has been lost. + let conflictedNote = conflictedNotes[0]; + expect(conflictedNote.id == note2conf.id).toBe(false); + for (let n in conflictedNote) { + if (!conflictedNote.hasOwnProperty(n)) continue; + if (n == 'id' || n == 'is_conflict') continue; + expect(conflictedNote[n]).toBe(note2conf[n], 'Property: ' + n); + } - it( - "should resolve folders conflicts", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - await synchronizer().start(); + let noteUpdatedFromRemote = await Note.load(note1.id); + for (let n in noteUpdatedFromRemote) { + if (!noteUpdatedFromRemote.hasOwnProperty(n)) continue; + expect(noteUpdatedFromRemote[n]).toBe(note2[n], 'Property: ' + n); + } + })); - await switchClient(2); // ---------------------------------- + it('should resolve folders conflicts', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); // ---------------------------------- - await sleep(0.1); + await synchronizer().start(); - let folder1_modRemote = await Folder.load(folder1.id); - folder1_modRemote.title = "folder1 UPDATE CLIENT 2"; - await Folder.save(folder1_modRemote); - folder1_modRemote = await Folder.load(folder1_modRemote.id); + await sleep(0.1); - await synchronizer().start(); + let folder1_modRemote = await Folder.load(folder1.id); + folder1_modRemote.title = "folder1 UPDATE CLIENT 2"; + await Folder.save(folder1_modRemote); + folder1_modRemote = await Folder.load(folder1_modRemote.id); - await switchClient(1); // ---------------------------------- + await synchronizer().start(); - await sleep(0.1); + await switchClient(1); // ---------------------------------- - let folder1_modLocal = await Folder.load(folder1.id); - folder1_modLocal.title = "folder1 UPDATE CLIENT 1"; - await Folder.save(folder1_modLocal); - folder1_modLocal = await Folder.load(folder1.id); + await sleep(0.1); - await synchronizer().start(); + let folder1_modLocal = await Folder.load(folder1.id); + folder1_modLocal.title = "folder1 UPDATE CLIENT 1"; + await Folder.save(folder1_modLocal); + folder1_modLocal = await Folder.load(folder1.id); - let folder1_final = await Folder.load(folder1.id); - expect(folder1_final.title).toBe(folder1_modRemote.title); - }) - ); + await synchronizer().start(); - it( - "should delete remote notes", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - await synchronizer().start(); + let folder1_final = await Folder.load(folder1.id); + expect(folder1_final.title).toBe(folder1_modRemote.title); + })); - await switchClient(2); + it('should delete remote notes', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - await Note.delete(note1.id); + await sleep(0.1); - await synchronizer().start(); + await Note.delete(note1.id); - let files = await fileApi().list(); - files = files.items; + await synchronizer().start(); - expect(files.length).toBe(1); - expect(files[0].path).toBe(Folder.systemPath(folder1)); + let files = await fileApi().list(); + files = files.items; - let deletedItems = await BaseItem.deletedItems(syncTargetId()); - expect(deletedItems.length).toBe(0); - }) - ); + expect(files.length).toBe(1); + expect(files[0].path).toBe(Folder.systemPath(folder1)); - it( - "should not created deleted_items entries for items deleted via sync", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - await synchronizer().start(); + let deletedItems = await BaseItem.deletedItems(syncTargetId()); + expect(deletedItems.length).toBe(0); + })); - await switchClient(2); + it('should not created deleted_items entries for items deleted via sync', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + await synchronizer().start(); - await synchronizer().start(); - await Folder.delete(folder1.id); - await synchronizer().start(); + await switchClient(2); - await switchClient(1); + await synchronizer().start(); + await Folder.delete(folder1.id); + await synchronizer().start(); - await synchronizer().start(); - let deletedItems = await BaseItem.deletedItems(syncTargetId()); - expect(deletedItems.length).toBe(0); - }) - ); + await switchClient(1); - it( - "should delete local notes", - asyncTest(async () => { - // For these tests we pass the context around for each user. This is to make sure that the "deletedItemsProcessed" - // property of the basicDelta() function is cleared properly at the end of a sync operation. If it is not cleared - // it means items will no longer be deleted locally via sync. + await synchronizer().start(); + let deletedItems = await BaseItem.deletedItems(syncTargetId()); + expect(deletedItems.length).toBe(0); + })); - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - let note2 = await Note.save({ title: "deux", parent_id: folder1.id }); - let context1 = await synchronizer().start(); + it('should delete local notes', asyncTest(async () => { + // For these tests we pass the context around for each user. This is to make sure that the "deletedItemsProcessed" + // property of the basicDelta() function is cleared properly at the end of a sync operation. If it is not cleared + // it means items will no longer be deleted locally via sync. - await switchClient(2); + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + let note2 = await Note.save({ title: "deux", parent_id: folder1.id }); + let context1 = await synchronizer().start(); - let context2 = await synchronizer().start(); - await Note.delete(note1.id); - context2 = await synchronizer().start({ context: context2 }); + await switchClient(2); - await switchClient(1); + let context2 = await synchronizer().start(); + await Note.delete(note1.id); + context2 = await synchronizer().start({ context: context2 }); - context1 = await synchronizer().start({ context: context1 }); - let items = await allItems(); - expect(items.length).toBe(2); - let deletedItems = await BaseItem.deletedItems(syncTargetId()); - expect(deletedItems.length).toBe(0); - await Note.delete(note2.id); - context1 = await synchronizer().start({ context: context1 }); - }) - ); + await switchClient(1); - it( - "should delete remote folder", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let folder2 = await Folder.save({ title: "folder2" }); - await synchronizer().start(); + context1 = await synchronizer().start({ context: context1 }); + let items = await allItems(); + expect(items.length).toBe(2); + let deletedItems = await BaseItem.deletedItems(syncTargetId()); + expect(deletedItems.length).toBe(0); + await Note.delete(note2.id); + context1 = await synchronizer().start({ context: context1 }); + })); - await switchClient(2); + it('should delete remote folder', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let folder2 = await Folder.save({ title: "folder2" }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - await Folder.delete(folder2.id); + await sleep(0.1); - await synchronizer().start(); + await Folder.delete(folder2.id); - let all = await allItems(); - await localItemsSameAsRemote(all, expect); - }) - ); + await synchronizer().start(); - it( - "should delete local folder", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let folder2 = await Folder.save({ title: "folder2" }); - await synchronizer().start(); + let all = await allItems(); + await localItemsSameAsRemote(all, expect); + })); - await switchClient(2); + it('should delete local folder', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let folder2 = await Folder.save({ title: "folder2" }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - await Folder.delete(folder2.id); + await sleep(0.1); - await synchronizer().start(); + await Folder.delete(folder2.id); - await switchClient(1); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(1); - let items = await allItems(); - await localItemsSameAsRemote(items, expect); - }) - ); + await synchronizer().start(); - it( - "should resolve conflict if remote folder has been deleted, but note has been added to folder locally", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - await synchronizer().start(); + let items = await allItems(); + await localItemsSameAsRemote(items, expect); + })); - await switchClient(2); + it('should resolve conflict if remote folder has been deleted, but note has been added to folder locally', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + await synchronizer().start(); - await synchronizer().start(); - await Folder.delete(folder1.id); - await synchronizer().start(); + await switchClient(2); - await switchClient(1); + await synchronizer().start(); + await Folder.delete(folder1.id); + await synchronizer().start(); - let note = await Note.save({ title: "note1", parent_id: folder1.id }); - await synchronizer().start(); - let items = await allItems(); - expect(items.length).toBe(1); - expect(items[0].title).toBe("note1"); - expect(items[0].is_conflict).toBe(1); - }) - ); + await switchClient(1); - it( - "should resolve conflict if note has been deleted remotely and locally", - asyncTest(async () => { - let folder = await Folder.save({ title: "folder" }); - let note = await Note.save({ title: "note", parent_id: folder.title }); - await synchronizer().start(); + let note = await Note.save({ title: "note1", parent_id: folder1.id }); + await synchronizer().start(); + let items = await allItems(); + expect(items.length).toBe(1); + expect(items[0].title).toBe('note1'); + expect(items[0].is_conflict).toBe(1); + })); - await switchClient(2); + it('should resolve conflict if note has been deleted remotely and locally', asyncTest(async () => { + let folder = await Folder.save({ title: "folder" }); + let note = await Note.save({ title: "note", parent_id: folder.title }); + await synchronizer().start(); - await synchronizer().start(); - await Note.delete(note.id); - await synchronizer().start(); + await switchClient(2); - await switchClient(1); + await synchronizer().start(); + await Note.delete(note.id); + await synchronizer().start(); - await Note.delete(note.id); - await synchronizer().start(); + await switchClient(1); - let items = await allItems(); - expect(items.length).toBe(1); - expect(items[0].title).toBe("folder"); + await Note.delete(note.id); + await synchronizer().start(); - await localItemsSameAsRemote(items, expect); - }) - ); + let items = await allItems(); + expect(items.length).toBe(1); + expect(items[0].title).toBe('folder'); - it( - "should cross delete all folders", - asyncTest(async () => { - // If client1 and 2 have two folders, client 1 deletes item 1 and client - // 2 deletes item 2, they should both end up with no items after sync. + await localItemsSameAsRemote(items, expect); + })); - let folder1 = await Folder.save({ title: "folder1" }); - let folder2 = await Folder.save({ title: "folder2" }); - await synchronizer().start(); + it('should cross delete all folders', asyncTest(async () => { + // If client1 and 2 have two folders, client 1 deletes item 1 and client + // 2 deletes item 2, they should both end up with no items after sync. - await switchClient(2); + let folder1 = await Folder.save({ title: "folder1" }); + let folder2 = await Folder.save({ title: "folder2" }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - await Folder.delete(folder1.id); + await sleep(0.1); - await switchClient(1); + await Folder.delete(folder1.id); - await Folder.delete(folder2.id); + await switchClient(1); - await synchronizer().start(); + await Folder.delete(folder2.id); - await switchClient(2); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - let items2 = await allItems(); + await synchronizer().start(); - await switchClient(1); + let items2 = await allItems(); - await synchronizer().start(); + await switchClient(1); - let items1 = await allItems(); + await synchronizer().start(); - expect(items1.length).toBe(0); - expect(items1.length).toBe(items2.length); - }) - ); + let items1 = await allItems(); - it( - "should handle conflict when remote note is deleted then local note is modified", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - await synchronizer().start(); + expect(items1.length).toBe(0); + expect(items1.length).toBe(items2.length); + })); - await switchClient(2); + it('should handle conflict when remote note is deleted then local note is modified', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - await Note.delete(note1.id); + await sleep(0.1); - await synchronizer().start(); + await Note.delete(note1.id); - await switchClient(1); + await synchronizer().start(); - let newTitle = "Modified after having been deleted"; - await Note.save({ id: note1.id, title: newTitle }); + await switchClient(1); - await synchronizer().start(); + let newTitle = 'Modified after having been deleted'; + await Note.save({ id: note1.id, title: newTitle }); - let conflictedNotes = await Note.conflictedNotes(); + await synchronizer().start(); - expect(conflictedNotes.length).toBe(1); - expect(conflictedNotes[0].title).toBe(newTitle); + let conflictedNotes = await Note.conflictedNotes(); - let unconflictedNotes = await Note.unconflictedNotes(); + expect(conflictedNotes.length).toBe(1); + expect(conflictedNotes[0].title).toBe(newTitle); - expect(unconflictedNotes.length).toBe(0); - }) - ); + let unconflictedNotes = await Note.unconflictedNotes(); - it( - "should handle conflict when remote folder is deleted then local folder is renamed", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let folder2 = await Folder.save({ title: "folder2" }); - let note1 = await Note.save({ title: "un", parent_id: folder1.id }); - await synchronizer().start(); + expect(unconflictedNotes.length).toBe(0); + })); - await switchClient(2); + it('should handle conflict when remote folder is deleted then local folder is renamed', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let folder2 = await Folder.save({ title: "folder2" }); + let note1 = await Note.save({ title: "un", parent_id: folder1.id }); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - await Folder.delete(folder1.id); + await sleep(0.1); - await synchronizer().start(); + await Folder.delete(folder1.id); - await switchClient(1); + await synchronizer().start(); - await sleep(0.1); + await switchClient(1); - let newTitle = "Modified after having been deleted"; - await Folder.save({ id: folder1.id, title: newTitle }); + await sleep(0.1); - await synchronizer().start(); + let newTitle = 'Modified after having been deleted'; + await Folder.save({ id: folder1.id, title: newTitle }); - let items = await allItems(); + await synchronizer().start(); - expect(items.length).toBe(1); - }) - ); + let items = await allItems(); - it( - "should allow duplicate folder titles", - asyncTest(async () => { - let localF1 = await Folder.save({ title: "folder" }); + expect(items.length).toBe(1); + })); - await switchClient(2); + it('should allow duplicate folder titles', asyncTest(async () => { + let localF1 = await Folder.save({ title: "folder" }); - let remoteF2 = await Folder.save({ title: "folder" }); - await synchronizer().start(); + await switchClient(2); - await switchClient(1); + let remoteF2 = await Folder.save({ title: "folder" }); + await synchronizer().start(); - await sleep(0.1); + await switchClient(1); - await synchronizer().start(); + await sleep(0.1); - let localF2 = await Folder.load(remoteF2.id); + await synchronizer().start(); - expect(localF2.title == remoteF2.title).toBe(true); + let localF2 = await Folder.load(remoteF2.id); - // Then that folder that has been renamed locally should be set in such a way - // that synchronizing it applies the title change remotely, and that new title - // should be retrieved by client 2. + expect(localF2.title == remoteF2.title).toBe(true); - await synchronizer().start(); + // Then that folder that has been renamed locally should be set in such a way + // that synchronizing it applies the title change remotely, and that new title + // should be retrieved by client 2. - await switchClient(2); - await sleep(0.1); + await synchronizer().start(); - await synchronizer().start(); + await switchClient(2); + await sleep(0.1); - remoteF2 = await Folder.load(remoteF2.id); + await synchronizer().start(); - expect(remoteF2.title == localF2.title).toBe(true); - }) - ); + remoteF2 = await Folder.load(remoteF2.id); + + expect(remoteF2.title == localF2.title).toBe(true); + })); async function shoudSyncTagTest(withEncryption) { let masterKey = null; if (withEncryption) { - Setting.setValue("encryption.enabled", true); + Setting.setValue('encryption.enabled', true); masterKey = await loadEncryptionMasterKey(); } let f1 = await Folder.save({ title: "folder" }); let n1 = await Note.save({ title: "mynote" }); let n2 = await Note.save({ title: "mynote2" }); - let tag = await Tag.save({ title: "mytag" }); + let tag = await Tag.save({ title: 'mytag' }); await synchronizer().start(); await switchClient(2); @@ -620,7 +559,7 @@ describe("Synchronizer", function() { await synchronizer().start(); if (withEncryption) { const masterKey_2 = await MasterKey.load(masterKey.id); - await encryptionService().loadMasterKey(masterKey_2, "123456", true); + await encryptionService().loadMasterKey(masterKey_2, '123456', true); let t = await Tag.load(tag.id); await Tag.decrypt(t); } @@ -651,58 +590,46 @@ describe("Synchronizer", function() { expect(remoteNoteIds[0]).toBe(noteIds[0]); } - it( - "should sync tags", - asyncTest(async () => { - await shoudSyncTagTest(false); - }) - ); + it('should sync tags', asyncTest(async () => { + await shoudSyncTagTest(false); + })); - it( - "should sync encrypted tags", - asyncTest(async () => { - await shoudSyncTagTest(true); - }) - ); + it('should sync encrypted tags', asyncTest(async () => { + await shoudSyncTagTest(true); + })); - it( - "should not sync notes with conflicts", - asyncTest(async () => { - let f1 = await Folder.save({ title: "folder" }); - let n1 = await Note.save({ title: "mynote", parent_id: f1.id, is_conflict: 1 }); - await synchronizer().start(); + it('should not sync notes with conflicts', asyncTest(async () => { + let f1 = await Folder.save({ title: "folder" }); + let n1 = await Note.save({ title: "mynote", parent_id: f1.id, is_conflict: 1 }); + await synchronizer().start(); - await switchClient(2); + await switchClient(2); - await synchronizer().start(); - let notes = await Note.all(); - let folders = await Folder.all(); - expect(notes.length).toBe(0); - expect(folders.length).toBe(1); - }) - ); + await synchronizer().start(); + let notes = await Note.all(); + let folders = await Folder.all() + expect(notes.length).toBe(0); + expect(folders.length).toBe(1); + })); - it( - "should not try to delete on remote conflicted notes that have been deleted", - asyncTest(async () => { - let f1 = await Folder.save({ title: "folder" }); - let n1 = await Note.save({ title: "mynote", parent_id: f1.id }); - await synchronizer().start(); + it('should not try to delete on remote conflicted notes that have been deleted', asyncTest(async () => { + let f1 = await Folder.save({ title: "folder" }); + let n1 = await Note.save({ title: "mynote", parent_id: f1.id }); + await synchronizer().start(); - await switchClient(2); + await switchClient(2); - await synchronizer().start(); - await Note.save({ id: n1.id, is_conflict: 1 }); - await Note.delete(n1.id); - const deletedItems = await BaseItem.deletedItems(syncTargetId()); - - expect(deletedItems.length).toBe(0); - }) - ); + await synchronizer().start(); + await Note.save({ id: n1.id, is_conflict: 1 }); + await Note.delete(n1.id); + const deletedItems = await BaseItem.deletedItems(syncTargetId()); + expect(deletedItems.length).toBe(0); + })); + async function ignorableNoteConflictTest(withEncryption) { if (withEncryption) { - Setting.setValue("encryption.enabled", true); + Setting.setValue('encryption.enabled', true); await loadEncryptionMasterKey(); } @@ -718,7 +645,7 @@ describe("Synchronizer", function() { await decryptionWorker().start(); } let note2 = await Note.load(note1.id); - note2.todo_completed = time.unixMs() - 1; + note2.todo_completed = time.unixMs()-1; await Note.save(note2); note2 = await Note.load(note2.id); await synchronizer().start(); @@ -745,7 +672,7 @@ describe("Synchronizer", function() { let notes = await Note.all(); expect(notes.length).toBe(1); expect(notes[0].id).toBe(note1.id); - expect(notes[0].todo_completed).toBe(note2.todo_completed); + expect(notes[0].todo_completed).toBe(note2.todo_completed); } else { // If the notes are encrypted however it's not possible to do this kind of // smart conflict resolving since we don't know the content, so in that @@ -759,398 +686,354 @@ describe("Synchronizer", function() { } } - it( - "should not consider it is a conflict if neither the title nor body of the note have changed", - asyncTest(async () => { - await ignorableNoteConflictTest(false); - }) - ); - - it( - "should always handle conflict if local or remote are encrypted", - asyncTest(async () => { - await ignorableNoteConflictTest(true); - }) - ); - - it( - "items should be downloaded again when user cancels in the middle of delta operation", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id }); - await synchronizer().start(); - - await switchClient(2); - - synchronizer().testingHooks_ = ["cancelDeltaLoop2"]; - let context = await synchronizer().start(); - let notes = await Note.all(); - expect(notes.length).toBe(0); - - synchronizer().testingHooks_ = []; - await synchronizer().start({ context: context }); - notes = await Note.all(); - expect(notes.length).toBe(1); - }) - ); - - it( - "should skip items that cannot be synced", - asyncTest(async () => { - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id }); - const noteId = note1.id; - await synchronizer().start(); - let disabledItems = await BaseItem.syncDisabledItems(syncTargetId()); - expect(disabledItems.length).toBe(0); - await Note.save({ id: noteId, title: "un mod" }); - synchronizer().testingHooks_ = ["rejectedByTarget"]; - await synchronizer().start(); - synchronizer().testingHooks_ = []; - await synchronizer().start(); // Another sync to check that this item is now excluded from sync - - await switchClient(2); - - await synchronizer().start(); - let notes = await Note.all(); - expect(notes.length).toBe(1); - expect(notes[0].title).toBe("un"); - - await switchClient(1); - - disabledItems = await BaseItem.syncDisabledItems(syncTargetId()); - expect(disabledItems.length).toBe(1); - }) - ); - - it( - "notes and folders should get encrypted when encryption is enabled", - asyncTest(async () => { - Setting.setValue("encryption.enabled", true); - const masterKey = await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "un", body: "to be encrypted", parent_id: folder1.id }); - await synchronizer().start(); - // After synchronisation, remote items should be encrypted but local ones remain plain text - note1 = await Note.load(note1.id); - expect(note1.title).toBe("un"); - - await switchClient(2); - - await synchronizer().start(); - let folder1_2 = await Folder.load(folder1.id); - let note1_2 = await Note.load(note1.id); - let masterKey_2 = await MasterKey.load(masterKey.id); - // On this side however it should be received encrypted - expect(!note1_2.title).toBe(true); - expect(!folder1_2.title).toBe(true); - expect(!!note1_2.encryption_cipher_text).toBe(true); - expect(!!folder1_2.encryption_cipher_text).toBe(true); - // Master key is already encrypted so it does not get re-encrypted during sync - expect(masterKey_2.content).toBe(masterKey.content); - expect(masterKey_2.checksum).toBe(masterKey.checksum); - // Now load the master key we got from client 1 and try to decrypt - await encryptionService().loadMasterKey(masterKey_2, "123456", true); - // Get the decrypted items back - await Folder.decrypt(folder1_2); - await Note.decrypt(note1_2); - folder1_2 = await Folder.load(folder1.id); - note1_2 = await Note.load(note1.id); - // Check that properties match the original items. Also check - // the encryption did not affect the updated_time timestamp. - expect(note1_2.title).toBe(note1.title); - expect(note1_2.body).toBe(note1.body); - expect(note1_2.updated_time).toBe(note1.updated_time); - expect(!note1_2.encryption_cipher_text).toBe(true); - expect(folder1_2.title).toBe(folder1.title); - expect(folder1_2.updated_time).toBe(folder1.updated_time); - expect(!folder1_2.encryption_cipher_text).toBe(true); - }) - ); - - it( - "should enable encryption automatically when downloading new master key (and none was previously available)", - asyncTest(async () => { - // Enable encryption on client 1 and sync an item - Setting.setValue("encryption.enabled", true); - await loadEncryptionMasterKey(); - let folder1 = await Folder.save({ title: "folder1" }); - await synchronizer().start(); - - await switchClient(2); - - // Synchronising should enable encryption since we're going to get a master key - expect(Setting.value("encryption.enabled")).toBe(false); - await synchronizer().start(); - expect(Setting.value("encryption.enabled")).toBe(true); - - // Check that we got the master key from client 1 - const masterKey = (await MasterKey.all())[0]; - expect(!!masterKey).toBe(true); - - // Since client 2 hasn't supplied a password yet, no master key is currently loaded - expect(encryptionService().loadedMasterKeyIds().length).toBe(0); - - // If we sync now, nothing should be sent to target since we don't have a password. - // Technically it's incorrect to set the property of an encrypted variable but it allows confirming - // that encryption doesn't work if user hasn't supplied a password. - await BaseItem.forceSync(folder1.id); - await synchronizer().start(); - - await switchClient(1); - - await synchronizer().start(); - folder1 = await Folder.load(folder1.id); - expect(folder1.title).toBe("folder1"); // Still at old value - - await switchClient(2); - - // Now client 2 set the master key password - Setting.setObjectKey("encryption.passwordCache", masterKey.id, "123456"); - await encryptionService().loadMasterKeysFromSettings(); - - // Now that master key should be loaded - expect(encryptionService().loadedMasterKeyIds()[0]).toBe(masterKey.id); - - // Decrypt all the data. Now change the title and sync again - this time the changes should be transmitted - await decryptionWorker().start(); - folder1_2 = await Folder.save({ id: folder1.id, title: "change test" }); - - // If we sync now, this time client 1 should get the changes we did earlier - await synchronizer().start(); - - await switchClient(1); - - await synchronizer().start(); - // Decrypt the data we just got - await decryptionWorker().start(); - folder1 = await Folder.load(folder1.id); - expect(folder1.title).toBe("change test"); // Got title from client 2 - }) - ); - - it( - "should encrypt existing notes too when enabling E2EE", - asyncTest(async () => { - // First create a folder, without encryption enabled, and sync it - let folder1 = await Folder.save({ title: "folder1" }); - await synchronizer().start(); - let files = await fileApi().list(); - let content = await fileApi().get(files.items[0].path); - expect(content.indexOf("folder1") >= 0).toBe(true); - - // Then enable encryption and sync again - let masterKey = await encryptionService().generateMasterKey("123456"); - masterKey = await MasterKey.save(masterKey); - await encryptionService().enableEncryption(masterKey, "123456"); - await encryptionService().loadMasterKeysFromSettings(); - await synchronizer().start(); - - // Even though the folder has not been changed it should have been synced again so that - // an encrypted version of it replaces the decrypted version. - files = await fileApi().list(); - expect(files.items.length).toBe(2); - // By checking that the folder title is not present, we can confirm that the item has indeed been encrypted - // One of the two items is the master key - content = await fileApi().get(files.items[0].path); - expect(content.indexOf("folder1") < 0).toBe(true); - content = await fileApi().get(files.items[1].path); - expect(content.indexOf("folder1") < 0).toBe(true); - }) - ); - - it( - "should sync resources", - asyncTest(async () => { - while (insideBeforeEach) await time.msleep(500); - - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - await shim.attachFileToNote(note1, __dirname + "/../tests/support/photo.jpg"); - let resource1 = (await Resource.all())[0]; - let resourcePath1 = Resource.fullPath(resource1); - await synchronizer().start(); - expect((await fileApi().list()).items.length).toBe(3); - - await switchClient(2); - - await synchronizer().start(); - let allResources = await Resource.all(); - expect(allResources.length).toBe(1); - let resource1_2 = allResources[0]; - let resourcePath1_2 = Resource.fullPath(resource1_2); - - expect(resource1_2.id).toBe(resource1.id); - expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true); - }) - ); - - it( - "should encryt resources", - asyncTest(async () => { - Setting.setValue("encryption.enabled", true); - const masterKey = await loadEncryptionMasterKey(); - - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - await shim.attachFileToNote(note1, __dirname + "/../tests/support/photo.jpg"); - let resource1 = (await Resource.all())[0]; - let resourcePath1 = Resource.fullPath(resource1); - await synchronizer().start(); - - await switchClient(2); - - await synchronizer().start(); - Setting.setObjectKey("encryption.passwordCache", masterKey.id, "123456"); - await encryptionService().loadMasterKeysFromSettings(); - - let resource1_2 = (await Resource.all())[0]; - resource1_2 = await Resource.decrypt(resource1_2); - let resourcePath1_2 = Resource.fullPath(resource1_2); - - expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true); - }) - ); - - it( - "should upload decrypted items to sync target after encryption disabled", - asyncTest(async () => { - Setting.setValue("encryption.enabled", true); - const masterKey = await loadEncryptionMasterKey(); - - let folder1 = await Folder.save({ title: "folder1" }); - await synchronizer().start(); - - let allEncrypted = await allSyncTargetItemsEncrypted(); - expect(allEncrypted).toBe(true); - - await encryptionService().disableEncryption(); - - await synchronizer().start(); - allEncrypted = await allSyncTargetItemsEncrypted(); - expect(allEncrypted).toBe(false); - }) - ); - - it( - "should not upload any item if encryption was enabled, and items have not been decrypted, and then encryption disabled", - asyncTest(async () => { - // For some reason I can't explain, this test is sometimes executed before beforeEach is finished - // which means it's going to fail in unexpected way. So the loop below wait for beforeEach to be done. - while (insideBeforeEach) await time.msleep(100); - - Setting.setValue("encryption.enabled", true); - const masterKey = await loadEncryptionMasterKey(); - - let folder1 = await Folder.save({ title: "folder1" }); - await synchronizer().start(); - - await switchClient(2); - - await synchronizer().start(); - expect(Setting.value("encryption.enabled")).toBe(true); - - // If we try to disable encryption now, it should throw an error because some items are - // currently encrypted. They must be decrypted first so that they can be sent as - // plain text to the sync target. - //let hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption()); - //expect(hasThrown).toBe(true); - - // Now supply the password, and decrypt the items - Setting.setObjectKey("encryption.passwordCache", masterKey.id, "123456"); - await encryptionService().loadMasterKeysFromSettings(); - await decryptionWorker().start(); - - // Try to disable encryption again - hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption()); - expect(hasThrown).toBe(false); - - // If we sync now the target should receive the decrypted items - await synchronizer().start(); - allEncrypted = await allSyncTargetItemsEncrypted(); - expect(allEncrypted).toBe(false); - }) - ); - - it( - "should encrypt remote resources after encryption has been enabled", - asyncTest(async () => { - while (insideBeforeEach) await time.msleep(100); - - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - await shim.attachFileToNote(note1, __dirname + "/../tests/support/photo.jpg"); - let resource1 = (await Resource.all())[0]; - await synchronizer().start(); - - expect(await allSyncTargetItemsEncrypted()).toBe(false); - - const masterKey = await loadEncryptionMasterKey(); - await encryptionService().enableEncryption(masterKey, "123456"); - await encryptionService().loadMasterKeysFromSettings(); - - await synchronizer().start(); - - expect(await allSyncTargetItemsEncrypted()).toBe(true); - }) - ); - - it( - "should upload encrypted resource, but it should not mark the blob as encrypted locally", - asyncTest(async () => { - while (insideBeforeEach) await time.msleep(100); - - let folder1 = await Folder.save({ title: "folder1" }); - let note1 = await Note.save({ title: "ma note", parent_id: folder1.id }); - await shim.attachFileToNote(note1, __dirname + "/../tests/support/photo.jpg"); - const masterKey = await loadEncryptionMasterKey(); - await encryptionService().enableEncryption(masterKey, "123456"); - await encryptionService().loadMasterKeysFromSettings(); - await synchronizer().start(); - - let resource1 = (await Resource.all())[0]; - expect(resource1.encryption_blob_encrypted).toBe(0); - }) - ); - - it( - "should create remote items with UTF-8 content", - asyncTest(async () => { - let folder = await Folder.save({ title: "Fahrräder" }); - await Note.save({ title: "Fahrräder", body: "Fahrräder", parent_id: folder.id }); - let all = await allItems(); - - await synchronizer().start(); - - await localItemsSameAsRemote(all, expect); - }) - ); - - it( - "should update remote items but not pull remote changes", - asyncTest(async () => { - let folder = await Folder.save({ title: "folder1" }); - let note = await Note.save({ title: "un", parent_id: folder.id }); - await synchronizer().start(); - - await switchClient(2); - - await synchronizer().start(); - await Note.save({ title: "deux", parent_id: folder.id }); - await synchronizer().start(); - - await switchClient(1); - - await Note.save({ title: "un UPDATE", id: note.id }); - await synchronizer().start({ syncSteps: ["update_remote"] }); - let all = await allItems(); - expect(all.length).toBe(2); - - await switchClient(2); - - await synchronizer().start(); - let note2 = await Note.load(note.id); - expect(note2.title).toBe("un UPDATE"); - }) - ); + it('should not consider it is a conflict if neither the title nor body of the note have changed', asyncTest(async () => { + await ignorableNoteConflictTest(false); + })); + + it('should always handle conflict if local or remote are encrypted', asyncTest(async () => { + await ignorableNoteConflictTest(true); + })); + + it('items should be downloaded again when user cancels in the middle of delta operation', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id }); + await synchronizer().start(); + + await switchClient(2); + + synchronizer().testingHooks_ = ['cancelDeltaLoop2']; + let context = await synchronizer().start(); + let notes = await Note.all(); + expect(notes.length).toBe(0); + + synchronizer().testingHooks_ = []; + await synchronizer().start({ context: context }); + notes = await Note.all(); + expect(notes.length).toBe(1); + })); + + it('should skip items that cannot be synced', asyncTest(async () => { + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", is_todo: 1, parent_id: folder1.id }); + const noteId = note1.id; + await synchronizer().start(); + let disabledItems = await BaseItem.syncDisabledItems(syncTargetId()); + expect(disabledItems.length).toBe(0); + await Note.save({ id: noteId, title: "un mod", }); + synchronizer().testingHooks_ = ['rejectedByTarget']; + await synchronizer().start(); + synchronizer().testingHooks_ = []; + await synchronizer().start(); // Another sync to check that this item is now excluded from sync + + await switchClient(2); + + await synchronizer().start(); + let notes = await Note.all(); + expect(notes.length).toBe(1); + expect(notes[0].title).toBe('un'); + + await switchClient(1); + + disabledItems = await BaseItem.syncDisabledItems(syncTargetId()); + expect(disabledItems.length).toBe(1); + })); + + it('notes and folders should get encrypted when encryption is enabled', asyncTest(async () => { + Setting.setValue('encryption.enabled', true); + const masterKey = await loadEncryptionMasterKey(); + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: "un", body: 'to be encrypted', parent_id: folder1.id }); + await synchronizer().start(); + // After synchronisation, remote items should be encrypted but local ones remain plain text + note1 = await Note.load(note1.id); + expect(note1.title).toBe('un'); + + await switchClient(2); + + await synchronizer().start(); + let folder1_2 = await Folder.load(folder1.id); + let note1_2 = await Note.load(note1.id); + let masterKey_2 = await MasterKey.load(masterKey.id); + // On this side however it should be received encrypted + expect(!note1_2.title).toBe(true); + expect(!folder1_2.title).toBe(true); + expect(!!note1_2.encryption_cipher_text).toBe(true); + expect(!!folder1_2.encryption_cipher_text).toBe(true); + // Master key is already encrypted so it does not get re-encrypted during sync + expect(masterKey_2.content).toBe(masterKey.content); + expect(masterKey_2.checksum).toBe(masterKey.checksum); + // Now load the master key we got from client 1 and try to decrypt + await encryptionService().loadMasterKey(masterKey_2, '123456', true); + // Get the decrypted items back + await Folder.decrypt(folder1_2); + await Note.decrypt(note1_2); + folder1_2 = await Folder.load(folder1.id); + note1_2 = await Note.load(note1.id); + // Check that properties match the original items. Also check + // the encryption did not affect the updated_time timestamp. + expect(note1_2.title).toBe(note1.title); + expect(note1_2.body).toBe(note1.body); + expect(note1_2.updated_time).toBe(note1.updated_time); + expect(!note1_2.encryption_cipher_text).toBe(true); + expect(folder1_2.title).toBe(folder1.title); + expect(folder1_2.updated_time).toBe(folder1.updated_time); + expect(!folder1_2.encryption_cipher_text).toBe(true); + })); + + it('should enable encryption automatically when downloading new master key (and none was previously available)',asyncTest(async () => { + // Enable encryption on client 1 and sync an item + Setting.setValue('encryption.enabled', true); + await loadEncryptionMasterKey(); + let folder1 = await Folder.save({ title: "folder1" }); + await synchronizer().start(); + + await switchClient(2); + + // Synchronising should enable encryption since we're going to get a master key + expect(Setting.value('encryption.enabled')).toBe(false); + await synchronizer().start(); + expect(Setting.value('encryption.enabled')).toBe(true); + + // Check that we got the master key from client 1 + const masterKey = (await MasterKey.all())[0]; + expect(!!masterKey).toBe(true); + + // Since client 2 hasn't supplied a password yet, no master key is currently loaded + expect(encryptionService().loadedMasterKeyIds().length).toBe(0); + + // If we sync now, nothing should be sent to target since we don't have a password. + // Technically it's incorrect to set the property of an encrypted variable but it allows confirming + // that encryption doesn't work if user hasn't supplied a password. + await BaseItem.forceSync(folder1.id); + await synchronizer().start(); + + await switchClient(1); + + await synchronizer().start(); + folder1 = await Folder.load(folder1.id); + expect(folder1.title).toBe('folder1'); // Still at old value + + await switchClient(2); + + // Now client 2 set the master key password + Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456'); + await encryptionService().loadMasterKeysFromSettings(); + + // Now that master key should be loaded + expect(encryptionService().loadedMasterKeyIds()[0]).toBe(masterKey.id); + + // Decrypt all the data. Now change the title and sync again - this time the changes should be transmitted + await decryptionWorker().start(); + folder1_2 = await Folder.save({ id: folder1.id, title: "change test" }); + + // If we sync now, this time client 1 should get the changes we did earlier + await synchronizer().start(); + + await switchClient(1); + + await synchronizer().start(); + // Decrypt the data we just got + await decryptionWorker().start(); + folder1 = await Folder.load(folder1.id); + expect(folder1.title).toBe('change test'); // Got title from client 2 + })); + + it('should encrypt existing notes too when enabling E2EE', asyncTest(async () => { + // First create a folder, without encryption enabled, and sync it + let folder1 = await Folder.save({ title: "folder1" }); + await synchronizer().start(); + let files = await fileApi().list() + let content = await fileApi().get(files.items[0].path); + expect(content.indexOf('folder1') >= 0).toBe(true) + + // Then enable encryption and sync again + let masterKey = await encryptionService().generateMasterKey('123456'); + masterKey = await MasterKey.save(masterKey); + await encryptionService().enableEncryption(masterKey, '123456'); + await encryptionService().loadMasterKeysFromSettings(); + await synchronizer().start(); + + // Even though the folder has not been changed it should have been synced again so that + // an encrypted version of it replaces the decrypted version. + files = await fileApi().list() + expect(files.items.length).toBe(2); + // By checking that the folder title is not present, we can confirm that the item has indeed been encrypted + // One of the two items is the master key + content = await fileApi().get(files.items[0].path); + expect(content.indexOf('folder1') < 0).toBe(true); + content = await fileApi().get(files.items[1].path); + expect(content.indexOf('folder1') < 0).toBe(true); + })); + + it('should sync resources', asyncTest(async () => { + while (insideBeforeEach) await time.msleep(500); + + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg'); + let resource1 = (await Resource.all())[0]; + let resourcePath1 = Resource.fullPath(resource1); + await synchronizer().start(); + expect((await fileApi().list()).items.length).toBe(3); + + await switchClient(2); + + await synchronizer().start(); + let allResources = await Resource.all(); + expect(allResources.length).toBe(1); + let resource1_2 = allResources[0]; + let resourcePath1_2 = Resource.fullPath(resource1_2); + + expect(resource1_2.id).toBe(resource1.id); + expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true); + })); + + it('should encryt resources', asyncTest(async () => { + Setting.setValue('encryption.enabled', true); + const masterKey = await loadEncryptionMasterKey(); + + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg'); + let resource1 = (await Resource.all())[0]; + let resourcePath1 = Resource.fullPath(resource1); + await synchronizer().start(); + + await switchClient(2); + + await synchronizer().start(); + Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456'); + await encryptionService().loadMasterKeysFromSettings(); + + let resource1_2 = (await Resource.all())[0]; + resource1_2 = await Resource.decrypt(resource1_2); + let resourcePath1_2 = Resource.fullPath(resource1_2); + + expect(fileContentEqual(resourcePath1, resourcePath1_2)).toBe(true); + })); + + it('should upload decrypted items to sync target after encryption disabled', asyncTest(async () => { + Setting.setValue('encryption.enabled', true); + const masterKey = await loadEncryptionMasterKey(); + + let folder1 = await Folder.save({ title: "folder1" }); + await synchronizer().start(); + + let allEncrypted = await allSyncTargetItemsEncrypted(); + expect(allEncrypted).toBe(true); + + await encryptionService().disableEncryption(); + + await synchronizer().start(); + allEncrypted = await allSyncTargetItemsEncrypted(); + expect(allEncrypted).toBe(false); + })); + + it('should not upload any item if encryption was enabled, and items have not been decrypted, and then encryption disabled', asyncTest(async () => { + // For some reason I can't explain, this test is sometimes executed before beforeEach is finished + // which means it's going to fail in unexpected way. So the loop below wait for beforeEach to be done. + while (insideBeforeEach) await time.msleep(100); + + Setting.setValue('encryption.enabled', true); + const masterKey = await loadEncryptionMasterKey(); + + let folder1 = await Folder.save({ title: "folder1" }); + await synchronizer().start(); + + await switchClient(2); + + await synchronizer().start(); + expect(Setting.value('encryption.enabled')).toBe(true); + + // If we try to disable encryption now, it should throw an error because some items are + // currently encrypted. They must be decrypted first so that they can be sent as + // plain text to the sync target. + //let hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption()); + //expect(hasThrown).toBe(true); + + // Now supply the password, and decrypt the items + Setting.setObjectKey('encryption.passwordCache', masterKey.id, '123456'); + await encryptionService().loadMasterKeysFromSettings(); + await decryptionWorker().start(); + + // Try to disable encryption again + hasThrown = await checkThrowAsync(async () => await encryptionService().disableEncryption()); + expect(hasThrown).toBe(false); + + // If we sync now the target should receive the decrypted items + await synchronizer().start(); + allEncrypted = await allSyncTargetItemsEncrypted(); + expect(allEncrypted).toBe(false); + })); + + it('should encrypt remote resources after encryption has been enabled', asyncTest(async () => { + while (insideBeforeEach) await time.msleep(100); + + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg'); + let resource1 = (await Resource.all())[0]; + await synchronizer().start(); + + expect(await allSyncTargetItemsEncrypted()).toBe(false); + + const masterKey = await loadEncryptionMasterKey(); + await encryptionService().enableEncryption(masterKey, '123456'); + await encryptionService().loadMasterKeysFromSettings(); + + await synchronizer().start(); + + expect(await allSyncTargetItemsEncrypted()).toBe(true); + })); + + it('should upload encrypted resource, but it should not mark the blob as encrypted locally', asyncTest(async () => { + while (insideBeforeEach) await time.msleep(100); + + let folder1 = await Folder.save({ title: "folder1" }); + let note1 = await Note.save({ title: 'ma note', parent_id: folder1.id }); + await shim.attachFileToNote(note1, __dirname + '/../tests/support/photo.jpg'); + const masterKey = await loadEncryptionMasterKey(); + await encryptionService().enableEncryption(masterKey, '123456'); + await encryptionService().loadMasterKeysFromSettings(); + await synchronizer().start(); + + let resource1 = (await Resource.all())[0]; + expect(resource1.encryption_blob_encrypted).toBe(0); + })); + + it('should create remote items with UTF-8 content', asyncTest(async () => { + let folder = await Folder.save({ title: "Fahrräder" }); + await Note.save({ title: "Fahrräder", body: "Fahrräder", parent_id: folder.id }); + let all = await allItems(); + + await synchronizer().start(); + + await localItemsSameAsRemote(all, expect); + })); + + it("should update remote items but not pull remote changes", asyncTest(async () => { + let folder = await Folder.save({ title: "folder1" }); + let note = await Note.save({ title: "un", parent_id: folder.id }); + await synchronizer().start(); + + await switchClient(2); + + await synchronizer().start(); + await Note.save({ title: "deux", parent_id: folder.id }); + await synchronizer().start(); + + await switchClient(1); + + await Note.save({ title: "un UPDATE", id: note.id }); + await synchronizer().start({ syncSteps: ["update_remote"] }); + let all = await allItems(); + expect(all.length).toBe(2); + + await switchClient(2); + + await synchronizer().start(); + let note2 = await Note.load(note.id); + expect(note2.title).toBe("un UPDATE"); + })); + }); diff --git a/CliClient/tests/test-utils.js b/CliClient/tests/test-utils.js index 45ed8c800..b0c5727b5 100644 --- a/CliClient/tests/test-utils.js +++ b/CliClient/tests/test-utils.js @@ -1,32 +1,32 @@ -const fs = require("fs-extra"); -const { JoplinDatabase } = require("lib/joplin-database.js"); -const { DatabaseDriverNode } = require("lib/database-driver-node.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const Note = require("lib/models/Note.js"); -const Resource = require("lib/models/Resource.js"); -const Tag = require("lib/models/Tag.js"); -const NoteTag = require("lib/models/NoteTag.js"); -const { Logger } = require("lib/logger.js"); -const Setting = require("lib/models/Setting.js"); -const MasterKey = require("lib/models/MasterKey"); -const BaseItem = require("lib/models/BaseItem.js"); -const { Synchronizer } = require("lib/synchronizer.js"); -const { FileApi } = require("lib/file-api.js"); -const { FileApiDriverMemory } = require("lib/file-api-driver-memory.js"); -const { FileApiDriverLocal } = require("lib/file-api-driver-local.js"); -const { FileApiDriverWebDav } = require("lib/file-api-driver-webdav.js"); -const { FsDriverNode } = require("lib/fs-driver-node.js"); -const { time } = require("lib/time-utils.js"); -const { shimInit } = require("lib/shim-init-node.js"); -const SyncTargetRegistry = require("lib/SyncTargetRegistry.js"); -const SyncTargetMemory = require("lib/SyncTargetMemory.js"); -const SyncTargetFilesystem = require("lib/SyncTargetFilesystem.js"); -const SyncTargetOneDrive = require("lib/SyncTargetOneDrive.js"); -const SyncTargetNextcloud = require("lib/SyncTargetNextcloud.js"); -const EncryptionService = require("lib/services/EncryptionService.js"); -const DecryptionWorker = require("lib/services/DecryptionWorker.js"); -const WebDavApi = require("lib/WebDavApi"); +const fs = require('fs-extra'); +const { JoplinDatabase } = require('lib/joplin-database.js'); +const { DatabaseDriverNode } = require('lib/database-driver-node.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const Note = require('lib/models/Note.js'); +const Resource = require('lib/models/Resource.js'); +const Tag = require('lib/models/Tag.js'); +const NoteTag = require('lib/models/NoteTag.js'); +const { Logger } = require('lib/logger.js'); +const Setting = require('lib/models/Setting.js'); +const MasterKey = require('lib/models/MasterKey'); +const BaseItem = require('lib/models/BaseItem.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const { FileApi } = require('lib/file-api.js'); +const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js'); +const { FileApiDriverLocal } = require('lib/file-api-driver-local.js'); +const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav.js'); +const { FsDriverNode } = require('lib/fs-driver-node.js'); +const { time } = require('lib/time-utils.js'); +const { shimInit } = require('lib/shim-init-node.js'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); +const SyncTargetMemory = require('lib/SyncTargetMemory.js'); +const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js'); +const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); +const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js'); +const EncryptionService = require('lib/services/EncryptionService.js'); +const DecryptionWorker = require('lib/services/DecryptionWorker.js'); +const WebDavApi = require('lib/WebDavApi'); let databases_ = []; let synchronizers_ = []; @@ -43,7 +43,7 @@ Resource.fsDriver_ = fsDriver; EncryptionService.fsDriver_ = fsDriver; FileApiDriverLocal.fsDriver_ = fsDriver; -const logDir = __dirname + "/../tests/logs"; +const logDir = __dirname + '/../tests/logs'; fs.mkdirpSync(logDir, 0o755); SyncTargetRegistry.addClass(SyncTargetMemory); @@ -54,26 +54,26 @@ SyncTargetRegistry.addClass(SyncTargetNextcloud); // const syncTargetId_ = SyncTargetRegistry.nameToId("nextcloud"); const syncTargetId_ = SyncTargetRegistry.nameToId("memory"); //const syncTargetId_ = SyncTargetRegistry.nameToId('filesystem'); -const syncDir = __dirname + "/../tests/sync"; +const syncDir = __dirname + '/../tests/sync'; -const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId("filesystem") ? 1001 : 100; //400; +const sleepTime = syncTargetId_ == SyncTargetRegistry.nameToId('filesystem') ? 1001 : 100;//400; -console.info("Testing with sync target: " + SyncTargetRegistry.idToName(syncTargetId_)); +console.info('Testing with sync target: ' + SyncTargetRegistry.idToName(syncTargetId_)); const logger = new Logger(); -logger.addTarget("console"); -logger.addTarget("file", { path: logDir + "/log.txt" }); +logger.addTarget('console'); +logger.addTarget('file', { path: logDir + '/log.txt' }); logger.setLevel(Logger.LEVEL_WARN); // Set to INFO to display sync process in console -BaseItem.loadClass("Note", Note); -BaseItem.loadClass("Folder", Folder); -BaseItem.loadClass("Resource", Resource); -BaseItem.loadClass("Tag", Tag); -BaseItem.loadClass("NoteTag", NoteTag); -BaseItem.loadClass("MasterKey", MasterKey); +BaseItem.loadClass('Note', Note); +BaseItem.loadClass('Folder', Folder); +BaseItem.loadClass('Resource', Resource); +BaseItem.loadClass('Tag', Tag); +BaseItem.loadClass('NoteTag', NoteTag); +BaseItem.loadClass('MasterKey', MasterKey); -Setting.setConstant("appId", "net.cozic.joplin-cli"); -Setting.setConstant("appType", "cli"); +Setting.setConstant('appId', 'net.cozic.joplin-cli'); +Setting.setConstant('appType', 'cli'); Setting.autoSaveEnabled = false; @@ -103,7 +103,7 @@ async function switchClient(id) { BaseItem.encryptionService_ = encryptionServices_[id]; Resource.encryptionService_ = encryptionServices_[id]; - Setting.setConstant("resourceDir", resourceDir(id)); + Setting.setConstant('resourceDir', resourceDir(id)); return Setting.load(); } @@ -112,16 +112,16 @@ async function clearDatabase(id = null) { if (id === null) id = currentClient_; let queries = [ - "DELETE FROM notes", - "DELETE FROM folders", - "DELETE FROM resources", - "DELETE FROM tags", - "DELETE FROM note_tags", - "DELETE FROM master_keys", - "DELETE FROM settings", - - "DELETE FROM deleted_items", - "DELETE FROM sync_items", + 'DELETE FROM notes', + 'DELETE FROM folders', + 'DELETE FROM resources', + 'DELETE FROM tags', + 'DELETE FROM note_tags', + 'DELETE FROM master_keys', + 'DELETE FROM settings', + + 'DELETE FROM deleted_items', + 'DELETE FROM sync_items', ]; await databases_[id].transactionExecBatch(queries); @@ -139,13 +139,13 @@ async function setupDatabase(id = null) { return; } - const filePath = __dirname + "/data/test-" + id + ".sqlite"; + const filePath = __dirname + '/data/test-' + id + '.sqlite'; try { await fs.unlink(filePath); } catch (error) { // Don't care if the file doesn't exist - } + }; databases_[id] = new JoplinDatabase(new DatabaseDriverNode()); await databases_[id].open({ name: filePath }); @@ -156,7 +156,7 @@ async function setupDatabase(id = null) { function resourceDir(id = null) { if (id === null) id = currentClient_; - return __dirname + "/data/resources-" + id; + return __dirname + '/data/resources-' + id; } async function setupDatabaseAndSynchronizer(id = null) { @@ -211,18 +211,16 @@ async function loadEncryptionMasterKey(id = null, useExisting = false) { let masterKey = null; - if (!useExisting) { - // Create it - masterKey = await service.generateMasterKey("123456"); + if (!useExisting) { // Create it + masterKey = await service.generateMasterKey('123456'); masterKey = await MasterKey.save(masterKey); - } else { - // Use the one already available + } else { // Use the one already available materKey = await MasterKey.all(); - if (!materKey.length) throw new Error("No mater key available"); + if (!materKey.length) throw new Error('No mater key available'); masterKey = materKey[0]; } - await service.loadMasterKey(masterKey, "123456", true); + await service.loadMasterKey(masterKey, '123456', true); return masterKey; } @@ -230,21 +228,21 @@ async function loadEncryptionMasterKey(id = null, useExisting = false) { function fileApi() { if (fileApi_) return fileApi_; - if (syncTargetId_ == SyncTargetRegistry.nameToId("filesystem")) { - fs.removeSync(syncDir); + if (syncTargetId_ == SyncTargetRegistry.nameToId('filesystem')) { + fs.removeSync(syncDir) fs.mkdirpSync(syncDir, 0o755); fileApi_ = new FileApi(syncDir, new FileApiDriverLocal()); - } else if (syncTargetId_ == SyncTargetRegistry.nameToId("memory")) { - fileApi_ = new FileApi("/root", new FileApiDriverMemory()); - } else if (syncTargetId_ == SyncTargetRegistry.nameToId("nextcloud")) { + } else if (syncTargetId_ == SyncTargetRegistry.nameToId('memory')) { + fileApi_ = new FileApi('/root', new FileApiDriverMemory()); + } else if (syncTargetId_ == SyncTargetRegistry.nameToId('nextcloud')) { const options = { - baseUrl: () => "http://nextcloud.local/remote.php/dav/files/admin/JoplinTest", - username: () => "admin", - password: () => "123456", + baseUrl: () => 'http://nextcloud.local/remote.php/dav/files/admin/JoplinTest', + username: () => 'admin', + password: () => '123456', }; const api = new WebDavApi(options); - fileApi_ = new FileApi("", new FileApiDriverWebDav(api)); + fileApi_ = new FileApi('', new FileApiDriverWebDav(api)); } // } else if (syncTargetId == Setting.SYNC_TARGET_ONEDRIVE) { @@ -290,9 +288,9 @@ async function checkThrowAsync(asyncFn) { } function fileContentEqual(path1, path2) { - const fs = require("fs-extra"); - const content1 = fs.readFileSync(path1, "base64"); - const content2 = fs.readFileSync(path2, "base64"); + const fs = require('fs-extra'); + const content1 = fs.readFileSync(path1, 'base64'); + const content2 = fs.readFileSync(path2, 'base64'); return content1 === content2; } @@ -306,24 +304,7 @@ function asyncTest(callback) { console.error(error); } done(); - }; + } } -module.exports = { - setupDatabase, - setupDatabaseAndSynchronizer, - db, - synchronizer, - fileApi, - sleep, - clearDatabase, - switchClient, - syncTargetId, - objectsEqual, - checkThrowAsync, - encryptionService, - loadEncryptionMasterKey, - fileContentEqual, - decryptionWorker, - asyncTest, -}; +module.exports = { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest }; \ No newline at end of file diff --git a/CliClientDemo/index.js b/CliClientDemo/index.js index 991dea36b..2bccfac51 100644 --- a/CliClientDemo/index.js +++ b/CliClientDemo/index.js @@ -1,31 +1,31 @@ #!/usr/bin/env node -"use strict"; +'use strict'; -const spawn = require("child_process").spawn; -const os = require("os"); -const fs = require("fs-extra"); +const spawn = require('child_process').spawn; +const os = require('os'); +const fs = require('fs-extra'); -const joplinPath = __dirname + "/node_modules/.bin/joplin"; -const profileDir = os.homedir() + "/.config/demo-joplin"; -const dbFilename = "database.sqlite"; +const joplinPath = __dirname + '/node_modules/.bin/joplin'; +const profileDir = os.homedir() + '/.config/demo-joplin'; +const dbFilename = 'database.sqlite'; fs.ensureDirSync(profileDir); -if (!fs.pathExistsSync(profileDir + "/" + dbFilename)) { - fs.copySync(__dirname + "/" + dbFilename, profileDir + "/" + dbFilename); +if (!fs.pathExistsSync(profileDir + '/' + dbFilename)) { + fs.copySync(__dirname + '/' + dbFilename, profileDir + '/' + dbFilename); } const opt = { cwd: __dirname, env: (function() { - process.env.NODE_PATH = "."; + process.env.NODE_PATH = '.'; return process.env; - })(), - stdio: [process.stdin, process.stdout, process.stderr], + }()), + stdio: [process.stdin, process.stdout, process.stderr] }; -const app = spawn(joplinPath, ["--is-demo", "--profile", profileDir], opt); +const app = spawn(joplinPath, ['--is-demo', '--profile', profileDir], opt); -app.on("close", code => { +app.on('close', (code) => { process.exit(code); -}); +}); \ No newline at end of file diff --git a/ElectronClient/app/ElectronAppWrapper.js b/ElectronClient/app/ElectronAppWrapper.js index 568e06205..718a887fb 100644 --- a/ElectronClient/app/ElectronAppWrapper.js +++ b/ElectronClient/app/ElectronAppWrapper.js @@ -1,13 +1,14 @@ -const { _ } = require("lib/locale.js"); -const { BrowserWindow, Menu, Tray } = require("electron"); -const { shim } = require("lib/shim"); -const url = require("url"); -const path = require("path"); -const urlUtils = require("lib/urlUtils.js"); -const { dirname, basename } = require("lib/path-utils"); -const fs = require("fs-extra"); +const { _ } = require('lib/locale.js'); +const { BrowserWindow, Menu, Tray } = require('electron'); +const { shim } = require('lib/shim'); +const url = require('url') +const path = require('path') +const urlUtils = require('lib/urlUtils.js'); +const { dirname, basename } = require('lib/path-utils'); +const fs = require('fs-extra'); class ElectronAppWrapper { + constructor(electronApp, env) { this.electronApp_ = electronApp; this.env_ = env; @@ -34,13 +35,13 @@ class ElectronAppWrapper { } createWindow() { - const windowStateKeeper = require("electron-window-state"); + const windowStateKeeper = require('electron-window-state'); // Load the previous state with fallback to defaults const windowState = windowStateKeeper({ defaultWidth: 800, defaultHeight: 600, - file: "window-state-" + this.env_ + ".json", + file: 'window-state-' + this.env_ + '.json', }); const windowOptions = { @@ -52,22 +53,20 @@ class ElectronAppWrapper { // Linux icon workaround for bug https://github.com/electron-userland/electron-builder/issues/2098 // Fix: https://github.com/electron-userland/electron-builder/issues/2269 - if (shim.isLinux()) windowOptions.icon = __dirname + "/build/icons/128x128.png"; + if (shim.isLinux()) windowOptions.icon = __dirname + '/build/icons/128x128.png'; - this.win_ = new BrowserWindow(windowOptions); + this.win_ = new BrowserWindow(windowOptions) - this.win_.loadURL( - url.format({ - pathname: path.join(__dirname, "index.html"), - protocol: "file:", - slashes: true, - }) - ); + this.win_.loadURL(url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true + })) // Uncomment this to view errors if the application does not start // if (this.env_ === 'dev') this.win_.webContents.openDevTools(); - this.win_.on("close", event => { + this.win_.on('close', (event) => { // If it's on macOS, the app is completely closed only if the user chooses to close the app (willQuitApp_ will be true) // otherwise the window is simply hidden, and will be re-open once the app is "activated" (which happens when the // user clicks on the icon in the task bar). @@ -75,7 +74,7 @@ class ElectronAppWrapper { // On Windows and Linux, the app is closed when the window is closed *except* if the tray icon is used. In which // case the app must be explicitely closed with Ctrl+Q or by right-clicking on the tray icon and selecting "Exit". - if (process.platform === "darwin") { + if (process.platform === 'darwin') { if (this.willQuitApp_) { this.win_ = null; } else { @@ -90,7 +89,7 @@ class ElectronAppWrapper { this.win_ = null; } } - }); + }) // Let us register listeners on the window, so we can update the state // automatically (the listeners will be removed when the window is closed) @@ -131,10 +130,10 @@ class ElectronAppWrapper { buildDir() { if (this.buildDir_) return this.buildDir_; - let dir = __dirname + "/build"; + let dir = __dirname + '/build'; if (!fs.pathExistsSync(dir)) { - dir = dirname(__dirname) + "/build"; - if (!fs.pathExistsSync(dir)) throw new Error("Cannot find build dir"); + dir = dirname(__dirname) + '/build'; + if (!fs.pathExistsSync(dir)) throw new Error('Cannot find build dir'); } this.buildDir_ = dir; @@ -142,15 +141,15 @@ class ElectronAppWrapper { } trayIconFilename_() { - let output = ""; + let output = ''; - if (process.platform === "darwin") { - output = "macos-16x16Template.png"; // Electron Template Image format + if (process.platform === 'darwin') { + output = 'macos-16x16Template.png'; // Electron Template Image format } else { - output = "16x16.png"; + output = '16x16.png'; } - if (this.env_ === "dev") output = "16x16-dev.png"; + if (this.env_ === 'dev') output = '16x16-dev.png' return output; } @@ -158,11 +157,11 @@ class ElectronAppWrapper { // Note: this must be called only after the "ready" event of the app has been dispatched createTray(contextMenu) { try { - this.tray_ = new Tray(this.buildDir() + "/icons/" + this.trayIconFilename_()); - this.tray_.setToolTip(this.electronApp_.getName()); - this.tray_.setContextMenu(contextMenu); + this.tray_ = new Tray(this.buildDir() + '/icons/' + this.trayIconFilename_()) + this.tray_.setToolTip(this.electronApp_.getName()) + this.tray_.setContextMenu(contextMenu) - this.tray_.on("click", () => { + this.tray_.on('click', () => { this.window().show(); }); } catch (error) { @@ -177,7 +176,7 @@ class ElectronAppWrapper { } ensureSingleInstance() { - if (this.env_ === "dev") return false; + if (this.env_ === 'dev') return false; return new Promise((resolve, reject) => { const alreadyRunning = this.electronApp_.makeSingleInstance((commandLine, workingDirectory) => { @@ -204,18 +203,19 @@ class ElectronAppWrapper { this.createWindow(); - this.electronApp_.on("before-quit", () => { + this.electronApp_.on('before-quit', () => { this.willQuitApp_ = true; - }); + }) - this.electronApp_.on("window-all-closed", () => { + this.electronApp_.on('window-all-closed', () => { this.electronApp_.quit(); - }); + }) - this.electronApp_.on("activate", () => { + this.electronApp_.on('activate', () => { this.win_.show(); - }); + }) } + } -module.exports = { ElectronAppWrapper }; +module.exports = { ElectronAppWrapper }; \ No newline at end of file diff --git a/ElectronClient/app/InteropServiceHelper.js b/ElectronClient/app/InteropServiceHelper.js index 22c439255..341a19c16 100644 --- a/ElectronClient/app/InteropServiceHelper.js +++ b/ElectronClient/app/InteropServiceHelper.js @@ -1,20 +1,21 @@ -const { _ } = require("lib/locale"); -const { bridge } = require("electron").remote.require("./bridge"); -const InteropService = require("lib/services/InteropService"); +const { _ } = require('lib/locale'); +const { bridge } = require('electron').remote.require('./bridge'); +const InteropService = require('lib/services/InteropService'); class InteropServiceHelper { + static async export(dispatch, module, options = null) { if (!options) options = {}; let path = null; - if (module.target === "file") { + if (module.target === 'file') { path = bridge().showSaveDialog({ - filters: [{ name: module.description, extensions: [module.fileExtension] }], + filters: [{ name: module.description, extensions: [module.fileExtension]}] }); } else { path = bridge().showOpenDialog({ - properties: ["openDirectory", "createDirectory"], + properties: ['openDirectory', 'createDirectory'], }); } @@ -23,8 +24,8 @@ class InteropServiceHelper { if (Array.isArray(path)) path = path[0]; dispatch({ - type: "WINDOW_COMMAND", - name: "showModalMessage", + type: 'WINDOW_COMMAND', + name: 'showModalMessage', message: _('Exporting to "%s" as "%s" format. Please wait...', path, module.format), }); @@ -37,13 +38,14 @@ class InteropServiceHelper { const service = new InteropService(); const result = await service.export(exportOptions); - console.info("Export result: ", result); + console.info('Export result: ', result); dispatch({ - type: "WINDOW_COMMAND", - name: "hideModalMessage", + type: 'WINDOW_COMMAND', + name: 'hideModalMessage', }); } + } -module.exports = InteropServiceHelper; +module.exports = InteropServiceHelper; \ No newline at end of file diff --git a/ElectronClient/app/app.js b/ElectronClient/app/app.js index e1ed1c19d..9a4b810a2 100644 --- a/ElectronClient/app/app.js +++ b/ElectronClient/app/app.js @@ -1,46 +1,47 @@ -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const { BaseApplication } = require("lib/BaseApplication"); -const { FoldersScreenUtils } = require("lib/folders-screen-utils.js"); -const Setting = require("lib/models/Setting.js"); -const { shim } = require("lib/shim.js"); -const BaseModel = require("lib/BaseModel.js"); -const MasterKey = require("lib/models/MasterKey"); -const { _, setLocale } = require("lib/locale.js"); -const os = require("os"); -const fs = require("fs-extra"); -const Tag = require("lib/models/Tag.js"); -const { reg } = require("lib/registry.js"); -const { sprintf } = require("sprintf-js"); -const { JoplinDatabase } = require("lib/joplin-database.js"); -const { DatabaseDriverNode } = require("lib/database-driver-node.js"); -const { ElectronAppWrapper } = require("./ElectronAppWrapper"); -const { defaultState } = require("lib/reducer.js"); -const packageInfo = require("./packageInfo.js"); -const AlarmService = require("lib/services/AlarmService.js"); -const AlarmServiceDriverNode = require("lib/services/AlarmServiceDriverNode"); -const DecryptionWorker = require("lib/services/DecryptionWorker"); -const InteropService = require("lib/services/InteropService"); -const InteropServiceHelper = require("./InteropServiceHelper.js"); +const { BaseApplication } = require('lib/BaseApplication'); +const { FoldersScreenUtils } = require('lib/folders-screen-utils.js'); +const Setting = require('lib/models/Setting.js'); +const { shim } = require('lib/shim.js'); +const BaseModel = require('lib/BaseModel.js'); +const MasterKey = require('lib/models/MasterKey'); +const { _, setLocale } = require('lib/locale.js'); +const os = require('os'); +const fs = require('fs-extra'); +const Tag = require('lib/models/Tag.js'); +const { reg } = require('lib/registry.js'); +const { sprintf } = require('sprintf-js'); +const { JoplinDatabase } = require('lib/joplin-database.js'); +const { DatabaseDriverNode } = require('lib/database-driver-node.js'); +const { ElectronAppWrapper } = require('./ElectronAppWrapper'); +const { defaultState } = require('lib/reducer.js'); +const packageInfo = require('./packageInfo.js'); +const AlarmService = require('lib/services/AlarmService.js'); +const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode'); +const DecryptionWorker = require('lib/services/DecryptionWorker'); +const InteropService = require('lib/services/InteropService'); +const InteropServiceHelper = require('./InteropServiceHelper.js'); -const { bridge } = require("electron").remote.require("./bridge"); +const { bridge } = require('electron').remote.require('./bridge'); const Menu = bridge().Menu; const MenuItem = bridge().MenuItem; const appDefaultState = Object.assign({}, defaultState, { route: { - type: "NAV_GO", - routeName: "Main", + type: 'NAV_GO', + routeName: 'Main', props: {}, }, navHistory: [], fileToImport: null, windowCommand: null, - noteVisiblePanes: ["editor", "viewer"], + noteVisiblePanes: ['editor', 'viewer'], windowContentSize: bridge().windowContentSize(), }); class Application extends BaseApplication { + constructor() { super(); this.lastMenuScreen_ = null; @@ -51,7 +52,7 @@ class Application extends BaseApplication { } checkForUpdateLoggerPath() { - return Setting.value("profileDir") + "/log-autoupdater.txt"; + return Setting.value('profileDir') + '/log-autoupdater.txt'; } reducer(state = appDefaultState, action) { @@ -59,9 +60,11 @@ class Application extends BaseApplication { try { switch (action.type) { - case "NAV_BACK": - case "NAV_GO": - const goingBack = action.type === "NAV_BACK"; + + case 'NAV_BACK': + case 'NAV_GO': + + const goingBack = action.type === 'NAV_BACK'; if (goingBack && !state.navHistory.length) break; @@ -81,47 +84,52 @@ class Application extends BaseApplication { action = newAction; } - + if (!goingBack) newNavHistory.push(currentRoute); - newState.navHistory = newNavHistory; + newState.navHistory = newNavHistory newState.route = action; break; - case "WINDOW_CONTENT_SIZE_SET": + case 'WINDOW_CONTENT_SIZE_SET': + newState = Object.assign({}, state); newState.windowContentSize = action.size; break; - case "WINDOW_COMMAND": + case 'WINDOW_COMMAND': + newState = Object.assign({}, state); let command = Object.assign({}, action); delete command.type; newState.windowCommand = command; break; - case "NOTE_VISIBLE_PANES_TOGGLE": + case 'NOTE_VISIBLE_PANES_TOGGLE': + let panes = state.noteVisiblePanes.slice(); if (panes.length === 2) { - panes = ["editor"]; - } else if (panes.indexOf("editor") >= 0) { - panes = ["viewer"]; - } else if (panes.indexOf("viewer") >= 0) { - panes = ["editor", "viewer"]; + panes = ['editor']; + } else if (panes.indexOf('editor') >= 0) { + panes = ['viewer']; + } else if (panes.indexOf('viewer') >= 0) { + panes = ['editor', 'viewer']; } else { - panes = ["editor", "viewer"]; + panes = ['editor', 'viewer']; } newState = Object.assign({}, state); newState.noteVisiblePanes = panes; break; - case "NOTE_VISIBLE_PANES_SET": + case 'NOTE_VISIBLE_PANES_SET': + newState = Object.assign({}, state); newState.noteVisiblePanes = action.panes; break; + } } catch (error) { - error.message = "In reducer: " + error.message + " Action: " + JSON.stringify(action); + error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action); throw error; } @@ -129,16 +137,16 @@ class Application extends BaseApplication { } async generalMiddleware(store, next, action) { - if ((action.type == "SETTING_UPDATE_ONE" && action.key == "locale") || action.type == "SETTING_UPDATE_ALL") { - setLocale(Setting.value("locale")); + if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'locale' || action.type == 'SETTING_UPDATE_ALL') { + setLocale(Setting.value('locale')); this.refreshMenu(); } - if ((action.type == "SETTING_UPDATE_ONE" && action.key == "showTrayIcon") || action.type == "SETTING_UPDATE_ALL") { + if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'showTrayIcon' || action.type == 'SETTING_UPDATE_ALL') { this.updateTray(); } - if ((action.type == "SETTING_UPDATE_ONE" && action.key == "style.editor.fontFamily") || action.type == "SETTING_UPDATE_ALL") { + if (action.type == 'SETTING_UPDATE_ONE' && action.key == 'style.editor.fontFamily' || action.type == 'SETTING_UPDATE_ALL') { this.updateEditorFont(); } @@ -146,19 +154,19 @@ class Application extends BaseApplication { if (!await reg.syncTarget().syncStarted()) reg.scheduleSync(5, { syncSteps: ["update_remote", "delete_remote"] }); } - if (["EVENT_NOTE_ALARM_FIELD_CHANGE", "NOTE_DELETE"].indexOf(action.type) >= 0) { - await AlarmService.updateNoteNotification(action.id, action.type === "NOTE_DELETE"); + if (['EVENT_NOTE_ALARM_FIELD_CHANGE', 'NOTE_DELETE'].indexOf(action.type) >= 0) { + await AlarmService.updateNoteNotification(action.id, action.type === 'NOTE_DELETE'); } const result = await super.generalMiddleware(store, next, action); const newState = store.getState(); - if (action.type === "NAV_GO" || action.type === "NAV_BACK") { + if (action.type === 'NAV_GO' || action.type === 'NAV_BACK') { app().updateMenu(newState.route.routeName); } - if (["NOTE_VISIBLE_PANES_TOGGLE", "NOTE_VISIBLE_PANES_SET"].indexOf(action.type) >= 0) { - Setting.setValue("noteVisiblePanes", newState.noteVisiblePanes); + if (['NOTE_VISIBLE_PANES_TOGGLE', 'NOTE_VISIBLE_PANES_SET'].indexOf(action.type) >= 0) { + Setting.setValue('noteVisiblePanes', newState.noteVisiblePanes); } return result; @@ -174,19 +182,19 @@ class Application extends BaseApplication { if (this.lastMenuScreen_ === screen) return; const sortNoteItems = []; - const sortNoteOptions = Setting.enumOptions("notes.sortOrder.field"); + const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field'); for (let field in sortNoteOptions) { if (!sortNoteOptions.hasOwnProperty(field)) continue; sortNoteItems.push({ label: sortNoteOptions[field], - screens: ["Main"], - type: "checkbox", - checked: Setting.value("notes.sortOrder.field") === field, + screens: ['Main'], + type: 'checkbox', + checked: Setting.value('notes.sortOrder.field') === field, click: () => { - Setting.setValue("notes.sortOrder.field", field); + Setting.setValue('notes.sortOrder.field', field); this.refreshMenu(); - }, - }); + } + }); } const importItems = []; @@ -195,36 +203,36 @@ class Application extends BaseApplication { const ioModules = ioService.modules(); for (let i = 0; i < ioModules.length; i++) { const module = ioModules[i]; - if (module.type === "exporter") { + if (module.type === 'exporter') { exportItems.push({ - label: module.format + " - " + module.description, - screens: ["Main"], + label: module.format + ' - ' + module.description, + screens: ['Main'], click: async () => { await InteropServiceHelper.export(this.dispatch.bind(this), module); - }, + } }); } else { for (let j = 0; j < module.sources.length; j++) { const moduleSource = module.sources[j]; - let label = [module.format + " - " + module.description]; + let label = [module.format + ' - ' + module.description]; if (module.sources.length > 1) { - label.push("(" + (moduleSource === "file" ? _("File") : _("Directory")) + ")"); + label.push('(' + (moduleSource === 'file' ? _('File') : _('Directory')) + ')'); } importItems.push({ - label: label.join(" "), - screens: ["Main"], + label: label.join(' '), + screens: ['Main'], click: async () => { let path = null; const selectedFolderId = this.store().getState().selectedFolderId; - if (moduleSource === "file") { + if (moduleSource === 'file') { path = bridge().showOpenDialog({ - filters: [{ name: module.description, extensions: [module.fileExtension] }], + filters: [{ name: module.description, extensions: [module.fileExtension]}] }); } else { path = bridge().showOpenDialog({ - properties: ["openDirectory", "createDirectory"], + properties: ['openDirectory', 'createDirectory'], }); } @@ -233,29 +241,29 @@ class Application extends BaseApplication { if (Array.isArray(path)) path = path[0]; this.dispatch({ - type: "WINDOW_COMMAND", - name: "showModalMessage", + type: 'WINDOW_COMMAND', + name: 'showModalMessage', message: _('Importing from "%s" as "%s" format. Please wait...', path, module.format), }); const importOptions = {}; importOptions.path = path; importOptions.format = module.format; - importOptions.destinationFolderId = !module.isNoteArchive && moduleSource === "file" ? selectedFolderId : null; + importOptions.destinationFolderId = !module.isNoteArchive && moduleSource === 'file' ? selectedFolderId : null; const service = new InteropService(); try { const result = await service.import(importOptions); - console.info("Import result: ", result); + console.info('Import result: ', result); } catch (error) { bridge().showErrorMessageBox(error.message); } this.dispatch({ - type: "WINDOW_COMMAND", - name: "hideModalMessage", + type: 'WINDOW_COMMAND', + name: 'hideModalMessage', }); - }, + } }); } } @@ -263,264 +271,216 @@ class Application extends BaseApplication { const template = [ { - label: _("File"), - submenu: [ - { - label: _("New note"), - accelerator: "CommandOrControl+N", - screens: ["Main"], - click: () => { - this.dispatch({ - type: "WINDOW_COMMAND", - name: "newNote", - }); - }, - }, - { - label: _("New to-do"), - accelerator: "CommandOrControl+T", - screens: ["Main"], - click: () => { - this.dispatch({ - type: "WINDOW_COMMAND", - name: "newTodo", - }); - }, - }, - { - label: _("New notebook"), - accelerator: "CommandOrControl+B", - screens: ["Main"], - click: () => { - this.dispatch({ - type: "WINDOW_COMMAND", - name: "newNotebook", - }); - }, - }, - { - type: "separator", - // }, { - // label: _('Import Evernote notes'), - // click: () => { - // const filePaths = bridge().showOpenDialog({ - // properties: ['openFile', 'createDirectory'], - // filters: [ - // { name: _('Evernote Export Files'), extensions: ['enex'] }, - // ] - // }); - // if (!filePaths || !filePaths.length) return; + label: _('File'), + submenu: [{ + label: _('New note'), + accelerator: 'CommandOrControl+N', + screens: ['Main'], + click: () => { + this.dispatch({ + type: 'WINDOW_COMMAND', + name: 'newNote', + }); + } + }, { + label: _('New to-do'), + accelerator: 'CommandOrControl+T', + screens: ['Main'], + click: () => { + this.dispatch({ + type: 'WINDOW_COMMAND', + name: 'newTodo', + }); + } + }, { + label: _('New notebook'), + accelerator: 'CommandOrControl+B', + screens: ['Main'], + click: () => { + this.dispatch({ + type: 'WINDOW_COMMAND', + name: 'newNotebook', + }); + } + }, { + type: 'separator', + // }, { + // label: _('Import Evernote notes'), + // click: () => { + // const filePaths = bridge().showOpenDialog({ + // properties: ['openFile', 'createDirectory'], + // filters: [ + // { name: _('Evernote Export Files'), extensions: ['enex'] }, + // ] + // }); + // if (!filePaths || !filePaths.length) return; - // this.dispatch({ - // type: 'NAV_GO', - // routeName: 'Import', - // props: { - // filePath: filePaths[0], - // }, - // }); - // } + // this.dispatch({ + // type: 'NAV_GO', + // routeName: 'Import', + // props: { + // filePath: filePaths[0], + // }, + // }); + // } + }, { + label: _('Import'), + submenu: importItems, + }, { + label: _('Export'), + submenu: exportItems, + }, { + type: 'separator', + platforms: ['darwin'], + }, { + label: _('Hide %s', 'Joplin'), + platforms: ['darwin'], + accelerator: 'CommandOrControl+H', + click: () => { bridge().electronApp().hide() } + }, { + type: 'separator', + }, { + label: _('Quit'), + accelerator: 'CommandOrControl+Q', + click: () => { bridge().electronApp().quit() } + }] + }, { + label: _('Edit'), + submenu: [{ + label: _('Copy'), + screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'], + role: 'copy', + accelerator: 'CommandOrControl+C', + }, { + label: _('Cut'), + screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'], + role: 'cut', + accelerator: 'CommandOrControl+X', + }, { + label: _('Paste'), + screens: ['Main', 'OneDriveLogin', 'Config', 'EncryptionConfig'], + role: 'paste', + accelerator: 'CommandOrControl+V', + }, { + type: 'separator', + screens: ['Main'], + }, { + label: _('Search in all the notes'), + screens: ['Main'], + accelerator: 'F6', + click: () => { + this.dispatch({ + type: 'WINDOW_COMMAND', + name: 'search', + }); }, - { - label: _("Import"), - submenu: importItems, + }], + }, { + label: _('View'), + submenu: [{ + label: _('Toggle editor layout'), + screens: ['Main'], + accelerator: 'CommandOrControl+L', + click: () => { + this.dispatch({ + type: 'WINDOW_COMMAND', + name: 'toggleVisiblePanes', + }); + } + }, { + type: 'separator', + screens: ['Main'], + }, { + label: Setting.settingMetadata('notes.sortOrder.field').label(), + screens: ['Main'], + submenu: sortNoteItems, + }, { + label: Setting.settingMetadata('notes.sortOrder.reverse').label(), + type: 'checkbox', + checked: Setting.value('notes.sortOrder.reverse'), + screens: ['Main'], + click: () => { + Setting.setValue('notes.sortOrder.reverse', !Setting.value('notes.sortOrder.reverse')); }, - { - label: _("Export"), - submenu: exportItems, + }, { + label: Setting.settingMetadata('uncompletedTodosOnTop').label(), + type: 'checkbox', + checked: Setting.value('uncompletedTodosOnTop'), + screens: ['Main'], + click: () => { + Setting.setValue('uncompletedTodosOnTop', !Setting.value('uncompletedTodosOnTop')); }, - { - type: "separator", - platforms: ["darwin"], - }, - { - label: _("Hide %s", "Joplin"), - platforms: ["darwin"], - accelerator: "CommandOrControl+H", - click: () => { - bridge() - .electronApp() - .hide(); - }, - }, - { - type: "separator", - }, - { - label: _("Quit"), - accelerator: "CommandOrControl+Q", - click: () => { - bridge() - .electronApp() - .quit(); - }, - }, - ], - }, - { - label: _("Edit"), - submenu: [ - { - label: _("Copy"), - screens: ["Main", "OneDriveLogin", "Config", "EncryptionConfig"], - role: "copy", - accelerator: "CommandOrControl+C", - }, - { - label: _("Cut"), - screens: ["Main", "OneDriveLogin", "Config", "EncryptionConfig"], - role: "cut", - accelerator: "CommandOrControl+X", - }, - { - label: _("Paste"), - screens: ["Main", "OneDriveLogin", "Config", "EncryptionConfig"], - role: "paste", - accelerator: "CommandOrControl+V", - }, - { - type: "separator", - screens: ["Main"], - }, - { - label: _("Search in all the notes"), - screens: ["Main"], - accelerator: "F6", - click: () => { - this.dispatch({ - type: "WINDOW_COMMAND", - name: "search", - }); - }, - }, - ], - }, - { - label: _("View"), - submenu: [ - { - label: _("Toggle editor layout"), - screens: ["Main"], - accelerator: "CommandOrControl+L", - click: () => { - this.dispatch({ - type: "WINDOW_COMMAND", - name: "toggleVisiblePanes", - }); - }, - }, - { - type: "separator", - screens: ["Main"], - }, - { - label: Setting.settingMetadata("notes.sortOrder.field").label(), - screens: ["Main"], - submenu: sortNoteItems, - }, - { - label: Setting.settingMetadata("notes.sortOrder.reverse").label(), - type: "checkbox", - checked: Setting.value("notes.sortOrder.reverse"), - screens: ["Main"], - click: () => { - Setting.setValue("notes.sortOrder.reverse", !Setting.value("notes.sortOrder.reverse")); - }, - }, - { - label: Setting.settingMetadata("uncompletedTodosOnTop").label(), - type: "checkbox", - checked: Setting.value("uncompletedTodosOnTop"), - screens: ["Main"], - click: () => { - Setting.setValue("uncompletedTodosOnTop", !Setting.value("uncompletedTodosOnTop")); - }, - }, - ], - }, - { - label: _("Tools"), - submenu: [ - { - label: _("Synchronisation status"), - click: () => { - this.dispatch({ - type: "NAV_GO", - routeName: "Status", - }); - }, - }, - { - type: "separator", - screens: ["Main"], - }, - { - label: _("Encryption options"), - click: () => { - this.dispatch({ - type: "NAV_GO", - routeName: "EncryptionConfig", - }); - }, - }, - { - label: _("General Options"), - accelerator: "CommandOrControl+,", - click: () => { - this.dispatch({ - type: "NAV_GO", - routeName: "Config", - }); - }, - }, - ], - }, - { - label: _("Help"), - submenu: [ - { - label: _("Website and documentation"), - accelerator: "F1", - click() { - bridge().openExternal("http://joplin.cozic.net"); - }, - }, - { - label: _("Make a donation"), - click() { - bridge().openExternal("http://joplin.cozic.net/donate"); - }, - }, - { - label: _("Check for updates..."), - click: () => { - bridge().checkForUpdates(false, bridge().window(), this.checkForUpdateLoggerPath()); - }, - }, - { - type: "separator", - screens: ["Main"], - }, - { - label: _("About Joplin"), - click: () => { - const p = packageInfo; - let message = [p.description, "", "Copyright © 2016-2018 Laurent Cozic", _("%s %s (%s, %s)", p.name, p.version, Setting.value("env"), process.platform)]; - bridge().showInfoMessageBox(message.join("\n"), { - icon: - bridge() - .electronApp() - .buildDir() + "/icons/32x32.png", - }); - }, - }, - ], + }], + }, { + label: _('Tools'), + submenu: [{ + label: _('Synchronisation status'), + click: () => { + this.dispatch({ + type: 'NAV_GO', + routeName: 'Status', + }); + } + }, { + type: 'separator', + screens: ['Main'], + },{ + label: _('Encryption options'), + click: () => { + this.dispatch({ + type: 'NAV_GO', + routeName: 'EncryptionConfig', + }); + } + },{ + label: _('General Options'), + accelerator: 'CommandOrControl+,', + click: () => { + this.dispatch({ + type: 'NAV_GO', + routeName: 'Config', + }); + } + }], + }, { + label: _('Help'), + submenu: [{ + label: _('Website and documentation'), + accelerator: 'F1', + click () { bridge().openExternal('http://joplin.cozic.net') } + }, { + label: _('Make a donation'), + click () { bridge().openExternal('http://joplin.cozic.net/donate') } + }, { + label: _('Check for updates...'), + click: () => { + bridge().checkForUpdates(false, bridge().window(), this.checkForUpdateLoggerPath()); + } + }, { + type: 'separator', + screens: ['Main'], + }, { + label: _('About Joplin'), + click: () => { + const p = packageInfo; + let message = [ + p.description, + '', + 'Copyright © 2016-2018 Laurent Cozic', + _('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), process.platform), + ]; + bridge().showInfoMessageBox(message.join('\n'), { + icon: bridge().electronApp().buildDir() + '/icons/32x32.png', + }); + } + }] }, ]; function isEmptyMenu(template) { for (let i = 0; i < template.length; i++) { const t = template[i]; - if (t.type !== "separator") return false; + if (t.type !== 'separator') return false; } return true; } @@ -534,7 +494,7 @@ class Application extends BaseApplication { if (t.screens && t.screens.indexOf(screen) < 0) continue; if (t.platforms && t.platforms.indexOf(platform) < 0) continue; if (t.submenu) t.submenu = removeUnwantedItems(t.submenu, screen); - if ("submenu" in t && isEmptyMenu(t.submenu)) continue; + if (('submenu' in t) && isEmptyMenu(t.submenu)) continue; output.push(t); } return output; @@ -556,41 +516,31 @@ class Application extends BaseApplication { const app = bridge().electronApp(); - if (app.trayShown() === Setting.value("showTrayIcon")) return; + if (app.trayShown() === Setting.value('showTrayIcon')) return; - if (!Setting.value("showTrayIcon")) { + if (!Setting.value('showTrayIcon')) { app.destroyTray(); } else { const contextMenu = Menu.buildFromTemplate([ - { - label: _("Open %s", app.electronApp().getName()), - click: () => { - app.window().show(); - }, - }, - { type: "separator" }, - { - label: _("Exit"), - click: () => { - app.quit(); - }, - }, - ]); + { label: _('Open %s', app.electronApp().getName()), click: () => { app.window().show(); } }, + { type: 'separator' }, + { label: _('Exit'), click: () => { app.quit() } }, + ]) app.createTray(contextMenu); } } updateEditorFont() { const fontFamilies = []; - if (Setting.value("style.editor.fontFamily")) fontFamilies.push('"' + Setting.value("style.editor.fontFamily") + '"'); - fontFamilies.push("monospace"); + if (Setting.value('style.editor.fontFamily')) fontFamilies.push('"' + Setting.value('style.editor.fontFamily') + '"'); + fontFamilies.push('monospace'); // The '*' and '!important' parts are necessary to make sure Russian text is displayed properly // https://github.com/laurent22/joplin/issues/155 - const css = ".ace_editor * { font-family: " + fontFamilies.join(", ") + " !important; }"; - const styleTag = document.createElement("style"); - styleTag.type = "text/css"; + const css = '.ace_editor * { font-family: ' + fontFamilies.join(', ') + ' !important; }'; + const styleTag = document.createElement('style'); + styleTag.type = 'text/css'; styleTag.appendChild(document.createTextNode(css)); document.head.appendChild(styleTag); } @@ -601,17 +551,13 @@ class Application extends BaseApplication { AlarmService.setDriver(new AlarmServiceDriverNode({ appName: packageInfo.build.appId })); AlarmService.setLogger(reg.logger()); - reg.setShowErrorMessageBoxHandler(message => { - bridge().showErrorMessageBox(message); - }); + reg.setShowErrorMessageBoxHandler((message) => { bridge().showErrorMessageBox(message) }); - if (Setting.value("openDevTools")) { - bridge() - .window() - .webContents.openDevTools(); + if (Setting.value('openDevTools')) { + bridge().window().webContents.openDevTools(); } - this.updateMenu("Main"); + this.updateMenu('Main'); this.initRedux(); @@ -625,39 +571,35 @@ class Application extends BaseApplication { const tags = await Tag.allWithNotes(); this.dispatch({ - type: "TAG_UPDATE_ALL", + type: 'TAG_UPDATE_ALL', items: tags, }); const masterKeys = await MasterKey.all(); this.dispatch({ - type: "MASTERKEY_UPDATE_ALL", + type: 'MASTERKEY_UPDATE_ALL', items: masterKeys, }); this.store().dispatch({ - type: "FOLDER_SELECT", - id: Setting.value("activeFolderId"), + type: 'FOLDER_SELECT', + id: Setting.value('activeFolderId'), }); // Note: Auto-update currently doesn't work in Linux: it downloads the update // but then doesn't install it on exit. if (shim.isWindows() || shim.isMac()) { const runAutoUpdateCheck = () => { - if (Setting.value("autoUpdateEnabled")) { + if (Setting.value('autoUpdateEnabled')) { bridge().checkForUpdates(true, bridge().window(), this.checkForUpdateLoggerPath()); } - }; - + } + // Initial check on startup - setTimeout(() => { - runAutoUpdateCheck(); - }, 5000); + setTimeout(() => { runAutoUpdateCheck() }, 5000); // Then every x hours - setInterval(() => { - runAutoUpdateCheck(); - }, 12 * 60 * 60 * 1000); + setInterval(() => { runAutoUpdateCheck() }, 12 * 60 * 60 * 1000); } this.updateTray(); @@ -666,7 +608,7 @@ class Application extends BaseApplication { AlarmService.garbageCollect(); }, 1000 * 60 * 60); - if (Setting.value("env") === "dev") { + if (Setting.value('env') === 'dev') { AlarmService.updateAllNotifications(); } else { reg.scheduleSync().then(() => { @@ -678,6 +620,7 @@ class Application extends BaseApplication { }); } } + } let application_ = null; @@ -687,4 +630,4 @@ function app() { return application_; } -module.exports = { app }; +module.exports = { app }; \ No newline at end of file diff --git a/ElectronClient/app/bridge.js b/ElectronClient/app/bridge.js index 755bc6543..46e516b9f 100644 --- a/ElectronClient/app/bridge.js +++ b/ElectronClient/app/bridge.js @@ -1,8 +1,9 @@ -const { _ } = require("lib/locale.js"); -const { dirname } = require("lib/path-utils.js"); -const { Logger } = require("lib/logger.js"); +const { _ } = require('lib/locale.js'); +const { dirname } = require('lib/path-utils.js'); +const { Logger } = require('lib/logger.js'); class Bridge { + constructor(electronWrapper) { this.electronWrapper_ = electronWrapper; this.autoUpdateLogger_ = null; @@ -39,9 +40,9 @@ class Bridge { } showSaveDialog(options) { - const { dialog } = require("electron"); + const {dialog} = require('electron'); if (!options) options = {}; - if (!("defaultPath" in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_; + if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_; const filePath = dialog.showSaveDialog(this.window(), options); if (filePath) { this.lastSelectedPath_ = filePath; @@ -50,10 +51,10 @@ class Bridge { } showOpenDialog(options) { - const { dialog } = require("electron"); + const {dialog} = require('electron'); if (!options) options = {}; - if (!("defaultPath" in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_; - if (!("createDirectory" in options)) options.createDirectory = true; + if (!('defaultPath' in options) && this.lastSelectedPath_) options.defaultPath = this.lastSelectedPath_; + if (!('createDirectory' in options)) options.createDirectory = true; const filePaths = dialog.showOpenDialog(this.window(), options); if (filePaths && filePaths.length) { this.lastSelectedPath_ = dirname(filePaths[0]); @@ -63,77 +64,71 @@ class Bridge { // Don't use this directly - call one of the showXxxxxxxMessageBox() instead showMessageBox_(window, options) { - const { dialog } = require("electron"); - const nativeImage = require("electron").nativeImage; + const {dialog} = require('electron'); + const nativeImage = require('electron').nativeImage if (!window) window = this.window(); return dialog.showMessageBox(window, options); } showErrorMessageBox(message) { return this.showMessageBox_(this.window(), { - type: "error", + type: 'error', message: message, }); } showConfirmMessageBox(message) { const result = this.showMessageBox_(this.window(), { - type: "question", + type: 'question', message: message, - buttons: [_("OK"), _("Cancel")], + buttons: [_('OK'), _('Cancel')], }); return result === 0; } showInfoMessageBox(message, options = {}) { - const result = this.showMessageBox_( - this.window(), - Object.assign( - {}, - { - type: "info", - message: message, - buttons: [_("OK")], - }, - options - ) - ); + const result = this.showMessageBox_(this.window(), Object.assign({}, { + type: 'info', + message: message, + buttons: [_('OK')], + }, options)); return result === 0; } get Menu() { - return require("electron").Menu; + return require('electron').Menu; } get MenuItem() { - return require("electron").MenuItem; + return require('electron').MenuItem; } openExternal(url) { - return require("electron").shell.openExternal(url); + return require('electron').shell.openExternal(url) } openItem(fullPath) { - return require("electron").shell.openItem(fullPath); + return require('electron').shell.openItem(fullPath) } checkForUpdates(inBackground, window, logFilePath) { - const { checkForUpdates } = require("./checkForUpdates.js"); + const { checkForUpdates } = require('./checkForUpdates.js'); checkForUpdates(inBackground, window, logFilePath); } + } let bridge_ = null; function initBridge(wrapper) { - if (bridge_) throw new Error("Bridge already initialized"); + if (bridge_) throw new Error('Bridge already initialized'); bridge_ = new Bridge(wrapper); return bridge_; } function bridge() { - if (!bridge_) throw new Error("Bridge not initialized"); + if (!bridge_) throw new Error('Bridge not initialized'); return bridge_; -} +} -module.exports = { bridge, initBridge }; +module.exports = { bridge, initBridge } \ No newline at end of file diff --git a/ElectronClient/app/checkForUpdates.js b/ElectronClient/app/checkForUpdates.js index 7d9ec8bc2..ef5eb3692 100644 --- a/ElectronClient/app/checkForUpdates.js +++ b/ElectronClient/app/checkForUpdates.js @@ -1,7 +1,7 @@ -const { dialog } = require("electron"); -const { autoUpdater } = require("electron-updater"); -const { Logger } = require("lib/logger.js"); -const { _ } = require("lib/locale.js"); +const { dialog } = require('electron') +const { autoUpdater } = require('electron-updater') +const { Logger } = require('lib/logger.js'); +const { _ } = require('lib/locale.js'); let autoUpdateLogger_ = new Logger(); let checkInBackground_ = false; @@ -14,51 +14,51 @@ let parentWindow_ = null; autoUpdater.autoDownload = false; function htmlToText_(html) { - let output = html.replace(/\n/g, ""); - output = output.replace(/
  • /g, "- "); - output = output.replace(/

    /g, ""); - output = output.replace(/<\/p>/g, "\n"); - output = output.replace(/<\/li>/g, "\n"); - output = output.replace(/

      /g, ""); - output = output.replace(/<\/ul>/g, ""); - output = output.replace(/<.*?>/g, ""); - output = output.replace(/<\/.*?>/g, ""); + let output = html.replace(/\n/g, ''); + output = output.replace(/
    • /g, '- '); + output = output.replace(/

      /g, ''); + output = output.replace(/<\/p>/g, '\n'); + output = output.replace(/<\/li>/g, '\n'); + output = output.replace(/

        /g, ''); + output = output.replace(/<\/ul>/g, ''); + output = output.replace(/<.*?>/g, ''); + output = output.replace(/<\/.*?>/g, ''); return output; } function showErrorMessageBox(message) { return dialog.showMessageBox(parentWindow_, { - type: "error", + type: 'error', message: message, }); } function onCheckStarted() { - autoUpdateLogger_.info("checkForUpdates: Starting..."); + autoUpdateLogger_.info('checkForUpdates: Starting...'); isCheckingForUpdate_ = true; } function onCheckEnded() { - autoUpdateLogger_.info("checkForUpdates: Done."); + autoUpdateLogger_.info('checkForUpdates: Done.'); isCheckingForUpdate_ = false; } -autoUpdater.on("error", error => { +autoUpdater.on('error', (error) => { autoUpdateLogger_.error(error); if (checkInBackground_) return onCheckEnded(); let msg = error == null ? "unknown" : (error.stack || error).toString(); - // Error messages can be very long even without stack trace so shorten + // Error messages can be very long even without stack trace so shorten // then so that the dialog box doesn't take the whole screen. - msg = msg.substr(0, 512).replace(/\\n/g, "\n"); - showErrorMessageBox(msg); - + msg = msg.substr(0,512).replace(/\\n/g, '\n'); + showErrorMessageBox(msg) + onCheckEnded(); -}); +}) function findDownloadFilename_(info) { // { version: '1.0.64', - // files: + // files: // [ { url: 'Joplin-1.0.64-mac.zip', // sha512: 'OlemXqhq/fSifx7EutvMzfoCI/1kGNl10i8nkvACEDfVXwP8hankDBXEC0+GxSArsZuxOh3U1+C+4j72SfIUew==' }, // { url: 'Joplin-1.0.64.dmg', @@ -80,47 +80,47 @@ function findDownloadFilename_(info) { for (let i = 0; i < info.files.length; i++) { const f = info.files[i].url; // Annoyingly this is called "url" but it's obviously not a url, so hopefully it won't change later on and become one. - if (f.indexOf(".exe") >= 0) return f; - if (f.indexOf(".dmg") >= 0) return f; + if (f.indexOf('.exe') >= 0) return f; + if (f.indexOf('.dmg') >= 0) return f; } return info.path; } -autoUpdater.on("update-available", info => { +autoUpdater.on('update-available', (info) => { const filename = findDownloadFilename_(info); if (!info.version || !filename) { if (checkInBackground_) return onCheckEnded(); - showErrorMessageBox("Could not get version info: " + JSON.stringify(info)); + showErrorMessageBox(('Could not get version info: ' + JSON.stringify(info))); return onCheckEnded(); } - const downloadUrl = "https://github.com/laurent22/joplin/releases/download/v" + info.version + "/" + filename; + const downloadUrl = 'https://github.com/laurent22/joplin/releases/download/v' + info.version + '/' + filename; - let releaseNotes = info.releaseNotes + ""; - if (releaseNotes) releaseNotes = "\n\n" + _("Release notes:\n\n%s", htmlToText_(releaseNotes)); + let releaseNotes = info.releaseNotes + ''; + if (releaseNotes) releaseNotes = '\n\n' + _('Release notes:\n\n%s', htmlToText_(releaseNotes)); const buttonIndex = dialog.showMessageBox(parentWindow_, { - type: "info", - message: _("An update is available, do you want to download it now?" + releaseNotes), - buttons: [_("Yes"), _("No")], + type: 'info', + message: _('An update is available, do you want to download it now?' + releaseNotes), + buttons: [_('Yes'), _('No')] }); onCheckEnded(); - if (buttonIndex === 0) require("electron").shell.openExternal(downloadUrl); -}); + if (buttonIndex === 0) require('electron').shell.openExternal(downloadUrl); +}) -autoUpdater.on("update-not-available", () => { +autoUpdater.on('update-not-available', () => { if (checkInBackground_) return onCheckEnded(); - dialog.showMessageBox({ message: _("Current version is up-to-date.") }); + dialog.showMessageBox({ message: _('Current version is up-to-date.') }) onCheckEnded(); -}); +}) function checkForUpdates(inBackground, window, logFilePath) { if (isCheckingForUpdate_) { - autoUpdateLogger_.info("checkForUpdates: Skipping check because it is already running"); + autoUpdateLogger_.info('checkForUpdates: Skipping check because it is already running'); return; } @@ -130,16 +130,16 @@ function checkForUpdates(inBackground, window, logFilePath) { if (logFilePath && !autoUpdateLogger_.targets().length) { autoUpdateLogger_ = new Logger(); - autoUpdateLogger_.addTarget("file", { path: logFilePath }); + autoUpdateLogger_.addTarget('file', { path: logFilePath }); autoUpdateLogger_.setLevel(Logger.LEVEL_DEBUG); - autoUpdateLogger_.info("checkForUpdates: Initializing..."); + autoUpdateLogger_.info('checkForUpdates: Initializing...'); autoUpdater.logger = autoUpdateLogger_; } checkInBackground_ = inBackground; try { - autoUpdater.checkForUpdates(); + autoUpdater.checkForUpdates() } catch (error) { autoUpdateLogger_.error(error); if (!checkInBackground_) showErrorMessageBox(error.message); @@ -147,4 +147,4 @@ function checkForUpdates(inBackground, window, logFilePath) { } } -module.exports.checkForUpdates = checkForUpdates; +module.exports.checkForUpdates = checkForUpdates \ No newline at end of file diff --git a/ElectronClient/app/compile-jsx.js b/ElectronClient/app/compile-jsx.js index 80139f2de..4663c7454 100644 --- a/ElectronClient/app/compile-jsx.js +++ b/ElectronClient/app/compile-jsx.js @@ -1,8 +1,8 @@ -const fs = require("fs-extra"); -const spawnSync = require("child_process").spawnSync; +const fs = require('fs-extra'); +const spawnSync = require('child_process').spawnSync; -const babelPath = __dirname + "/node_modules/.bin/babel" + (process.platform === "win32" ? ".cmd" : ""); -const guiPath = __dirname + "/gui"; +const babelPath = __dirname + '/node_modules/.bin/babel' + (process.platform === 'win32' ? '.cmd' : ''); +const guiPath = __dirname + '/gui'; function fileIsNewerThan(path1, path2) { if (!fs.existsSync(path2)) return true; @@ -13,26 +13,26 @@ function fileIsNewerThan(path1, path2) { return stat1.mtime > stat2.mtime; } -fs.readdirSync(guiPath).forEach(filename => { - const jsxPath = guiPath + "/" + filename; - const p = jsxPath.split("."); +fs.readdirSync(guiPath).forEach((filename) => { + const jsxPath = guiPath + '/' + filename; + const p = jsxPath.split('.'); if (p.length <= 1) return; const ext = p[p.length - 1]; - if (ext !== "jsx") return; + if (ext !== 'jsx') return; p.pop(); - const basePath = p.join("."); + const basePath = p.join('.'); - const jsPath = basePath + ".min.js"; + const jsPath = basePath + '.min.js'; if (fileIsNewerThan(jsxPath, jsPath)) { - console.info("Compiling " + jsxPath + "..."); - const result = spawnSync(babelPath, ["--presets", "react", "--out-file", jsPath, jsxPath]); + console.info('Compiling ' + jsxPath + '...'); + const result = spawnSync(babelPath, ['--presets', 'react', '--out-file',jsPath, jsxPath]); if (result.status !== 0) { const msg = []; if (result.stdout) msg.push(result.stdout.toString()); if (result.stderr) msg.push(result.stderr.toString()); - console.error(msg.join("\n")); + console.error(msg.join('\n')); if (result.error) console.error(result.error); process.exit(result.status); } diff --git a/ElectronClient/app/compile-package-info.js b/ElectronClient/app/compile-package-info.js index e6e078909..e7ed05fa0 100644 --- a/ElectronClient/app/compile-package-info.js +++ b/ElectronClient/app/compile-package-info.js @@ -1,11 +1,11 @@ -const fs = require("fs-extra"); +const fs = require('fs-extra'); // Electron Builder strip off certain important keys from package.json, which we need, in particular build.appId // so this script is used to preserve the keys that we need. -const packageInfo = require(__dirname + "/package.json"); +const packageInfo = require(__dirname + '/package.json'); -let removeKeys = ["scripts", "devDependencies", "optionalDependencies", "dependencies"]; +let removeKeys = ['scripts', 'devDependencies', 'optionalDependencies', 'dependencies']; for (let i = 0; i < removeKeys.length; i++) { delete packageInfo[removeKeys[i]]; @@ -16,8 +16,8 @@ const appId = packageInfo.build.appId; delete packageInfo.build; packageInfo.build = { appId: appId }; -let fileContent = "// Auto-generated by compile-package-info.js\n// Do not change directly\nconst packageInfo = " + JSON.stringify(packageInfo, null, 4) + ";"; +let fileContent = "// Auto-generated by compile-package-info.js\n// Do not change directly\nconst packageInfo = " + JSON.stringify(packageInfo, null, 4) + ';'; fileContent += "\n"; fileContent += "module.exports = packageInfo;"; -fs.writeFileSync(__dirname + "/packageInfo.js", fileContent); +fs.writeFileSync(__dirname + '/packageInfo.js', fileContent); \ No newline at end of file diff --git a/ElectronClient/app/eventManager.js b/ElectronClient/app/eventManager.js index 77fde03aa..08b109192 100644 --- a/ElectronClient/app/eventManager.js +++ b/ElectronClient/app/eventManager.js @@ -1,6 +1,7 @@ -const events = require("events"); +const events = require('events'); class EventManager { + constructor() { this.emitter_ = new events.EventEmitter(); } @@ -16,8 +17,9 @@ class EventManager { removeListener(eventName, callback) { return this.emitter_.removeListener(eventName, callback); } + } const eventManager = new EventManager(); -module.exports = eventManager; +module.exports = eventManager; \ No newline at end of file diff --git a/ElectronClient/app/gui/dialogs.js b/ElectronClient/app/gui/dialogs.js index 4cb27204e..e2853f1a0 100644 --- a/ElectronClient/app/gui/dialogs.js +++ b/ElectronClient/app/gui/dialogs.js @@ -1,11 +1,12 @@ -const smalltalk = require("smalltalk"); +const smalltalk = require('smalltalk'); class Dialogs { - async alert(message, title = "") { + + async alert(message, title = '') { await smalltalk.alert(title, message); } - async confirm(message, title = "") { + async confirm(message, title = '') { try { await smalltalk.confirm(title, message); return true; @@ -14,7 +15,7 @@ class Dialogs { } } - async prompt(message, title = "", defaultValue = "", options = null) { + async prompt(message, title = '', defaultValue = '', options = null) { if (options === null) options = {}; try { @@ -24,8 +25,9 @@ class Dialogs { return null; } } + } const dialogs = new Dialogs(); -module.exports = dialogs; +module.exports = dialogs; \ No newline at end of file diff --git a/ElectronClient/app/main-html.js b/ElectronClient/app/main-html.js index 1e21b4f6f..d127c883d 100644 --- a/ElectronClient/app/main-html.js +++ b/ElectronClient/app/main-html.js @@ -1,7 +1,7 @@ // This is the initialization for the Electron RENDERER process // Make it possible to require("/lib/...") without specifying full path -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); // Disable React message in console "Download the React DevTools for a better development experience" // https://stackoverflow.com/questions/42196819/disable-hide-download-the-react-devtools#42196820 @@ -12,21 +12,21 @@ __REACT_DEVTOOLS_GLOBAL_HOOK__ = { onCommitFiberUnmount: function() {}, }; -const { app } = require("./app.js"); -const Folder = require("lib/models/Folder.js"); -const Resource = require("lib/models/Resource.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const Note = require("lib/models/Note.js"); -const Tag = require("lib/models/Tag.js"); -const NoteTag = require("lib/models/NoteTag.js"); -const MasterKey = require("lib/models/MasterKey"); -const Setting = require("lib/models/Setting.js"); -const { Logger } = require("lib/logger.js"); -const { FsDriverNode } = require("lib/fs-driver-node.js"); -const { shimInit } = require("lib/shim-init-node.js"); -const EncryptionService = require("lib/services/EncryptionService"); -const { bridge } = require("electron").remote.require("./bridge"); -const { FileApiDriverLocal } = require("lib/file-api-driver-local.js"); +const { app } = require('./app.js'); +const Folder = require('lib/models/Folder.js'); +const Resource = require('lib/models/Resource.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); +const NoteTag = require('lib/models/NoteTag.js'); +const MasterKey = require('lib/models/MasterKey'); +const Setting = require('lib/models/Setting.js'); +const { Logger } = require('lib/logger.js'); +const { FsDriverNode } = require('lib/fs-driver-node.js'); +const { shimInit } = require('lib/shim-init-node.js'); +const EncryptionService = require('lib/services/EncryptionService'); +const { bridge } = require('electron').remote.require('./bridge'); +const { FileApiDriverLocal } = require('lib/file-api-driver-local.js'); const fsDriver = new FsDriverNode(); Logger.fsDriver_ = fsDriver; @@ -36,50 +36,45 @@ FileApiDriverLocal.fsDriver_ = fsDriver; // That's not good, but it's to avoid circular dependency issues // in the BaseItem class. -BaseItem.loadClass("Note", Note); -BaseItem.loadClass("Folder", Folder); -BaseItem.loadClass("Resource", Resource); -BaseItem.loadClass("Tag", Tag); -BaseItem.loadClass("NoteTag", NoteTag); -BaseItem.loadClass("MasterKey", MasterKey); +BaseItem.loadClass('Note', Note); +BaseItem.loadClass('Folder', Folder); +BaseItem.loadClass('Resource', Resource); +BaseItem.loadClass('Tag', Tag); +BaseItem.loadClass('NoteTag', NoteTag); +BaseItem.loadClass('MasterKey', MasterKey); -Setting.setConstant("appId", "net.cozic.joplin-desktop"); -Setting.setConstant("appType", "desktop"); +Setting.setConstant('appId', 'net.cozic.joplin-desktop'); +Setting.setConstant('appType', 'desktop'); shimInit(); // Disable drag and drop of links inside application (which would // open it as if the whole app was a browser) -document.addEventListener("dragover", event => event.preventDefault()); -document.addEventListener("drop", event => event.preventDefault()); +document.addEventListener('dragover', event => event.preventDefault()); +document.addEventListener('drop', event => event.preventDefault()); // Disable middle-click (which would open a new browser window, but we don't want this) -document.addEventListener("auxclick", event => event.preventDefault()); +document.addEventListener('auxclick', event => event.preventDefault()); // Each link (rendered as a button or list item) has its own custom click event // so disable the default. In particular this will disable Ctrl+Clicking a link // which would open a new browser window. -document.addEventListener("click", event => event.preventDefault()); +document.addEventListener('click', (event) => event.preventDefault()); -app() - .start(bridge().processArgv()) - .then(() => { - require("./gui/Root.min.js"); - }) - .catch(error => { - if (error.code == "flagError") { - bridge().showErrorMessageBox(error.message); - } else { - // If something goes wrong at this stage we don't have a console or a log file - // so display the error in a message box. - let msg = ["Fatal error:", error.message]; - if (error.fileName) msg.push(error.fileName); - if (error.lineNumber) msg.push(error.lineNumber); - if (error.stack) msg.push(error.stack); - bridge().showErrorMessageBox(msg.join("\n")); - } +app().start(bridge().processArgv()).then(() => { + require('./gui/Root.min.js'); +}).catch((error) => { + if (error.code == 'flagError') { + bridge().showErrorMessageBox(error.message); + } else { + // If something goes wrong at this stage we don't have a console or a log file + // so display the error in a message box. + let msg = ['Fatal error:', error.message]; + if (error.fileName) msg.push(error.fileName); + if (error.lineNumber) msg.push(error.lineNumber); + if (error.stack) msg.push(error.stack); + bridge().showErrorMessageBox(msg.join('\n')); + } - bridge() - .electronApp() - .exit(1); - }); + bridge().electronApp().exit(1); +}); \ No newline at end of file diff --git a/ElectronClient/app/main.js b/ElectronClient/app/main.js index 412508c44..08e2140c3 100644 --- a/ElectronClient/app/main.js +++ b/ElectronClient/app/main.js @@ -1,27 +1,27 @@ // This is the basic initialization for the Electron MAIN process // Make it possible to require("/lib/...") without specifying full path -require("app-module-path").addPath(__dirname); +require('app-module-path').addPath(__dirname); -const electronApp = require("electron").app; -const { ElectronAppWrapper } = require("./ElectronAppWrapper"); -const { initBridge } = require("./bridge"); -const { Logger } = require("lib/logger.js"); -const { FsDriverNode } = require("lib/fs-driver-node.js"); +const electronApp = require('electron').app; +const { ElectronAppWrapper } = require('./ElectronAppWrapper'); +const { initBridge } = require('./bridge'); +const { Logger } = require('lib/logger.js'); +const { FsDriverNode } = require('lib/fs-driver-node.js'); -process.on("unhandledRejection", (reason, p) => { - console.error("Unhandled promise rejection", p, "reason:", reason); +process.on('unhandledRejection', (reason, p) => { + console.error('Unhandled promise rejection', p, 'reason:', reason); process.exit(1); }); // Flags are parsed properly in BaseApplication, however it's better to have // the env as early as possible to enable debugging capabilities. function envFromArgs(args) { - if (!args) return "prod"; - const envIndex = args.indexOf("--env"); - const devIndex = args.indexOf("dev"); - if (envIndex === devIndex - 1) return "dev"; - return "prod"; + if (!args) return 'prod'; + const envIndex = args.indexOf('--env'); + const devIndex = args.indexOf('dev'); + if (envIndex === devIndex - 1) return 'dev'; + return 'prod'; } Logger.fsDriver_ = new FsDriverNode(); @@ -32,7 +32,7 @@ const wrapper = new ElectronAppWrapper(electronApp, env); initBridge(wrapper); -wrapper.start().catch(error => { - console.error("Electron App fatal error:"); +wrapper.start().catch((error) => { + console.error('Electron App fatal error:'); console.error(error); -}); +}); \ No newline at end of file diff --git a/ElectronClient/app/theme.js b/ElectronClient/app/theme.js index d0d640148..53df659d8 100644 --- a/ElectronClient/app/theme.js +++ b/ElectronClient/app/theme.js @@ -1,13 +1,13 @@ -const Setting = require("lib/models/Setting.js"); +const Setting = require('lib/models/Setting.js'); const globalStyle = { - fontSize: 12 * Setting.value("style.zoom") / 100, - fontFamily: "sans-serif", + fontSize: 12 * Setting.value('style.zoom')/100, + fontFamily: 'sans-serif', margin: 15, // No text and no interactive component should be within this margin itemMarginTop: 10, itemMarginBottom: 10, backgroundColor: "#ffffff", - backgroundColorTransparent: "rgba(255,255,255,0.9)", + backgroundColorTransparent: 'rgba(255,255,255,0.9)', oddBackgroundColor: "#dddddd", color: "#222222", // For regular text colorError: "red", @@ -15,7 +15,7 @@ const globalStyle = { colorFaded: "#777777", // For less important text fontSizeSmaller: 14, dividerColor: "#dddddd", - selectedColor: "#e5e5e5", + selectedColor: '#e5e5e5', disabledOpacity: 0.3, buttonMinWidth: 50, buttonMinHeight: 30, @@ -39,18 +39,18 @@ const globalStyle = { }; // For WebView - must correspond to the properties above -globalStyle.htmlFontSize = globalStyle.fontSize + "px"; -globalStyle.htmlColor = "black"; // Note: CSS in WebView component only supports named colors or rgb() notation -globalStyle.htmlBackgroundColor = "white"; -globalStyle.htmlDividerColor = "rgb(150,150,150)"; -globalStyle.htmlLinkColor = "blue"; -globalStyle.htmlLineHeight = "20px"; +globalStyle.htmlFontSize = globalStyle.fontSize + 'px'; +globalStyle.htmlColor ='black'; // Note: CSS in WebView component only supports named colors or rgb() notation +globalStyle.htmlBackgroundColor ='white'; +globalStyle.htmlDividerColor = 'rgb(150,150,150)'; +globalStyle.htmlLinkColor ='blue'; +globalStyle.htmlLineHeight ='20px'; globalStyle.marginRight = globalStyle.margin; globalStyle.marginLeft = globalStyle.margin; globalStyle.marginTop = globalStyle.margin; globalStyle.marginBottom = globalStyle.margin; -globalStyle.htmlMarginLeft = (globalStyle.marginLeft / 10 * 0.6).toFixed(2) + "em"; +globalStyle.htmlMarginLeft = ((globalStyle.marginLeft / 10) * 0.6).toFixed(2) + 'em'; globalStyle.icon = { color: globalStyle.color, @@ -66,7 +66,7 @@ globalStyle.textStyle = { color: globalStyle.color, fontFamily: globalStyle.fontFamily, fontSize: globalStyle.fontSize, - lineHeight: "1.6em", + lineHeight: '1.6em', }; globalStyle.textStyle2 = Object.assign({}, globalStyle.textStyle, { @@ -82,28 +82,28 @@ globalStyle.h2Style.fontSize *= 1.3; let themeCache_ = {}; function themeStyle(theme) { - if (!theme) throw new Error("Theme must be specified"); + if (!theme) throw new Error('Theme must be specified'); if (themeCache_[theme]) return themeCache_[theme]; let output = Object.assign({}, globalStyle); if (theme == Setting.THEME_LIGHT) return output; - output.backgroundColor = "#1D2024"; - output.color = "#dddddd"; - output.colorFaded = "#777777"; - output.dividerColor = "#555555"; - output.selectedColor = "#333333"; + output.backgroundColor = '#1D2024'; + output.color = '#dddddd'; + output.colorFaded = '#777777'; + output.dividerColor = '#555555'; + output.selectedColor = '#333333'; output.raisedBackgroundColor = "#0F2051"; output.raisedColor = "#788BC3"; output.raisedHighlightedColor = "#ffffff"; - output.htmlColor = "rgb(220,220,220)"; - output.htmlBackgroundColor = "rgb(29,32,36)"; - output.htmlLinkColor = "rgb(166,166,255)"; + output.htmlColor = 'rgb(220,220,220)'; + output.htmlBackgroundColor = 'rgb(29,32,36)'; + output.htmlLinkColor = 'rgb(166,166,255)'; themeCache_[theme] = output; return themeCache_[theme]; } -module.exports = { themeStyle }; +module.exports = { themeStyle }; \ No newline at end of file diff --git a/ElectronClient/app/update-readme-download.js b/ElectronClient/app/update-readme-download.js index f521d46a3..4de6e5675 100644 --- a/ElectronClient/app/update-readme-download.js +++ b/ElectronClient/app/update-readme-download.js @@ -1,11 +1,11 @@ -"use strict"; +'use strict'; -const fs = require("fs-extra"); -const https = require("https"); -const request = require("request"); +const fs = require('fs-extra'); +const https = require('https'); +const request = require('request'); -const url = "https://api.github.com/repos/laurent22/joplin/releases/latest"; -const readmePath = __dirname + "/../../README.md"; +const url = 'https://api.github.com/repos/laurent22/joplin/releases/latest'; +const readmePath = __dirname + '/../../README.md'; async function msleep(ms) { return new Promise((resolve, reject) => { @@ -17,23 +17,20 @@ async function msleep(ms) { async function gitHubLatestRelease() { return new Promise((resolve, reject) => { - request.get( - { - url: url, - json: true, - headers: { "User-Agent": "Joplin Readme Updater" }, - }, - (error, response, data) => { - if (error) { - reject(error); - } else if (response.statusCode !== 200) { - console.warn(data); - reject(new Error("Error HTTP " + response.statusCode)); - } else { - resolve(data); - } + request.get({ + url: url, + json: true, + headers: {'User-Agent': 'Joplin Readme Updater'} + }, (error, response, data) => { + if (error) { + reject(error); + } else if (response.statusCode !== 200) { + console.warn(data); + reject(new Error('Error HTTP ' + response.statusCode)); + } else { + resolve(data); } - ); + }); }); } @@ -44,26 +41,26 @@ function downloadUrl(release, os) { const asset = release.assets[i]; const name = asset.name; - if (name.indexOf(".dmg") > 0 && os === "macos") return asset.browser_download_url; - if (name.indexOf(".exe") > 0 && os === "windows") return asset.browser_download_url; - if (name.indexOf(".AppImage") > 0 && os === "linux") return asset.browser_download_url; + if (name.indexOf('.dmg') > 0 && os === 'macos') return asset.browser_download_url; + if (name.indexOf('.exe') > 0 && os === 'windows') return asset.browser_download_url; + if (name.indexOf('.AppImage') > 0 && os === 'linux') return asset.browser_download_url; } } function readmeContent() { - if (!fs.existsSync(readmePath)) throw new Error("Cannot find " + readmePath); - return fs.readFileSync(readmePath, "utf8"); + if (!fs.existsSync(readmePath)) throw new Error('Cannot find ' + readmePath); + return fs.readFileSync(readmePath, 'utf8'); } function setReadmeContent(content) { - if (!fs.existsSync(readmePath)) throw new Error("Cannot find " + readmePath); + if (!fs.existsSync(readmePath)) throw new Error('Cannot find ' + readmePath); return fs.writeFileSync(readmePath, content); } async function main(argv) { const waitForVersion = argv.length === 3 ? argv[2] : null; - if (waitForVersion) console.info("Waiting for version " + waitForVersion + " to be released before updating readme..."); + if (waitForVersion) console.info('Waiting for version ' + waitForVersion + ' to be released before updating readme...'); let release = null; while (true) { @@ -73,18 +70,18 @@ async function main(argv) { if (release.tag_name !== waitForVersion) { await msleep(60000 * 5); } else { - console.info("Got version " + waitForVersion); + console.info('Got version ' + waitForVersion); break; } } - const winUrl = downloadUrl(release, "windows"); - const macOsUrl = downloadUrl(release, "macos"); - const linuxUrl = downloadUrl(release, "linux"); + const winUrl = downloadUrl(release, 'windows'); + const macOsUrl = downloadUrl(release, 'macos'); + const linuxUrl = downloadUrl(release, 'linux'); - console.info("Windows: ", winUrl); - console.info("macOS: ", macOsUrl); - console.info("Linux: ", linuxUrl); + console.info('Windows: ', winUrl); + console.info('macOS: ', macOsUrl); + console.info('Linux: ', linuxUrl); let content = readmeContent(); @@ -94,9 +91,9 @@ async function main(argv) { setReadmeContent(content); - console.info("git pull && git add -A && git commit -m 'Update readme downloads' && git push"); + console.info("git pull && git add -A && git commit -m 'Update readme downloads' && git push") } -main(process.argv).catch(error => { - console.error("Fatal error", error); -}); +main(process.argv).catch((error) => { + console.error('Fatal error', error); +}); \ No newline at end of file diff --git a/ReactNativeClient/index.android.js b/ReactNativeClient/index.android.js index 5c97e4201..d0e4f551b 100644 --- a/ReactNativeClient/index.android.js +++ b/ReactNativeClient/index.android.js @@ -1,3 +1,3 @@ -const { main } = require("./main.js"); +const { main } = require('./main.js'); -main(); +main(); \ No newline at end of file diff --git a/ReactNativeClient/index.ios.js b/ReactNativeClient/index.ios.js index 5c97e4201..d0e4f551b 100644 --- a/ReactNativeClient/index.ios.js +++ b/ReactNativeClient/index.ios.js @@ -1,3 +1,3 @@ -const { main } = require("./main.js"); +const { main } = require('./main.js'); -main(); +main(); \ No newline at end of file diff --git a/ReactNativeClient/index.js b/ReactNativeClient/index.js index 449793513..7d03ea719 100644 --- a/ReactNativeClient/index.js +++ b/ReactNativeClient/index.js @@ -1,4 +1,4 @@ -const { main } = require("./main.js"); +const { main } = require('./main.js'); main(); diff --git a/ReactNativeClient/lib/ArrayUtils.js b/ReactNativeClient/lib/ArrayUtils.js index e4ef2bba7..ba6623ee7 100644 --- a/ReactNativeClient/lib/ArrayUtils.js +++ b/ReactNativeClient/lib/ArrayUtils.js @@ -4,44 +4,45 @@ ArrayUtils.unique = function(array) { return array.filter(function(elem, index, self) { return index === self.indexOf(elem); }); -}; +} ArrayUtils.removeElement = function(array, element) { const index = array.indexOf(element); if (index < 0) return array; array.splice(index, 1); return array; -}; +} // https://stackoverflow.com/a/10264318/561309 ArrayUtils.binarySearch = function(items, value) { - var startIndex = 0, - stopIndex = items.length - 1, - middle = Math.floor((stopIndex + startIndex) / 2); + var startIndex = 0, + stopIndex = items.length - 1, + middle = Math.floor((stopIndex + startIndex)/2); + + while(items[middle] != value && startIndex < stopIndex){ - while (items[middle] != value && startIndex < stopIndex) { //adjust search area - if (value < items[middle]) { + if (value < items[middle]){ stopIndex = middle - 1; - } else if (value > items[middle]) { + } else if (value > items[middle]){ startIndex = middle + 1; } //recalculate middle - middle = Math.floor((stopIndex + startIndex) / 2); + middle = Math.floor((stopIndex + startIndex)/2); } //make sure it's the right value - return items[middle] != value ? -1 : middle; -}; + return (items[middle] != value) ? -1 : middle; +} ArrayUtils.findByKey = function(array, key, value) { for (let i = 0; i < array.length; i++) { const o = array[i]; - if (typeof o !== "object") continue; + if (typeof o !== 'object') continue; if (o[key] === value) return o; } return null; -}; +} -module.exports = ArrayUtils; +module.exports = ArrayUtils; \ No newline at end of file diff --git a/ReactNativeClient/lib/BaseApplication.js b/ReactNativeClient/lib/BaseApplication.js index 882483465..103e06484 100644 --- a/ReactNativeClient/lib/BaseApplication.js +++ b/ReactNativeClient/lib/BaseApplication.js @@ -1,36 +1,36 @@ -const { createStore, applyMiddleware } = require("redux"); -const { reducer, defaultState, stateUtils } = require("lib/reducer.js"); -const { JoplinDatabase } = require("lib/joplin-database.js"); -const { Database } = require("lib/database.js"); -const { FoldersScreenUtils } = require("lib/folders-screen-utils.js"); -const { DatabaseDriverNode } = require("lib/database-driver-node.js"); -const BaseModel = require("lib/BaseModel.js"); -const Folder = require("lib/models/Folder.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const Note = require("lib/models/Note.js"); -const Tag = require("lib/models/Tag.js"); -const Setting = require("lib/models/Setting.js"); -const { Logger } = require("lib/logger.js"); -const { splitCommandString } = require("lib/string-utils.js"); -const { sprintf } = require("sprintf-js"); -const { reg } = require("lib/registry.js"); -const { time } = require("lib/time-utils.js"); -const BaseSyncTarget = require("lib/BaseSyncTarget.js"); -const { fileExtension } = require("lib/path-utils.js"); -const { shim } = require("lib/shim.js"); -const { _, setLocale, defaultLocale, closestSupportedLocale } = require("lib/locale.js"); -const os = require("os"); -const fs = require("fs-extra"); -const JoplinError = require("lib/JoplinError"); -const EventEmitter = require("events"); -const SyncTargetRegistry = require("lib/SyncTargetRegistry.js"); -const SyncTargetFilesystem = require("lib/SyncTargetFilesystem.js"); -const SyncTargetOneDrive = require("lib/SyncTargetOneDrive.js"); -const SyncTargetOneDriveDev = require("lib/SyncTargetOneDriveDev.js"); -const SyncTargetNextcloud = require("lib/SyncTargetNextcloud.js"); -const SyncTargetWebDAV = require("lib/SyncTargetWebDAV.js"); -const EncryptionService = require("lib/services/EncryptionService"); -const DecryptionWorker = require("lib/services/DecryptionWorker"); +const { createStore, applyMiddleware } = require('redux'); +const { reducer, defaultState, stateUtils } = require('lib/reducer.js'); +const { JoplinDatabase } = require('lib/joplin-database.js'); +const { Database } = require('lib/database.js'); +const { FoldersScreenUtils } = require('lib/folders-screen-utils.js'); +const { DatabaseDriverNode } = require('lib/database-driver-node.js'); +const BaseModel = require('lib/BaseModel.js'); +const Folder = require('lib/models/Folder.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const Note = require('lib/models/Note.js'); +const Tag = require('lib/models/Tag.js'); +const Setting = require('lib/models/Setting.js'); +const { Logger } = require('lib/logger.js'); +const { splitCommandString } = require('lib/string-utils.js'); +const { sprintf } = require('sprintf-js'); +const { reg } = require('lib/registry.js'); +const { time } = require('lib/time-utils.js'); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { fileExtension } = require('lib/path-utils.js'); +const { shim } = require('lib/shim.js'); +const { _, setLocale, defaultLocale, closestSupportedLocale } = require('lib/locale.js'); +const os = require('os'); +const fs = require('fs-extra'); +const JoplinError = require('lib/JoplinError'); +const EventEmitter = require('events'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); +const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js'); +const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); +const SyncTargetOneDriveDev = require('lib/SyncTargetOneDriveDev.js'); +const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js'); +const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js'); +const EncryptionService = require('lib/services/EncryptionService'); +const DecryptionWorker = require('lib/services/DecryptionWorker'); SyncTargetRegistry.addClass(SyncTargetFilesystem); SyncTargetRegistry.addClass(SyncTargetOneDrive); @@ -39,15 +39,16 @@ SyncTargetRegistry.addClass(SyncTargetNextcloud); SyncTargetRegistry.addClass(SyncTargetWebDAV); class BaseApplication { + constructor() { this.logger_ = new Logger(); this.dbLogger_ = new Logger(); this.eventEmitter_ = new EventEmitter(); - // Note: this is basically a cache of state.selectedFolderId. It should *only* + // Note: this is basically a cache of state.selectedFolderId. It should *only* // be derived from the state and not set directly since that would make the // state and UI out of sync. - this.currentFolder_ = null; + this.currentFolder_ = null; } logger() { @@ -64,7 +65,7 @@ class BaseApplication { async refreshCurrentFolder() { let newFolder = null; - + if (this.currentFolder_) newFolder = await Folder.load(this.currentFolder_.id); if (!newFolder) newFolder = await Folder.defaultFolder(); @@ -74,11 +75,11 @@ class BaseApplication { switchCurrentFolder(folder) { if (!this.hasGui()) { this.currentFolder_ = Object.assign({}, folder); - Setting.setValue("activeFolderId", folder ? folder.id : ""); + Setting.setValue('activeFolderId', folder ? folder.id : ''); } else { this.dispatch({ - type: "FOLDER_SELECT", - id: folder ? folder.id : "", + type: 'FOLDER_SELECT', + id: folder ? folder.id : '', }); } } @@ -93,54 +94,54 @@ class BaseApplication { while (argv.length) { let arg = argv[0]; let nextArg = argv.length >= 2 ? argv[1] : null; - - if (arg == "--profile") { - if (!nextArg) throw new JoplinError(_("Usage: %s", "--profile "), "flagError"); + + if (arg == '--profile') { + if (!nextArg) throw new JoplinError(_('Usage: %s', '--profile '), 'flagError'); matched.profileDir = nextArg; argv.splice(0, 2); continue; } - if (arg == "--env") { - if (!nextArg) throw new JoplinError(_("Usage: %s", "--env "), "flagError"); + if (arg == '--env') { + if (!nextArg) throw new JoplinError(_('Usage: %s', '--env '), 'flagError'); matched.env = nextArg; argv.splice(0, 2); continue; } - if (arg == "--is-demo") { - Setting.setConstant("isDemo", true); + if (arg == '--is-demo') { + Setting.setConstant('isDemo', true); argv.splice(0, 1); continue; } - if (arg == "--open-dev-tools") { - Setting.setConstant("openDevTools", true); + if (arg == '--open-dev-tools') { + Setting.setConstant('openDevTools', true); argv.splice(0, 1); continue; } - if (arg == "--update-geolocation-disabled") { + if (arg == '--update-geolocation-disabled') { Note.updateGeolocationEnabled_ = false; argv.splice(0, 1); continue; } - if (arg == "--stack-trace-enabled") { + if (arg == '--stack-trace-enabled') { this.showStackTraces_ = true; argv.splice(0, 1); continue; } - if (arg == "--log-level") { - if (!nextArg) throw new JoplinError(_("Usage: %s", "--log-level "), "flagError"); + if (arg == '--log-level') { + if (!nextArg) throw new JoplinError(_('Usage: %s', '--log-level '), 'flagError'); matched.logLevel = Logger.levelStringToId(nextArg); argv.splice(0, 2); continue; } - if (arg.length && arg[0] == "-") { - throw new JoplinError(_("Unknown flag: %s", arg), "flagError"); + if (arg.length && arg[0] == '-') { + throw new JoplinError(_('Unknown flag: %s', arg), 'flagError'); } else { break; } @@ -148,7 +149,7 @@ class BaseApplication { if (setDefaults) { if (!matched.logLevel) matched.logLevel = Logger.LEVEL_INFO; - if (!matched.env) matched.env = "prod"; + if (!matched.env) matched.env = 'prod'; } return { @@ -169,23 +170,23 @@ class BaseApplication { async refreshNotes(state) { let parentType = state.notesParentType; let parentId = null; - - if (parentType === "Folder") { + + if (parentType === 'Folder') { parentId = state.selectedFolderId; parentType = BaseModel.TYPE_FOLDER; - } else if (parentType === "Tag") { + } else if (parentType === 'Tag') { parentId = state.selectedTagId; parentType = BaseModel.TYPE_TAG; - } else if (parentType === "Search") { + } else if (parentType === 'Search') { parentId = state.selectedSearchId; parentType = BaseModel.TYPE_SEARCH; } - this.logger().debug("Refreshing notes:", parentType, parentId); + this.logger().debug('Refreshing notes:', parentType, parentId); let options = { order: stateUtils.notesOrder(state.settings), - uncompletedTodosOnTop: Setting.value("uncompletedTodosOnTop"), + uncompletedTodosOnTop: Setting.value('uncompletedTodosOnTop'), caseInsensitive: true, }; @@ -206,33 +207,33 @@ class BaseApplication { let search = BaseModel.byId(state.searches, parentId); notes = await Note.previews(null, { fields: fields, - anywherePattern: "*" + search.query_pattern + "*", + anywherePattern: '*' + search.query_pattern + '*', }); } } this.store().dispatch({ - type: "NOTE_UPDATE_ALL", + type: 'NOTE_UPDATE_ALL', notes: notes, notesSource: source, }); this.store().dispatch({ - type: "NOTE_SELECT", + type: 'NOTE_SELECT', id: notes.length ? notes[0].id : null, }); } reducerActionToString(action) { let o = [action.type]; - if ("id" in action) o.push(action.id); - if ("noteId" in action) o.push(action.noteId); - if ("folderId" in action) o.push(action.folderId); - if ("tagId" in action) o.push(action.tagId); - if ("tag" in action) o.push(action.tag.id); - if ("folder" in action) o.push(action.folder.id); - if ("notesSource" in action) o.push(JSON.stringify(action.notesSource)); - return o.join(", "); + if ('id' in action) o.push(action.id); + if ('noteId' in action) o.push(action.noteId); + if ('folderId' in action) o.push(action.folderId); + if ('tagId' in action) o.push(action.tagId); + if ('tag' in action) o.push(action.tag.id); + if ('folder' in action) o.push(action.folder.id); + if ('notesSource' in action) o.push(JSON.stringify(action.notesSource)); + return o.join(', '); } hasGui() { @@ -240,43 +241,43 @@ class BaseApplication { } uiType() { - return this.hasGui() ? "gui" : "cli"; + return this.hasGui() ? 'gui' : 'cli'; } generalMiddlewareFn() { - const middleware = store => next => action => { + const middleware = store => next => (action) => { return this.generalMiddleware(store, next, action); - }; + } return middleware; } async generalMiddleware(store, next, action) { - this.logger().debug("Reducer action", this.reducerActionToString(action)); + this.logger().debug('Reducer action', this.reducerActionToString(action)); const result = next(action); const newState = store.getState(); let refreshNotes = false; - if (action.type == "FOLDER_SELECT" || action.type === "FOLDER_DELETE") { - Setting.setValue("activeFolderId", newState.selectedFolderId); + if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE') { + Setting.setValue('activeFolderId', newState.selectedFolderId); this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null; refreshNotes = true; } - if (this.hasGui() && ((action.type == "SETTING_UPDATE_ONE" && action.key == "uncompletedTodosOnTop") || action.type == "SETTING_UPDATE_ALL")) { + if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop') || action.type == 'SETTING_UPDATE_ALL')) { refreshNotes = true; } - if (this.hasGui() && ((action.type == "SETTING_UPDATE_ONE" && action.key.indexOf("notes.sortOrder") === 0) || action.type == "SETTING_UPDATE_ALL")) { + if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key.indexOf('notes.sortOrder') === 0) || action.type == 'SETTING_UPDATE_ALL')) { refreshNotes = true; } - if (action.type == "TAG_SELECT" || action.type === "TAG_DELETE") { + if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') { refreshNotes = true; } - if (action.type == "SEARCH_SELECT" || action.type === "SEARCH_DELETE") { + if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') { refreshNotes = true; } @@ -284,19 +285,19 @@ class BaseApplication { await this.refreshNotes(newState); } - if ((action.type == "SETTING_UPDATE_ONE" && (action.key == "dateFormat" || action.key == "timeFormat")) || action.type == "SETTING_UPDATE_ALL") { - time.setDateFormat(Setting.value("dateFormat")); - time.setTimeFormat(Setting.value("timeFormat")); + if ((action.type == 'SETTING_UPDATE_ONE' && (action.key == 'dateFormat' || action.key == 'timeFormat')) || (action.type == 'SETTING_UPDATE_ALL')) { + time.setDateFormat(Setting.value('dateFormat')); + time.setTimeFormat(Setting.value('timeFormat')); } - if ((action.type == "SETTING_UPDATE_ONE" && action.key.indexOf("encryption.") === 0) || action.type == "SETTING_UPDATE_ALL") { + if ((action.type == 'SETTING_UPDATE_ONE' && (action.key.indexOf('encryption.') === 0)) || (action.type == 'SETTING_UPDATE_ALL')) { if (this.hasGui()) { await EncryptionService.instance().loadMasterKeysFromSettings(); DecryptionWorker.instance().scheduleStart(); const loadedMasterKeyIds = EncryptionService.instance().loadedMasterKeyIds(); this.dispatch({ - type: "MASTERKEY_REMOVE_NOT_LOADED", + type: 'MASTERKEY_REMOVE_NOT_LOADED', ids: loadedMasterKeyIds, }); @@ -306,22 +307,22 @@ class BaseApplication { } } - if (action.type === "NOTE_UPDATE_ONE") { + if (action.type === 'NOTE_UPDATE_ONE') { // If there is a conflict, we refresh the folders so as to display "Conflicts" folder if (action.note && action.note.is_conflict) { await FoldersScreenUtils.refreshFolders(); } } - if ((this.hasGui() && action.type == "SETTING_UPDATE_ONE" && action.key == "sync.interval") || action.type == "SETTING_UPDATE_ALL") { + if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') { reg.setupRecurrentSync(); } - if (this.hasGui() && action.type === "SYNC_GOT_ENCRYPTED_ITEM") { + if (this.hasGui() && action.type === 'SYNC_GOT_ENCRYPTED_ITEM') { DecryptionWorker.instance().scheduleStart(); } - return result; + return result; } dispatch(action) { @@ -343,17 +344,17 @@ class BaseApplication { async readFlagsFromFile(flagPath) { if (!fs.existsSync(flagPath)) return {}; - let flagContent = fs.readFileSync(flagPath, "utf8"); + let flagContent = fs.readFileSync(flagPath, 'utf8'); if (!flagContent) return {}; flagContent = flagContent.trim(); let flags = splitCommandString(flagContent); - flags.splice(0, 0, "cmd"); - flags.splice(0, 0, "node"); + flags.splice(0, 0, 'cmd'); + flags.splice(0, 0, 'node'); flags = await this.handleStartFlags_(flags, false); - + return flags.matched; } @@ -364,65 +365,65 @@ class BaseApplication { let initArgs = startFlags.matched; if (argv.length) this.showPromptString_ = false; - if (process.argv[1].indexOf("joplindev") >= 0) { - if (!initArgs.profileDir) initArgs.profileDir = "/mnt/d/Temp/TestNotes2"; + if (process.argv[1].indexOf('joplindev') >= 0) { + if (!initArgs.profileDir) initArgs.profileDir = '/mnt/d/Temp/TestNotes2'; initArgs.logLevel = Logger.LEVEL_DEBUG; - initArgs.env = "dev"; + initArgs.env = 'dev'; } - let appName = initArgs.env == "dev" ? "joplindev" : "joplin"; - if (Setting.value("appId").indexOf("-desktop") >= 0) appName += "-desktop"; - Setting.setConstant("appName", appName); + let appName = initArgs.env == 'dev' ? 'joplindev' : 'joplin'; + if (Setting.value('appId').indexOf('-desktop') >= 0) appName += '-desktop'; + Setting.setConstant('appName', appName); - const profileDir = initArgs.profileDir ? initArgs.profileDir : os.homedir() + "/.config/" + Setting.value("appName"); - const resourceDir = profileDir + "/resources"; - const tempDir = profileDir + "/tmp"; + const profileDir = initArgs.profileDir ? initArgs.profileDir : os.homedir() + '/.config/' + Setting.value('appName'); + const resourceDir = profileDir + '/resources'; + const tempDir = profileDir + '/tmp'; - Setting.setConstant("env", initArgs.env); - Setting.setConstant("profileDir", profileDir); - Setting.setConstant("resourceDir", resourceDir); - Setting.setConstant("tempDir", tempDir); + Setting.setConstant('env', initArgs.env); + Setting.setConstant('profileDir', profileDir); + Setting.setConstant('resourceDir', resourceDir); + Setting.setConstant('tempDir', tempDir); await fs.mkdirp(profileDir, 0o755); await fs.mkdirp(resourceDir, 0o755); await fs.mkdirp(tempDir, 0o755); - const extraFlags = await this.readFlagsFromFile(profileDir + "/flags.txt"); + const extraFlags = await this.readFlagsFromFile(profileDir + '/flags.txt'); initArgs = Object.assign(initArgs, extraFlags); - this.logger_.addTarget("file", { path: profileDir + "/log.txt" }); + this.logger_.addTarget('file', { path: profileDir + '/log.txt' }); //this.logger_.addTarget('console'); this.logger_.setLevel(initArgs.logLevel); reg.setLogger(this.logger_); - reg.dispatch = o => {}; + reg.dispatch = (o) => {}; - this.dbLogger_.addTarget("file", { path: profileDir + "/log-database.txt" }); + this.dbLogger_.addTarget('file', { path: profileDir + '/log-database.txt' }); this.dbLogger_.setLevel(initArgs.logLevel); - if (Setting.value("env") === "dev") { + if (Setting.value('env') === 'dev') { this.dbLogger_.setLevel(Logger.LEVEL_WARN); } - this.logger_.info("Profile directory: " + profileDir); + this.logger_.info('Profile directory: ' + profileDir); this.database_ = new JoplinDatabase(new DatabaseDriverNode()); - this.database_.setLogExcludedQueryTypes(["SELECT"]); + this.database_.setLogExcludedQueryTypes(['SELECT']); this.database_.setLogger(this.dbLogger_); - await this.database_.open({ name: profileDir + "/database.sqlite" }); + await this.database_.open({ name: profileDir + '/database.sqlite' }); reg.setDb(this.database_); BaseModel.db_ = this.database_; await Setting.load(); - if (Setting.value("firstStart")) { + if (Setting.value('firstStart')) { const locale = shim.detectAndSetLocale(Setting); - reg.logger().info("First start: detected locale as " + locale); - if (Setting.value("env") === "dev") Setting.setValue("sync.target", SyncTargetRegistry.nameToId("onedrive_dev")); - Setting.setValue("firstStart", 0); + reg.logger().info('First start: detected locale as ' + locale); + if (Setting.value('env') === 'dev') Setting.setValue('sync.target', SyncTargetRegistry.nameToId('onedrive_dev')); + Setting.setValue('firstStart', 0) } else { - setLocale(Setting.value("locale")); + setLocale(Setting.value('locale')); } EncryptionService.instance().setLogger(this.logger_); @@ -431,14 +432,15 @@ class BaseApplication { DecryptionWorker.instance().setEncryptionService(EncryptionService.instance()); await EncryptionService.instance().loadMasterKeysFromSettings(); - let currentFolderId = Setting.value("activeFolderId"); + let currentFolderId = Setting.value('activeFolderId'); let currentFolder = null; if (currentFolderId) currentFolder = await Folder.load(currentFolderId); if (!currentFolder) currentFolder = await Folder.defaultFolder(); - Setting.setValue("activeFolderId", currentFolder ? currentFolder.id : ""); + Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : ''); return argv; } + } -module.exports = { BaseApplication }; +module.exports = { BaseApplication }; \ No newline at end of file diff --git a/ReactNativeClient/lib/BaseModel.js b/ReactNativeClient/lib/BaseModel.js index e4d54368e..8f08e3e34 100644 --- a/ReactNativeClient/lib/BaseModel.js +++ b/ReactNativeClient/lib/BaseModel.js @@ -1,21 +1,22 @@ -const { Log } = require("lib/log.js"); -const { Database } = require("lib/database.js"); -const { uuid } = require("lib/uuid.js"); -const { time } = require("lib/time-utils.js"); -const Mutex = require("async-mutex").Mutex; +const { Log } = require('lib/log.js'); +const { Database } = require('lib/database.js'); +const { uuid } = require('lib/uuid.js'); +const { time } = require('lib/time-utils.js'); +const Mutex = require('async-mutex').Mutex; class BaseModel { + static modelType() { - throw new Error("Must be overriden"); + throw new Error('Must be overriden'); } static tableName() { - throw new Error("Must be overriden"); + throw new Error('Must be overriden'); } static addModelMd(model) { if (!model) return model; - + if (Array.isArray(model)) { let output = []; for (let i = 0; i < model.length; i++) { @@ -49,7 +50,7 @@ class BaseModel { const e = BaseModel.typeEnum_[i]; if (e[1] === type) return e[0].substr(5).toLowerCase(); } - throw new Error("Unknown model type: " + type); + throw new Error('Unknown model type: ' + type); } static hasField(name) { @@ -64,7 +65,7 @@ class BaseModel { let p = withPrefix === true ? this.tableName() : withPrefix; let temp = []; for (let i = 0; i < output.length; i++) { - temp.push(p + "." + output[i]); + temp.push(p + '.' + output[i]); } return temp; @@ -76,7 +77,7 @@ class BaseModel { if (fields[i].name == name) return fields[i].type; } if (defaultValue !== null) return defaultValue; - throw new Error("Unknown field: " + name); + throw new Error('Unknown field: ' + name); } static fields() { @@ -99,24 +100,22 @@ class BaseModel { } else { options = Object.assign({}, options); } - if (!("isNew" in options)) options.isNew = "auto"; - if (!("autoTimestamp" in options)) options.autoTimestamp = true; + if (!('isNew' in options)) options.isNew = 'auto'; + if (!('autoTimestamp' in options)) options.autoTimestamp = true; return options; } static count(options = null) { if (!options) options = {}; - let sql = "SELECT count(*) as total FROM `" + this.tableName() + "`"; - if (options.where) sql += " WHERE " + options.where; - return this.db() - .selectOne(sql) - .then(r => { - return r ? r["total"] : 0; - }); + let sql = 'SELECT count(*) as total FROM `' + this.tableName() + '`'; + if (options.where) sql += ' WHERE ' + options.where; + return this.db().selectOne(sql).then((r) => { + return r ? r['total'] : 0; + }); } static load(id) { - return this.loadByField("id", id); + return this.loadByField('id', id); } static shortId(id) { @@ -133,7 +132,7 @@ class BaseModel { // } static loadByPartialId(partialId) { - return this.modelSelectAll("SELECT * FROM `" + this.tableName() + "` WHERE `id` LIKE ?", [partialId + "%"]); + return this.modelSelectAll('SELECT * FROM `' + this.tableName() + '` WHERE `id` LIKE ?', [partialId + '%']); } static applySqlOptions(options, sql, params = null) { @@ -144,46 +143,46 @@ class BaseModel { for (let i = 0; i < options.order.length; i++) { const o = options.order[i]; let item = o.by; - if (options.caseInsensitive === true) item += " COLLATE NOCASE"; - if (o.dir) item += " " + o.dir; + if (options.caseInsensitive === true) item += ' COLLATE NOCASE'; + if (o.dir) item += ' ' + o.dir; items.push(item); } - sql += " ORDER BY " + items.join(", "); + sql += ' ORDER BY ' + items.join(', '); } - - if (options.limit) sql += " LIMIT " + options.limit; + + if (options.limit) sql += ' LIMIT ' + options.limit; return { sql: sql, params: params }; } static async allIds(options = null) { - let q = this.applySqlOptions(options, "SELECT id FROM `" + this.tableName() + "`"); + let q = this.applySqlOptions(options, 'SELECT id FROM `' + this.tableName() + '`'); const rows = await this.db().selectAll(q.sql, q.params); - return rows.map(r => r.id); + return rows.map((r) => r.id); } static async all(options = null) { - let q = this.applySqlOptions(options, "SELECT * FROM `" + this.tableName() + "`"); + let q = this.applySqlOptions(options, 'SELECT * FROM `' + this.tableName() + '`'); return this.modelSelectAll(q.sql); } static async search(options = null) { if (!options) options = {}; - if (!options.fields) options.fields = "*"; + if (!options.fields) options.fields = '*'; let conditions = options.conditions ? options.conditions.slice(0) : []; let params = options.conditionsParams ? options.conditionsParams.slice(0) : []; if (options.titlePattern) { - let pattern = options.titlePattern.replace(/\*/g, "%"); - conditions.push("title LIKE ?"); + let pattern = options.titlePattern.replace(/\*/g, '%'); + conditions.push('title LIKE ?'); params.push(pattern); } - if ("limit" in options && options.limit <= 0) return []; + if ('limit' in options && options.limit <= 0) return []; - let sql = "SELECT " + this.db().escapeFields(options.fields) + " FROM `" + this.tableName() + "`"; - if (conditions.length) sql += " WHERE " + conditions.join(" AND "); + let sql = 'SELECT ' + this.db().escapeFields(options.fields) + ' FROM `' + this.tableName() + '`'; + if (conditions.length) sql += ' WHERE ' + conditions.join(' AND '); let query = this.applySqlOptions(options, sql, params); return this.modelSelectAll(query.sql, query.params); @@ -191,32 +190,28 @@ class BaseModel { static modelSelectOne(sql, params = null) { if (params === null) params = []; - return this.db() - .selectOne(sql, params) - .then(model => { - return this.filter(this.addModelMd(model)); - }); + return this.db().selectOne(sql, params).then((model) => { + return this.filter(this.addModelMd(model)); + }); } static modelSelectAll(sql, params = null) { if (params === null) params = []; - return this.db() - .selectAll(sql, params) - .then(models => { - return this.filterArray(this.addModelMd(models)); - }); + return this.db().selectAll(sql, params).then((models) => { + return this.filterArray(this.addModelMd(models)); + }); } static loadByField(fieldName, fieldValue, options = null) { if (!options) options = {}; - if (!("caseInsensitive" in options)) options.caseInsensitive = false; - let sql = "SELECT * FROM `" + this.tableName() + "` WHERE `" + fieldName + "` = ?"; - if (options.caseInsensitive) sql += " COLLATE NOCASE"; + if (!('caseInsensitive' in options)) options.caseInsensitive = false; + let sql = 'SELECT * FROM `' + this.tableName() + '` WHERE `' + fieldName + '` = ?'; + if (options.caseInsensitive) sql += ' COLLATE NOCASE'; return this.modelSelectOne(sql, [fieldValue]); } static loadByTitle(fieldValue) { - return this.modelSelectOne("SELECT * FROM `" + this.tableName() + "` WHERE `title` = ?", [fieldValue]); + return this.modelSelectOne('SELECT * FROM `' + this.tableName() + '` WHERE `title` = ?', [fieldValue]); } static diffObjects(oldModel, newModel) { @@ -225,7 +220,7 @@ class BaseModel { for (let i = 0; i < fields.length; i++) { output[fields[i]] = newModel[fields[i]]; } - if ("type_" in newModel) output.type_ = newModel.type_; + if ('type_' in newModel) output.type_ = newModel.type_; return output; // let output = {}; // let type = null; @@ -247,7 +242,7 @@ class BaseModel { let output = []; for (let n in newModel) { if (!newModel.hasOwnProperty(n)) continue; - if (n == "type_") continue; + if (n == 'type_') continue; if (!(n in oldModel) || newModel[n] !== oldModel[n]) { output.push(n); } @@ -263,14 +258,12 @@ class BaseModel { static saveMutex(modelOrId) { const noLockMutex = { - acquire: function() { - return null; - }, + acquire: function() { return null; } }; if (!modelOrId) return noLockMutex; - let modelId = typeof modelOrId === "string" ? modelOrId : modelOrId.id; + let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id; if (!modelId) return noLockMutex; @@ -286,7 +279,7 @@ class BaseModel { if (!release) return; if (!modelOrId) return release(); - let modelId = typeof modelOrId === "string" ? modelOrId : modelOrId.id; + let modelId = typeof modelOrId === 'string' ? modelOrId : modelOrId.id; if (!modelId) return release(); @@ -298,7 +291,7 @@ class BaseModel { } static saveQuery(o, options) { - let temp = {}; + let temp = {} let fieldNames = this.fieldNames(); for (let i = 0; i < fieldNames.length; i++) { let n = fieldNames[i]; @@ -313,7 +306,7 @@ class BaseModel { const filtered = {}; for (let k in temp) { if (!temp.hasOwnProperty(k)) continue; - if (k !== "id" && options.fields.indexOf(k) < 0) continue; + if (k !== 'id' && options.fields.indexOf(k) < 0) continue; filtered[k] = temp[k]; } temp = filtered; @@ -326,14 +319,14 @@ class BaseModel { const timeNow = time.unixMs(); - if (options.autoTimestamp && this.hasField("updated_time")) { + if (options.autoTimestamp && this.hasField('updated_time')) { o.updated_time = timeNow; } // The purpose of user_updated_time is to allow the user to manually set the time of a note (in which case // options.autoTimestamp will be `false`). However note that if the item is later changed, this timestamp // will be set again to the current time. - if (options.autoTimestamp && this.hasField("user_updated_time")) { + if (options.autoTimestamp && this.hasField('user_updated_time')) { o.user_updated_time = timeNow; } @@ -343,15 +336,15 @@ class BaseModel { o.id = modelId; } - if (!o.created_time && this.hasField("created_time")) { + if (!o.created_time && this.hasField('created_time')) { o.created_time = timeNow; } - if (!o.user_created_time && this.hasField("user_created_time")) { + if (!o.user_created_time && this.hasField('user_created_time')) { o.user_created_time = o.created_time ? o.created_time : timeNow; } - if (!o.user_updated_time && this.hasField("user_updated_time")) { + if (!o.user_updated_time && this.hasField('user_updated_time')) { o.user_updated_time = o.updated_time ? o.updated_time : timeNow; } @@ -414,10 +407,10 @@ class BaseModel { o = Object.assign({}, o); if (modelId) o.id = modelId; - if ("updated_time" in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time; - if ("created_time" in saveQuery.modObject) o.created_time = saveQuery.modObject.created_time; - if ("user_updated_time" in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time; - if ("user_created_time" in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time; + if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time; + if ('created_time' in saveQuery.modObject) o.created_time = saveQuery.modObject.created_time; + if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time; + if ('user_created_time' in saveQuery.modObject) o.user_created_time = saveQuery.modObject.user_created_time; o = this.addModelMd(o); if (isDiffSaving) { @@ -430,7 +423,7 @@ class BaseModel { output = this.filter(o); } catch (error) { - Log.error("Cannot save model", error); + Log.error('Cannot save model', error); } this.releaseSaveMutex(o, mutexRelease); @@ -439,7 +432,7 @@ class BaseModel { } static isNew(object, options) { - if (options && "isNew" in options) { + if (options && ('isNew' in options)) { // options.isNew can be "auto" too if (options.isNew === true) return true; if (options.isNew === false) return false; @@ -467,7 +460,7 @@ class BaseModel { if (output[n] === true) { output[n] = 1; } else if (output[n] === false) { - output[n] = 0; + output[n] = 0; } else { const t = this.fieldType(n, Database.TYPE_UNKNOWN); if (t === Database.TYPE_INT) { @@ -475,42 +468,43 @@ class BaseModel { } } } - + return output; } static delete(id, options = null) { - if (!id) throw new Error("Cannot delete object without an ID"); + if (!id) throw new Error('Cannot delete object without an ID'); options = this.modOptions(options); - return this.db().exec("DELETE FROM " + this.tableName() + " WHERE id = ?", [id]); + return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]); } static batchDelete(ids, options = null) { if (!ids.length) return; options = this.modOptions(options); - return this.db().exec("DELETE FROM " + this.tableName() + ' WHERE id IN ("' + ids.join('","') + '")'); - } + return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id IN ("' + ids.join('","') + '")'); + } static db() { - if (!this.db_) throw new Error("Accessing database before it has been initialised"); - return this.db_; + if (!this.db_) throw new Error('Accessing database before it has been initialised'); + return this.db_; } static isReady() { return !!this.db_; } + } BaseModel.typeEnum_ = [ - ["TYPE_NOTE", 1], - ["TYPE_FOLDER", 2], - ["TYPE_SETTING", 3], - ["TYPE_RESOURCE", 4], - ["TYPE_TAG", 5], - ["TYPE_NOTE_TAG", 6], - ["TYPE_SEARCH", 7], - ["TYPE_ALARM", 8], - ["TYPE_MASTER_KEY", 9], + ['TYPE_NOTE', 1], + ['TYPE_FOLDER', 2], + ['TYPE_SETTING', 3], + ['TYPE_RESOURCE', 4], + ['TYPE_TAG', 5], + ['TYPE_NOTE_TAG', 6], + ['TYPE_SEARCH', 7], + ['TYPE_ALARM', 8], + ['TYPE_MASTER_KEY', 9], ]; for (let i = 0; i < BaseModel.typeEnum_.length; i++) { @@ -532,4 +526,4 @@ BaseModel.db_ = null; BaseModel.dispatch = function(o) {}; BaseModel.saveMutexes_ = {}; -module.exports = BaseModel; +module.exports = BaseModel; \ No newline at end of file diff --git a/ReactNativeClient/lib/BaseSyncTarget.js b/ReactNativeClient/lib/BaseSyncTarget.js index 9f8f7f123..b8b0f332d 100644 --- a/ReactNativeClient/lib/BaseSyncTarget.js +++ b/ReactNativeClient/lib/BaseSyncTarget.js @@ -1,6 +1,7 @@ -const EncryptionService = require("lib/services/EncryptionService.js"); +const EncryptionService = require('lib/services/EncryptionService.js'); class BaseSyncTarget { + constructor(db, options = null) { this.db_ = db; this.synchronizer_ = null; @@ -14,7 +15,7 @@ class BaseSyncTarget { } option(name, defaultValue = null) { - return this.options_ && name in this.options_ ? this.options_[name] : defaultValue; + return this.options_ && (name in this.options_) ? this.options_[name] : defaultValue; } logger() { @@ -38,25 +39,25 @@ class BaseSyncTarget { } static id() { - throw new Error("id() not implemented"); + throw new Error('id() not implemented'); } // Note: it cannot be called just "name()" because that's a reserved keyword and // it would throw an obscure error in React Native. static targetName() { - throw new Error("targetName() not implemented"); + throw new Error('targetName() not implemented'); } static label() { - throw new Error("label() not implemented"); + throw new Error('label() not implemented'); } async initSynchronizer() { - throw new Error("initSynchronizer() not implemented"); + throw new Error('initSynchronizer() not implemented'); } async initFileApi() { - throw new Error("initFileApi() not implemented"); + throw new Error('initFileApi() not implemented'); } async fileApi() { @@ -75,32 +76,32 @@ class BaseSyncTarget { async synchronizer() { if (this.synchronizer_) return this.synchronizer_; - if (this.initState_ == "started") { + if (this.initState_ == 'started') { // Synchronizer is already being initialized, so wait here till it's done. return new Promise((resolve, reject) => { const iid = setInterval(() => { - if (this.initState_ == "ready") { + if (this.initState_ == 'ready') { clearInterval(iid); resolve(this.synchronizer_); } - if (this.initState_ == "error") { + if (this.initState_ == 'error') { clearInterval(iid); - reject(new Error("Could not initialise synchroniser")); + reject(new Error('Could not initialise synchroniser')); } }, 1000); }); } else { - this.initState_ = "started"; + this.initState_ = 'started'; try { this.synchronizer_ = await this.initSynchronizer(); this.synchronizer_.setLogger(this.logger()); this.synchronizer_.setEncryptionService(EncryptionService.instance()); this.synchronizer_.dispatch = BaseSyncTarget.dispatch; - this.initState_ = "ready"; + this.initState_ = 'ready'; return this.synchronizer_; } catch (error) { - this.initState_ = "error"; + this.initState_ = 'error'; throw error; } } @@ -110,10 +111,11 @@ class BaseSyncTarget { if (!this.synchronizer_) return false; if (!this.isAuthenticated()) return false; const sync = await this.synchronizer(); - return sync.state() != "idle"; + return sync.state() != 'idle'; } + } -BaseSyncTarget.dispatch = action => {}; +BaseSyncTarget.dispatch = (action) => {}; -module.exports = BaseSyncTarget; +module.exports = BaseSyncTarget; \ No newline at end of file diff --git a/ReactNativeClient/lib/Cache.js b/ReactNativeClient/lib/Cache.js index 6a929b5c8..78c66ef94 100644 --- a/ReactNativeClient/lib/Cache.js +++ b/ReactNativeClient/lib/Cache.js @@ -1,4 +1,5 @@ class Cache { + async getItem(name) { let output = null; try { @@ -21,13 +22,14 @@ class Cache { // Defaults to not saving to cache } } + } Cache.storage = async function() { if (Cache.storage_) return Cache.storage_; - Cache.storage_ = require("node-persist"); - await Cache.storage_.init({ dir: require("os").tmpdir() + "/joplin-cache", ttl: 1000 * 60 }); + Cache.storage_ = require('node-persist'); + await Cache.storage_.init({ dir: require('os').tmpdir() + '/joplin-cache', ttl: 1000 * 60 }); return Cache.storage_; -}; +} -module.exports = Cache; +module.exports = Cache; \ No newline at end of file diff --git a/ReactNativeClient/lib/JoplinError.js b/ReactNativeClient/lib/JoplinError.js index 11724fcf4..1287f5f6a 100644 --- a/ReactNativeClient/lib/JoplinError.js +++ b/ReactNativeClient/lib/JoplinError.js @@ -1,8 +1,10 @@ class JoplinError extends Error { + constructor(message, code = null) { super(message); this.code = code; } + } -module.exports = JoplinError; +module.exports = JoplinError; \ No newline at end of file diff --git a/ReactNativeClient/lib/MdToHtml.js b/ReactNativeClient/lib/MdToHtml.js index 36f31bcba..855339cf3 100644 --- a/ReactNativeClient/lib/MdToHtml.js +++ b/ReactNativeClient/lib/MdToHtml.js @@ -1,13 +1,14 @@ -const MarkdownIt = require("markdown-it"); -const Entities = require("html-entities").AllHtmlEntities; -const htmlentities = new Entities().encode; -const Resource = require("lib/models/Resource.js"); -const ModelCache = require("lib/ModelCache"); -const { shim } = require("lib/shim.js"); -const md5 = require("md5"); -const MdToHtml_Katex = require("lib/MdToHtml_Katex"); +const MarkdownIt = require('markdown-it'); +const Entities = require('html-entities').AllHtmlEntities; +const htmlentities = (new Entities()).encode; +const Resource = require('lib/models/Resource.js'); +const ModelCache = require('lib/ModelCache'); +const { shim } = require('lib/shim.js'); +const md5 = require('md5'); +const MdToHtml_Katex = require('lib/MdToHtml_Katex'); class MdToHtml { + constructor(options = null) { if (!options) options = {}; @@ -18,7 +19,7 @@ class MdToHtml { this.modelCache_ = new ModelCache(); // Must include last "/" - this.resourceBaseUrl_ = "resourceBaseUrl" in options ? options.resourceBaseUrl : null; + this.resourceBaseUrl_ = ('resourceBaseUrl' in options) ? options.resourceBaseUrl : null; } makeContentKey(resources, body, style, options) { @@ -31,26 +32,26 @@ class MdToHtml { k.push(md5(escape(body))); // https://github.com/pvorb/node-md5/issues/41 k.push(md5(JSON.stringify(style))); k.push(md5(JSON.stringify(options))); - return k.join("_"); + return k.join('_'); } renderAttrs_(attrs) { - if (!attrs) return ""; + if (!attrs) return ''; let output = []; for (let i = 0; i < attrs.length; i++) { const n = attrs[i][0]; const v = attrs[i].length >= 2 ? attrs[i][1] : null; - if (n === "alt" && !v) { + if (n === 'alt' && !v) { continue; - } else if (n === "src") { + } else if (n === 'src') { output.push('src="' + htmlentities(v) + '"'); } else { - output.push(n + '="' + (v ? htmlentities(v) : "") + '"'); + output.push(n + '="' + (v ? htmlentities(v) : '') + '"'); } } - return output.join(" "); + return output.join(' '); } getAttr_(attrs, name) { @@ -72,7 +73,7 @@ class MdToHtml { } renderImage_(attrs, options) { - const loadResource = async id => { + const loadResource = async (id) => { // console.info('Loading resource: ' + id); // Initially set to to an empty object to make @@ -87,17 +88,17 @@ class MdToHtml { if (!resource) { // Can happen for example if an image is attached to a note, but the resource hasn't // been downloaded from the sync target yet. - console.warn("Cannot load resource: " + id); + console.warn('Cannot load resource: ' + id); return; } this.loadedResources_[id] = resource; if (options.onResourceLoaded) options.onResourceLoaded(); - }; + } - const title = this.getAttr_(attrs, "title"); - const href = this.getAttr_(attrs, "src"); + const title = this.getAttr_(attrs, 'title'); + const href = this.getAttr_(attrs, 'src'); if (!Resource.isResourceUrl(href)) { return ''; @@ -107,27 +108,27 @@ class MdToHtml { const resource = this.loadedResources_[resourceId]; if (!resource) { loadResource(resourceId); - return ""; + return ''; } - if (!resource.id) return ""; // Resource is being loaded + if (!resource.id) return ''; // Resource is being loaded - const mime = resource.mime ? resource.mime.toLowerCase() : ""; - if (mime == "image/png" || mime == "image/jpg" || mime == "image/jpeg" || mime == "image/gif") { - let src = "./" + Resource.filename(resource); + const mime = resource.mime ? resource.mime.toLowerCase() : ''; + if (mime == 'image/png' || mime == 'image/jpg' || mime == 'image/jpeg' || mime == 'image/gif') { + let src = './' + Resource.filename(resource); if (this.resourceBaseUrl_ !== null) src = this.resourceBaseUrl_ + src; let output = ''; return output; } - - return "[Image: " + htmlentities(resource.title) + " (" + htmlentities(mime) + ")]"; + + return '[Image: ' + htmlentities(resource.title) + ' (' + htmlentities(mime) + ')]'; } renderOpenLink_(attrs, options) { - let href = this.getAttr_(attrs, "href"); - const text = this.getAttr_(attrs, "text"); + let href = this.getAttr_(attrs, 'href'); + const text = this.getAttr_(attrs, 'text'); const isResourceUrl = Resource.isResourceUrl(href); - const title = isResourceUrl ? this.getAttr_(attrs, "title") : href; + const title = isResourceUrl ? this.getAttr_(attrs, 'title') : href; if (isResourceUrl && !this.supportsResourceLinks_) { // In mobile, links to local resources, such as PDF, etc. currently aren't supported. @@ -150,13 +151,13 @@ class MdToHtml { } renderCloseLink_(attrs, options) { - const href = this.getAttr_(attrs, "href"); + const href = this.getAttr_(attrs, 'href'); const isResourceUrl = Resource.isResourceUrl(href); if (isResourceUrl && !this.supportsResourceLinks_) { - return ")"; + return ')'; } else { - return ""; + return ''; } } @@ -164,7 +165,7 @@ class MdToHtml { if (!language) return null; const handlers = {}; - handlers["katex"] = new MdToHtml_Katex(); + handlers['katex'] = new MdToHtml_Katex(); return language in handlers ? handlers[language] : null; } @@ -189,61 +190,61 @@ class MdToHtml { for (let i = 0; i < tokens.length; i++) { let t = tokens[i]; - const nextToken = i < tokens.length ? tokens[i + 1] : null; + const nextToken = i < tokens.length ? tokens[i+1] : null; let tag = t.tag; let openTag = null; let closeTag = null; let attrs = t.attrs ? t.attrs : []; let tokenContent = t.content ? t.content : null; - const isCodeBlock = tag === "code" && t.block; - const isInlineCode = t.type === "code_inline"; + const isCodeBlock = tag === 'code' && t.block; + const isInlineCode = t.type === 'code_inline'; const codeBlockLanguage = t && t.info ? t.info : null; let rendererPlugin = null; - let rendererPluginOptions = { tagType: "inline" }; + let rendererPluginOptions = { tagType: 'inline' }; if (isCodeBlock) rendererPlugin = this.rendererPlugin_(codeBlockLanguage); - if (previousToken && previousToken.tag === "li" && tag === "p") { + if (previousToken && previousToken.tag === 'li' && tag === 'p') { // Markdown-it render list items as
      • Text

      • which makes it // complicated to style and layout the HTML, so we remove this extra //

        here and below in closeTag. openTag = null; } else if (isInlineCode) { openTag = null; - } else if (tag && t.type.indexOf("html_inline") >= 0) { + } else if (tag && t.type.indexOf('html_inline') >= 0) { openTag = null; - } else if (tag && t.type.indexOf("_open") >= 0) { + } else if (tag && t.type.indexOf('_open') >= 0) { openTag = tag; - } else if (tag && t.type.indexOf("_close") >= 0) { + } else if (tag && t.type.indexOf('_close') >= 0) { closeTag = tag; - } else if (tag && t.type.indexOf("inline") >= 0) { + } else if (tag && t.type.indexOf('inline') >= 0) { openTag = tag; - } else if (t.type === "link_open") { - openTag = "a"; + } else if (t.type === 'link_open') { + openTag = 'a'; } else if (isCodeBlock) { if (rendererPlugin) { openTag = null; } else { - openTag = "pre"; + openTag = 'pre'; } } if (openTag) { - if (openTag === "a") { + if (openTag === 'a') { anchorAttrs.push(attrs); output.push(this.renderOpenLink_(attrs, options)); } else { const attrsHtml = this.renderAttrs_(attrs); - output.push("<" + openTag + (attrsHtml ? " " + attrsHtml : "") + ">"); + output.push('<' + openTag + (attrsHtml ? ' ' + attrsHtml : '') + '>'); } } if (isCodeBlock) { - const codeAttrs = ["code"]; + const codeAttrs = ['code']; if (!rendererPlugin) { if (codeBlockLanguage) codeAttrs.push(t.info); // t.info contains the language when the token is a codeblock - output.push(''); + output.push(''); } } else if (isInlineCode) { const result = this.parseInlineCodeLanguage_(tokenContent); @@ -253,30 +254,30 @@ class MdToHtml { } if (!rendererPlugin) { - output.push(""); + output.push(''); } } - if (t.type === "math_inline" || t.type === "math_block") { - rendererPlugin = this.rendererPlugin_("katex"); - rendererPluginOptions = { tagType: t.type === "math_block" ? "block" : "inline" }; + if (t.type === 'math_inline' || t.type === 'math_block') { + rendererPlugin = this.rendererPlugin_('katex'); + rendererPluginOptions = { tagType: t.type === 'math_block' ? 'block' : 'inline' }; } if (rendererPlugin) { - rendererPlugin.loadAssets().catch(error => { - console.warn("MdToHtml: Error loading assets for " + rendererPlugin.name() + ": ", error.message); + rendererPlugin.loadAssets().catch((error) => { + console.warn('MdToHtml: Error loading assets for ' + rendererPlugin.name() + ': ', error.message); }); } - if (t.type === "image") { - if (tokenContent) attrs.push(["title", tokenContent]); + if (t.type === 'image') { + if (tokenContent) attrs.push(['title', tokenContent]); output.push(this.renderImage_(attrs, options)); - } else if (t.type === "html_inline") { + } else if (t.type === 'html_inline') { output.push(t.content); - } else if (t.type === "softbreak") { - output.push("
        "); - } else if (t.type === "hr") { - output.push("


        "); + } else if (t.type === 'softbreak') { + output.push('
        '); + } else if (t.type === 'hr') { + output.push('
        '); } else { if (t.children) { const parsedChildren = this.renderTokens_(markdownIt, t.children, options); @@ -284,7 +285,7 @@ class MdToHtml { } else { if (tokenContent) { if ((isCodeBlock || isInlineCode) && rendererPlugin) { - output = rendererPlugin.processContent(output, tokenContent, isCodeBlock ? "block" : "inline"); + output = rendererPlugin.processContent(output, tokenContent, isCodeBlock ? 'block' : 'inline'); } else if (rendererPlugin) { output = rendererPlugin.processContent(output, tokenContent, rendererPluginOptions.tagType); } else { @@ -293,12 +294,12 @@ class MdToHtml { } } } - - if (nextToken && nextToken.tag === "li" && t.tag === "p") { - closeTag = null; - } else if (t.type === "link_close") { - closeTag = "a"; - } else if (tag && t.type.indexOf("inline") >= 0) { + + if (nextToken && nextToken.tag === 'li' && t.tag === 'p') { + closeTag = null; + } else if (t.type === 'link_close') { + closeTag = 'a'; + } else if (tag && t.type.indexOf('inline') >= 0) { closeTag = openTag; } else if (isCodeBlock) { if (!rendererPlugin) closeTag = openTag; @@ -306,19 +307,19 @@ class MdToHtml { if (isCodeBlock) { if (!rendererPlugin) { - output.push("
        "); + output.push('
        '); } } else if (isInlineCode) { if (!rendererPlugin) { - output.push("
        "); + output.push('
        '); } } if (closeTag) { - if (closeTag === "a") { + if (closeTag === 'a') { output.push(this.renderCloseLink_(anchorAttrs.pop(), options)); } else { - output.push(""); + output.push(''); } } @@ -335,22 +336,22 @@ class MdToHtml { // Insert the extra CSS at the top of the HTML - const temp = [""); + temp.push(''); output = temp.concat(output); - return output.join(""); + return output.join(''); } render(body, style, options = null) { if (!options) options = {}; - if (!options.postMessageSyntax) options.postMessageSyntax = "postMessage"; - if (!options.paddingBottom) options.paddingBottom = "0"; + if (!options.postMessageSyntax) options.postMessageSyntax = 'postMessage'; + if (!options.paddingBottom) options.paddingBottom = '0'; const cacheKey = this.makeContentKey(this.loadedResources_, body, style, options); if (this.cachedContentKey_ === cacheKey) return this.cachedContent_; @@ -366,7 +367,7 @@ class MdToHtml { // library. It is better this way as then it is possible to conditionally load the CSS required by // Katex and use an up-to-date version of Katex (as of 2018, the plugin is still using 0.6, which is // buggy instead of 0.9). - md.use(require("markdown-it-katex")); + md.use(require('markdown-it-katex')); // Hack to make checkboxes clickable. Ideally, checkboxes should be parsed properly in // renderTokens_(), but for now this hack works. Marking it with HORRIBLE_HACK so @@ -375,11 +376,11 @@ class MdToHtml { if (HORRIBLE_HACK) { let counter = -1; - while (body.indexOf("- [ ]") >= 0 || body.indexOf("- [X]") >= 0) { + while (body.indexOf('- [ ]') >= 0 || body.indexOf('- [X]') >= 0) { body = body.replace(/- \[(X| )\]/, function(v, p1) { - let s = p1 == " " ? "NOTICK" : "TICK"; + let s = p1 == ' ' ? 'NOTICK' : 'TICK'; counter++; - return "- mJOPmCHECKBOXm" + s + "m" + counter + "m"; + return '- mJOPmCHECKBOXm' + s + 'm' + counter + 'm'; }); } } @@ -395,10 +396,10 @@ class MdToHtml { if (HORRIBLE_HACK) { let loopCount = 0; - while (renderedBody.indexOf("mJOPm") >= 0) { + while (renderedBody.indexOf('mJOPm') >= 0) { renderedBody = renderedBody.replace(/mJOPmCHECKBOXm([A-Z]+)m(\d+)m/, function(v, type, index) { - const js = options.postMessageSyntax + "('checkboxclick:" + type + ":" + index + "'); this.classList.contains('tick') ? this.classList.remove('tick') : this.classList.add('tick'); return false;"; - return '' + "" + ""; + const js = options.postMessageSyntax + "('checkboxclick:" + type + ':' + index + "'); this.classList.contains('tick') ? this.classList.remove('tick') : this.classList.add('tick'); return false;"; + return '' + '' + ''; }); if (loopCount++ >= 9999) break; } @@ -412,29 +413,16 @@ class MdToHtml { b,strong{font-weight:bolder}small{font-size:80%}img{border-style:none} `; - const fontFamily = "sans-serif"; + const fontFamily = 'sans-serif'; - const css = - ` + const css = ` body { - font-size: ` + - style.htmlFontSize + - `; - color: ` + - style.htmlColor + - `; - line-height: ` + - style.htmlLineHeight + - `; - background-color: ` + - style.htmlBackgroundColor + - `; - font-family: ` + - fontFamily + - `; - padding-bottom: ` + - options.paddingBottom + - `; + font-size: ` + style.htmlFontSize + `; + color: ` + style.htmlColor + `; + line-height: ` + style.htmlLineHeight + `; + background-color: ` + style.htmlBackgroundColor + `; + font-family: ` + fontFamily + `; + padding-bottom: ` + options.paddingBottom + `; } p, h1, h2, h3, h4, h5, h6, ul, table { margin-top: 0; @@ -453,9 +441,7 @@ class MdToHtml { font-weight: bold; } a { - color: ` + - style.htmlLinkColor + - ` + color: ` + style.htmlLinkColor + ` } ul { padding-left: 1.3em; @@ -480,9 +466,7 @@ class MdToHtml { width: 1.65em; /* Need to cut a bit the right border otherwise the SVG will display a black line */ height: 1.7em; margin-right: .3em; - background-color: ` + - style.htmlColor + - `; + background-color: ` + style.htmlColor + `; /* Awesome Font square-o */ -webkit-mask: url("data:image/svg+xml;utf8,"); } @@ -497,24 +481,14 @@ class MdToHtml { td, th { border: 1px solid silver; padding: .5em 1em .5em 1em; - font-size: ` + - style.htmlFontSize + - `; - color: ` + - style.htmlColor + - `; - background-color: ` + - style.htmlBackgroundColor + - `; - font-family: ` + - fontFamily + - `; + font-size: ` + style.htmlFontSize + `; + color: ` + style.htmlColor + `; + background-color: ` + style.htmlBackgroundColor + `; + font-family: ` + fontFamily + `; } hr { border: none; - border-bottom: 1px solid ` + - style.htmlDividerColor + - `; + border-bottom: 1px solid ` + style.htmlDividerColor + `; } img { width: auto; @@ -527,7 +501,7 @@ class MdToHtml { } `; - const styleHtml = ""; //+ ''; + const styleHtml = ''; //+ ''; const output = styleHtml + renderedBody; @@ -538,30 +512,31 @@ class MdToHtml { toggleTickAt(body, index) { let counter = -1; - while (body.indexOf("- [ ]") >= 0 || body.indexOf("- [X]") >= 0) { + while (body.indexOf('- [ ]') >= 0 || body.indexOf('- [X]') >= 0) { counter++; body = body.replace(/- \[(X| )\]/, function(v, p1) { - let s = p1 == " " ? "NOTICK" : "TICK"; + let s = p1 == ' ' ? 'NOTICK' : 'TICK'; if (index == counter) { - s = s == "NOTICK" ? "TICK" : "NOTICK"; + s = s == 'NOTICK' ? 'TICK' : 'NOTICK'; } - return "°°JOP°CHECKBOX°" + s + "°°"; + return '°°JOP°CHECKBOX°' + s + '°°'; }); } - body = body.replace(/°°JOP°CHECKBOX°NOTICK°°/g, "- [ ]"); - body = body.replace(/°°JOP°CHECKBOX°TICK°°/g, "- [X]"); + body = body.replace(/°°JOP°CHECKBOX°NOTICK°°/g, '- [ ]'); + body = body.replace(/°°JOP°CHECKBOX°TICK°°/g, '- [X]'); return body; } handleCheckboxClick(msg, noteBody) { - msg = msg.split(":"); + msg = msg.split(':'); let index = Number(msg[msg.length - 1]); let currentState = msg[msg.length - 2]; // Not really needed but keep it anyway - return this.toggleTickAt(noteBody, index); + return this.toggleTickAt(noteBody, index); } + } -module.exports = MdToHtml; +module.exports = MdToHtml; \ No newline at end of file diff --git a/ReactNativeClient/lib/MdToHtml_Katex.js b/ReactNativeClient/lib/MdToHtml_Katex.js index ca389f6af..9f3d31591 100644 --- a/ReactNativeClient/lib/MdToHtml_Katex.js +++ b/ReactNativeClient/lib/MdToHtml_Katex.js @@ -1,22 +1,23 @@ -const { shim } = require("lib/shim"); -const katex = require("katex"); -const katexCss = require("lib/csstojs/katex.css.js"); -const Setting = require("lib/models/Setting"); +const { shim } = require('lib/shim'); +const katex = require('katex'); +const katexCss = require('lib/csstojs/katex.css.js'); +const Setting = require('lib/models/Setting'); class MdToHtml_Katex { + name() { - return "katex"; + return 'katex'; } processContent(renderedTokens, content, tagType) { try { let renderered = katex.renderToString(content); - if (tagType === "block") renderered = "

        " + renderered + "

        "; + if (tagType === 'block') renderered = '

        ' + renderered + '

        '; renderedTokens.push(renderered); } catch (error) { - renderedTokens.push("Cannot render Katex content: " + error.message); + renderedTokens.push('Cannot render Katex content: ' + error.message); } return renderedTokens; } @@ -33,14 +34,15 @@ class MdToHtml_Katex { if (shim.isReactNative()) { // Fonts must go under the resourceDir directory because this is the baseUrl of NoteBodyViewer - const baseDir = Setting.value("resourceDir"); - await shim.fsDriver().mkdir(baseDir + "/fonts"); - - await shim.fetchBlob("https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Main-Regular.woff2", { overwrite: false, path: baseDir + "/fonts/KaTeX_Main-Regular.woff2" }); - await shim.fetchBlob("https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Math-Italic.woff2", { overwrite: false, path: baseDir + "/fonts/KaTeX_Math-Italic.woff2" }); - await shim.fetchBlob("https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Size1-Regular.woff2", { overwrite: false, path: baseDir + "/fonts/KaTeX_Size1-Regular.woff2" }); + const baseDir = Setting.value('resourceDir'); + await shim.fsDriver().mkdir(baseDir + '/fonts'); + + await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Main-Regular.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Main-Regular.woff2' }); + await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Math-Italic.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Math-Italic.woff2' }); + await shim.fetchBlob('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/fonts/KaTeX_Size1-Regular.woff2', { overwrite: false, path: baseDir + '/fonts/KaTeX_Size1-Regular.woff2' }); } } + } -module.exports = MdToHtml_Katex; +module.exports = MdToHtml_Katex; \ No newline at end of file diff --git a/ReactNativeClient/lib/ModelCache.js b/ReactNativeClient/lib/ModelCache.js index 32fb765c8..a2e2a5f3d 100644 --- a/ReactNativeClient/lib/ModelCache.js +++ b/ReactNativeClient/lib/ModelCache.js @@ -1,4 +1,5 @@ class ModelCache { + constructor(maxSize) { this.cache_ = []; this.maxSize_ = maxSize; @@ -7,7 +8,7 @@ class ModelCache { fromCache(ModelClass, id) { for (let i = 0; i < this.cache_.length; i++) { const c = this.cache_[i]; - if (c.id === id && c.modelType === ModelClass.modelType()) return c; + if (c.id === id && c.modelType === ModelClass.modelType()) return c } return null; } @@ -32,6 +33,7 @@ class ModelCache { this.cache(ModelClass, id, output); return output; } + } -module.exports = ModelCache; +module.exports = ModelCache; \ No newline at end of file diff --git a/ReactNativeClient/lib/ObjectUtils.js b/ReactNativeClient/lib/ObjectUtils.js index e30b9dabc..366156e5f 100644 --- a/ReactNativeClient/lib/ObjectUtils.js +++ b/ReactNativeClient/lib/ObjectUtils.js @@ -13,8 +13,8 @@ ObjectUtils.sortByValue = function(object) { temp.sort(function(a, b) { let v1 = a.value; let v2 = b.value; - if (typeof v1 === "string") v1 = v1.toLowerCase(); - if (typeof v2 === "string") v2 = v2.toLowerCase(); + if (typeof v1 === 'string') v1 = v1.toLowerCase(); + if (typeof v2 === 'string') v2 = v2.toLowerCase(); if (v1 === v2) return 0; return v1 < v2 ? -1 : +1; }); @@ -26,11 +26,11 @@ ObjectUtils.sortByValue = function(object) { } return output; -}; +} ObjectUtils.fieldsEqual = function(o1, o2) { - if ((!o1 || !o2) && o1 !== o2) return false; - + if ((!o1 || !o2) && (o1 !== o2)) return false; + for (let k in o1) { if (!o1.hasOwnProperty(k)) continue; if (o1[k] !== o2[k]) return false; @@ -42,6 +42,6 @@ ObjectUtils.fieldsEqual = function(o1, o2) { if (c1.length !== c2.length) return false; return true; -}; +} -module.exports = ObjectUtils; +module.exports = ObjectUtils; \ No newline at end of file diff --git a/ReactNativeClient/lib/SyncTargetFilesystem.js b/ReactNativeClient/lib/SyncTargetFilesystem.js index d077d09db..6dd4f4851 100644 --- a/ReactNativeClient/lib/SyncTargetFilesystem.js +++ b/ReactNativeClient/lib/SyncTargetFilesystem.js @@ -1,21 +1,22 @@ -const BaseSyncTarget = require("lib/BaseSyncTarget.js"); -const { _ } = require("lib/locale.js"); -const Setting = require("lib/models/Setting.js"); -const { FileApi } = require("lib/file-api.js"); -const { FileApiDriverLocal } = require("lib/file-api-driver-local.js"); -const { Synchronizer } = require("lib/synchronizer.js"); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const { FileApi } = require('lib/file-api.js'); +const { FileApiDriverLocal } = require('lib/file-api-driver-local.js'); +const { Synchronizer } = require('lib/synchronizer.js'); class SyncTargetFilesystem extends BaseSyncTarget { + static id() { return 2; } static targetName() { - return "filesystem"; + return 'filesystem'; } static label() { - return _("File system"); + return _('File system'); } isAuthenticated() { @@ -23,7 +24,7 @@ class SyncTargetFilesystem extends BaseSyncTarget { } async initFileApi() { - const syncPath = Setting.value("sync.2.path"); + const syncPath = Setting.value('sync.2.path'); const driver = new FileApiDriverLocal(); const fileApi = new FileApi(syncPath, driver); fileApi.setLogger(this.logger()); @@ -33,8 +34,9 @@ class SyncTargetFilesystem extends BaseSyncTarget { } async initSynchronizer() { - return new Synchronizer(this.db(), await this.fileApi(), Setting.value("appType")); + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); } + } -module.exports = SyncTargetFilesystem; +module.exports = SyncTargetFilesystem; \ No newline at end of file diff --git a/ReactNativeClient/lib/SyncTargetMemory.js b/ReactNativeClient/lib/SyncTargetMemory.js index 0c5bc0fa6..d0ac33461 100644 --- a/ReactNativeClient/lib/SyncTargetMemory.js +++ b/ReactNativeClient/lib/SyncTargetMemory.js @@ -1,21 +1,22 @@ -const BaseSyncTarget = require("lib/BaseSyncTarget.js"); -const { _ } = require("lib/locale.js"); -const Setting = require("lib/models/Setting.js"); -const { FileApi } = require("lib/file-api.js"); -const { FileApiDriverMemory } = require("lib/file-api-driver-memory.js"); -const { Synchronizer } = require("lib/synchronizer.js"); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const { FileApi } = require('lib/file-api.js'); +const { FileApiDriverMemory } = require('lib/file-api-driver-memory.js'); +const { Synchronizer } = require('lib/synchronizer.js'); class SyncTargetMemory extends BaseSyncTarget { + static id() { return 1; } static targetName() { - return "memory"; + return 'memory'; } static label() { - return "Memory"; + return 'Memory'; } isAuthenticated() { @@ -23,15 +24,16 @@ class SyncTargetMemory extends BaseSyncTarget { } initFileApi() { - const fileApi = new FileApi("/root", new FileApiDriverMemory()); + const fileApi = new FileApi('/root', new FileApiDriverMemory()); fileApi.setLogger(this.logger()); fileApi.setSyncTargetId(SyncTargetMemory.id()); return fileApi; } async initSynchronizer() { - return new Synchronizer(this.db(), await this.fileApi(), Setting.value("appType")); + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); } + } -module.exports = SyncTargetMemory; +module.exports = SyncTargetMemory; \ No newline at end of file diff --git a/ReactNativeClient/lib/SyncTargetNextcloud.js b/ReactNativeClient/lib/SyncTargetNextcloud.js index ec9092240..b050146a7 100644 --- a/ReactNativeClient/lib/SyncTargetNextcloud.js +++ b/ReactNativeClient/lib/SyncTargetNextcloud.js @@ -1,16 +1,17 @@ // The Nextcloud sync target is essentially a wrapper over the WebDAV sync target, // thus all the calls to SyncTargetWebDAV to avoid duplicate code. -const BaseSyncTarget = require("lib/BaseSyncTarget.js"); -const { _ } = require("lib/locale.js"); -const Setting = require("lib/models/Setting.js"); -const { FileApi } = require("lib/file-api.js"); -const { Synchronizer } = require("lib/synchronizer.js"); -const WebDavApi = require("lib/WebDavApi"); -const SyncTargetWebDAV = require("lib/SyncTargetWebDAV"); -const { FileApiDriverWebDav } = require("lib/file-api-driver-webdav"); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const WebDavApi = require('lib/WebDavApi'); +const SyncTargetWebDAV = require('lib/SyncTargetWebDAV'); +const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav'); class SyncTargetNextcloud extends BaseSyncTarget { + static id() { return 5; } @@ -20,11 +21,11 @@ class SyncTargetNextcloud extends BaseSyncTarget { } static targetName() { - return "nextcloud"; + return 'nextcloud'; } static label() { - return _("Nextcloud"); + return _('Nextcloud'); } isAuthenticated() { @@ -37,9 +38,9 @@ class SyncTargetNextcloud extends BaseSyncTarget { async initFileApi() { const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetNextcloud.id(), { - path: Setting.value("sync.5.path"), - username: Setting.value("sync.5.username"), - password: Setting.value("sync.5.password"), + path: Setting.value('sync.5.path'), + username: Setting.value('sync.5.username'), + password: Setting.value('sync.5.password'), }); fileApi.setLogger(this.logger()); @@ -48,8 +49,9 @@ class SyncTargetNextcloud extends BaseSyncTarget { } async initSynchronizer() { - return new Synchronizer(this.db(), await this.fileApi(), Setting.value("appType")); + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); } + } -module.exports = SyncTargetNextcloud; +module.exports = SyncTargetNextcloud; \ No newline at end of file diff --git a/ReactNativeClient/lib/SyncTargetOneDrive.js b/ReactNativeClient/lib/SyncTargetOneDrive.js index c510f44bd..50f0a04eb 100644 --- a/ReactNativeClient/lib/SyncTargetOneDrive.js +++ b/ReactNativeClient/lib/SyncTargetOneDrive.js @@ -1,13 +1,14 @@ -const BaseSyncTarget = require("lib/BaseSyncTarget.js"); -const { _ } = require("lib/locale.js"); -const { OneDriveApi } = require("lib/onedrive-api.js"); -const Setting = require("lib/models/Setting.js"); -const { parameters } = require("lib/parameters.js"); -const { FileApi } = require("lib/file-api.js"); -const { Synchronizer } = require("lib/synchronizer.js"); -const { FileApiDriverOneDrive } = require("lib/file-api-driver-onedrive.js"); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const { OneDriveApi } = require('lib/onedrive-api.js'); +const Setting = require('lib/models/Setting.js'); +const { parameters } = require('lib/parameters.js'); +const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js'); class SyncTargetOneDrive extends BaseSyncTarget { + static id() { return 3; } @@ -18,11 +19,11 @@ class SyncTargetOneDrive extends BaseSyncTarget { } static targetName() { - return "onedrive"; + return 'onedrive'; } static label() { - return _("OneDrive"); + return _('OneDrive'); } isAuthenticated() { @@ -38,35 +39,35 @@ class SyncTargetOneDrive extends BaseSyncTarget { } authRouteName() { - return "OneDriveLogin"; + return 'OneDriveLogin'; } api() { if (this.api_) return this.api_; - const isPublic = Setting.value("appType") != "cli"; + const isPublic = Setting.value('appType') != 'cli'; this.api_ = new OneDriveApi(this.oneDriveParameters().id, this.oneDriveParameters().secret, isPublic); this.api_.setLogger(this.logger()); - this.api_.on("authRefreshed", a => { - this.logger().info("Saving updated OneDrive auth."); - Setting.setValue("sync." + this.syncTargetId() + ".auth", a ? JSON.stringify(a) : null); + this.api_.on('authRefreshed', (a) => { + this.logger().info('Saving updated OneDrive auth.'); + Setting.setValue('sync.' + this.syncTargetId() + '.auth', a ? JSON.stringify(a) : null); }); - let auth = Setting.value("sync." + this.syncTargetId() + ".auth"); + let auth = Setting.value('sync.' + this.syncTargetId() + '.auth'); if (auth) { try { auth = JSON.parse(auth); } catch (error) { - this.logger().warn("Could not parse OneDrive auth token"); + this.logger().warn('Could not parse OneDrive auth token'); this.logger().warn(error); auth = null; } this.api_.setAuth(auth); } - + return this.api_; } @@ -79,9 +80,10 @@ class SyncTargetOneDrive extends BaseSyncTarget { } async initSynchronizer() { - if (!this.isAuthenticated()) throw new Error("User is not authentified"); - return new Synchronizer(this.db(), await this.fileApi(), Setting.value("appType")); + if (!this.isAuthenticated()) throw new Error('User is not authentified'); + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); } + } -module.exports = SyncTargetOneDrive; +module.exports = SyncTargetOneDrive; \ No newline at end of file diff --git a/ReactNativeClient/lib/SyncTargetOneDriveDev.js b/ReactNativeClient/lib/SyncTargetOneDriveDev.js index af95fb689..f60f430d3 100644 --- a/ReactNativeClient/lib/SyncTargetOneDriveDev.js +++ b/ReactNativeClient/lib/SyncTargetOneDriveDev.js @@ -1,24 +1,25 @@ -const BaseSyncTarget = require("lib/BaseSyncTarget.js"); -const SyncTargetOneDrive = require("lib/SyncTargetOneDrive.js"); -const { _ } = require("lib/locale.js"); -const { OneDriveApi } = require("lib/onedrive-api.js"); -const Setting = require("lib/models/Setting.js"); -const { parameters } = require("lib/parameters.js"); -const { FileApi } = require("lib/file-api.js"); -const { Synchronizer } = require("lib/synchronizer.js"); -const { FileApiDriverOneDrive } = require("lib/file-api-driver-onedrive.js"); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); +const { _ } = require('lib/locale.js'); +const { OneDriveApi } = require('lib/onedrive-api.js'); +const Setting = require('lib/models/Setting.js'); +const { parameters } = require('lib/parameters.js'); +const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js'); class SyncTargetOneDriveDev extends SyncTargetOneDrive { + static id() { return 4; } static targetName() { - return "onedrive_dev"; + return 'onedrive_dev'; } static label() { - return _("OneDrive Dev (For testing only)"); + return _('OneDrive Dev (For testing only)'); } syncTargetId() { @@ -26,10 +27,11 @@ class SyncTargetOneDriveDev extends SyncTargetOneDrive { } oneDriveParameters() { - return parameters("dev").oneDrive; + return parameters('dev').oneDrive; } + } const staticSelf = SyncTargetOneDriveDev; -module.exports = SyncTargetOneDriveDev; +module.exports = SyncTargetOneDriveDev; \ No newline at end of file diff --git a/ReactNativeClient/lib/SyncTargetRegistry.js b/ReactNativeClient/lib/SyncTargetRegistry.js index 8b8993688..0181dbeb3 100644 --- a/ReactNativeClient/lib/SyncTargetRegistry.js +++ b/ReactNativeClient/lib/SyncTargetRegistry.js @@ -1,7 +1,8 @@ class SyncTargetRegistry { + static classById(syncTargetId) { const info = SyncTargetRegistry.reg_[syncTargetId]; - if (!info) throw new Error("Invalid id: " + syncTargetId); + if (!info) throw new Error('Invalid id: ' + syncTargetId); return info.classRef; } @@ -20,7 +21,7 @@ class SyncTargetRegistry { if (!this.reg_.hasOwnProperty(n)) continue; if (this.reg_[n].name === name) return this.reg_[n].id; } - throw new Error("Name not found: " + name); + throw new Error('Name not found: ' + name); } static idToMetadata(id) { @@ -28,7 +29,7 @@ class SyncTargetRegistry { if (!this.reg_.hasOwnProperty(n)) continue; if (this.reg_[n].id === id) return this.reg_[n]; } - throw new Error("ID not found: " + id); + throw new Error('ID not found: ' + id); } static idToName(id) { @@ -43,8 +44,9 @@ class SyncTargetRegistry { } return output; } + } SyncTargetRegistry.reg_ = {}; -module.exports = SyncTargetRegistry; +module.exports = SyncTargetRegistry; \ No newline at end of file diff --git a/ReactNativeClient/lib/SyncTargetWebDAV.js b/ReactNativeClient/lib/SyncTargetWebDAV.js index 34ef3fc1b..7c67fb5eb 100644 --- a/ReactNativeClient/lib/SyncTargetWebDAV.js +++ b/ReactNativeClient/lib/SyncTargetWebDAV.js @@ -1,12 +1,13 @@ -const BaseSyncTarget = require("lib/BaseSyncTarget.js"); -const { _ } = require("lib/locale.js"); -const Setting = require("lib/models/Setting.js"); -const { FileApi } = require("lib/file-api.js"); -const { Synchronizer } = require("lib/synchronizer.js"); -const WebDavApi = require("lib/WebDavApi"); -const { FileApiDriverWebDav } = require("lib/file-api-driver-webdav"); +const BaseSyncTarget = require('lib/BaseSyncTarget.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const { FileApi } = require('lib/file-api.js'); +const { Synchronizer } = require('lib/synchronizer.js'); +const WebDavApi = require('lib/WebDavApi'); +const { FileApiDriverWebDav } = require('lib/file-api-driver-webdav'); class SyncTargetWebDAV extends BaseSyncTarget { + static id() { return 6; } @@ -16,11 +17,11 @@ class SyncTargetWebDAV extends BaseSyncTarget { } static targetName() { - return "webdav"; + return 'webdav'; } static label() { - return _("WebDAV"); + return _('WebDAV'); } isAuthenticated() { @@ -36,7 +37,7 @@ class SyncTargetWebDAV extends BaseSyncTarget { const api = new WebDavApi(apiOptions); const driver = new FileApiDriverWebDav(api); - const fileApi = new FileApi("", driver); + const fileApi = new FileApi('', driver); fileApi.setSyncTargetId(syncTargetId); return fileApi; } @@ -44,19 +45,19 @@ class SyncTargetWebDAV extends BaseSyncTarget { static async checkConfig(options) { const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetWebDAV.id(), options); fileApi.requestRepeatCount_ = 0; - + const output = { ok: false, - errorMessage: "", + errorMessage: '', }; - + try { - const result = await fileApi.stat(""); - if (!result) throw new Error("WebDAV directory not found: " + options.path); + const result = await fileApi.stat(''); + if (!result) throw new Error('WebDAV directory not found: ' + options.path); output.ok = true; } catch (error) { output.errorMessage = error.message; - if (error.code) output.errorMessage += " (Code " + error.code + ")"; + if (error.code) output.errorMessage += ' (Code ' + error.code + ')'; } return output; @@ -64,9 +65,9 @@ class SyncTargetWebDAV extends BaseSyncTarget { async initFileApi() { const fileApi = await SyncTargetWebDAV.newFileApi_(SyncTargetWebDAV.id(), { - path: Setting.value("sync.6.path"), - username: Setting.value("sync.6.username"), - password: Setting.value("sync.6.password"), + path: Setting.value('sync.6.path'), + username: Setting.value('sync.6.username'), + password: Setting.value('sync.6.password'), }); fileApi.setLogger(this.logger()); @@ -75,8 +76,9 @@ class SyncTargetWebDAV extends BaseSyncTarget { } async initSynchronizer() { - return new Synchronizer(this.db(), await this.fileApi(), Setting.value("appType")); + return new Synchronizer(this.db(), await this.fileApi(), Setting.value('appType')); } + } -module.exports = SyncTargetWebDAV; +module.exports = SyncTargetWebDAV; \ No newline at end of file diff --git a/ReactNativeClient/lib/WebDavApi.js b/ReactNativeClient/lib/WebDavApi.js index a91187ef9..ce6dbb3d9 100644 --- a/ReactNativeClient/lib/WebDavApi.js +++ b/ReactNativeClient/lib/WebDavApi.js @@ -1,10 +1,10 @@ -const { Logger } = require("lib/logger.js"); -const { shim } = require("lib/shim.js"); -const parseXmlString = require("xml2js").parseString; -const JoplinError = require("lib/JoplinError"); -const URL = require("url-parse"); -const { rtrimSlashes, ltrimSlashes } = require("lib/path-utils.js"); -const base64 = require("base-64"); +const { Logger } = require('lib/logger.js'); +const { shim } = require('lib/shim.js'); +const parseXmlString = require('xml2js').parseString; +const JoplinError = require('lib/JoplinError'); +const URL = require('url-parse'); +const { rtrimSlashes, ltrimSlashes } = require('lib/path-utils.js'); +const base64 = require('base-64'); // Note that the d: namespace (the DAV namespace) is specific to Nextcloud. The RFC for example uses "D:" however // we make all the tags and attributes lowercase so we handle both the Nextcloud style and RFC. Hopefully other @@ -13,6 +13,7 @@ const base64 = require("base-64"); // In general, we should only deal with things in "d:", which is the standard DAV namespace. class WebDavApi { + constructor(options) { this.logger_ = new Logger(); this.options_ = options; @@ -32,9 +33,9 @@ class WebDavApi { // Note: Non-ASCII passwords will throw an error about Latin1 characters - https://github.com/laurent22/joplin/issues/246 // Tried various things like the below, but it didn't work on React Native: //return base64.encode(utf8.encode(this.options_.username() + ':' + this.options_.password())); - return base64.encode(this.options_.username() + ":" + this.options_.password()); + return base64.encode(this.options_.username() + ':' + this.options_.password()); } catch (error) { - error.message = "Cannot encode username/password: " + error.message; + error.message = 'Cannot encode username/password: ' + error.message; throw error; } } @@ -51,15 +52,15 @@ class WebDavApi { async xmlToJson(xml) { let davNamespaces = []; // Yes, there can be more than one... xmlns:a="DAV:" xmlns:D="DAV:" - const nameProcessor = name => { - if (name.indexOf("xmlns:") !== 0) { + const nameProcessor = (name) => { + if (name.indexOf('xmlns:') !== 0) { // Check if the current name is within the DAV namespace. If it is, normalise it // by moving it to the "d:" namespace, which is what all the functions are using. - const p = name.split(":"); + const p = name.split(':'); if (p.length == 2) { const ns = p[0]; if (davNamespaces.indexOf(ns) >= 0) { - name = "d:" + p[1]; + name = 'd:' + p[1]; } } } @@ -67,17 +68,17 @@ class WebDavApi { }; const attrValueProcessor = (value, name) => { - if (value.toLowerCase() === "dav:") { - const p = name.split(":"); + if (value.toLowerCase() === 'dav:') { + const p = name.split(':'); davNamespaces.push(p[p.length - 1]); } - }; + } const options = { tagNameProcessors: [nameProcessor], attrNameProcessors: [nameProcessor], - attrValueProcessors: [attrValueProcessor], - }; + attrValueProcessors: [attrValueProcessor] + } return new Promise((resolve, reject) => { parseXmlString(xml, options, (error, result) => { @@ -90,7 +91,7 @@ class WebDavApi { }); } - valueFromJson(json, keys, type) { + valueFromJson(json, keys, type) { let output = json; for (let i = 0; i < keys.length; i++) { @@ -98,12 +99,12 @@ class WebDavApi { // console.info(key, typeof key, typeof output, typeof output === 'object' && (key in output), Array.isArray(output)); - if (typeof key === "number" && !Array.isArray(output)) return null; - if (typeof key === "string" && (typeof output !== "object" || !(key in output))) return null; + if (typeof key === 'number' && !Array.isArray(output)) return null; + if (typeof key === 'string' && (typeof output !== 'object' || !(key in output))) return null; output = output[key]; } - if (type === "string") { + if (type === 'string') { // If the XML has not attribute the value is directly a string // If the XML node has attributes, the value is under "_". // Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}: @@ -111,17 +112,17 @@ class WebDavApi { // For this XML, the value will be "Thu, 01 Feb 2018 17:24:05 GMT" // Thu, 01 Feb 2018 17:24:05 GMT - if (typeof output === "object" && "_" in output) output = output["_"]; - if (typeof output !== "string") return null; + if (typeof output === 'object' && '_' in output) output = output['_']; + if (typeof output !== 'string') return null; return output; } - if (type === "object") { - if (!Array.isArray(output) && typeof output === "object") return output; + if (type === 'object') { + if (!Array.isArray(output) && typeof output === 'object') return output; return null; } - if (type === "array") { + if (type === 'array') { return Array.isArray(output) ? output : null; } @@ -129,23 +130,23 @@ class WebDavApi { } stringFromJson(json, keys) { - return this.valueFromJson(json, keys, "string"); + return this.valueFromJson(json, keys, 'string'); } objectFromJson(json, keys) { - return this.valueFromJson(json, keys, "object"); + return this.valueFromJson(json, keys, 'object'); } arrayFromJson(json, keys) { - return this.valueFromJson(json, keys, "array"); + return this.valueFromJson(json, keys, 'array'); } resourcePropByName(resource, outputType, propName) { - const propStats = resource["d:propstat"]; + const propStats = resource['d:propstat']; let output = null; - if (!Array.isArray(propStats)) throw new Error("Missing d:propstat property"); + if (!Array.isArray(propStats)) throw new Error('Missing d:propstat property'); for (let i = 0; i < propStats.length; i++) { - const props = propStats[i]["d:prop"]; + const props = propStats[i]['d:prop']; if (!Array.isArray(props) || !props.length) continue; const prop = props[0]; if (Array.isArray(prop[propName])) { @@ -154,7 +155,7 @@ class WebDavApi { } } - if (outputType === "string") { + if (outputType === 'string') { // If the XML has not attribute the value is directly a string // If the XML node has attributes, the value is under "_". // Eg for this XML, the string will be under {"_":"Thu, 01 Feb 2018 17:24:05 GMT"}: @@ -164,24 +165,24 @@ class WebDavApi { output = output[0]; - if (typeof output === "object" && "_" in output) output = output["_"]; - if (typeof output !== "string") return null; + if (typeof output === 'object' && '_' in output) output = output['_']; + if (typeof output !== 'string') return null; return output; } - if (outputType === "array") { + if (outputType === 'array') { return output; } - throw new Error("Invalid output type: " + outputType); + throw new Error('Invalid output type: ' + outputType); } async execPropFind(path, depth, fields = null, options = null) { - if (fields === null) fields = ["d:getlastmodified"]; + if (fields === null) fields = ['d:getlastmodified']; - let fieldsXml = ""; + let fieldsXml = ''; for (let i = 0; i < fields.length; i++) { - fieldsXml += "<" + fields[i] + "/>"; + fieldsXml += '<' + fields[i] + '/>'; } // To find all available properties: @@ -191,34 +192,31 @@ class WebDavApi { // // `; - const body = - ` + const body = ` - ` + - fieldsXml + - ` + ` + fieldsXml + ` `; - return this.exec("PROPFIND", path, body, { Depth: depth }, options); + return this.exec('PROPFIND', path, body, { 'Depth': depth }, options); } requestToCurl_(url, options) { let output = []; - output.push("curl"); - if (options.method) output.push("-X " + options.method); + output.push('curl'); + if (options.method) output.push('-X ' + options.method); if (options.headers) { for (let n in options.headers) { if (!options.headers.hasOwnProperty(n)) continue; - output.push("-H " + '"' + n + ": " + options.headers[n] + '"'); + output.push('-H ' + '"' + n + ': ' + options.headers[n] + '"'); } } - if (options.body) output.push("--data " + "'" + options.body + "'"); + if (options.body) output.push('--data ' + "'" + options.body + "'"); output.push(url); - return output.join(" "); - } + return output.join(' '); + } // curl -u admin:123456 'http://nextcloud.local/remote.php/dav/files/admin/' -X PROPFIND --data ' // @@ -227,15 +225,15 @@ class WebDavApi { // // ' - async exec(method, path = "", body = null, headers = null, options = null) { + async exec(method, path = '', body = null, headers = null, options = null) { if (headers === null) headers = {}; if (options === null) options = {}; - if (!options.responseFormat) options.responseFormat = "json"; - if (!options.target) options.target = "string"; + if (!options.responseFormat) options.responseFormat = 'json'; + if (!options.target) options.target = 'string'; const authToken = this.authToken(); - if (authToken) headers["Authorization"] = "Basic " + authToken; + if (authToken) headers['Authorization'] = 'Basic ' + authToken; // On iOS, the network lib appends a If-None-Match header to PROPFIND calls, which is kind of correct because // the call is idempotent and thus could be cached. According to RFC-7232 though only GET and HEAD should have @@ -246,7 +244,7 @@ class WebDavApi { // The "solution", an ugly one, is to send a purposely invalid string as eTag, which will bypass the If-None-Match check - Seafile // finds out that no resource has this ID and simply sends the requested data. // Also add a random value to make sure the eTag is unique for each call. - if (["GET", "HEAD"].indexOf(method) < 0) headers["If-None-Match"] = "JoplinIgnore-" + Math.floor(Math.random() * 100000); + if (['GET', 'HEAD'].indexOf(method) < 0) headers['If-None-Match'] = 'JoplinIgnore-' + Math.floor(Math.random() * 100000); const fetchOptions = {}; fetchOptions.headers = headers; @@ -254,24 +252,23 @@ class WebDavApi { if (options.path) fetchOptions.path = options.path; if (body) fetchOptions.body = body; - const url = this.baseUrl() + "/" + path; + const url = this.baseUrl() + '/' + path; let response = null; // console.info('WebDAV Call', method + ' ' + url, headers, options); // console.info(this.requestToCurl_(url, fetchOptions)); - if (options.source == "file" && (method == "POST" || method == "PUT")) { + if (options.source == 'file' && (method == 'POST' || method == 'PUT')) { if (fetchOptions.path) { const fileStat = await shim.fsDriver().stat(fetchOptions.path); - if (fileStat) fetchOptions.headers["Content-Length"] = fileStat.size + ""; + if (fileStat) fetchOptions.headers['Content-Length'] = fileStat.size + ''; } response = await shim.uploadBlob(url, fetchOptions); - } else if (options.target == "string") { - if (typeof body === "string") fetchOptions.headers["Content-Length"] = shim.stringByteLength(body) + ""; + } else if (options.target == 'string') { + if (typeof body === 'string') fetchOptions.headers['Content-Length'] = shim.stringByteLength(body) + ''; response = await shim.fetch(url, fetchOptions); - } else { - // file + } else { // file response = await shim.fetchBlob(url, fetchOptions); } @@ -283,22 +280,22 @@ class WebDavApi { const newError = (message, code = 0) => { // Gives a shorter response for error messages. Useful for cases where a full HTML page is accidentally loaded instead of // JSON. That way the error message will still show there's a problem but without filling up the log or screen. - const shortResponseText = (responseText + "").substr(0, 1024); - return new JoplinError(method + " " + path + ": " + message + " (" + code + "): " + shortResponseText, code); - }; + const shortResponseText = (responseText + '').substr(0, 1024); + return new JoplinError(method + ' ' + path + ': ' + message + ' (' + code + '): ' + shortResponseText, code); + } let responseJson_ = null; const loadResponseJson = async () => { if (!responseText) return null; if (responseJson_) return responseJson_; responseJson_ = await this.xmlToJson(responseText); - if (!responseJson_) throw newError("Cannot parse XML response", response.status); + if (!responseJson_) throw newError('Cannot parse XML response', response.status); return responseJson_; - }; + } if (!response.ok) { // When using fetchBlob we only get a string (not xml or json) back - if (options.target === "file") throw newError("fetchBlob error", response.status); + if (options.target === 'file') throw newError('fetchBlob error', response.status); let json = null; try { @@ -307,30 +304,31 @@ class WebDavApi { // Just send back the plain text in newErro() } - if (json && json["d:error"]) { - const code = json["d:error"]["s:exception"] ? json["d:error"]["s:exception"].join(" ") : response.status; - const message = json["d:error"]["s:message"] ? json["d:error"]["s:message"].join("\n") : "Unknown error 1"; - throw newError(message + " (Exception " + code + ")", response.status); + if (json && json['d:error']) { + const code = json['d:error']['s:exception'] ? json['d:error']['s:exception'].join(' ') : response.status; + const message = json['d:error']['s:message'] ? json['d:error']['s:message'].join("\n") : 'Unknown error 1'; + throw newError(message + ' (Exception ' + code + ')', response.status); } - throw newError("Unknown error 2", response.status); + throw newError('Unknown error 2', response.status); } - - if (options.responseFormat === "text") return responseText; + + if (options.responseFormat === 'text') return responseText; // The following methods may have a response depending on the server but it's not // standard (some return a plain string, other XML, etc.) and we don't check the // response anyway since we rely on the HTTP status code so return null. - if (["MKCOL", "DELETE", "PUT", "MOVE"].indexOf(method) >= 0) return null; + if (['MKCOL', 'DELETE', 'PUT', 'MOVE'].indexOf(method) >= 0) return null; const output = await loadResponseJson(); // Check that we didn't get for example an HTML page (as an error) instead of the JSON response // null responses are possible, for example for DELETE calls - if (output !== null && typeof output === "object" && !("d:multistatus" in output)) throw newError("Not a valid WebDAV response"); + if (output !== null && typeof output === 'object' && !('d:multistatus' in output)) throw newError('Not a valid WebDAV response'); return output; } + } -module.exports = WebDavApi; +module.exports = WebDavApi; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/Dropdown.js b/ReactNativeClient/lib/components/Dropdown.js index fcaaf7531..2d5eec8ed 100644 --- a/ReactNativeClient/lib/components/Dropdown.js +++ b/ReactNativeClient/lib/components/Dropdown.js @@ -1,8 +1,9 @@ -const React = require("react"); -const { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View } = require("react-native"); -const { ItemList } = require("lib/components/ItemList.js"); +const React = require('react'); +const { TouchableOpacity, TouchableWithoutFeedback , Dimensions, Text, Modal, View } = require('react-native'); +const { ItemList } = require('lib/components/ItemList.js'); class Dropdown extends React.Component { + constructor() { super(); @@ -20,7 +21,7 @@ class Dropdown extends React.Component { // https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element this.headerRef_.measure((fx, fy, width, height, px, py) => { this.setState({ - headerSize: { x: px, y: py, width: width, height: height }, + headerSize: { x: px, y: py, width: width, height: height } }); }); } @@ -28,12 +29,12 @@ class Dropdown extends React.Component { render() { const items = this.props.items; const itemHeight = 60; - const windowHeight = Dimensions.get("window").height - 50; + const windowHeight = Dimensions.get('window').height - 50; // Dimensions doesn't return quite the right dimensions so leave an extra gap to make // sure nothing is off screen. const listMaxHeight = windowHeight; - const listHeight = Math.min(items.length * itemHeight, listMaxHeight); //Dimensions.get('window').height - this.state.headerSize.y - this.state.headerSize.height - 50; + const listHeight = Math.min(items.length * itemHeight, listMaxHeight); //Dimensions.get('window').height - this.state.headerSize.y - this.state.headerSize.height - 50; const maxListTop = windowHeight - listHeight; const listTop = Math.min(maxListTop, this.state.headerSize.y + this.state.headerSize.height); @@ -46,12 +47,12 @@ class Dropdown extends React.Component { const itemListStyle = Object.assign({}, this.props.itemListStyle ? this.props.itemListStyle : {}, { borderWidth: 1, - borderColor: "#ccc", + borderColor: '#ccc', }); const itemWrapperStyle = Object.assign({}, this.props.itemWrapperStyle ? this.props.itemWrapperStyle : {}, { - flex: 1, - justifyContent: "center", + flex:1, + justifyContent: 'center', height: itemHeight, paddingLeft: 20, paddingRight: 10, @@ -64,8 +65,8 @@ class Dropdown extends React.Component { //paddingLeft: 20, //paddingRight: 20, flex: 1, - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', }); const headerStyle = Object.assign({}, this.props.headerStyle ? this.props.headerStyle : {}, { @@ -77,9 +78,11 @@ class Dropdown extends React.Component { marginRight: 10, }); - const itemStyle = Object.assign({}, this.props.itemStyle ? this.props.itemStyle : {}, {}); + const itemStyle = Object.assign({}, this.props.itemStyle ? this.props.itemStyle : {}, { + + }); - let headerLabel = "..."; + let headerLabel = '...'; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.value === this.props.selectedValue) { @@ -90,61 +93,34 @@ class Dropdown extends React.Component { const closeList = () => { this.setState({ listVisible: false }); - }; + } - const itemRenderer = item => { + const itemRenderer= (item) => { return ( - { - closeList(); - if (this.props.onValueChange) this.props.onValueChange(item.value); - }} - > - - {item.label} - + { closeList(); if (this.props.onValueChange) this.props.onValueChange(item.value); }}> + {item.label} ); - }; + } return ( - - (this.headerRef_ = ref)} - onPress={() => { - this.updateHeaderCoordinates(); - this.setState({ listVisible: true }); - }} - > - - {headerLabel} - - {"▼"} + + this.headerRef_ = ref} onPress={() => { + this.updateHeaderCoordinates(); + this.setState({ listVisible: true }); + }}> + {headerLabel} + {'▼'} - { - closeList(); - }} - > - { - closeList(); - }} - > - + { closeList(); }} > + { closeList() }}> + { - return itemRenderer(item); - }} + itemRenderer={(item) => { return itemRenderer(item) }} /> @@ -155,4 +131,4 @@ class Dropdown extends React.Component { } } -module.exports = { Dropdown }; +module.exports = { Dropdown }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/ItemList.js b/ReactNativeClient/lib/components/ItemList.js index 97a8e8b4b..0e6be7026 100644 --- a/ReactNativeClient/lib/components/ItemList.js +++ b/ReactNativeClient/lib/components/ItemList.js @@ -1,7 +1,8 @@ -const React = require("react"); -const { Text, TouchableHighlight, View, StyleSheet, ScrollView } = require("react-native"); +const React = require('react'); +const { Text, TouchableHighlight, View, StyleSheet, ScrollView } = require('react-native'); class ItemList extends React.Component { + constructor() { super(); @@ -76,36 +77,27 @@ class ItemList extends React.Component { const items = this.props.items; const blankItem = function(key, height) { - return ; - }; + return + } - itemComps = [blankItem("top", this.state.topItemIndex * this.props.itemHeight)]; + itemComps = [blankItem('top', this.state.topItemIndex * this.props.itemHeight)]; for (let i = this.state.topItemIndex; i <= this.state.bottomItemIndex; i++) { const itemComp = this.props.itemRenderer(items[i]); itemComps.push(itemComp); } - itemComps.push(blankItem("bottom", (items.length - this.state.bottomItemIndex - 1) * this.props.itemHeight)); + itemComps.push(blankItem('bottom', (items.length - this.state.bottomItemIndex - 1) * this.props.itemHeight)); } else { itemComps = this.props.itemComponents; } return ( - { - this.onLayout(event); - }} - style={style} - onScroll={event => { - this.onScroll(event); - }} - > - {itemComps} + { this.onLayout(event); }} style={style} onScroll={ (event) => { this.onScroll(event) }}> + { itemComps } ); } } -module.exports = { ItemList }; +module.exports = { ItemList }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/ModalDialog.js b/ReactNativeClient/lib/components/ModalDialog.js index e927a42c4..2ecc2c3e3 100644 --- a/ReactNativeClient/lib/components/ModalDialog.js +++ b/ReactNativeClient/lib/components/ModalDialog.js @@ -1,9 +1,10 @@ -const React = require("react"); -const { Text, Modal, View, StyleSheet, Button } = require("react-native"); -const { themeStyle } = require("lib/components/global-style.js"); -const { _ } = require("lib/locale"); +const React = require('react'); +const { Text, Modal, View, StyleSheet, Button } = require('react-native'); +const { themeStyle } = require('lib/components/global-style.js'); +const { _ } = require('lib/locale'); class ModalDialog extends React.Component { + constructor() { super(); this.styles_ = {}; @@ -19,20 +20,20 @@ class ModalDialog extends React.Component { let styles = { modalWrapper: { flex: 1, - justifyContent: "center", + justifyContent: 'center', }, modalContentWrapper: { - flex: 1, - flexDirection: "column", + flex:1, + flexDirection: 'column', backgroundColor: theme.backgroundColor, borderWidth: 1, - borderColor: theme.dividerColor, + borderColor:theme.dividerColor, margin: 20, padding: 10, }, modalContentWrapper2: { paddingTop: 10, - flex: 1, + flex:1, }, title: { borderBottomWidth: 1, @@ -40,7 +41,7 @@ class ModalDialog extends React.Component { paddingBottom: 10, }, buttonRow: { - flexDirection: "row", + flexDirection: 'row', borderTopWidth: 1, borderTopColor: theme.dividerColor, paddingTop: 10, @@ -56,16 +57,18 @@ class ModalDialog extends React.Component { return ( - {}}> + { }} > Title - {ContentComponent} + + {ContentComponent} + - - - - @@ -75,4 +78,4 @@ class ModalDialog extends React.Component { } } -module.exports = ModalDialog; +module.exports = ModalDialog; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/action-button.js b/ReactNativeClient/lib/components/action-button.js index ab8c1a072..752793a15 100644 --- a/ReactNativeClient/lib/components/action-button.js +++ b/ReactNativeClient/lib/components/action-button.js @@ -1,25 +1,25 @@ -const React = require("react"); -const Component = React.Component; -const { StyleSheet, Text } = require("react-native"); -const Icon = require("react-native-vector-icons/Ionicons").default; -const ReactNativeActionButton = require("react-native-action-button").default; -const { connect } = require("react-redux"); -const { globalStyle } = require("lib/components/global-style.js"); -const { Log } = require("lib/log.js"); -const { _ } = require("lib/locale.js"); +const React = require('react'); const Component = React.Component; +const { StyleSheet, Text } = require('react-native'); +const Icon = require('react-native-vector-icons/Ionicons').default; +const ReactNativeActionButton = require('react-native-action-button').default; +const { connect } = require('react-redux'); +const { globalStyle } = require('lib/components/global-style.js'); +const { Log } = require('lib/log.js'); +const { _ } = require('lib/locale.js'); const styles = StyleSheet.create({ actionButtonIcon: { fontSize: 20, height: 22, - color: "white", + color: 'white', }, itemText: { // fontSize: 14, // Cannot currently set fontsize since the bow surrounding the label has a fixed size - }, + } }); class ActionButtonComponent extends React.Component { + constructor() { super(); this.state = { @@ -28,35 +28,35 @@ class ActionButtonComponent extends React.Component { } componentWillReceiveProps(newProps) { - if ("buttonIndex" in newProps) { + if ('buttonIndex' in newProps) { this.setState({ buttonIndex: newProps.buttonIndex }); } } newTodo_press() { this.props.dispatch({ - type: "NAV_GO", - routeName: "Note", + type: 'NAV_GO', + routeName: 'Note', noteId: null, folderId: this.props.parentFolderId, - itemType: "todo", + itemType: 'todo', }); } newNote_press() { this.props.dispatch({ - type: "NAV_GO", - routeName: "Note", + type: 'NAV_GO', + routeName: 'Note', noteId: null, folderId: this.props.parentFolderId, - itemType: "note", + itemType: 'note', }); } newFolder_press() { this.props.dispatch({ - type: "NAV_GO", - routeName: "Folder", + type: 'NAV_GO', + routeName: 'Folder', folderId: null, }); } @@ -67,39 +67,33 @@ class ActionButtonComponent extends React.Component { if (this.props.addFolderNoteButtons) { if (this.props.folders.length) { buttons.push({ - title: _("New to-do"), - onPress: () => { - this.newTodo_press(); - }, - color: "#9b59b6", - icon: "md-checkbox-outline", + title: _('New to-do'), + onPress: () => { this.newTodo_press() }, + color: '#9b59b6', + icon: 'md-checkbox-outline', }); buttons.push({ - title: _("New note"), - onPress: () => { - this.newNote_press(); - }, - color: "#9b59b6", - icon: "md-document", + title: _('New note'), + onPress: () => { this.newNote_press() }, + color: '#9b59b6', + icon: 'md-document', }); } buttons.push({ - title: _("New notebook"), - onPress: () => { - this.newFolder_press(); - }, - color: "#3498db", - icon: "md-folder", + title: _('New notebook'), + onPress: () => { this.newFolder_press() }, + color: '#3498db', + icon: 'md-folder', }); } let buttonComps = []; for (let i = 0; i < buttons.length; i++) { let button = buttons[i]; - let buttonTitle = button.title ? button.title : ""; - let key = buttonTitle.replace(/\s/g, "_") + "_" + button.icon; + let buttonTitle = button.title ? button.title : ''; + let key = buttonTitle.replace(/\s/g, '_') + '_' + button.icon; buttonComps.push( @@ -108,41 +102,41 @@ class ActionButtonComponent extends React.Component { } if (!buttonComps.length && !this.props.mainButton) { - return ; + return } let mainButton = this.props.mainButton ? this.props.mainButton : {}; - let mainIcon = mainButton.icon ? : ; + let mainIcon = mainButton.icon ? : if (this.props.multiStates) { - if (!this.props.buttons || !this.props.buttons.length) throw new Error("Multi-state button requires at least one state"); - if (this.state.buttonIndex < 0 || this.state.buttonIndex >= this.props.buttons.length) throw new Error("Button index out of bounds: " + this.state.buttonIndex + "/" + this.props.buttons.length); + if (!this.props.buttons || !this.props.buttons.length) throw new Error('Multi-state button requires at least one state'); + if (this.state.buttonIndex < 0 || this.state.buttonIndex >= this.props.buttons.length) throw new Error('Button index out of bounds: ' + this.state.buttonIndex + '/' + this.props.buttons.length); let button = this.props.buttons[this.state.buttonIndex]; - let mainIcon = ; + let mainIcon = return ( { - button.onPress(); - }} + onPress={() => { button.onPress() }} /> ); } else { return ( - - {buttonComps} + + { buttonComps } ); } } } -const ActionButton = connect(state => { - return { - folders: state.folders, - locale: state.settings.locale, - }; -})(ActionButtonComponent); +const ActionButton = connect( + (state) => { + return { + folders: state.folders, + locale: state.settings.locale, + }; + } +)(ActionButtonComponent) -module.exports = { ActionButton }; +module.exports = { ActionButton }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/app-nav.js b/ReactNativeClient/lib/components/app-nav.js index 67feba3fc..f6028fd0e 100644 --- a/ReactNativeClient/lib/components/app-nav.js +++ b/ReactNativeClient/lib/components/app-nav.js @@ -1,25 +1,25 @@ -const React = require("react"); -const Component = React.Component; -const { connect } = require("react-redux"); -const { NotesScreen } = require("lib/components/screens/notes.js"); -const { SearchScreen } = require("lib/components/screens/search.js"); -const { KeyboardAvoidingView, Keyboard, Platform, View } = require("react-native"); -const { _ } = require("lib/locale.js"); -const { themeStyle } = require("lib/components/global-style.js"); +const React = require('react'); const Component = React.Component; +const { connect } = require('react-redux'); +const { NotesScreen } = require('lib/components/screens/notes.js'); +const { SearchScreen } = require('lib/components/screens/search.js'); +const { KeyboardAvoidingView, Keyboard, Platform, View } = require('react-native'); +const { _ } = require('lib/locale.js'); +const { themeStyle } = require('lib/components/global-style.js'); class AppNavComponent extends Component { + constructor() { super(); this.previousRouteName_ = null; this.state = { autoCompletionBarExtraHeight: 0, // Extra padding for the auto completion bar at the top of the keyboard - }; + } } componentWillMount() { - if (Platform.OS === "ios") { - this.keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", this.keyboardDidShow.bind(this)); - this.keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", this.keyboardDidHide.bind(this)); + if (Platform.OS === 'ios') { + this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this)); + this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this)); } } @@ -30,16 +30,16 @@ class AppNavComponent extends Component { this.keyboardDidHideListener = null; } - keyboardDidShow() { - this.setState({ autoCompletionBarExtraHeight: 30 }); + keyboardDidShow () { + this.setState({ autoCompletionBarExtraHeight: 30 }) } - keyboardDidHide() { - this.setState({ autoCompletionBarExtraHeight: 0 }); + keyboardDidHide () { + this.setState({ autoCompletionBarExtraHeight:0 }) } render() { - if (!this.props.route) throw new Error("Route must not be null"); + if (!this.props.route) throw new Error('Route must not be null'); // Note: certain screens are kept into memory, in particular Notes and Search // so that the scroll position is not lost when the user navigate away from them. @@ -49,9 +49,9 @@ class AppNavComponent extends Component { let notesScreenVisible = false; let searchScreenVisible = false; - if (route.routeName == "Notes") { + if (route.routeName == 'Notes') { notesScreenVisible = true; - } else if (route.routeName == "Search") { + } else if (route.routeName == 'Search') { searchScreenVisible = true; } else { Screen = this.props.screens[route.routeName].screen; @@ -60,30 +60,33 @@ class AppNavComponent extends Component { // Keep the search screen loaded if the user is viewing a note from that search screen // so that if the back button is pressed, the screen is still loaded. However, unload // it if navigating away. - let searchScreenLoaded = searchScreenVisible || (this.previousRouteName_ == "Search" && route.routeName == "Note"); + let searchScreenLoaded = searchScreenVisible || (this.previousRouteName_ == 'Search' && route.routeName == 'Note'); this.previousRouteName_ = route.routeName; const theme = themeStyle(this.props.theme); - const style = { flex: 1, backgroundColor: theme.backgroundColor }; + const style = { flex: 1, backgroundColor: theme.backgroundColor } return ( - + - {searchScreenLoaded && } - {!notesScreenVisible && !searchScreenVisible && } + { searchScreenLoaded && } + { (!notesScreenVisible && !searchScreenVisible) && } ); } + } -const AppNav = connect(state => { - return { - route: state.route, - theme: state.settings.theme, - }; -})(AppNavComponent); +const AppNav = connect( + (state) => { + return { + route: state.route, + theme: state.settings.theme, + }; + } +)(AppNavComponent) -module.exports = { AppNav }; +module.exports = { AppNav }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/base-screen.js b/ReactNativeClient/lib/components/base-screen.js index 16d146272..86d94a8a9 100644 --- a/ReactNativeClient/lib/components/base-screen.js +++ b/ReactNativeClient/lib/components/base-screen.js @@ -1,7 +1,6 @@ -const React = require("react"); -const Component = React.Component; -const { StyleSheet } = require("react-native"); -const { globalStyle, themeStyle } = require("lib/components/global-style.js"); +const React = require('react'); const Component = React.Component; +const { StyleSheet } = require('react-native'); +const { globalStyle, themeStyle } = require('lib/components/global-style.js'); const styleObject_ = { screen: { @@ -15,6 +14,7 @@ const styles_ = StyleSheet.create(styleObject_); let rootStyles_ = {}; class BaseScreenComponent extends React.Component { + styles() { return styles_; } @@ -34,6 +34,7 @@ class BaseScreenComponent extends React.Component { }); return rootStyles_[themeId]; } + } -module.exports = { BaseScreenComponent }; +module.exports = { BaseScreenComponent }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/checkbox.js b/ReactNativeClient/lib/components/checkbox.js index 5eabf5bb3..78bfdb627 100644 --- a/ReactNativeClient/lib/components/checkbox.js +++ b/ReactNativeClient/lib/components/checkbox.js @@ -1,7 +1,6 @@ -const React = require("react"); -const Component = React.Component; -const { StyleSheet, View, TouchableHighlight } = require("react-native"); -const Icon = require("react-native-vector-icons/Ionicons").default; +const React = require('react'); const Component = React.Component; +const { StyleSheet, View, TouchableHighlight } = require('react-native'); +const Icon = require('react-native-vector-icons/Ionicons').default; const styles = { checkboxIcon: { @@ -12,11 +11,12 @@ const styles = { }; class Checkbox extends Component { + constructor() { super(); this.state = { checked: false, - }; + } } componentWillMount() { @@ -24,7 +24,7 @@ class Checkbox extends Component { } componentWillReceiveProps(newProps) { - if ("checked" in newProps) { + if ('checked' in newProps) { this.setState({ checked: newProps.checked }); } } @@ -36,11 +36,11 @@ class Checkbox extends Component { } render() { - const iconName = this.state.checked ? "md-checkbox-outline" : "md-square-outline"; + const iconName = this.state.checked ? 'md-checkbox-outline' : 'md-square-outline'; let style = this.props.style ? Object.assign({}, this.props.style) : {}; - style.justifyContent = "center"; - style.alignItems = "center"; + style.justifyContent = 'center'; + style.alignItems = 'center'; let checkboxIconStyle = Object.assign({}, styles.checkboxIcon); if (style.color) checkboxIconStyle.color = style.color; @@ -51,20 +51,21 @@ class Checkbox extends Component { if (style.paddingRight) checkboxIconStyle.marginRight = style.paddingRight; const thStyle = { - justifyContent: "center", - alignItems: "center", + justifyContent: 'center', + alignItems: 'center', }; - if (style && style.display === "none") return ; + if (style && style.display === 'none') return //if (style.display) thStyle.display = style.display; return ( this.onPress()} style={thStyle}> - + ); } + } -module.exports = { Checkbox }; +module.exports = { Checkbox }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/global-style.js b/ReactNativeClient/lib/components/global-style.js index b5469011f..c407c99b5 100644 --- a/ReactNativeClient/lib/components/global-style.js +++ b/ReactNativeClient/lib/components/global-style.js @@ -1,4 +1,4 @@ -const Setting = require("lib/models/Setting.js"); +const Setting = require('lib/models/Setting.js'); const globalStyle = { fontSize: 16, @@ -12,7 +12,7 @@ const globalStyle = { colorFaded: "#777777", // For less important text fontSizeSmaller: 14, dividerColor: "#dddddd", - selectedColor: "#e5e5e5", + selectedColor: '#e5e5e5', disabledOpacity: 0.2, raisedBackgroundColor: "#0080EF", @@ -22,19 +22,19 @@ const globalStyle = { warningBackgroundColor: "#FFD08D", // For WebView - must correspond to the properties above - htmlFontSize: "16px", - htmlColor: "black", // Note: CSS in WebView component only supports named colors or rgb() notation - htmlBackgroundColor: "white", - htmlDividerColor: "Gainsboro", - htmlLinkColor: "blue", - htmlLineHeight: "20px", + htmlFontSize: '16px', + htmlColor: 'black', // Note: CSS in WebView component only supports named colors or rgb() notation + htmlBackgroundColor: 'white', + htmlDividerColor: 'Gainsboro', + htmlLinkColor: 'blue', + htmlLineHeight: '20px', }; globalStyle.marginRight = globalStyle.margin; globalStyle.marginLeft = globalStyle.margin; globalStyle.marginTop = globalStyle.margin; globalStyle.marginBottom = globalStyle.margin; -globalStyle.htmlMarginLeft = (globalStyle.marginLeft / 10 * 0.6).toFixed(2) + "em"; +globalStyle.htmlMarginLeft = ((globalStyle.marginLeft / 10) * 0.6).toFixed(2) + 'em'; globalStyle.icon = { color: globalStyle.color, @@ -54,22 +54,22 @@ function themeStyle(theme) { let output = Object.assign({}, globalStyle); if (theme == Setting.THEME_LIGHT) return output; - output.backgroundColor = "#1D2024"; - output.color = "#dddddd"; - output.colorFaded = "#777777"; - output.dividerColor = "#555555"; - output.selectedColor = "#333333"; + output.backgroundColor = '#1D2024'; + output.color = '#dddddd'; + output.colorFaded = '#777777'; + output.dividerColor = '#555555'; + output.selectedColor = '#333333'; output.raisedBackgroundColor = "#0F2051"; output.raisedColor = "#788BC3"; output.raisedHighlightedColor = "#ffffff"; - output.htmlColor = "rgb(220,220,220)"; - output.htmlBackgroundColor = "rgb(29,32,36)"; - output.htmlLinkColor = "rgb(166,166,255)"; + output.htmlColor = 'rgb(220,220,220)'; + output.htmlBackgroundColor = 'rgb(29,32,36)'; + output.htmlLinkColor = 'rgb(166,166,255)'; themeCache_[theme] = output; return themeCache_[theme]; } -module.exports = { globalStyle, themeStyle }; +module.exports = { globalStyle, themeStyle }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/note-body-viewer.js b/ReactNativeClient/lib/components/note-body-viewer.js index dd7a62f57..aae7e3bb5 100644 --- a/ReactNativeClient/lib/components/note-body-viewer.js +++ b/ReactNativeClient/lib/components/note-body-viewer.js @@ -1,19 +1,19 @@ -const React = require("react"); -const Component = React.Component; -const { Platform, WebView, View, Linking } = require("react-native"); -const { globalStyle } = require("lib/components/global-style.js"); -const Resource = require("lib/models/Resource.js"); -const Setting = require("lib/models/Setting.js"); -const { reg } = require("lib/registry.js"); -const MdToHtml = require("lib/MdToHtml.js"); +const React = require('react'); const Component = React.Component; +const { Platform, WebView, View, Linking } = require('react-native'); +const { globalStyle } = require('lib/components/global-style.js'); +const Resource = require('lib/models/Resource.js'); +const Setting = require('lib/models/Setting.js'); +const { reg } = require('lib/registry.js'); +const MdToHtml = require('lib/MdToHtml.js'); class NoteBodyViewer extends Component { + constructor() { super(); this.state = { resources: {}, webViewLoaded: false, - }; + } this.isMounted_ = false; } @@ -48,31 +48,28 @@ class NoteBodyViewer extends Component { onResourceLoaded: () => { this.forceUpdate(); }, - paddingBottom: "3.8em", // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text) + paddingBottom: '3.8em', // Extra bottom padding to make it possible to scroll past the action button (so that it doesn't overlap the text) }; - let html = this.mdToHtml_.render(note ? note.body : "", this.props.webViewStyle, mdOptions); + let html = this.mdToHtml_.render(note ? note.body : '', this.props.webViewStyle, mdOptions); - html = - ` + html = ` - ` + - html + - ` + ` + html + ` `; - let webViewStyle = {}; + let webViewStyle = {} // On iOS, the onLoadEnd() event is never fired so always // display the webview (don't do the little trick // to avoid the white flash). - if (Platform.OS !== "ios") { + if (Platform.OS !== 'ios') { webViewStyle.opacity = this.state.webViewLoaded ? 1 : 0.01; } @@ -97,24 +94,24 @@ class NoteBodyViewer extends Component { // `baseUrl` is where the images will be loaded from. So images must use a path relative to resourceDir. const source = { html: html, - baseUrl: "file://" + Setting.value("resourceDir") + "/", + baseUrl: 'file://' + Setting.value('resourceDir') + '/', }; return ( this.onLoadEnd()} - onError={e => reg.logger().error("WebView error", e)} - onMessage={event => { + onError={(e) => reg.logger().error('WebView error', e) } + onMessage={(event) => { let msg = event.nativeEvent.data; - if (msg.indexOf("checkboxclick:") === 0) { + if (msg.indexOf('checkboxclick:') === 0) { const newBody = this.mdToHtml_.handleCheckboxClick(msg, note.body); if (onCheckboxChange) onCheckboxChange(newBody); - } else if (msg.indexOf("bodyscroll:") === 0) { + } else if (msg.indexOf('bodyscroll:') === 0) { //msg = msg.split(':'); //this.bodyScrollTop_ = Number(msg[1]); } else { @@ -125,6 +122,7 @@ class NoteBodyViewer extends Component { ); } + } -module.exports = { NoteBodyViewer }; +module.exports = { NoteBodyViewer }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/note-item.js b/ReactNativeClient/lib/components/note-item.js index 66b1bac2c..f000eed12 100644 --- a/ReactNativeClient/lib/components/note-item.js +++ b/ReactNativeClient/lib/components/note-item.js @@ -1,16 +1,16 @@ -const React = require("react"); -const Component = React.Component; -const { connect } = require("react-redux"); -const { ListView, Text, TouchableOpacity, View, StyleSheet } = require("react-native"); -const { Log } = require("lib/log.js"); -const { _ } = require("lib/locale.js"); -const { Checkbox } = require("lib/components/checkbox.js"); -const { reg } = require("lib/registry.js"); -const Note = require("lib/models/Note.js"); -const { time } = require("lib/time-utils.js"); -const { globalStyle, themeStyle } = require("lib/components/global-style.js"); +const React = require('react'); const Component = React.Component; +const { connect } = require('react-redux'); +const { ListView, Text, TouchableOpacity , View, StyleSheet } = require('react-native'); +const { Log } = require('lib/log.js'); +const { _ } = require('lib/locale.js'); +const { Checkbox } = require('lib/components/checkbox.js'); +const { reg } = require('lib/registry.js'); +const Note = require('lib/models/Note.js'); +const { time } = require('lib/time-utils.js'); +const { globalStyle, themeStyle } = require('lib/components/global-style.js'); class NoteItemComponent extends Component { + constructor() { super(); this.styles_ = {}; @@ -18,8 +18,8 @@ class NoteItemComponent extends Component { noteItem_press(noteId) { this.props.dispatch({ - type: "NAV_GO", - routeName: "Note", + type: 'NAV_GO', + routeName: 'Note', noteId: noteId, }); } @@ -32,11 +32,11 @@ class NoteItemComponent extends Component { let styles = { listItem: { - flexDirection: "row", + flexDirection: 'row', //height: 40, borderBottomWidth: 1, borderBottomColor: theme.dividerColor, - alignItems: "flex-start", + alignItems: 'flex-start', paddingLeft: theme.marginLeft, paddingRight: theme.marginRight, paddingTop: theme.itemMarginTop, @@ -69,13 +69,13 @@ class NoteItemComponent extends Component { return this.styles_[this.props.theme]; } - async todoCheckbox_change(checked) { + async todoCheckbox_change(checked) { if (!this.props.note) return; const newNote = { id: this.props.note.id, todo_completed: checked ? time.unixMs() : 0, - }; + } await Note.save(newNote); } @@ -85,13 +85,13 @@ class NoteItemComponent extends Component { if (this.props.noteSelectionEnabled) { this.props.dispatch({ - type: "NOTE_SELECTION_TOGGLE", + type: 'NOTE_SELECTION_TOGGLE', id: this.props.note.id, }); } else { this.props.dispatch({ - type: "NAV_GO", - routeName: "Note", + type: 'NAV_GO', + routeName: 'Note', noteId: this.props.note.id, }); } @@ -101,7 +101,7 @@ class NoteItemComponent extends Component { if (!this.props.note) return; this.props.dispatch({ - type: this.props.noteSelectionEnabled ? "NOTE_SELECTION_TOGGLE" : "NOTE_SELECTION_START", + type: this.props.noteSelectionEnabled ? 'NOTE_SELECTION_TOGGLE' : 'NOTE_SELECTION_START', id: this.props.note.id, }); } @@ -114,7 +114,7 @@ class NoteItemComponent extends Component { const theme = themeStyle(this.props.theme); // IOS: display: none crashes the app - let checkboxStyle = !isTodo ? { display: "none" } : { color: theme.color }; + let checkboxStyle = !isTodo ? { display: 'none' } : { color: theme.color }; if (isTodo) { checkboxStyle.paddingRight = 10; @@ -127,17 +127,21 @@ class NoteItemComponent extends Component { const listItemStyle = isTodo ? this.styles().listItemWithCheckbox : this.styles().listItem; const listItemTextStyle = isTodo ? this.styles().listItemTextWithCheckbox : this.styles().listItemText; - const opacityStyle = isTodo && checkboxChecked ? { opacity: 0.4 } : {}; + const opacityStyle = isTodo && checkboxChecked ? {opacity: 0.4} : {}; const isSelected = this.props.noteSelectionEnabled && this.props.selectedNoteIds.indexOf(note.id) >= 0; const selectionWrapperStyle = isSelected ? this.styles().selectionWrapperSelected : this.styles().selectionWrapper; return ( - this.onPress()} onLongPress={() => this.onLongPress()} activeOpacity={0.5}> - - - - this.todoCheckbox_change(checked)} /> + this.onPress()} onLongPress={() => this.onLongPress() } activeOpacity={0.5}> + + + + this.todoCheckbox_change(checked)} + /> {Note.displayTitle(note)} @@ -145,14 +149,17 @@ class NoteItemComponent extends Component { ); } + } -const NoteItem = connect(state => { - return { - theme: state.settings.theme, - noteSelectionEnabled: state.noteSelectionEnabled, - selectedNoteIds: state.selectedNoteIds, - }; -})(NoteItemComponent); +const NoteItem = connect( + (state) => { + return { + theme: state.settings.theme, + noteSelectionEnabled: state.noteSelectionEnabled, + selectedNoteIds: state.selectedNoteIds, + }; + } +)(NoteItemComponent) -module.exports = { NoteItem }; +module.exports = { NoteItem }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/note-list.js b/ReactNativeClient/lib/components/note-list.js index fc5c47d34..1f668e9f3 100644 --- a/ReactNativeClient/lib/components/note-list.js +++ b/ReactNativeClient/lib/components/note-list.js @@ -1,24 +1,22 @@ -const React = require("react"); -const Component = React.Component; -const { connect } = require("react-redux"); -const { ListView, Text, TouchableHighlight, Switch, View, StyleSheet } = require("react-native"); -const { Log } = require("lib/log.js"); -const { _ } = require("lib/locale.js"); -const { Checkbox } = require("lib/components/checkbox.js"); -const { NoteItem } = require("lib/components/note-item.js"); -const { reg } = require("lib/registry.js"); -const Note = require("lib/models/Note.js"); -const Setting = require("lib/models/Setting.js"); -const { time } = require("lib/time-utils.js"); -const { themeStyle } = require("lib/components/global-style.js"); +const React = require('react'); const Component = React.Component; +const { connect } = require('react-redux'); +const { ListView, Text, TouchableHighlight, Switch, View, StyleSheet } = require('react-native'); +const { Log } = require('lib/log.js'); +const { _ } = require('lib/locale.js'); +const { Checkbox } = require('lib/components/checkbox.js'); +const { NoteItem } = require('lib/components/note-item.js'); +const { reg } = require('lib/registry.js'); +const Note = require('lib/models/Note.js'); +const Setting = require('lib/models/Setting.js'); +const { time } = require('lib/time-utils.js'); +const { themeStyle } = require('lib/components/global-style.js'); class NoteListComponent extends Component { + constructor() { super(); const ds = new ListView.DataSource({ - rowHasChanged: (r1, r2) => { - return r1 !== r2; - }, + rowHasChanged: (r1, r2) => { return r1 !== r2; } }); this.state = { dataSource: ds, @@ -52,8 +50,8 @@ class NoteListComponent extends Component { } filterNotes(notes) { - const todoFilter = "all"; //Setting.value('todoFilter'); - if (todoFilter == "all") return notes; + const todoFilter = 'all'; //Setting.value('todoFilter'); + if (todoFilter == 'all') return notes; const now = time.unixMs(); const maxInterval = 1000 * 60 * 60 * 24; @@ -63,8 +61,8 @@ class NoteListComponent extends Component { for (let i = 0; i < notes.length; i++) { const note = notes[i]; if (note.is_todo) { - if (todoFilter == "recent" && note.user_updated_time < notRecentTime && !!note.todo_completed) continue; - if (todoFilter == "nonCompleted" && !!note.todo_completed) continue; + if (todoFilter == 'recent' && note.user_updated_time < notRecentTime && !!note.todo_completed) continue; + if (todoFilter == 'nonCompleted' && !!note.todo_completed) continue; } output.push(note); } @@ -94,28 +92,30 @@ class NoteListComponent extends Component { if (this.state.dataSource.getRowCount()) { return ( (this.rootRef_ = ref)} + ref={(ref) => this.rootRef_ = ref} dataSource={this.state.dataSource} - renderRow={note => { - return ; + renderRow={(note) => { + return }} enableEmptySections={true} /> ); } else { - const noItemMessage = _("There are currently no notes. Create one by clicking on the (+) button."); - return {noItemMessage}; + const noItemMessage = _('There are currently no notes. Create one by clicking on the (+) button.'); + return {noItemMessage}; } } } -const NoteList = connect(state => { - return { - items: state.notes, - notesSource: state.notesSource, - theme: state.settings.theme, - noteSelectionEnabled: state.noteSelectionEnabled, - }; -})(NoteListComponent); +const NoteList = connect( + (state) => { + return { + items: state.notes, + notesSource: state.notesSource, + theme: state.settings.theme, + noteSelectionEnabled: state.noteSelectionEnabled, + }; + } +)(NoteListComponent) -module.exports = { NoteList }; +module.exports = { NoteList }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/screen-header.js b/ReactNativeClient/lib/components/screen-header.js index 58c907332..af0d123a7 100644 --- a/ReactNativeClient/lib/components/screen-header.js +++ b/ReactNativeClient/lib/components/screen-header.js @@ -1,27 +1,26 @@ -const React = require("react"); -const Component = React.Component; -const { connect } = require("react-redux"); -const { Platform, View, Text, Button, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions } = require("react-native"); -const Icon = require("react-native-vector-icons/Ionicons").default; -const { Log } = require("lib/log.js"); -const { BackButtonService } = require("lib/services/back-button.js"); -const NavService = require("lib/services/NavService.js"); -const { ReportService } = require("lib/services/report.js"); -const { Menu, MenuOptions, MenuOption, MenuTrigger } = require("react-native-popup-menu"); -const { _ } = require("lib/locale.js"); -const Setting = require("lib/models/Setting.js"); -const Note = require("lib/models/Note.js"); -const Folder = require("lib/models/Folder.js"); -const { FileApi } = require("lib/file-api.js"); -const { FileApiDriverOneDrive } = require("lib/file-api-driver-onedrive.js"); -const { reg } = require("lib/registry.js"); -const { themeStyle } = require("lib/components/global-style.js"); -const { ItemList } = require("lib/components/ItemList.js"); -const { Dropdown } = require("lib/components/Dropdown.js"); -const { time } = require("lib/time-utils"); -const RNFS = require("react-native-fs"); -const { dialogs } = require("lib/dialogs.js"); -const DialogBox = require("react-native-dialogbox").default; +const React = require('react'); const Component = React.Component; +const { connect } = require('react-redux'); +const { Platform, View, Text, Button, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions } = require('react-native'); +const Icon = require('react-native-vector-icons/Ionicons').default; +const { Log } = require('lib/log.js'); +const { BackButtonService } = require('lib/services/back-button.js'); +const NavService = require('lib/services/NavService.js'); +const { ReportService } = require('lib/services/report.js'); +const { Menu, MenuOptions, MenuOption, MenuTrigger } = require('react-native-popup-menu'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting.js'); +const Note = require('lib/models/Note.js'); +const Folder = require('lib/models/Folder.js'); +const { FileApi } = require('lib/file-api.js'); +const { FileApiDriverOneDrive } = require('lib/file-api-driver-onedrive.js'); +const { reg } = require('lib/registry.js'); +const { themeStyle } = require('lib/components/global-style.js'); +const { ItemList } = require('lib/components/ItemList.js'); +const { Dropdown } = require('lib/components/Dropdown.js'); +const { time } = require('lib/time-utils'); +const RNFS = require('react-native-fs'); +const { dialogs } = require('lib/dialogs.js'); +const DialogBox = require('react-native-dialogbox').default; // Rather than applying a padding to the whole bar, it is applied to each // individual component (button, picker, etc.) so that the touchable areas @@ -30,13 +29,14 @@ const DialogBox = require("react-native-dialogbox").default; const PADDING_V = 10; class ScreenHeaderComponent extends Component { + constructor() { super(); this.styles_ = {}; } styles() { - const themeId = Setting.value("theme"); + const themeId = Setting.value('theme'); if (this.styles_[themeId]) return this.styles_[themeId]; this.styles_ = {}; @@ -44,21 +44,21 @@ class ScreenHeaderComponent extends Component { let styleObject = { container: { - flexDirection: "column", + flexDirection: 'column', backgroundColor: theme.raisedBackgroundColor, - alignItems: "center", - shadowColor: "#000000", + alignItems: 'center', + shadowColor: '#000000', elevation: 5, - paddingTop: Platform.OS === "ios" ? 15 : 0, // Extra padding for iOS because the top icons are there + paddingTop: Platform.OS === 'ios' ? 15 : 0, // Extra padding for iOS because the top icons are there }, divider: { borderBottomWidth: 1, borderColor: theme.dividerColor, - backgroundColor: "#0000ff", + backgroundColor: "#0000ff" }, sideMenuButton: { flex: 1, - alignItems: "center", + alignItems: 'center', backgroundColor: theme.raisedBackgroundColor, paddingLeft: theme.marginLeft, paddingRight: 5, @@ -76,8 +76,8 @@ class ScreenHeaderComponent extends Component { }, saveButton: { flex: 0, - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', padding: 10, borderWidth: 1, borderColor: theme.raisedHighlightedColor, @@ -85,9 +85,9 @@ class ScreenHeaderComponent extends Component { marginRight: 8, }, saveButtonText: { - textAlignVertical: "center", + textAlignVertical: 'center', color: theme.raisedHighlightedColor, - fontWeight: "bold", + fontWeight: 'bold', }, savedButtonIcon: { fontSize: 20, @@ -103,7 +103,7 @@ class ScreenHeaderComponent extends Component { fontSize: 25, paddingRight: theme.marginRight, color: theme.raisedColor, - fontWeight: "bold", + fontWeight: 'bold', }, contextMenu: { backgroundColor: theme.raisedBackgroundColor, @@ -113,7 +113,7 @@ class ScreenHeaderComponent extends Component { }, contextMenuItemText: { flex: 1, - textAlignVertical: "center", + textAlignVertical: 'center', paddingLeft: theme.marginLeft, paddingRight: theme.marginRight, paddingTop: theme.itemMarginTop, @@ -124,22 +124,22 @@ class ScreenHeaderComponent extends Component { }, titleText: { flex: 1, - textAlignVertical: "center", + textAlignVertical: 'center', marginLeft: 0, color: theme.raisedHighlightedColor, - fontWeight: "bold", + fontWeight: 'bold', fontSize: theme.fontSize, }, warningBox: { backgroundColor: "#ff9900", - flexDirection: "row", + flexDirection: 'row', padding: theme.marginLeft, }, }; styleObject.topIcon = Object.assign({}, theme.icon); styleObject.topIcon.flex = 1; - styleObject.topIcon.textAlignVertical = "center"; + styleObject.topIcon.textAlignVertical = 'center'; styleObject.topIcon.color = theme.raisedColor; styleObject.backButton = Object.assign({}, styleObject.iconButton); @@ -153,7 +153,7 @@ class ScreenHeaderComponent extends Component { } sideMenuButton_press() { - this.props.dispatch({ type: "SIDE_MENU_TOGGLE" }); + this.props.dispatch({ type: 'SIDE_MENU_TOGGLE' }); } async backButton_press() { @@ -161,72 +161,79 @@ class ScreenHeaderComponent extends Component { } searchButton_press() { - NavService.go("Search"); + NavService.go('Search'); } async deleteButton_press() { // Dialog needs to be displayed as a child of the parent component, otherwise // it won't be visible within the header component. - const ok = await dialogs.confirm(this.props.parentComponent, _("Delete these notes?")); + const ok = await dialogs.confirm(this.props.parentComponent, _('Delete these notes?')); if (!ok) return; const noteIds = this.props.selectedNoteIds; - this.props.dispatch({ type: "NOTE_SELECTION_END" }); + this.props.dispatch({ type: 'NOTE_SELECTION_END' }); await Note.batchDelete(noteIds); } menu_select(value) { - if (typeof value == "function") { + if (typeof(value) == 'function') { value(); } } log_press() { - NavService.go("Log"); + NavService.go('Log'); } status_press() { - NavService.go("Status"); + NavService.go('Status'); } config_press() { - NavService.go("Config"); + NavService.go('Config'); } encryptionConfig_press() { - NavService.go("EncryptionConfig"); + NavService.go('EncryptionConfig'); } warningBox_press() { - NavService.go("EncryptionConfig"); + NavService.go('EncryptionConfig'); } async debugReport_press() { const service = new ReportService(); const logItems = await reg.logger().lastEntries(null); - const logItemRows = [["Date", "Level", "Message"]]; + const logItemRows = [ + ['Date','Level','Message'] + ]; for (let i = 0; i < logItems.length; i++) { const item = logItems[i]; - logItemRows.push([time.formatMsToLocal(item.timestamp, "MM-DDTHH:mm:ss"), item.level, item.message]); + logItemRows.push([ + time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss'), + item.level, + item.message + ]); } const logItemCsv = service.csvCreate(logItemRows); - const itemListCsv = await service.basicItemList({ format: "csv" }); - const filePath = RNFS.ExternalDirectoryPath + "/syncReport-" + new Date().getTime() + ".txt"; + const itemListCsv = await service.basicItemList({ format: 'csv' }); + const filePath = RNFS.ExternalDirectoryPath + '/syncReport-' + (new Date()).getTime() + '.txt'; const finalText = [logItemCsv, itemListCsv].join("\n================================================================================\n"); await RNFS.writeFile(filePath, finalText); - alert("Debug report exported to " + filePath); + alert('Debug report exported to ' + filePath); } render() { + function sideMenuButton(styles, onPress) { return ( - + ); @@ -236,7 +243,7 @@ class ScreenHeaderComponent extends Component { return ( - + ); @@ -245,11 +252,13 @@ class ScreenHeaderComponent extends Component { function saveButton(styles, onPress, disabled, show) { if (!show) return null; - const icon = disabled ? : ; + const icon = disabled ? : ; return ( - - {icon} + + + { icon } + ); } @@ -258,7 +267,7 @@ class ScreenHeaderComponent extends Component { return ( - + ); @@ -268,7 +277,7 @@ class ScreenHeaderComponent extends Component { return ( - + ); @@ -278,7 +287,7 @@ class ScreenHeaderComponent extends Component { return ( - + ); @@ -292,74 +301,68 @@ class ScreenHeaderComponent extends Component { let o = this.props.menuOptions[i]; if (o.isDivider) { - menuOptionComponents.push(); + menuOptionComponents.push(); } else { menuOptionComponents.push( - + {o.title} - - ); + ); } } if (this.props.showAdvancedOptions) { if (menuOptionComponents.length) { - menuOptionComponents.push(); + menuOptionComponents.push(); } menuOptionComponents.push( - this.log_press()} key={"menuOption_log"} style={this.styles().contextMenuItem}> - {_("Log")} - - ); + this.log_press()} key={'menuOption_log'} style={this.styles().contextMenuItem}> + {_('Log')} + ); menuOptionComponents.push( - this.status_press()} key={"menuOption_status"} style={this.styles().contextMenuItem}> - {_("Status")} - - ); + this.status_press()} key={'menuOption_status'} style={this.styles().contextMenuItem}> + {_('Status')} + ); - if (Platform.OS === "android") { + if (Platform.OS === 'android') { menuOptionComponents.push( - this.debugReport_press()} key={"menuOption_debugReport"} style={this.styles().contextMenuItem}> - {_("Export Debug Report")} - - ); - } + this.debugReport_press()} key={'menuOption_debugReport'} style={this.styles().contextMenuItem}> + {_('Export Debug Report')} + ); + } } if (menuOptionComponents.length) { - menuOptionComponents.push(); + menuOptionComponents.push(); } menuOptionComponents.push( - this.encryptionConfig_press()} key={"menuOption_encryptionConfig"} style={this.styles().contextMenuItem}> - {_("Encryption Config")} - - ); + this.encryptionConfig_press()} key={'menuOption_encryptionConfig'} style={this.styles().contextMenuItem}> + {_('Encryption Config')} + ); menuOptionComponents.push( - this.config_press()} key={"menuOption_config"} style={this.styles().contextMenuItem}> - {_("Configuration")} - - ); + this.config_press()} key={'menuOption_config'} style={this.styles().contextMenuItem}> + {_('Configuration')} + ); } else { menuOptionComponents.push( - this.deleteButton_press()} key={"menuOption_delete"} style={this.styles().contextMenuItem}> - {_("Delete")} - - ); + this.deleteButton_press()} key={'menuOption_delete'} style={this.styles().contextMenuItem}> + {_('Delete')} + ); } const createTitleComponent = () => { - const themeId = Setting.value("theme"); + const themeId = Setting.value('theme'); const theme = themeStyle(themeId); const folderPickerOptions = this.props.folderPickerOptions; if (folderPickerOptions && folderPickerOptions.enabled) { - const titlePickerItems = mustSelect => { + + const titlePickerItems = (mustSelect) => { let output = []; - if (mustSelect) output.push({ label: _("Move to notebook..."), value: null }); + if (mustSelect) output.push({ label: _('Move to notebook...'), value: null }); for (let i = 0; i < this.props.folders.length; i++) { let f = this.props.folders[i]; output.push({ label: Folder.displayTitle(f), value: f.id }); @@ -370,13 +373,13 @@ class ScreenHeaderComponent extends Component { return a.label.toLowerCase() < b.label.toLowerCase() ? -1 : +1; }); return output; - }; + } return ( 1 ? await dialogs.confirm(this.props.parentComponent, _('Move %d notes to notebook "%s"?', noteIds.length, folder.title)) : true; if (!ok) return; - this.props.dispatch({ type: "NOTE_SELECTION_END" }); + this.props.dispatch({ type: 'NOTE_SELECTION_END' }); for (let i = 0; i < noteIds.length; i++) { await Note.moveToFolder(noteIds[i], folderId); } @@ -415,14 +418,14 @@ class ScreenHeaderComponent extends Component { /> ); } else { - let title = "title" in this.props && this.props.title !== null ? this.props.title : ""; - return {title}; + let title = 'title' in this.props && this.props.title !== null ? this.props.title : ''; + return {title} } - }; + } const warningComp = this.props.showMissingMasterKeyMessage ? ( this.warningBox_press()} activeOpacity={0.8}> - {_("Press to set the decryption password.")} + {_('Press to set the decryption password.')} ) : null; @@ -432,64 +435,58 @@ class ScreenHeaderComponent extends Component { const searchButtonComp = this.props.noteSelectionEnabled ? null : searchButton(this.styles(), () => this.searchButton_press()); const deleteButtonComp = this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press()) : null; const sortButtonComp = this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null; - const windowHeight = Dimensions.get("window").height - 50; + const windowHeight = Dimensions.get('window').height - 50; const menuComp = ( - this.menu_select(value)} style={this.styles().contextMenu}> + this.menu_select(value)} style={this.styles().contextMenu}> - + - {menuOptionComponents} + + { menuOptionComponents } + ); return ( - - - {sideMenuComp} - {backButtonComp} - {saveButton( - this.styles(), - () => { - if (this.props.onSaveButtonPress) this.props.onSaveButtonPress(); - }, - this.props.saveButtonDisabled === true, - this.props.showSaveButton === true - )} - {titleComp} - {searchButtonComp} - {deleteButtonComp} - {sortButtonComp} - {menuComp} + + + { sideMenuComp } + { backButtonComp } + { saveButton(this.styles(), () => { if (this.props.onSaveButtonPress) this.props.onSaveButtonPress() }, this.props.saveButtonDisabled === true, this.props.showSaveButton === true) } + { titleComp } + { searchButtonComp } + { deleteButtonComp } + { sortButtonComp } + { menuComp } - {warningComp} - { - this.dialogbox = dialogbox; - }} - /> + { warningComp } + { this.dialogbox = dialogbox }}/> ); } + } ScreenHeaderComponent.defaultProps = { menuOptions: [], }; -const ScreenHeader = connect(state => { - return { - historyCanGoBack: state.historyCanGoBack, - locale: state.settings.locale, - folders: state.folders, - theme: state.settings.theme, - showAdvancedOptions: state.settings.showAdvancedOptions, - noteSelectionEnabled: state.noteSelectionEnabled, - selectedNoteIds: state.selectedNoteIds, - showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && state.masterKeys.length, - }; -})(ScreenHeaderComponent); +const ScreenHeader = connect( + (state) => { + return { + historyCanGoBack: state.historyCanGoBack, + locale: state.settings.locale, + folders: state.folders, + theme: state.settings.theme, + showAdvancedOptions: state.settings.showAdvancedOptions, + noteSelectionEnabled: state.noteSelectionEnabled, + selectedNoteIds: state.selectedNoteIds, + showMissingMasterKeyMessage: state.notLoadedMasterKeys.length && state.masterKeys.length, + }; + } +)(ScreenHeaderComponent) -module.exports = { ScreenHeader }; +module.exports = { ScreenHeader }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/screens/config.js b/ReactNativeClient/lib/components/screens/config.js index 61026c4b1..4ea8b5ea5 100644 --- a/ReactNativeClient/lib/components/screens/config.js +++ b/ReactNativeClient/lib/components/screens/config.js @@ -1,17 +1,17 @@ -const React = require("react"); -const Component = React.Component; -const { Platform, TouchableOpacity, Linking, View, Switch, Slider, StyleSheet, Text, Button, ScrollView, TextInput } = require("react-native"); -const { connect } = require("react-redux"); -const { ScreenHeader } = require("lib/components/screen-header.js"); -const { _, setLocale } = require("lib/locale.js"); -const { BaseScreenComponent } = require("lib/components/base-screen.js"); -const { Dropdown } = require("lib/components/Dropdown.js"); -const { themeStyle } = require("lib/components/global-style.js"); -const Setting = require("lib/models/Setting.js"); -const shared = require("lib/components/shared/config-shared.js"); -const SyncTargetRegistry = require("lib/SyncTargetRegistry"); +const React = require('react'); const Component = React.Component; +const { Platform, TouchableOpacity, Linking, View, Switch, Slider, StyleSheet, Text, Button, ScrollView, TextInput } = require('react-native'); +const { connect } = require('react-redux'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { _, setLocale } = require('lib/locale.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { Dropdown } = require('lib/components/Dropdown.js'); +const { themeStyle } = require('lib/components/global-style.js'); +const Setting = require('lib/models/Setting.js'); +const shared = require('lib/components/shared/config-shared.js'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry'); class ConfigScreenComponent extends BaseScreenComponent { + static navigationOptions(options) { return { header: null }; } @@ -24,7 +24,7 @@ class ConfigScreenComponent extends BaseScreenComponent { this.checkSyncConfig_ = async () => { await shared.checkSyncConfig(this, this.state.settings); - }; + } this.saveButton_press = () => { return shared.saveSettings(this); @@ -45,13 +45,13 @@ class ConfigScreenComponent extends BaseScreenComponent { let styles = { body: { flex: 1, - justifyContent: "flex-start", - flexDirection: "column", + justifyContent: 'flex-start', + flexDirection: 'column', }, settingContainer: { flex: 1, - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', borderBottomWidth: 1, borderBottomColor: theme.dividerColor, paddingTop: theme.marginTop, @@ -60,7 +60,7 @@ class ConfigScreenComponent extends BaseScreenComponent { paddingRight: theme.marginRight, }, settingText: { - fontWeight: "bold", + fontWeight: 'bold', color: theme.color, fontSize: theme.fontSize, flex: 1, @@ -74,25 +74,25 @@ class ConfigScreenComponent extends BaseScreenComponent { color: theme.color, flex: 1, }, - }; + } - if (Platform.OS === "ios") { + if (Platform.OS === 'ios') { styles.settingControl.borderBottomWidth = 1; styles.settingControl.borderBottomColor = theme.dividerColor; } styles.switchSettingText = Object.assign({}, styles.settingText); - styles.switchSettingText.width = "80%"; + styles.switchSettingText.width = '80%'; styles.switchSettingContainer = Object.assign({}, styles.settingContainer); - styles.switchSettingContainer.flexDirection = "row"; - styles.switchSettingContainer.justifyContent = "space-between"; + styles.switchSettingContainer.flexDirection = 'row'; + styles.switchSettingContainer.justifyContent = 'space-between'; styles.linkText = Object.assign({}, styles.settingText); styles.linkText.borderBottomWidth = 1; styles.linkText.borderBottomColor = theme.color; styles.linkText.flex = 0; - styles.linkText.fontWeight = "normal"; + styles.linkText.fontWeight = 'normal'; styles.switchSettingControl = Object.assign({}, styles.settingControl); delete styles.switchSettingControl.color; @@ -110,7 +110,7 @@ class ConfigScreenComponent extends BaseScreenComponent { const updateSettingValue = (key, value) => { return shared.updateSettingValue(this, key, value); - }; + } const md = Setting.settingMetadata(key); @@ -126,9 +126,7 @@ class ConfigScreenComponent extends BaseScreenComponent { return ( - - {md.label()} - + {md.label()} { - updateSettingValue(key, itemValue); - }} + onValueChange={(itemValue, itemIndex) => { updateSettingValue(key, itemValue); }} /> ); } else if (md.type == Setting.TYPE_BOOL) { return ( - - {md.label()} - - updateSettingValue(key, value)} /> + {md.label()} + updateSettingValue(key, value)} /> ); } else if (md.type == Setting.TYPE_INT) { return ( - - {md.label()} - - updateSettingValue(key, value)} /> + {md.label()} + updateSettingValue(key, value)} /> ); } else if (md.type == Setting.TYPE_STRING) { return ( - - {md.label()} - - updateSettingValue(key, value)} secureTextEntry={!!md.secure} /> + {md.label()} + updateSettingValue(key, value)} secureTextEntry={!!md.secure} /> ); } else { @@ -188,91 +178,77 @@ class ConfigScreenComponent extends BaseScreenComponent { render() { const settings = this.state.settings; - const settingComps = shared.settingsToComponents(this, "mobile", settings); + const settingComps = shared.settingsToComponents(this, 'mobile', settings); - const syncTargetMd = SyncTargetRegistry.idToMetadata(settings["sync.target"]); + const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']); if (syncTargetMd.supportsConfigCheck) { const messages = shared.checkSyncConfigMessages(this); const statusComp = !messages.length ? null : ( - + {messages[0]} - {messages.length >= 1 ? ( - - {messages[1]} - - ) : null} - - ); + {messages.length >= 1 ? ({messages[1]}) : null} + ); settingComps.push( - - - ); @@ -136,45 +136,24 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { const onEnableClick = async () => { try { const password = this.state.passwordPromptAnswer; - if (!password) throw new Error(_("Password cannot be empty")); + if (!password) throw new Error(_('Password cannot be empty')); await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password); this.setState({ passwordPromptShow: false }); } catch (error) { await dialogs.error(this, error.message); } - }; + } return ( - - - {_( - "Enabling encryption means *all* your notes and attachments are going to be re-synchronised and sent encrypted to the sync target. Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below." - )} - - { - this.setState({ passwordPromptAnswer: text }); - }} - /> - - - - - @@ -192,7 +171,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { for (let i = 0; i < masterKeys.length; i++) { const mk = masterKeys[i]; - mkComps.push(this.renderMasterKey(i + 1, mk)); + mkComps.push(this.renderMasterKey(i+1, mk)); const idx = nonExistingMasterKeyIds.indexOf(mk.id); if (idx >= 0) nonExistingMasterKeyIds.splice(idx, 1); @@ -200,10 +179,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { const onToggleButtonClick = async () => { if (this.props.encryptionEnabled) { - const ok = await dialogs.confirm( - this, - _("Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?") - ); + const ok = await dialogs.confirm(this, _('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?')); if (!ok) return; try { @@ -214,7 +190,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { } else { this.setState({ passwordPromptShow: true, - passwordPromptAnswer: "", + passwordPromptAnswer: '', }); return; } @@ -226,37 +202,26 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { const rows = []; for (let i = 0; i < nonExistingMasterKeyIds.length; i++) { const id = nonExistingMasterKeyIds[i]; - rows.push( - - {id} - - ); + rows.push({id}); } nonExistingMasterKeySection = ( - {_("Missing Master Keys")} - - {_( - "The master keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation." - )} - - {rows} + {_('Missing Master Keys')} + {_('The master keys with these IDs are used to encrypt some of your items, however the application does not currently have access to them. It is likely they will eventually be downloaded via synchronisation.')} + {rows} ); } const passwordPromptComp = this.state.passwordPromptShow ? this.passwordPromptComponent() : null; - const toggleButton = !this.state.passwordPromptShow ? ( - - : null; return ( - + + {/* Important: This is a *beta* feature. It has been extensively tested and is already in use by some users, but it is possible that some bugs remain. If you wish to you use it, it is recommended that you keep a backup of your data. The simplest way is to regularly backup your notes from the desktop or terminal application. @@ -264,34 +229,33 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { { Linking.openURL('http://joplin.cozic.net/help/e2ee.html') }}>http://joplin.cozic.net/help/e2ee.html */} - {_("Status")} - {_("Encryption is: %s", this.props.encryptionEnabled ? _("Enabled") : _("Disabled"))} + {_('Status')} + {_('Encryption is: %s', this.props.encryptionEnabled ? _('Enabled') : _('Disabled'))} {decryptedItemsInfo} {toggleButton} {passwordPromptComp} {mkComps} {nonExistingMasterKeySection} - + - { - this.dialogbox = dialogbox; - }} - /> + { this.dialogbox = dialogbox }}/> ); } + } -const EncryptionConfigScreen = connect(state => { - return { - theme: state.settings.theme, - masterKeys: state.masterKeys, - passwords: state.settings["encryption.passwordCache"], - encryptionEnabled: state.settings["encryption.enabled"], - activeMasterKeyId: state.settings["encryption.activeMasterKeyId"], - notLoadedMasterKeys: state.notLoadedMasterKeys, - }; -})(EncryptionConfigScreenComponent); +const EncryptionConfigScreen = connect( + (state) => { + return { + theme: state.settings.theme, + masterKeys: state.masterKeys, + passwords: state.settings['encryption.passwordCache'], + encryptionEnabled: state.settings['encryption.enabled'], + activeMasterKeyId: state.settings['encryption.activeMasterKeyId'], + notLoadedMasterKeys: state.notLoadedMasterKeys, + }; + } +)(EncryptionConfigScreenComponent) -module.exports = { EncryptionConfigScreen }; +module.exports = { EncryptionConfigScreen }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/screens/folder.js b/ReactNativeClient/lib/components/screens/folder.js index 09db8dc07..288f4f4a9 100644 --- a/ReactNativeClient/lib/components/screens/folder.js +++ b/ReactNativeClient/lib/components/screens/folder.js @@ -1,19 +1,19 @@ -const React = require("react"); -const Component = React.Component; -const { View, Button, TextInput, StyleSheet } = require("react-native"); -const { connect } = require("react-redux"); -const { Log } = require("lib/log.js"); -const { ActionButton } = require("lib/components/action-button.js"); -const Folder = require("lib/models/Folder.js"); -const BaseModel = require("lib/BaseModel.js"); -const { ScreenHeader } = require("lib/components/screen-header.js"); -const { reg } = require("lib/registry.js"); -const { BaseScreenComponent } = require("lib/components/base-screen.js"); -const { dialogs } = require("lib/dialogs.js"); -const { themeStyle } = require("lib/components/global-style.js"); -const { _ } = require("lib/locale.js"); +const React = require('react'); const Component = React.Component; +const { View, Button, TextInput, StyleSheet } = require('react-native'); +const { connect } = require('react-redux'); +const { Log } = require('lib/log.js'); +const { ActionButton } = require('lib/components/action-button.js'); +const Folder = require('lib/models/Folder.js'); +const BaseModel = require('lib/BaseModel.js'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { reg } = require('lib/registry.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { dialogs } = require('lib/dialogs.js'); +const { themeStyle } = require('lib/components/global-style.js'); +const { _ } = require('lib/locale.js'); class FolderScreenComponent extends BaseScreenComponent { + static navigationOptions(options) { return { header: null }; } @@ -52,7 +52,7 @@ class FolderScreenComponent extends BaseScreenComponent { lastSavedFolder: Object.assign({}, folder), }); } else { - Folder.load(this.props.folderId).then(folder => { + Folder.load(this.props.folderId).then((folder) => { this.setState({ folder: folder, lastSavedFolder: Object.assign({}, folder), @@ -72,12 +72,12 @@ class FolderScreenComponent extends BaseScreenComponent { this.setState((prevState, props) => { let folder = Object.assign({}, prevState.folder); folder[propName] = propValue; - return { folder: folder }; + return { folder: folder } }); } title_changeText(text) { - this.folderComponent_change("title", text); + this.folderComponent_change('title', text); } async saveFolderButton_press() { @@ -86,7 +86,7 @@ class FolderScreenComponent extends BaseScreenComponent { try { folder = await Folder.save(folder, { userSideValidation: true }); } catch (error) { - dialogs.error(this, _("The notebook could not be saved: %s", error.message)); + dialogs.error(this, _('The notebook could not be saved: %s', error.message)); return; } @@ -96,8 +96,8 @@ class FolderScreenComponent extends BaseScreenComponent { }); this.props.dispatch({ - type: "NAV_GO", - routeName: "Notes", + type: 'NAV_GO', + routeName: 'Notes', folderId: folder.id, }); } @@ -107,23 +107,27 @@ class FolderScreenComponent extends BaseScreenComponent { return ( - this.saveFolderButton_press()} /> - this.title_changeText(text)} /> - { - this.dialogbox = dialogbox; - }} + this.saveFolderButton_press()} /> + this.title_changeText(text)} /> + { this.dialogbox = dialogbox }}/> ); } + } -const FolderScreen = connect(state => { - return { - folderId: state.selectedFolderId, - theme: state.settings.theme, - }; -})(FolderScreenComponent); +const FolderScreen = connect( + (state) => { + return { + folderId: state.selectedFolderId, + theme: state.settings.theme, + }; + } +)(FolderScreenComponent) -module.exports = { FolderScreen }; +module.exports = { FolderScreen }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/screens/log.js b/ReactNativeClient/lib/components/screens/log.js index d412b832b..cf737e146 100644 --- a/ReactNativeClient/lib/components/screens/log.js +++ b/ReactNativeClient/lib/components/screens/log.js @@ -1,17 +1,17 @@ -const React = require("react"); -const Component = React.Component; -const { ListView, View, Text, Button, StyleSheet, Platform } = require("react-native"); -const { connect } = require("react-redux"); -const { Log } = require("lib/log.js"); -const { reg } = require("lib/registry.js"); -const { ScreenHeader } = require("lib/components/screen-header.js"); -const { time } = require("lib/time-utils"); -const { themeStyle } = require("lib/components/global-style.js"); -const { Logger } = require("lib/logger.js"); -const { BaseScreenComponent } = require("lib/components/base-screen.js"); -const { _ } = require("lib/locale.js"); +const React = require('react'); const Component = React.Component; +const { ListView, View, Text, Button, StyleSheet, Platform } = require('react-native'); +const { connect } = require('react-redux'); +const { Log } = require('lib/log.js'); +const { reg } = require('lib/registry.js'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { time } = require('lib/time-utils'); +const { themeStyle } = require('lib/components/global-style.js'); +const { Logger } = require('lib/logger.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { _ } = require('lib/locale.js'); class LogScreenComponent extends BaseScreenComponent { + static navigationOptions(options) { return { header: null }; } @@ -19,9 +19,7 @@ class LogScreenComponent extends BaseScreenComponent { constructor() { super(); const ds = new ListView.DataSource({ - rowHasChanged: (r1, r2) => { - return r1 !== r2; - }, + rowHasChanged: (r1, r2) => { return r1 !== r2; } }); this.state = { dataSource: ds, @@ -38,21 +36,20 @@ class LogScreenComponent extends BaseScreenComponent { let styles = { row: { - flexDirection: "row", + flexDirection: 'row', paddingLeft: 1, paddingRight: 1, - paddingTop: 0, - paddingBottom: 0, + paddingTop:0, + paddingBottom:0, }, rowText: { fontSize: 10, - color: theme.color, + color: theme.color, }, }; - if (Platform.OS !== "ios") { - // Crashes on iOS with error "Unrecognized font family 'monospace'" - styles.rowText.fontFamily = "monospace"; + if (Platform.OS !== 'ios') { // Crashes on iOS with error "Unrecognized font family 'monospace'" + styles.rowText.fontFamily = 'monospace'; } styles.rowTextError = Object.assign({}, styles.rowText); @@ -73,15 +70,12 @@ class LogScreenComponent extends BaseScreenComponent { if (showErrorsOnly === null) showErrorsOnly = this.state.showErrorsOnly; let levels = [Logger.LEVEL_DEBUG, Logger.LEVEL_INFO, Logger.LEVEL_WARN, Logger.LEVEL_ERROR]; - if (showErrorsOnly) levels = [Logger.LEVEL_WARN, Logger.LEVEL_ERROR]; + if (showErrorsOnly) levels = [Logger.LEVEL_WARN, Logger.LEVEL_ERROR] - reg - .logger() - .lastEntries(1000, { levels: levels }) - .then(entries => { - const newDataSource = this.state.dataSource.cloneWithRows(entries); - this.setState({ dataSource: newDataSource }); - }); + reg.logger().lastEntries(1000, { levels: levels }).then((entries) => { + const newDataSource = this.state.dataSource.cloneWithRows(entries); + this.setState({ dataSource: newDataSource }); + }); } toggleErrorsOnly() { @@ -91,50 +85,47 @@ class LogScreenComponent extends BaseScreenComponent { } render() { - let renderRow = item => { + let renderRow = (item) => { let textStyle = this.styles().rowText; if (item.level == Logger.LEVEL_WARN) textStyle = this.styles().rowTextWarn; if (item.level == Logger.LEVEL_ERROR) textStyle = this.styles().rowTextError; - + return ( - {time.formatMsToLocal(item.timestamp, "MM-DDTHH:mm:ss") + ": " + item.message} + {time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss') + ': ' + item.message} ); - }; + } // `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39 return ( - - - - - ); } + } -const OneDriveLoginScreen = connect(state => { - return {}; -})(OneDriveLoginScreenComponent); +const OneDriveLoginScreen = connect( + (state) => { + return {}; + } +)(OneDriveLoginScreenComponent) -module.exports = { OneDriveLoginScreen }; +module.exports = { OneDriveLoginScreen }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/screens/search.js b/ReactNativeClient/lib/components/screens/search.js index 4b9febe0a..6ace75715 100644 --- a/ReactNativeClient/lib/components/screens/search.js +++ b/ReactNativeClient/lib/components/screens/search.js @@ -1,18 +1,18 @@ -const React = require("react"); -const Component = React.Component; -const { ListView, StyleSheet, View, TextInput, FlatList, TouchableHighlight } = require("react-native"); -const { connect } = require("react-redux"); -const { ScreenHeader } = require("lib/components/screen-header.js"); -const Icon = require("react-native-vector-icons/Ionicons").default; -const { _ } = require("lib/locale.js"); -const Note = require("lib/models/Note.js"); -const { NoteItem } = require("lib/components/note-item.js"); -const { BaseScreenComponent } = require("lib/components/base-screen.js"); -const { themeStyle } = require("lib/components/global-style.js"); -const { dialogs } = require("lib/dialogs.js"); -const DialogBox = require("react-native-dialogbox").default; +const React = require('react'); const Component = React.Component; +const { ListView, StyleSheet, View, TextInput, FlatList, TouchableHighlight } = require('react-native'); +const { connect } = require('react-redux'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const Icon = require('react-native-vector-icons/Ionicons').default; +const { _ } = require('lib/locale.js'); +const Note = require('lib/models/Note.js'); +const { NoteItem } = require('lib/components/note-item.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { themeStyle } = require('lib/components/global-style.js'); +const { dialogs } = require('lib/dialogs.js'); +const DialogBox = require('react-native-dialogbox').default; class SearchScreenComponent extends BaseScreenComponent { + static navigationOptions(options) { return { header: null }; } @@ -20,7 +20,7 @@ class SearchScreenComponent extends BaseScreenComponent { constructor() { super(); this.state = { - query: "", + query: '', notes: [], }; this.isMounted_ = false; @@ -38,12 +38,12 @@ class SearchScreenComponent extends BaseScreenComponent { flex: 1, }, searchContainer: { - flexDirection: "row", - alignItems: "center", + flexDirection: 'row', + alignItems: 'center', borderWidth: 1, borderColor: theme.dividerColor, - }, - }; + } + } styles.searchTextInput = Object.assign({}, theme.lineInput); styles.searchTextInput.paddingLeft = theme.marginLeft; @@ -72,7 +72,7 @@ class SearchScreenComponent extends BaseScreenComponent { componentWillReceiveProps(newProps) { let newState = {}; - if ("query" in newProps) newState.query = newProps.query; + if ('query' in newProps) newState.query = newProps.query; if (Object.getOwnPropertyNames(newState).length) { this.setState(newState); @@ -85,15 +85,15 @@ class SearchScreenComponent extends BaseScreenComponent { if (!query) return; this.props.dispatch({ - type: "SEARCH_QUERY", + type: 'SEARCH_QUERY', query: query, }); } clearButton_press() { this.props.dispatch({ - type: "SEARCH_QUERY", - query: "", + type: 'SEARCH_QUERY', + query: '', }); } @@ -102,10 +102,10 @@ class SearchScreenComponent extends BaseScreenComponent { query = query === null ? this.state.query.trim : query.trim(); - let notes = []; + let notes = [] if (query) { - let p = query.split(" "); + let p = query.split(' '); let temp = []; for (let i = 0; i < p.length; i++) { let t = p[i].trim(); @@ -114,7 +114,7 @@ class SearchScreenComponent extends BaseScreenComponent { } notes = await Note.previews(null, { - anywherePattern: "*" + temp.join("*") + "*", + anywherePattern: '*' + temp.join('*') + '*', }); } @@ -135,7 +135,7 @@ class SearchScreenComponent extends BaseScreenComponent { let rootStyle = { flex: 1, backgroundColor: theme.backgroundColor, - }; + } if (!this.props.visible) { rootStyle.flex = 0.001; // This is a bit of a hack but it seems to work fine - it makes the component invisible but without unmounting it @@ -146,7 +146,7 @@ class SearchScreenComponent extends BaseScreenComponent { return ( { - this.searchTextInput_submit(); - }} - onChangeText={text => this.searchTextInput_changeText(text)} + underlineColorAndroid="#ffffff00" + onSubmitEditing={() => { this.searchTextInput_submit() }} + onChangeText={(text) => this.searchTextInput_changeText(text) } value={this.state.query} /> - this.clearButton_press()}> - + this.clearButton_press() }> + - item.id} renderItem={event => } /> + item.id} + renderItem={(event) => } + /> - { - this.dialogbox = dialogbox; - }} - /> + { this.dialogbox = dialogbox }}/> ); } + } -const SearchScreen = connect(state => { - return { - query: state.searchQuery, - theme: state.settings.theme, - noteSelectionEnabled: state.noteSelectionEnabled, - }; -})(SearchScreenComponent); +const SearchScreen = connect( + (state) => { + return { + query: state.searchQuery, + theme: state.settings.theme, + noteSelectionEnabled: state.noteSelectionEnabled, + }; + } +)(SearchScreenComponent) -module.exports = { SearchScreen }; +module.exports = { SearchScreen }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/screens/status.js b/ReactNativeClient/lib/components/screens/status.js index 9c38d63d9..8ce07e0b9 100644 --- a/ReactNativeClient/lib/components/screens/status.js +++ b/ReactNativeClient/lib/components/screens/status.js @@ -1,20 +1,19 @@ -const React = require("react"); -const Component = React.Component; -const { ListView, StyleSheet, View, Text, Button, FlatList } = require("react-native"); -const Setting = require("lib/models/Setting.js"); -const { connect } = require("react-redux"); -const { Log } = require("lib/log.js"); -const { reg } = require("lib/registry.js"); -const { ScreenHeader } = require("lib/components/screen-header.js"); -const { time } = require("lib/time-utils"); -const { Logger } = require("lib/logger.js"); -const BaseItem = require("lib/models/BaseItem.js"); -const { Database } = require("lib/database.js"); -const Folder = require("lib/models/Folder.js"); -const { ReportService } = require("lib/services/report.js"); -const { _ } = require("lib/locale.js"); -const { BaseScreenComponent } = require("lib/components/base-screen.js"); -const { globalStyle, themeStyle } = require("lib/components/global-style.js"); +const React = require('react'); const Component = React.Component; +const { ListView, StyleSheet, View, Text, Button, FlatList } = require('react-native'); +const Setting = require('lib/models/Setting.js'); +const { connect } = require('react-redux'); +const { Log } = require('lib/log.js'); +const { reg } = require('lib/registry.js'); +const { ScreenHeader } = require('lib/components/screen-header.js'); +const { time } = require('lib/time-utils'); +const { Logger } = require('lib/logger.js'); +const BaseItem = require('lib/models/BaseItem.js'); +const { Database } = require('lib/database.js'); +const Folder = require('lib/models/Folder.js'); +const { ReportService } = require('lib/services/report.js'); +const { _ } = require('lib/locale.js'); +const { BaseScreenComponent } = require('lib/components/base-screen.js'); +const { globalStyle, themeStyle } = require('lib/components/global-style.js'); const styles = StyleSheet.create({ body: { @@ -24,6 +23,7 @@ const styles = StyleSheet.create({ }); class StatusScreenComponent extends BaseScreenComponent { + static navigationOptions(options) { return { header: null }; } @@ -41,7 +41,7 @@ class StatusScreenComponent extends BaseScreenComponent { async resfreshScreen() { let service = new ReportService(); - let report = await service.status(Setting.value("sync.target")); + let report = await service.status(Setting.value('sync.target')); this.setState({ report: report }); } @@ -66,54 +66,57 @@ class StatusScreenComponent extends BaseScreenComponent { let section = report[i]; let style = Object.assign({}, baseStyle); - style.fontWeight = "bold"; + style.fontWeight = 'bold'; if (i > 0) style.paddingTop = 20; - lines.push({ key: "section_" + i, isSection: true, text: section.title }); + lines.push({ key: 'section_' + i, isSection: true, text: section.title }); for (let n in section.body) { if (!section.body.hasOwnProperty(n)) continue; style = Object.assign({}, baseStyle); - lines.push({ key: "item_" + i + "_" + n, text: section.body[n] }); + lines.push({ key: 'item_' + i + '_' + n, text: section.body[n] }); } - lines.push({ key: "divider2_" + i, isDivider: true }); + lines.push({ key: 'divider2_' + i, isDivider: true }); } - return ( - { - let style = Object.assign({}, baseStyle); - if (item.isSection === true) { - style.fontWeight = "bold"; - style.marginBottom = 5; - } - if (item.isDivider) { - return ; - } else { - return {item.text}; - } - }} - /> - ); + return ( { + let style = Object.assign({}, baseStyle); + if (item.isSection === true) { + style.fontWeight = 'bold'; + style.marginBottom = 5; + } + if (item.isDivider) { + return (); + } else { + return ({item.text}); + } + }} + />); } let body = renderBody(this.state.report); return ( - - {body} -