From 6a4c6594a7401a00b52c110494d1454c1c27ee30 Mon Sep 17 00:00:00 2001 From: antelle Date: Sun, 24 Jul 2016 20:11:25 +0300 Subject: [PATCH] auto-type popup --- app/scripts/auto-type/index.js | 75 ++++++++++++- app/scripts/comp/alerts.js | 3 + app/scripts/comp/launcher.js | 23 ++-- app/scripts/util/locale.js | 4 + app/scripts/util/theme-changer.js | 6 +- .../views/auto-type/auto-type-popup-view.js | 100 ++++++++++++++++++ app/scripts/views/details/details-view.js | 13 +-- app/styles/areas/_auto-type.scss | 3 + app/styles/main.scss | 1 + app/templates/auto-type/popup.hbs | 13 +++ app/templates/settings/settings-shortcuts.hbs | 2 +- electron/app.js | 7 +- 12 files changed, 221 insertions(+), 29 deletions(-) create mode 100644 app/scripts/views/auto-type/auto-type-popup-view.js create mode 100644 app/styles/areas/_auto-type.scss create mode 100644 app/templates/auto-type/popup.hbs diff --git a/app/scripts/auto-type/index.js b/app/scripts/auto-type/index.js index fbfc619a..d7f956c4 100644 --- a/app/scripts/auto-type/index.js +++ b/app/scripts/auto-type/index.js @@ -1,9 +1,13 @@ 'use strict'; -var AutoTypeParser = require('./auto-type-parser'), +var Backbone = require('backbone'), + AutoTypeParser = require('./auto-type-parser'), AutoTypeHelperFactory = require('./auto-type-helper-factory'), Launcher = require('../comp/launcher'), + Alerts = require('../comp/alerts'), + AutoTypePopupView = require('../views/auto-type/auto-type-popup-view'), Logger = require('../util/logger'), + Locale = require('../util/locale'), Timeouts = require('../const/timeouts'); var logger = new Logger('auto-type'); @@ -14,6 +18,43 @@ var AutoType = { enabled: !!Launcher, + selectEntryView: null, + + init: function() { + Backbone.on('auto-type', this.handleEvent.bind(this)); + }, + + handleEvent: function(e) { + let entry = e && entry || null; + logger.debug('Auto type event', entry); + if (entry) { + this.hideWindow(() => { this.runAndHandleResult(entry); }); + } else { + if (this.selectEntryView) { + return; + } + if (Launcher.isAppFocused()) { + return Alerts.error({ + header: Locale.autoTypeError, + body: Locale.autoTypeErrorGlobal, + skipIfAlertDisplayed: true + }); + } + this.selectEntryAndRun(); + } + }, + + runAndHandleResult: function(entry) { + this.run(entry, err => { + if (err) { + Alerts.error({ + header: Locale.autoTypeError, + body: Locale.autoTypeErrorGeneric.replace('{}', err.toString()) + }); + } + }); + }, + run: function(entry, callback) { var sequence = entry.getEffectiveAutoTypeSeq(); logger.debug('Start', sequence); @@ -83,7 +124,8 @@ var AutoType = { hideWindow: function(callback) { logger.debug('Hide window'); - if (Launcher.hideWindowIfActive()) { + if (Launcher.isAppFocused()) { + Launcher.hideApp(); setTimeout(callback, Timeouts.AutoTypeAfterHide); } else { callback(); @@ -98,9 +140,36 @@ var AutoType = { } else { logger.debug('Window title', title, url); } - return callback(err, title); + return callback(err, title, url); }); + }, + + selectEntryAndRun: function() { + this.getActiveWindowTitle((e, title, url) => { + let entries = this.getMatchingEntries(title, url); + if (entries.length === 1) { + this.runAndHandleResult(entries[0]); + return; + } + Launcher.hideMainWindow(); + this.selectEntryView = new AutoTypePopupView().render(); + this.selectEntryView.on('closed', e => { + Launcher.unhideMainWindow(); + Launcher.hideApp(); + logger.debug('Popup closed', e.result); + this.selectEntryView = null; + // this.hideWindow(() => { /* this.runAndHandleResult(e.result); */ }); + }); + }); + }, + + getMatchingEntries: function() { + return []; } }; +if (AutoType.enabled) { + AutoType.init(); +} + module.exports = AutoType; diff --git a/app/scripts/comp/alerts.js b/app/scripts/comp/alerts.js index 32a39c44..fc868c63 100644 --- a/app/scripts/comp/alerts.js +++ b/app/scripts/comp/alerts.js @@ -14,6 +14,9 @@ var Alerts = { }, alert: function(config) { + if (config.skipIfAlertDisplayed && Alerts.alertDisplayed) { + return null; + } Alerts.alertDisplayed = true; var view = new ModalView({ model: config }); view.render(); diff --git a/app/scripts/comp/launcher.js b/app/scripts/comp/launcher.js index 315a3cf3..01a1239a 100644 --- a/app/scripts/comp/launcher.js +++ b/app/scripts/comp/launcher.js @@ -125,19 +125,22 @@ if (window.process && window.process.versions && window.process.versions.electro openWindow: function(opts) { return this.remoteApp().openWindow(opts); }, - hideWindowIfActive: function() { + hideApp: function() { var app = this.remoteApp(); - var win = app.getMainWindow(); - var visible = win.isVisible(), focused = win.isFocused(); - if (!visible || !focused) { - return false; - } - if (process.platform === 'darwin') { - app.hide(); + if (this.canMinimize()) { + app.getMainWindow().minimize(); } else { - win.minimize(); + app.hide(); } - return true; + }, + hideMainWindow: function() { + this.remoteApp().getMainWindow().hide(); + }, + unhideMainWindow: function() { + this.remoteApp().getMainWindow().showInactive(); + }, + isAppFocused: function() { + return !!this.electron().remote.BrowserWindow.getFocusedWindow(); }, spawn: function(config) { var ts = logger.ts(); diff --git a/app/scripts/util/locale.js b/app/scripts/util/locale.js index e7b9b0f9..0e6a6ecc 100644 --- a/app/scripts/util/locale.js +++ b/app/scripts/util/locale.js @@ -253,6 +253,10 @@ var Locale = { autoTypeModifiers: 'Modifier keys', autoTypeKeys: 'Keys', autoTypeLink: 'more...', + autoTypeError: 'Auto-type error', + autoTypeErrorGeneric: 'There was an error performing auto-type: {}', + autoTypeErrorGlobal: 'To use system-wide shortcut, please focus the app where you want to type your password', + autoTypePopup: 'KeeWeb: Auto Type', appSecWarn: 'Not Secure!', appSecWarnBody1: 'You have loaded this app with insecure connection. ' + diff --git a/app/scripts/util/theme-changer.js b/app/scripts/util/theme-changer.js index 6f64f0a3..417fab6c 100644 --- a/app/scripts/util/theme-changer.js +++ b/app/scripts/util/theme-changer.js @@ -16,13 +16,17 @@ var ThemeChanger = { document.body.classList.remove(cls); } }); - document.body.classList.add('th-' + theme); + document.body.classList.add(this.getThemeClass(theme)); var metaThemeColor = document.head.querySelector('meta[name=theme-color]'); if (metaThemeColor) { metaThemeColor.content = window.getComputedStyle(document.body).backgroundColor; } }, + getThemeClass: function(theme) { + return 'th-' + theme; + }, + setFontSize: function(fontSize) { document.documentElement.style.fontSize = fontSize ? (12 + fontSize * 2) + 'px' : ''; } diff --git a/app/scripts/views/auto-type/auto-type-popup-view.js b/app/scripts/views/auto-type/auto-type-popup-view.js new file mode 100644 index 00000000..ba811502 --- /dev/null +++ b/app/scripts/views/auto-type/auto-type-popup-view.js @@ -0,0 +1,100 @@ +'use strict'; + +const Backbone = require('backbone'); +const Launcher = require('../../comp/launcher'); +const Locale = require('../../util/locale'); +const ThemeChanger = require('../../util/theme-changer'); +const Keys = require('../../const/keys'); +const AppSettingsModel = require('../../models/app-settings-model'); + +class AutoTypePopupView { + constructor() { + this.template = require('templates/auto-type/popup.hbs'); + this.popupWindow = null; + this.result = null; + } + + render() { + let themeClass = ThemeChanger.getThemeClass(AppSettingsModel.instance.get('theme')); + let styleSheet = document.styleSheets[0]; + let css = styleSheet.ownerNode.textContent; + if (!css) { + // dev mode, external stylesheet + css = _.map(styleSheet.rules, rule => rule.cssText).join('\n'); + } + let html = this.template({ + themeClass: themeClass, + css: css + }); + + this.popupWindow = Launcher.openWindow({ + show: false, + width: 600, + minWidth: 600, + height: 300, + minHeight: 300, + minimizable: false, + maximizable: false, + alwaysOnTop: true, + fullscreenable: false, + title: Locale.autoTypePopup, + modal: true + // icon: TODO + }); + this.popupWindow.on('closed', () => this.remove()); + this.popupWindow.on('blur', () => this.popupWindow.close()); + this.popupWindow.on('ready-to-show', () => this.popupWindow.show()); + this.popupWindow.loadURL('data:text/html;charset=utf-8,' + encodeURI(html)); + this.popupWindow.webContents.executeJavaScript('(' + this.init.toString() + ')()'); + + Backbone.on('auto-type-popup-keydown', e => this.keydown(e)); + Backbone.on('auto-type-popup-keypress', e => this.keypress(e)); + Backbone.on('auto-type-popup-select', e => this.select(e)); + + return this; + } + + remove() { + if (this.popupWindow) { + this.popupWindow = null; + Backbone.off('auto-type-popup-keydown'); + Backbone.off('auto-type-popup-keypress'); + Backbone.off('auto-type-popup-select'); + this.trigger('closed', { result: this.result }); + } + } + + init() { + // note: this function will be executed in popup + function emitBackboneEvent(name, arg) { + window.require('electron').remote.app.emitBackboneEvent('auto-type-popup-' + name, arg); + } + document.body.addEventListener('keydown', e => { + emitBackboneEvent('keydown', {keyCode: e.keyCode}); + }); + document.body.addEventListener('keypress', e => { + emitBackboneEvent('keypress', {keyCode: e.keyCode, text: e.key}); + }); + } + + keydown(e) { + if (e.keyCode === Keys.DOM_VK_ESCAPE) { + return this.popupWindow.close(); + } else if (e.keyCode === Keys.DOM_VK_ENTER || e.keyCode === Keys.DOM_VK_RETURN) { + this.result = 'entry'; + return this.popupWindow.close(); + } + } + + keypress(e) { + // TODO + } + + select(e) { + this.result = e.result; + } +} + +_.extend(AutoTypePopupView.prototype, Backbone.Events); + +module.exports = AutoTypePopupView; diff --git a/app/scripts/views/details/details-view.js b/app/scripts/views/details/details-view.js index 77db6997..d37b1609 100644 --- a/app/scripts/views/details/details-view.js +++ b/app/scripts/views/details/details-view.js @@ -67,7 +67,6 @@ var DetailsView = Backbone.View.extend({ this.initScroll(); this.listenTo(Backbone, 'select-entry', this.showEntry); this.listenTo(Backbone, 'copy-password', this.copyPassword); - this.listenTo(Backbone, 'auto-type', this.autoTypeGlobal); this.listenTo(Backbone, 'copy-user', this.copyUserName); this.listenTo(Backbone, 'copy-url', this.copyUrl); this.listenTo(Backbone, 'toggle-settings', this.settingsToggled); @@ -794,17 +793,7 @@ var DetailsView = Backbone.View.extend({ }, autoType: function() { - var entry = this.model; - // AutoType.getActiveWindowTitle(function() { - // console.log(arguments); - // }); - AutoType.hideWindow(() => { - AutoType.run(entry); - }); - }, - - autoTypeGlobal: function() { - // TODO + Backbone.emit('auto-type', { entry: this.model }); } }); diff --git a/app/styles/areas/_auto-type.scss b/app/styles/areas/_auto-type.scss new file mode 100644 index 00000000..b0f60e75 --- /dev/null +++ b/app/styles/areas/_auto-type.scss @@ -0,0 +1,3 @@ +.at-popup { + padding: $base-padding; +} diff --git a/app/styles/main.scss b/app/styles/main.scss index 67c09360..7bab87a9 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -22,6 +22,7 @@ @import "common/tip"; @import "areas/app"; +@import "areas/auto-type"; @import "areas/details"; @import "areas/footer"; @import "areas/grp"; diff --git a/app/templates/auto-type/popup.hbs b/app/templates/auto-type/popup.hbs new file mode 100644 index 00000000..4d0c9171 --- /dev/null +++ b/app/templates/auto-type/popup.hbs @@ -0,0 +1,13 @@ + + + + + {{res 'autoTypePopup'}} + + + +Hello! +{{res 'autoTypePopup'}} + + + diff --git a/app/templates/settings/settings-shortcuts.hbs b/app/templates/settings/settings-shortcuts.hbs index d3d9e84d..8af4d5bd 100644 --- a/app/templates/settings/settings-shortcuts.hbs +++ b/app/templates/settings/settings-shortcuts.hbs @@ -24,6 +24,6 @@
{{{global}}}C {{res 'setShCopyPassGlobal'}}
{{{global}}}B {{res 'setShCopyUserGlobal'}}
{{{global}}}U {{res 'setShCopyUrlGlobal'}}
- {{!--
{{{global}}}T {{res 'setShAutoTypeGlobal'}}
--}} +
{{{global}}}T {{res 'setShAutoTypeGlobal'}}
{{/if}} diff --git a/electron/app.js b/electron/app.js index ece8da56..446270c5 100644 --- a/electron/app.js +++ b/electron/app.js @@ -88,6 +88,7 @@ app.minimizeApp = function () { app.getMainWindow = function () { return mainWindow; }; +app.emitBackboneEvent = emitBackboneEvent; function checkSingleInstance() { var shouldQuit = app.makeSingleInstance((/* commandLine, workingDirectory */) => { @@ -112,6 +113,7 @@ function createMainWindow() { }); setMenu(); mainWindow.loadURL('file://' + htmlPath); + mainWindow.setContentProtection(true); mainWindow.webContents.on('dom-ready', () => { setTimeout(() => { mainWindow.show(); @@ -215,8 +217,9 @@ function restoreMainWindowPosition() { }); } -function emitBackboneEvent(e) { - mainWindow.webContents.executeJavaScript('Backbone.trigger("' + e + '");'); +function emitBackboneEvent(e, arg) { + arg = JSON.stringify(arg); + mainWindow.webContents.executeJavaScript(`Backbone.trigger('${e}', ${arg});`); } function setMenu() {