From e6e1a19477ec66416792037300b57b2d55f3718a Mon Sep 17 00:00:00 2001 From: RainLoop Team Date: Mon, 22 Aug 2016 00:30:34 +0300 Subject: [PATCH] Add cmd interface --- dev/App/Abstract.js | 9 +- dev/App/Admin.js | 4 +- dev/App/User.js | 118 +++--- dev/Common/Cmd.js | 337 +++++++++++++++++- dev/Common/Translator.js | 2 +- dev/Common/Utils.js | 5 +- dev/External/ko.js | 21 +- dev/Html/Cmds/Done.html | 1 + dev/Html/Cmds/Error.html | 1 + dev/Html/Cmds/Help.html | 1 + dev/Html/Cmds/LangEmpty.html | 1 + dev/Html/Cmds/Main.html | 1 + dev/Html/Cmds/ThemeEmpty.html | 1 + dev/Html/Cmds/Version.html | 1 + dev/Styles/Cmd.less | 67 +++- dev/View/User/Login.js | 4 +- gulpfile.js | 7 +- .../0.0.0/app/libraries/RainLoop/Actions.php | 3 + .../libraries/RainLoop/Config/Application.php | 1 + .../0.0.0/app/templates/Views/Common/Cmd.html | 10 + .../app/templates/Views/Common/PopupsAsk.html | 52 +-- webpack.config.builder.js | 4 +- 22 files changed, 540 insertions(+), 111 deletions(-) create mode 100644 dev/Html/Cmds/Done.html create mode 100644 dev/Html/Cmds/Error.html create mode 100644 dev/Html/Cmds/Help.html create mode 100644 dev/Html/Cmds/LangEmpty.html create mode 100644 dev/Html/Cmds/Main.html create mode 100644 dev/Html/Cmds/ThemeEmpty.html create mode 100644 dev/Html/Cmds/Version.html create mode 100644 rainloop/v/0.0.0/app/templates/Views/Common/Cmd.html diff --git a/dev/App/Abstract.js b/dev/App/Abstract.js index fd22b0553..7a2a86e52 100644 --- a/dev/App/Abstract.js +++ b/dev/App/Abstract.js @@ -115,9 +115,12 @@ class AbstractApp extends AbstractBoot detectDropdownVisibility(); }); - key('ctrl+shift+`', KeyState.All, () => { - toggleCmd(); - }); + if (Settings.appSettingsGet('allowCmdInterface')) + { + key('ctrl+shift+`', KeyState.All, () => { + toggleCmd(); + }); + } } remote() { diff --git a/dev/App/Admin.js b/dev/App/Admin.js index f4d57f85f..ea5a7b4e9 100644 --- a/dev/App/Admin.js +++ b/dev/App/Admin.js @@ -4,7 +4,7 @@ import _ from '_'; import ko from 'ko'; import progressJs from 'progressJs'; -import * as Links from 'Common/Links'; +import {root} from 'Common/Links'; import {getNotification} from 'Common/Translator'; import {StorageResultType, Notification} from 'Common/Enums'; import {pInt, isNormal, isArray, inArray, isUnd} from 'Common/Utils'; @@ -225,7 +225,7 @@ class AdminApp extends AbstractApp if (!Settings.appSettingsGet('allowAdminPanel')) { routeOff(); - setHash(Links.root(), true); + setHash(root(), true); routeOff(); _.defer(() => { diff --git a/dev/App/User.js b/dev/App/User.js index 5a77cfbfc..113742a3b 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -22,11 +22,24 @@ import { } from 'Common/Globals'; import {UNUSED_OPTION_VALUE} from 'Common/Consts'; -import * as Plugins from 'Common/Plugins'; -import * as Links from 'Common/Links'; +import {runHook} from 'Common/Plugins'; +import {momentNowUnix, reload as momentReload} from 'Common/Momentor'; + +import { + initMessageFlagsFromCache, setFolderHash, getFolderHash, getFolderInboxName, + getFolderFromCacheList, clearMessageFlagsFromCacheByFolder, + storeMessageFlagsToCacheBySetAction, + storeMessageFlagsToCacheByFolderAndUid +} from 'Common/Cache'; + +import { + userBackground, mailBox, root, + openPgpWorkerJs, openPgpJs, + socialGoogle, socialTwitter, socialFacebook +} from 'Common/Links'; + import * as Events from 'Common/Events'; -import * as Momentor from 'Common/Momentor'; -import * as Cache from 'Common/Cache'; + import {getNotification, i18n} from 'Common/Translator'; import SocialStore from 'Stores/Social'; @@ -114,7 +127,7 @@ class AppUser extends AbstractApp _.delay(() => { $('#rl-bg') .attr('style', 'background-image: none !important;') - .backstretch(Links.userBackground(Settings.settingsGet('UserBackgroundHash')), { + .backstretch(userBackground(Settings.settingsGet('UserBackgroundHash')), { fade: bAnimationSupported ? 1000 : 0, centeredX: true, centeredY: true @@ -143,9 +156,9 @@ class AppUser extends AbstractApp reloadFlagsCurrentMessageListAndMessageFromCache() { _.each(MessageStore.messageList(), (message) => { - Cache.initMessageFlagsFromCache(message); + initMessageFlagsFromCache(message); }); - Cache.initMessageFlagsFromCache(MessageStore.message()); + initMessageFlagsFromCache(MessageStore.message()); } /** @@ -159,7 +172,7 @@ class AppUser extends AbstractApp if (bDropCurrenFolderCache) { - Cache.setFolderHash(FolderStore.currentFolderFullNameRaw(), ''); + setFolderHash(FolderStore.currentFolderFullNameRaw(), ''); } if (bDropPagePosition) @@ -168,7 +181,7 @@ class AppUser extends AbstractApp MessageStore.messageListPageBeforeThread(1); iOffset = 0; - setHash(Links.mailBox( + setHash(mailBox( FolderStore.currentFolderFullNameHash(), MessageStore.messageListPage(), MessageStore.messageListSearch(), @@ -205,7 +218,7 @@ class AppUser extends AbstractApp } recacheInboxMessageList() { - Remote.messageList(noop, Cache.getFolderInboxName(), 0, SettingsStore.messagesPerPage(), '', '', true); + Remote.messageList(noop, getFolderInboxName(), 0, SettingsStore.messagesPerPage(), '', '', true); } /** @@ -246,7 +259,7 @@ class AppUser extends AbstractApp const isSpam = sSpamFolder === item.To, isTrash = sTrashFolder === item.To, - isHam = !isSpam && sSpamFolder === item.From && Cache.getFolderInboxName() === item.To; + isHam = !isSpam && sSpamFolder === item.From && getFolderInboxName() === item.To; Remote.messagesMove(this.moveOrDeleteResponseHelper, item.From, item.To, item.Uid, isSpam ? 'SPAM' : (isHam ? 'HAM' : ''), isSpam || isTrash); @@ -294,11 +307,11 @@ class AppUser extends AbstractApp { if (oData && isArray(oData.Result) && 2 === oData.Result.length) { - Cache.setFolderHash(oData.Result[0], oData.Result[1]); + setFolderHash(oData.Result[0], oData.Result[1]); } else { - Cache.setFolderHash(FolderStore.currentFolderFullNameRaw(), ''); + setFolderHash(FolderStore.currentFolderFullNameRaw(), ''); if (oData && -1 < inArray(oData.ErrorCode, [Notification.CantMoveMessage, Notification.CantCopyMessage])) @@ -336,18 +349,18 @@ class AppUser extends AbstractApp switch (iDeleteType) { case FolderType.Spam: - oMoveFolder = Cache.getFolderFromCacheList(FolderStore.spamFolder()); + oMoveFolder = getFolderFromCacheList(FolderStore.spamFolder()); nSetSystemFoldersNotification = SetSystemFoldersNotification.Spam; break; case FolderType.NotSpam: - oMoveFolder = Cache.getFolderFromCacheList(Cache.getFolderInboxName()); + oMoveFolder = getFolderFromCacheList(getFolderInboxName()); break; case FolderType.Trash: - oMoveFolder = Cache.getFolderFromCacheList(FolderStore.trashFolder()); + oMoveFolder = getFolderFromCacheList(FolderStore.trashFolder()); nSetSystemFoldersNotification = SetSystemFoldersNotification.Trash; break; case FolderType.Archive: - oMoveFolder = Cache.getFolderFromCacheList(FolderStore.archiveFolder()); + oMoveFolder = getFolderFromCacheList(FolderStore.archiveFolder()); nSetSystemFoldersNotification = SetSystemFoldersNotification.Archive; break; // no default @@ -394,8 +407,8 @@ class AppUser extends AbstractApp if (sFromFolderFullNameRaw !== sToFolderFullNameRaw && isArray(aUidForMove) && 0 < aUidForMove.length) { const - oFromFolder = Cache.getFolderFromCacheList(sFromFolderFullNameRaw), - oToFolder = Cache.getFolderFromCacheList(sToFolderFullNameRaw); + oFromFolder = getFolderFromCacheList(sFromFolderFullNameRaw), + oToFolder = getFolderFromCacheList(sToFolderFullNameRaw); if (oFromFolder && oToFolder) { @@ -649,14 +662,14 @@ class AppUser extends AbstractApp check = false, unreadCountChange = false; - const folderFromCache = Cache.getFolderFromCacheList(data.Result.Folder); + const folderFromCache = getFolderFromCacheList(data.Result.Folder); if (folderFromCache) { - folderFromCache.interval = Momentor.momentNowUnix(); + folderFromCache.interval = momentNowUnix(); if (data.Result.Hash) { - Cache.setFolderHash(data.Result.Folder, data.Result.Hash); + setFolderHash(data.Result.Folder, data.Result.Hash); } if (isNormal(data.Result.MessageCount)) @@ -676,7 +689,7 @@ class AppUser extends AbstractApp if (unreadCountChange) { - Cache.clearMessageFlagsFromCacheByFolder(folderFromCache.fullNameRaw); + clearMessageFlagsFromCacheByFolder(folderFromCache.fullNameRaw); } if (data.Result.Flags) @@ -687,7 +700,7 @@ class AppUser extends AbstractApp { check = true; const flags = data.Result.Flags[uid]; - Cache.storeMessageFlagsToCacheByFolderAndUid(folderFromCache.fullNameRaw, uid.toString(), [ + storeMessageFlagsToCacheByFolderAndUid(folderFromCache.fullNameRaw, uid.toString(), [ !flags.IsSeen, !!flags.IsFlagged, !!flags.IsAnswered, !!flags.IsForwarded, !!flags.IsReadReceipt ]); } @@ -701,14 +714,14 @@ class AppUser extends AbstractApp MessageStore.initUidNextAndNewMessages(folderFromCache.fullNameRaw, data.Result.UidNext, data.Result.NewMessages); - const hash = Cache.getFolderHash(data.Result.Folder); + const hash = getFolderHash(data.Result.Folder); if (data.Result.Hash !== hash || '' === hash || unreadCountChange) { if (folderFromCache.fullNameRaw === FolderStore.currentFolderFullNameRaw()) { this.reloadMessageList(); } - else if (Cache.getFolderInboxName() === folderFromCache.fullNameRaw) + else if (getFolderInboxName() === folderFromCache.fullNameRaw) { this.recacheInboxMessageList(); } @@ -733,12 +746,12 @@ class AppUser extends AbstractApp { if (oData && oData.Result && oData.Result.List && isNonEmptyArray(oData.Result.List)) { - const utc = Momentor.momentNowUnix(); + const utc = momentNowUnix(); _.each(oData.Result.List, (oItem) => { const - hash = Cache.getFolderHash(oItem.Folder), - folder = Cache.getFolderFromCacheList(oItem.Folder); + hash = getFolderHash(oItem.Folder), + folder = getFolderFromCacheList(oItem.Folder); let unreadCountChange = false; @@ -748,7 +761,7 @@ class AppUser extends AbstractApp if (oItem.Hash) { - Cache.setFolderHash(oItem.Folder, oItem.Hash); + setFolderHash(oItem.Folder, oItem.Hash); } if (isNormal(oItem.MessageCount)) @@ -768,7 +781,7 @@ class AppUser extends AbstractApp if (unreadCountChange) { - Cache.clearMessageFlagsFromCacheByFolder(folder.fullNameRaw); + clearMessageFlagsFromCacheByFolder(folder.fullNameRaw); } if (oItem.Hash !== hash || '' === hash) @@ -829,11 +842,10 @@ class AppUser extends AbstractApp case MessageSetAction.SetSeen: _.each(rootUids, (sSubUid) => { - alreadyUnread += Cache.storeMessageFlagsToCacheBySetAction( - sFolderFullNameRaw, sSubUid, iSetAction); + alreadyUnread += storeMessageFlagsToCacheBySetAction(sFolderFullNameRaw, sSubUid, iSetAction); }); - folder = Cache.getFolderFromCacheList(sFolderFullNameRaw); + folder = getFolderFromCacheList(sFolderFullNameRaw); if (folder) { folder.messageCountUnread(folder.messageCountUnread() - alreadyUnread); @@ -845,11 +857,11 @@ class AppUser extends AbstractApp case MessageSetAction.UnsetSeen: _.each(rootUids, (sSubUid) => { - alreadyUnread += Cache.storeMessageFlagsToCacheBySetAction( + alreadyUnread += storeMessageFlagsToCacheBySetAction( sFolderFullNameRaw, sSubUid, iSetAction); }); - folder = Cache.getFolderFromCacheList(sFolderFullNameRaw); + folder = getFolderFromCacheList(sFolderFullNameRaw); if (folder) { folder.messageCountUnread(folder.messageCountUnread() - alreadyUnread + rootUids.length); @@ -861,8 +873,7 @@ class AppUser extends AbstractApp case MessageSetAction.SetFlag: _.each(rootUids, (sSubUid) => { - Cache.storeMessageFlagsToCacheBySetAction( - sFolderFullNameRaw, sSubUid, iSetAction); + storeMessageFlagsToCacheBySetAction(sFolderFullNameRaw, sSubUid, iSetAction); }); Remote.messageSetFlagged(noop, sFolderFullNameRaw, rootUids, true); @@ -871,8 +882,7 @@ class AppUser extends AbstractApp case MessageSetAction.UnsetFlag: _.each(rootUids, (sSubUid) => { - Cache.storeMessageFlagsToCacheBySetAction( - sFolderFullNameRaw, sSubUid, iSetAction); + storeMessageFlagsToCacheBySetAction(sFolderFullNameRaw, sSubUid, iSetAction); }); Remote.messageSetFlagged(noop, sFolderFullNameRaw, rootUids, false); @@ -886,15 +896,15 @@ class AppUser extends AbstractApp } googleConnect() { - window.open(Links.socialGoogle(), 'Google', 'left=200,top=100,width=650,height=600,menubar=no,status=no,resizable=yes,scrollbars=yes'); + window.open(socialGoogle(), 'Google', 'left=200,top=100,width=650,height=600,menubar=no,status=no,resizable=yes,scrollbars=yes'); } twitterConnect() { - window.open(Links.socialTwitter(), 'Twitter', 'left=200,top=100,width=650,height=350,menubar=no,status=no,resizable=yes,scrollbars=yes'); + window.open(socialTwitter(), 'Twitter', 'left=200,top=100,width=650,height=350,menubar=no,status=no,resizable=yes,scrollbars=yes'); } facebookConnect() { - window.open(Links.socialFacebook(), 'Facebook', 'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'); + window.open(socialFacebook(), 'Facebook', 'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'); } /** @@ -1221,13 +1231,13 @@ class AppUser extends AbstractApp LoginUserScreen ]); - Plugins.runHook('rl-start-login-screens'); + runHook('rl-start-login-screens'); Events.pub('rl.bootstart-login-screens'); } else { routeOff(); - setHash(Links.root(), true); + setHash(root(), true); routeOff(); _.defer(() => { @@ -1300,7 +1310,7 @@ class AppUser extends AbstractApp if ('' !== startupUrl) { routeOff(); - setHash(Links.root(startupUrl), true); + setHash(root(startupUrl), true); routeOn(); } @@ -1314,7 +1324,7 @@ class AppUser extends AbstractApp { try { - PgpStore.openpgp.initWorker({path: Links.openPgpWorkerJs()}); + PgpStore.openpgp.initWorker({path: openPgpWorkerJs()}); } catch (e) { @@ -1336,7 +1346,7 @@ class AppUser extends AbstractApp } else { - jassl(Links.openPgpJs()).then(() => { + jassl(openPgpJs()).then(() => { if (window.openpgp) { openpgpCallback(window.openpgp); @@ -1360,10 +1370,10 @@ class AppUser extends AbstractApp this.socialUsers(true); } - Events.sub('interval.2m', () => this.folderInformation(Cache.getFolderInboxName())); + Events.sub('interval.2m', () => this.folderInformation(getFolderInboxName())); Events.sub('interval.3m', () => { const sF = FolderStore.currentFolderFullNameRaw(); - if (Cache.getFolderInboxName() !== sF) + if (getFolderInboxName() !== sF) { this.folderInformation(sF); } @@ -1385,7 +1395,7 @@ class AppUser extends AbstractApp _.delay(() => { const sF = FolderStore.currentFolderFullNameRaw(); - if (Cache.getFolderInboxName() !== sF) + if (getFolderInboxName() !== sF) { this.folderInformation(sF); } @@ -1396,7 +1406,7 @@ class AppUser extends AbstractApp Events.sub('rl.auto-logout', () => this.logout()); - Plugins.runHook('rl-start-user-screens'); + runHook('rl-start-user-screens'); Events.pub('rl.bootstart-user-screens'); if (Settings.settingsGet('WelcomePageUrl')) @@ -1478,9 +1488,9 @@ class AppUser extends AbstractApp }; } - Events.sub('interval.1m', () => Momentor.reload()); + Events.sub('interval.1m', () => momentReload()); - Plugins.runHook('rl-start-screens'); + runHook('rl-start-screens'); Events.pub('rl.bootstart-end'); } } diff --git a/dev/Common/Cmd.js b/dev/Common/Cmd.js index 5eff7107d..b3595dd39 100644 --- a/dev/Common/Cmd.js +++ b/dev/Common/Cmd.js @@ -1,12 +1,315 @@ -// import window from 'window'; +import window from 'window'; import $ from '$'; import _ from '_'; -import {$body} from 'Common/Globals'; +import ko from 'ko'; +import {$html, $body} from 'Common/Globals'; +import {EventKeyCode} from 'Common/Enums'; +import {trim, inArray, changeTheme} from 'Common/Utils'; +import {reload as translatorReload} from 'Common/Translator'; + +import * as Settings from 'Storage/Settings'; + +import ThemeStore from 'Stores/Theme'; +import LanguageStore from 'Stores/Language'; let - opened = false, - cmdDom = null; + cmdDom = null, + contoller = null; + +/** + * @params {string} cmd + * @returns {string} + */ +function cmdError(cmd) { + return require('Html/Cmds/Error.html').replace('{{ cmd }}', cmd); +} + +/** + * @returns {string} + */ +function cmdClear(dom) { + dom.find('.rl-cmd-history-data').empty(); + return ''; +} + +/** + * @returns {string} + */ +function cmdHelp(cmds) { + return require('Html/Cmds/Help.html').replace('{{ commands }}', cmds.join(' ')); +} + +/** + * @returns {string} + */ +function cmdGlass() { + $html.toggleClass('glass', !$html.hasClass('glass')); + return ''; +} + +/** + * @returns {string} + */ +function cmdTheme(param, themes) { + if (param && -1 < inArray(param, themes)) + { + changeTheme(param); + return ''; + } + return require('Html/Cmds/ThemeEmpty.html').replace('{{ themes }}', themes.join(', ')); +} + +/** + * @returns {string} + */ +function cmdLang(param, isAdmin, langs) { + if (param && -1 < inArray(param, langs)) + { + translatorReload(isAdmin, param); + return ''; + } + return require('Html/Cmds/LangEmpty.html').replace('{{ langs }}', langs.join(', ')); +} + +/** + * @returns {string} + */ +function cmdVersion() { + return require('Html/Cmds/Version.html').replace('{{ version }}', + Settings.appSettingsGet('version') + ' (' + Settings.appSettingsGet('appVersionType') + ')'); +} + +class CmdContoller +{ + constructor(dom) + { + this.dom = dom; + + this.opened = ko.observable(false); + this.cmd = ko.observable(''); + this.focused = ko.observable(false); + + this.themes = ThemeStore.themes; + + this.cmdHistory = []; + this.cmdHistoryShift = 0; + + this.cmdHelper = ko.observable(''); + + this.cmds = ['help', 'version', 'glass', 'clear', 'theme', 'lang']; + this.cmdsWithParameters = ['theme', 'lang']; + + this.isAdmin = Settings.appSettingsGet('admin'); + } + + runCmd(cmd, params, isTab) { + + let + result = '', + values = null; + + this.cmdHelper(''); + + if (isTab) + { + switch (cmd) { + case 'lang': + values = (this.isAdmin ? LanguageStore.languagesAdmin() : LanguageStore.languages()) + .filter((line) => 0 === line.lastIndexOf(params, 0)); + break; + case 'theme': + values = ThemeStore.themes().filter((line) => 0 === line.lastIndexOf(params, 0)); + break; + default: + break; + } + + if (cmd && values) + { + if (1 === values.length && values[0]) + { + this.cmd(cmd + ' ' + values[0]); + } + else if (1 < values.length && values[0] && values[1]) + { + let + sub = '', + index = 0; + + const + list = values[0].split(''), + len = list.length; + + for (; index < len; index++) + { + if (values[1][index] === list[index]) + { + sub += list[index]; + } + else + { + break; + } + } + + if (sub) + { + this.cmdHelper('[' + values.join(', ') + ']'); + this.cmd(cmd + ' ' + sub); + } + } + } + + return ''; + } + + switch (cmd) { + case 'hi': + result = 'hello'; + break; + case '?': + case 'ls': + case 'help': + result = cmdHelp(this.cmds); + break; + case 'glass': + result = cmdGlass(); + break; + case 'v': + case 'version': + result = cmdVersion(); + break; + case 'clear': + result = cmdClear(this.dom); + break; + case 'theme': + result = cmdTheme(params, ThemeStore.themes()); + break; + case 'lang': + result = cmdLang(params, this.isAdmin, this.isAdmin ? LanguageStore.languagesAdmin() : LanguageStore.languages()); + break; + default: + result = cmdError(cmd); + break; + } + + return result; + } + + onCmd(isTab) { + const + cmdLine = trim(this.cmd()).replace(/[\s]+/, ' '), + cmdParts = cmdLine.replace().split(/[\s]+/), + cmd = cmdParts.shift(); + + if (isTab) + { + if (-1 < inArray(cmd, this.cmds)) + { + const result = this.runCmd(cmd, cmdParts.join(' '), true); + if (result) + { + this.cmd(result); + } + } + else + { + const values = this.cmds.filter((line) => line !== cmd && 0 === line.lastIndexOf(cmd, 0)); + if (1 === values.length && values[0]) + { + this.cmd(values[0] + (-1 < inArray(values[0], this.cmdsWithParameters) ? ' ' : '')); + } + } + } + else + { + this.cmdHistory.unshift(cmdLine); + this.cmdHistory = _.uniq(this.cmdHistory); + this.cmdHistoryShift = 0; + + const + result = this.runCmd(cmd, cmdParts.join(' '), false), + h = this.dom.find('.rl-cmd-history-data'); + + if (h && h[0]) + { + h.append($('
').html(require('Html/Cmds/Main.html').replace('{{ cmd }}', cmdLine))); + if (result) + { + h.append($('
').html(result)); + } + + _.delay(() => { + this.dom.find('.rl-cmd-history').scrollTop(h.height()); + }, 50); + } + } + } + + onEsc() { + this.opened(false); + return false; + } + + onTab() { + this.onCmd(true); + return false; + } + + onEnter() { + this.onCmd(false); + this.cmd(''); + return false; + } + + onKeyDown(event) { + if (event && event.keyCode && + !event.metaKey && !event.ctrlKey && !event.shiftKey && 0 < this.cmdHistory.length) + { + const code = window.parseInt(event.keyCode, 10); + if (EventKeyCode.Up === code || EventKeyCode.Down === code) + { + if (this.cmdHistory[this.cmdHistoryShift]) + { + this.cmd(this.cmdHistory[this.cmdHistoryShift]); + if (EventKeyCode.Up === code) + { + this.cmdHistoryShift += 1; + } + else if (EventKeyCode.Down === code) + { + this.cmdHistoryShift -= 1; + } + } + else + { + this.cmdHistoryShift = 0; + } + + return false; + } + } + + return true; + } +} + +/** + * @returns {void} + */ +export function bind(dom) +{ + if (!contoller) + { + contoller = new CmdContoller(dom); + + ko.applyBindingAccessorsToNode(dom[0], { + translatorInit: true, + template: () => ({name: 'Cmd'}) + }, contoller); + } +} /** * @returns {void} @@ -15,8 +318,10 @@ function init() { if (null === cmdDom) { - cmdDom = $('
'); + cmdDom = $('
'); cmdDom.appendTo($body); + + bind(cmdDom); } } @@ -25,11 +330,21 @@ function init() */ export function toggle() { - init(); + if (Settings.appSettingsGet('allowCmdInterface')) + { + init(); - opened = !opened; - - _.delay(() => { - cmdDom.toggleClass('opened', opened); - }, 50); + _.delay(() => { + if (contoller) + { + contoller.opened(!contoller.opened()); + if (contoller.opened()) + { + _.delay(() => { + contoller.focused(true); + }, 50); + } + } + }, 50); + } } diff --git a/dev/Common/Translator.js b/dev/Common/Translator.js index 3e6fce605..87a5f0cb0 100644 --- a/dev/Common/Translator.js +++ b/dev/Common/Translator.js @@ -312,7 +312,7 @@ export function reload(admin, language) reloadData(); - const isRtl = -1 < inArray(language, ['ar', 'ar_sa', 'he', 'he_he', 'ur', 'ur_ir']); + const isRtl = -1 < inArray((language || '').toLowerCase(), ['ar', 'ar_sa', 'he', 'he_he', 'ur', 'ur_ir']); $html .removeClass('rl-changing-language') diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index 0b90f8442..8acc237a9 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -1239,10 +1239,10 @@ let /** * @param {string} value - * @param {function} themeTrigger + * @param {function=} themeTrigger = noop * @returns {void} */ -export function changeTheme(value, themeTrigger) +export function changeTheme(value, themeTrigger = noop) { const themeLink = $('#app-theme-link'), @@ -1272,6 +1272,7 @@ export function changeTheme(value, themeTrigger) } window.clearTimeout(__themeTimer); + themeTrigger(SaveSettingsStep.Animate); if (__themeAjax && __themeAjax.abort) diff --git a/dev/External/ko.js b/dev/External/ko.js index 4e57f33f2..816dd17bb 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -382,6 +382,23 @@ ko.bindingHandlers.resizecrop = { } }; +ko.bindingHandlers.onKeyDown = { + init: (element, fValueAccessor, fAllBindingsAccessor, viewModel) => { + $(element).on('keydown.koOnKeyDown', (event) => { + if (event) + { + return fValueAccessor().call(viewModel, event); + } + + return true; + }); + + ko.utils.domNodeDisposal.addDisposeCallback(element, () => { + $(element).off('keydown.koOnKeyDown'); + }); + } +}; + ko.bindingHandlers.onEnter = { init: (element, fValueAccessor, fAllBindingsAccessor, viewModel) => { $(element).on('keypress.koOnEnter', (event) => { @@ -431,7 +448,7 @@ ko.bindingHandlers.onTab = { ko.bindingHandlers.onEsc = { init: (element, fValueAccessor, fAllBindingsAccessor, viewModel) => { - $(element).on('keypress.koOnEsc', (event) => { + $(element).on('keyup.koOnEsc', (event) => { if (event && 27 === window.parseInt(event.keyCode, 10)) { $(element).trigger('change'); @@ -440,7 +457,7 @@ ko.bindingHandlers.onEsc = { }); ko.utils.domNodeDisposal.addDisposeCallback(element, () => { - $(element).off('keypress.koOnEsc'); + $(element).off('keyup.koOnEsc'); }); } }; diff --git a/dev/Html/Cmds/Done.html b/dev/Html/Cmds/Done.html new file mode 100644 index 000000000..d0e065e74 --- /dev/null +++ b/dev/Html/Cmds/Done.html @@ -0,0 +1 @@ +done! \ No newline at end of file diff --git a/dev/Html/Cmds/Error.html b/dev/Html/Cmds/Error.html new file mode 100644 index 000000000..4b9ac34ff --- /dev/null +++ b/dev/Html/Cmds/Error.html @@ -0,0 +1 @@ +Command not found: {{ cmd }} \ No newline at end of file diff --git a/dev/Html/Cmds/Help.html b/dev/Html/Cmds/Help.html new file mode 100644 index 000000000..6e4756b64 --- /dev/null +++ b/dev/Html/Cmds/Help.html @@ -0,0 +1 @@ + commands: {{ commands }} \ No newline at end of file diff --git a/dev/Html/Cmds/LangEmpty.html b/dev/Html/Cmds/LangEmpty.html new file mode 100644 index 000000000..ffca3b4e7 --- /dev/null +++ b/dev/Html/Cmds/LangEmpty.html @@ -0,0 +1 @@ +lang [{{ langs }}] \ No newline at end of file diff --git a/dev/Html/Cmds/Main.html b/dev/Html/Cmds/Main.html new file mode 100644 index 000000000..4839ba5d1 --- /dev/null +++ b/dev/Html/Cmds/Main.html @@ -0,0 +1 @@ +> {{ cmd }} \ No newline at end of file diff --git a/dev/Html/Cmds/ThemeEmpty.html b/dev/Html/Cmds/ThemeEmpty.html new file mode 100644 index 000000000..99e6b3408 --- /dev/null +++ b/dev/Html/Cmds/ThemeEmpty.html @@ -0,0 +1 @@ +theme [{{ themes }}] \ No newline at end of file diff --git a/dev/Html/Cmds/Version.html b/dev/Html/Cmds/Version.html new file mode 100644 index 000000000..6a2aec8a5 --- /dev/null +++ b/dev/Html/Cmds/Version.html @@ -0,0 +1 @@ + version: {{ version }} \ No newline at end of file diff --git a/dev/Styles/Cmd.less b/dev/Styles/Cmd.less index abbb76310..bad041b83 100644 --- a/dev/Styles/Cmd.less +++ b/dev/Styles/Cmd.less @@ -7,12 +7,75 @@ top: auto; height: 0; - background: rgba(0, 0, 0, .8); + background: rgba(0, 0, 0, .85); border-top: 1px solid #000; + overflow: hidden; + font-family: monospace; + .transition(height 0.1s ease-out); &.opened { - height: 200px; + height: 300px; + } + + .rl-cmd-clr-error { + color: #CD3131; + } + + .rl-cmd-clr-info { + color: #BFBF00; + } + + .rl-cmd-clr-success { + color: #31FF40; + } + + .rl-cmd-wrp { + position: relative; + height: 100%; + } + + .rl-cmd-input-helper { + color: #666; + } + + .rl-cmd-input-prefix { + color: #31FF40; + display: inline-block; + } + + .rl-cmd-input-wrp { + position: absolute; + bottom: 0; + left: 10px; + right: 10px; + } + + .rl-cmd-input { + background: none; + border: none; + color: white; + display: inline-block; + width: calc(~'100% - 30px'); + font-family: monospace; + + &:focus{ + background: none; + border: none; + } + } + + .rl-cmd-history { + color: white; + font-family: monospace; + position: absolute; + top: 10px; + bottom: 60px; + left: 10px; + right: -30px; + overflow: hidden; + overflow-x: hidden; + overflow-y: auto; } } diff --git a/dev/View/User/Login.js b/dev/View/User/Login.js index 084198e3e..9d9f6b7d9 100644 --- a/dev/View/User/Login.js +++ b/dev/View/User/Login.js @@ -431,11 +431,11 @@ class LoginUserView extends AbstractViewNext _.delay(() => { - LanguageStore.language.subscribe((sValue) => { + LanguageStore.language.subscribe((value) => { this.langRequest(true); - translatorReload(false, sValue).then(() => { + translatorReload(false, value).then(() => { this.langRequest(false); this.bSendLanguage = true; }, () => { diff --git a/gulpfile.js b/gulpfile.js index 8a3c861fe..0447f683b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -491,10 +491,9 @@ gulp.task('rainloop:setup', ['rainloop:copy'], function() { fs.writeFileSync(dist + 'data/EMPTY', versionFull); fs.writeFileSync(dist + 'index.php', fs.readFileSync('index.php', 'utf8') - .replace('\'APP_VERSION\', \'0.0.0\'', '\'APP_VERSION\', \'' + versionFull + '\'')); - - fs.writeFileSync(dist + 'index.php', fs.readFileSync('index.php', 'utf8') - .replace('\'APP_VERSION_TYPE\', \'source\'', '\'APP_VERSION\', \'' + (cfg.community ? 'community' : 'standard') + '\'')); + .replace('\'APP_VERSION\', \'0.0.0\'', '\'APP_VERSION\', \'' + versionFull + '\'') + .replace('\'APP_VERSION_TYPE\', \'source\'', '\'APP_VERSION_TYPE\', \'' + (cfg.community ? 'community' : 'standard') + '\'') + ); fs.writeFileSync(dist + 'rainloop/v/' + versionFull + '/index.php.root', fs.readFileSync(dist + 'index.php')); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 33f463bfd..3c8d180f9 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -1441,6 +1441,7 @@ class Actions return \array_merge(array( 'version' => APP_VERSION, + 'admin' => $bAdmin, 'mobile' => $bMobile, 'mobileDevice' => $bMobileDevice, 'webPath' => \RainLoop\Utils::WebPath(), @@ -1461,11 +1462,13 @@ class Actions 'materialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true), 'folderSpecLimit' => (int) $oConfig->Get('labs', 'folders_spec_limit', 50), 'faviconStatus' => (bool) $oConfig->Get('labs', 'favicon_status', true), + 'allowCmdInterface' => (bool) $oConfig->Get('labs', 'allow_cmd', false), 'useNativeScrollbars' => (bool) $oConfig->Get('interface', 'use_native_scrollbars', false), 'listPermanentFiltered' => '' !== \trim(\RainLoop\Api::Config()->Get('labs', 'imap_message_list_permanent_filter', '')), 'themes' => $this->GetThemes($bMobile, false), 'languages' => $this->GetLanguages(false), 'languagesAdmin' => $this->GetLanguages(true), + 'appVersionType' => APP_VERSION_TYPE, 'attachmentsActions' => $aAttachmentsActions ), $bAdmin ? array( 'adminHostUse' => '' !== $oConfig->Get('security', 'admin_panel_host', ''), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 47ec8cd4e..aa1f4392f 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -446,6 +446,7 @@ Enables caching in the system'), 'startup_url' => array(''), 'nice_social_redirect' => array(true), 'strict_html_parser' => array(false), + 'allow_cmd' => array(false), 'dev_email' => array(''), 'dev_password' => array('') ), diff --git a/rainloop/v/0.0.0/app/templates/Views/Common/Cmd.html b/rainloop/v/0.0.0/app/templates/Views/Common/Cmd.html new file mode 100644 index 000000000..9cd70b5f3 --- /dev/null +++ b/rainloop/v/0.0.0/app/templates/Views/Common/Cmd.html @@ -0,0 +1,10 @@ +
+
+
+
+
+
#
+ +
+
+
\ No newline at end of file diff --git a/rainloop/v/0.0.0/app/templates/Views/Common/PopupsAsk.html b/rainloop/v/0.0.0/app/templates/Views/Common/PopupsAsk.html index 4054b3b61..93df1f523 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Common/PopupsAsk.html +++ b/rainloop/v/0.0.0/app/templates/Views/Common/PopupsAsk.html @@ -1,26 +1,26 @@ -
- -
+
+ +
diff --git a/webpack.config.builder.js b/webpack.config.builder.js index a0ae112fe..72c0ae5e4 100644 --- a/webpack.config.builder.js +++ b/webpack.config.builder.js @@ -65,7 +65,7 @@ module.exports = function(publicPath, pro, es6) { "transform-es2015-function-name", // ["transform-es2015-arrow-functions")], "transform-es2015-block-scoped-functions", -// ["transform-es2015-classes", loose], +// ["transform-es2015-classes", {loose: true}], // "transform-es2015-object-super", "transform-es2015-shorthand-properties", "transform-es2015-duplicate-keys", @@ -104,7 +104,7 @@ module.exports = function(publicPath, pro, es6) { // other 'transform-runtime', -'transform-decorators-legacy' +'transform-decorators-legacy' // -> transform-decorators // from stage-2 ] } },