mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Merge branch 'master' into plugin-2fa
This commit is contained in:
commit
2064c1613b
41 changed files with 261 additions and 167 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import 'External/User/ko';
|
||||
|
||||
import { isArray, isNonEmptyArray, pInt, pString } from 'Common/Utils';
|
||||
import { isArray, arrayLength, pInt, pString } from 'Common/Utils';
|
||||
import { isPosNumeric, delegateRunOnDestroy, mailToHelper } from 'Common/UtilsUser';
|
||||
|
||||
import {
|
||||
|
|
@ -261,7 +261,7 @@ class AppUser extends AbstractApp {
|
|||
setFolderHash(FolderUserStore.currentFolderFullNameRaw(), '');
|
||||
alert(getNotification(iError));
|
||||
} else if (FolderUserStore.currentFolder()) {
|
||||
if (isArray(oData.Result) && 2 === oData.Result.length) {
|
||||
if (2 === arrayLength(oData.Result)) {
|
||||
setFolderHash(oData.Result[0], oData.Result[1]);
|
||||
} else {
|
||||
setFolderHash(FolderUserStore.currentFolderFullNameRaw(), '');
|
||||
|
|
@ -348,7 +348,7 @@ class AppUser extends AbstractApp {
|
|||
* @param {boolean=} bCopy = false
|
||||
*/
|
||||
moveMessagesToFolder(sFromFolderFullNameRaw, aUidForMove, sToFolderFullNameRaw, bCopy) {
|
||||
if (sFromFolderFullNameRaw !== sToFolderFullNameRaw && isArray(aUidForMove) && aUidForMove.length) {
|
||||
if (sFromFolderFullNameRaw !== sToFolderFullNameRaw && arrayLength(aUidForMove)) {
|
||||
const oFromFolder = getFolderFromCacheList(sFromFolderFullNameRaw),
|
||||
oToFolder = getFolderFromCacheList(sToFolderFullNameRaw);
|
||||
|
||||
|
|
@ -519,10 +519,9 @@ class AppUser extends AbstractApp {
|
|||
Remote.quota((iError, data) => {
|
||||
if (
|
||||
!iError &&
|
||||
isArray(data.Result) &&
|
||||
1 < data.Result.length &&
|
||||
isPosNumeric(data.Result[0], true) &&
|
||||
isPosNumeric(data.Result[1], true)
|
||||
1 < arrayLength(data.Result) &&
|
||||
isPosNumeric(data.Result[0]) &&
|
||||
isPosNumeric(data.Result[1])
|
||||
) {
|
||||
QuotaUserStore.populateData(pInt(data.Result[1]), pInt(data.Result[0]));
|
||||
}
|
||||
|
|
@ -614,9 +613,9 @@ class AppUser extends AbstractApp {
|
|||
*/
|
||||
folderInformationMultiply(boot = false) {
|
||||
const folders = FolderUserStore.getNextFolderNames(refreshFolders);
|
||||
if (isNonEmptyArray(folders)) {
|
||||
if (arrayLength(folders)) {
|
||||
Remote.folderInformationMultiply((iError, oData) => {
|
||||
if (!iError && isNonEmptyArray(oData.Result.List)) {
|
||||
if (!iError && arrayLength(oData.Result.List)) {
|
||||
const utc = Date.now();
|
||||
oData.Result.List.forEach(item => {
|
||||
const hash = getFolderHash(item.Folder),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { MessageSetAction } from 'Common/EnumsUser';
|
||||
import { isNonEmptyArray, pInt } from 'Common/Utils';
|
||||
import { arrayLength, pInt } from 'Common/Utils';
|
||||
|
||||
let FOLDERS_CACHE = {},
|
||||
FOLDERS_NAME_CACHE = {},
|
||||
|
|
@ -255,7 +255,7 @@ export class MessageFlagsCache
|
|||
* @param {Array} flags
|
||||
*/
|
||||
static storeByFolderAndUid(folder, uid, flags) {
|
||||
if (isNonEmptyArray(flags)) {
|
||||
if (arrayLength(flags)) {
|
||||
this.setFor(folder, uid, flags);
|
||||
}
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ export class MessageFlagsCache
|
|||
let unread = 0;
|
||||
const flags = this.getFor(folder, uid);
|
||||
|
||||
if (isNonEmptyArray(flags)) {
|
||||
if (arrayLength(flags)) {
|
||||
if (flags[0]) {
|
||||
unread = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint key-spacing: 0 */
|
||||
/* eslint quote-props: 0 */
|
||||
|
||||
import { isNonEmptyArray } from 'Common/Utils';
|
||||
import { arrayLength } from 'Common/Utils';
|
||||
|
||||
const
|
||||
cache = {},
|
||||
|
|
@ -250,7 +250,7 @@ export const FileInfo = {
|
|||
* @returns {string}
|
||||
*/
|
||||
getCombinedIconClass: data => {
|
||||
if (isNonEmptyArray(data)) {
|
||||
if (arrayLength(data)) {
|
||||
let icons = data
|
||||
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1]) : '')
|
||||
.validUnique();
|
||||
|
|
|
|||
|
|
@ -40,19 +40,18 @@ export const keyScope = (()=>{
|
|||
keyScope(keyScopeFake);
|
||||
}
|
||||
});
|
||||
return ko.computed({
|
||||
read: () => keyScopeFake,
|
||||
write: value => {
|
||||
return value => {
|
||||
if (value) {
|
||||
if (Scope.Menu !== value) {
|
||||
keyScopeFake = value;
|
||||
if (dropdownVisibility()) {
|
||||
value = Scope.Menu;
|
||||
}
|
||||
}
|
||||
|
||||
keyScopeReal(value);
|
||||
shortcuts.setScope(value);
|
||||
} else {
|
||||
return keyScopeFake;
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
keyScopeReal.subscribe(value => shortcuts.setScope(value));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { doc, elementById } from 'Common/Globals';
|
|||
|
||||
export const
|
||||
isArray = Array.isArray,
|
||||
isNonEmptyArray = array => isArray(array) && array.length,
|
||||
arrayLength = array => isArray(array) && array.length,
|
||||
isFunction = v => typeof v === 'function';
|
||||
|
||||
/**
|
||||
|
|
@ -116,7 +116,7 @@ export function changeTheme(value, themeTrigger = ()=>{}) {
|
|||
}
|
||||
rl.fetchJSON(url, init)
|
||||
.then(data => {
|
||||
if (data && isArray(data) && 2 === data.length) {
|
||||
if (2 === arrayLength(data)) {
|
||||
themeStyle.textContent = data[1];
|
||||
themeStyle.dataset.href = url;
|
||||
themeStyle.dataset.theme = data[0];
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import { createElement } from 'Common/Globals';
|
|||
* @param {boolean=} includeZero = true
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isPosNumeric(value, includeZero = true) {
|
||||
return null != value && (includeZero ? /^[0-9]*$/ : /^[1-9]+[0-9]*$/).test(value.toString());
|
||||
export function isPosNumeric(value) {
|
||||
return null != value && /^[0-9]*$/.test(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -395,13 +395,13 @@ export function computedPaginatorHelper(koCurrentPage, koPageCount) {
|
|||
if (3 === prev) {
|
||||
fAdd(2, false);
|
||||
} else if (3 < prev) {
|
||||
fAdd(Math.round((prev - 1) / 2), false, '...');
|
||||
fAdd(Math.round((prev - 1) / 2), false, '…');
|
||||
}
|
||||
|
||||
if (pageCount - 2 === next) {
|
||||
fAdd(pageCount - 1, true);
|
||||
} else if (pageCount - 2 > next) {
|
||||
fAdd(Math.round((pageCount + next) / 2), true, '...');
|
||||
fAdd(Math.round((pageCount + next) / 2), true, '…');
|
||||
}
|
||||
|
||||
// first and last
|
||||
|
|
@ -434,22 +434,20 @@ export function mailToHelper(mailToUrl) {
|
|||
mailToUrl = mailToUrl.toString().substr(7);
|
||||
|
||||
let to = [],
|
||||
cc = null,
|
||||
bcc = null,
|
||||
params = {};
|
||||
|
||||
const email = mailToUrl.replace(/\?.+$/, ''),
|
||||
query = mailToUrl.replace(/^[^?]*\?/, '');
|
||||
query = mailToUrl.replace(/^[^?]*\?/, ''),
|
||||
toEmailModel = value => null != value ? EmailModel.parseEmailLine(decodeURIComponent(value)) : null;
|
||||
|
||||
query.split('&').forEach(temp => {
|
||||
temp = temp.split('=');
|
||||
params[decodeURIComponent(temp[0])] = decodeURIComponent(temp[1]);
|
||||
});
|
||||
|
||||
if (undefined !== params.to) {
|
||||
to = EmailModel.parseEmailLine(decodeURIComponent(email + ',' + params.to));
|
||||
if (null != params.to) {
|
||||
to = Object.values(
|
||||
to.reduce((result, value) => {
|
||||
toEmailModel(email + ',' + params.to).reduce((result, value) => {
|
||||
if (value) {
|
||||
if (result[value.email]) {
|
||||
if (!result[value.email].name) {
|
||||
|
|
@ -466,20 +464,12 @@ export function mailToHelper(mailToUrl) {
|
|||
to = EmailModel.parseEmailLine(email);
|
||||
}
|
||||
|
||||
if (undefined !== params.cc) {
|
||||
cc = EmailModel.parseEmailLine(decodeURIComponent(params.cc));
|
||||
}
|
||||
|
||||
if (undefined !== params.bcc) {
|
||||
bcc = EmailModel.parseEmailLine(decodeURIComponent(params.bcc));
|
||||
}
|
||||
|
||||
showMessageComposer([
|
||||
ComposeType.Empty,
|
||||
null,
|
||||
to,
|
||||
cc,
|
||||
bcc,
|
||||
toEmailModel(params.cc),
|
||||
toEmailModel(params.bcc),
|
||||
null == params.subject ? null : decodeURIComponent(params.subject),
|
||||
null == params.body ? null : plainToHtml(decodeURIComponent(params.body))
|
||||
]);
|
||||
|
|
|
|||
4
dev/External/ko.js
vendored
4
dev/External/ko.js
vendored
|
|
@ -1,7 +1,7 @@
|
|||
import { i18nToNodes } from 'Common/Translator';
|
||||
import { doc, createElement } from 'Common/Globals';
|
||||
import { SaveSettingsStep } from 'Common/Enums';
|
||||
import { isNonEmptyArray, isFunction } from 'Common/Utils';
|
||||
import { arrayLength, isFunction } from 'Common/Utils';
|
||||
|
||||
const
|
||||
koValue = value => !ko.isObservable(value) && isFunction(value) ? value() : ko.unwrap(value);
|
||||
|
|
@ -136,7 +136,7 @@ ko.extenders.limitedList = (target, limitedList) => {
|
|||
const currentValue = ko.unwrap(target),
|
||||
list = ko.unwrap(limitedList);
|
||||
|
||||
if (isNonEmptyArray(list)) {
|
||||
if (arrayLength(list)) {
|
||||
if (list.includes(newValue)) {
|
||||
target(newValue);
|
||||
} else if (list.includes(currentValue, list)) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { isArray, isNonEmptyArray } from 'Common/Utils';
|
||||
import { isArray, arrayLength } from 'Common/Utils';
|
||||
|
||||
export class AbstractScreen {
|
||||
constructor(screenName, viewModels = []) {
|
||||
|
|
@ -42,7 +42,7 @@ export class AbstractScreen {
|
|||
if (!this.__started) {
|
||||
this.__started = true;
|
||||
const routes = this.routes();
|
||||
if (isNonEmptyArray(routes)) {
|
||||
if (arrayLength(routes)) {
|
||||
let route = new Crossroads(),
|
||||
fMatcher = (this.onRoute || (()=>{})).bind(this);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { doc, $htmlCL } from 'Common/Globals';
|
||||
import { isNonEmptyArray, isFunction } from 'Common/Utils';
|
||||
import { arrayLength, isFunction } from 'Common/Utils';
|
||||
|
||||
let currentScreen = null,
|
||||
defaultScreenName = '';
|
||||
|
|
@ -227,7 +227,7 @@ function screenOnRoute(screenName, subPart) {
|
|||
currentScreen.onHide && currentScreen.onHide();
|
||||
currentScreen.onHideWithDelay && setTimeout(()=>currentScreen.onHideWithDelay(), 500);
|
||||
|
||||
if (isNonEmptyArray(currentScreen.viewModels)) {
|
||||
if (arrayLength(currentScreen.viewModels)) {
|
||||
currentScreen.viewModels.forEach(ViewModelClass => {
|
||||
if (
|
||||
ViewModelClass.__vm &&
|
||||
|
|
@ -251,7 +251,7 @@ function screenOnRoute(screenName, subPart) {
|
|||
if (currentScreen && !isSameScreen) {
|
||||
currentScreen.onShow && currentScreen.onShow();
|
||||
|
||||
if (isNonEmptyArray(currentScreen.viewModels)) {
|
||||
if (arrayLength(currentScreen.viewModels)) {
|
||||
currentScreen.viewModels.forEach(ViewModelClass => {
|
||||
if (
|
||||
ViewModelClass.__vm &&
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { isNonEmptyArray } from 'Common/Utils';
|
||||
import { arrayLength } from 'Common/Utils';
|
||||
import { ContactPropertyModel, ContactPropertyType } from 'Model/ContactProperty';
|
||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ export class ContactModel extends AbstractModel {
|
|||
let name = '',
|
||||
email = '';
|
||||
|
||||
if (isNonEmptyArray(this.properties)) {
|
||||
if (arrayLength(this.properties)) {
|
||||
this.properties.forEach(property => {
|
||||
if (property) {
|
||||
if (ContactPropertyType.FirstName === property.type()) {
|
||||
|
|
@ -52,7 +52,7 @@ export class ContactModel extends AbstractModel {
|
|||
const contact = super.reviveFromJson(json);
|
||||
if (contact) {
|
||||
let list = [];
|
||||
if (isNonEmptyArray(json.properties)) {
|
||||
if (arrayLength(json.properties)) {
|
||||
json.properties.forEach(property => {
|
||||
property = ContactPropertyModel.reviveFromJson(property);
|
||||
property && list.push(property);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { isNonEmptyArray, pString } from 'Common/Utils';
|
||||
import { arrayLength, pString } from 'Common/Utils';
|
||||
import { delegateRunOnDestroy } from 'Common/UtilsUser';
|
||||
import { i18n } from 'Common/Translator';
|
||||
import { getFolderFromCacheList } from 'Common/Cache';
|
||||
|
|
@ -230,7 +230,7 @@ export class FilterModel extends AbstractModel {
|
|||
|
||||
filter.conditions([]);
|
||||
|
||||
if (isNonEmptyArray(json.Conditions)) {
|
||||
if (arrayLength(json.Conditions)) {
|
||||
filter.conditions(
|
||||
json.Conditions.map(aData => FilterConditionModel.reviveFromJson(aData)).filter(v => v)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ export class FolderModel extends AbstractModel {
|
|||
folder.messageCountAll = ko.computed({
|
||||
read: folder.privateMessageCountAll,
|
||||
write: (iValue) => {
|
||||
if (isPosNumeric(iValue, true)) {
|
||||
if (isPosNumeric(iValue)) {
|
||||
folder.privateMessageCountAll(iValue);
|
||||
} else {
|
||||
folder.privateMessageCountAll.valueHasMutated();
|
||||
|
|
@ -238,7 +238,7 @@ export class FolderModel extends AbstractModel {
|
|||
folder.messageCountUnread = ko.computed({
|
||||
read: folder.privateMessageCountUnread,
|
||||
write: (value) => {
|
||||
if (isPosNumeric(value, true)) {
|
||||
if (isPosNumeric(value)) {
|
||||
folder.privateMessageCountUnread(value);
|
||||
} else {
|
||||
folder.privateMessageCountUnread.valueHasMutated();
|
||||
|
|
@ -371,6 +371,6 @@ export class FolderModel extends AbstractModel {
|
|||
* @returns {string}
|
||||
*/
|
||||
printableFullName() {
|
||||
return this.fullName.split(this.delimiter).join(' / ');
|
||||
return this.fullName.replace(this.delimiter, ' / ');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { MessagePriority } from 'Common/EnumsUser';
|
|||
import { i18n } from 'Common/Translator';
|
||||
|
||||
import { encodeHtml } from 'Common/Html';
|
||||
import { isArray, isNonEmptyArray } from 'Common/Utils';
|
||||
import { isArray, arrayLength } from 'Common/Utils';
|
||||
|
||||
import { serverRequestRaw } from 'Common/Links';
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ export class MessageModel extends AbstractModel {
|
|||
*/
|
||||
fromDkimData() {
|
||||
let result = ['none', ''];
|
||||
if (isNonEmptyArray(this.from) && 1 === this.from.length && this.from[0] && this.from[0].dkimStatus) {
|
||||
if (1 === arrayLength(this.from) && this.from[0] && this.from[0].dkimStatus) {
|
||||
result = [this.from[0].dkimStatus, this.from[0].dkimValue || ''];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { isNonEmptyArray } from 'Common/Utils';
|
||||
import { arrayLength } from 'Common/Utils';
|
||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ export class OpenPgpKeyModel extends AbstractModel {
|
|||
|
||||
this.index = index;
|
||||
this.id = ID;
|
||||
this.ids = isNonEmptyArray(IDs) ? IDs : [ID];
|
||||
this.ids = arrayLength(IDs) ? IDs : [ID];
|
||||
this.guid = guID;
|
||||
this.user = '';
|
||||
this.users = userIDs;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import ko from 'ko';
|
|||
|
||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||
import { FilterModel } from 'Model/Filter';
|
||||
import { isNonEmptyArray, pString } from 'Common/Utils';
|
||||
import { arrayLength, pString } from 'Common/Utils';
|
||||
|
||||
const SIEVE_FILE_NAME = 'rainloop.user';
|
||||
|
||||
|
|
@ -327,7 +327,7 @@ export class SieveScriptModel extends AbstractModel
|
|||
if (script) {
|
||||
if (script.allowFilters()) {
|
||||
script.filters(
|
||||
isNonEmptyArray(json.filters)
|
||||
arrayLength(json.filters)
|
||||
? json.filters.map(aData => FilterModel.reviveFromJson(aData)).filter(v => v)
|
||||
: sieveScriptToFilters(script.body())
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ class RemoteAdminFetch extends AbstractFetchRemote {
|
|||
* @param {string} sLogin
|
||||
* @param {string} sPassword
|
||||
*/
|
||||
adminLogin(fCallback, sLogin, sPassword) {
|
||||
adminLogin(fCallback, sLogin, sPassword, sCode) {
|
||||
this.defaultRequest(fCallback, 'AdminLogin', {
|
||||
Login: sLogin,
|
||||
Password: sPassword
|
||||
Password: sPassword,
|
||||
TOTP: sCode
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { isArray, isNonEmptyArray, pString, pInt } from 'Common/Utils';
|
||||
import { isArray, arrayLength, pString, pInt } from 'Common/Utils';
|
||||
|
||||
import {
|
||||
getFolderHash,
|
||||
|
|
@ -371,7 +371,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
|||
let request = true;
|
||||
const uids = [];
|
||||
|
||||
if (isNonEmptyArray(list)) {
|
||||
if (arrayLength(list)) {
|
||||
request = false;
|
||||
list.forEach(messageListItem => {
|
||||
if (!MessageFlagsCache.getFor(messageListItem.folder, messageListItem.uid)) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ko from 'ko';
|
|||
import { MESSAGES_PER_PAGE_VALUES } from 'Common/Consts';
|
||||
import { SaveSettingsStep } from 'Common/Enums';
|
||||
import { EditorDefaultType, Layout } from 'Common/EnumsUser';
|
||||
import { SettingsGet } from 'Common/Globals';
|
||||
import { Settings, SettingsGet } from 'Common/Globals';
|
||||
import { isArray, settingsSaveHelperSimpleFunction, addObservablesTo, addSubscribablesTo } from 'Common/Utils';
|
||||
import { i18n, trigger as translatorTrigger, reload as translatorReload, convertLangName } from 'Common/Translator';
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ export class GeneralUserSettings {
|
|||
enableSoundNotification: value => Remote.saveSetting('SoundNotification', value ? 1 : 0),
|
||||
notificationSound: value => {
|
||||
Remote.saveSetting('NotificationSound', value);
|
||||
rl.settings.set('NotificationSound', value);
|
||||
Settings.set('NotificationSound', value);
|
||||
},
|
||||
|
||||
replySameFolder: value => Remote.saveSetting('ReplySameFolder', value ? 1 : 0),
|
||||
|
|
@ -138,9 +138,7 @@ export class GeneralUserSettings {
|
|||
|
||||
editMainIdentity() {
|
||||
const identity = this.identityMain();
|
||||
if (identity) {
|
||||
showScreenPopup(IdentityPopupView, [identity]);
|
||||
}
|
||||
identity && showScreenPopup(IdentityPopupView, [identity]);
|
||||
}
|
||||
|
||||
testSoundNotification() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ko from 'ko';
|
|||
import { Scope, Notification } from 'Common/Enums';
|
||||
import { MessageSetAction } from 'Common/EnumsUser';
|
||||
import { doc, $htmlCL, createElement, elementById } from 'Common/Globals';
|
||||
import { isNonEmptyArray, pInt, pString, addObservablesTo, addSubscribablesTo } from 'Common/Utils';
|
||||
import { arrayLength, pInt, pString, addObservablesTo, addSubscribablesTo } from 'Common/Utils';
|
||||
import { plainToHtml } from 'Common/UtilsUser';
|
||||
|
||||
import {
|
||||
|
|
@ -237,7 +237,7 @@ export const MessageUserStore = new class {
|
|||
|
||||
initUidNextAndNewMessages(folder, uidNext, newMessages) {
|
||||
if (getFolderInboxName() === folder && uidNext) {
|
||||
if (isNonEmptyArray(newMessages)) {
|
||||
if (arrayLength(newMessages)) {
|
||||
newMessages.forEach(item => addNewMessageCache(folder, item.Uid));
|
||||
|
||||
NotificationUserStore.playSoundNotification();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { i18n } from 'Common/Translator';
|
||||
import { isArray, isNonEmptyArray, pString } from 'Common/Utils';
|
||||
import { isArray, arrayLength, pString } from 'Common/Utils';
|
||||
import { createElement } from 'Common/Globals';
|
||||
|
||||
import { AccountUserStore } from 'Stores/User/Account';
|
||||
|
|
@ -54,7 +54,7 @@ function domControlEncryptedClickHelper(store, dom, armoredMessage, recipients)
|
|||
decryptedMessage.getText()
|
||||
);
|
||||
} else if (validPrivateKey) {
|
||||
const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null,
|
||||
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
|
||||
additional = keyIds
|
||||
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
|
||||
: '';
|
||||
|
|
@ -110,7 +110,7 @@ function domControlSignedClickHelper(store, dom, armoredMessage) {
|
|||
message.getText()
|
||||
);
|
||||
} else {
|
||||
const keyIds = isNonEmptyArray(signingKeyIds) ? signingKeyIds : null,
|
||||
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
|
||||
additional = keyIds
|
||||
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
|
||||
: '';
|
||||
|
|
@ -186,7 +186,7 @@ export const PgpUserStore = new class {
|
|||
}).flat().filter(v => v)
|
||||
: [];
|
||||
|
||||
if (!result.length && isNonEmptyArray(recipients)) {
|
||||
if (!result.length && arrayLength(recipients)) {
|
||||
result = recipients.map(sEmail => {
|
||||
const keys = sEmail ? this.findAllPrivateKeysByEmailNotNative(sEmail) : null;
|
||||
return keys
|
||||
|
|
|
|||
|
|
@ -79,14 +79,13 @@
|
|||
}
|
||||
|
||||
.controls {
|
||||
.inputLoginForm, .inputLogin, .inputPassword {
|
||||
.input-block-level {
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.inputLogin, .inputPassword {
|
||||
.inputIcon {
|
||||
padding-right: 35px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -198,13 +198,9 @@ html.rl-no-preview-pane {
|
|||
}
|
||||
}
|
||||
|
||||
&.message-focused {
|
||||
.b-message-list-wrapper {
|
||||
&.focused .b-message-list-wrapper {
|
||||
background-color: #000;
|
||||
}
|
||||
.b-content {
|
||||
opacity: 0.97;
|
||||
}
|
||||
border-color: #9d9d9d;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -458,7 +458,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.message-focused .b-content {
|
||||
&.focused .b-content {
|
||||
z-index: 101;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
border-color: darken(@rlMainDarkColor, 5%);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class LoginAdminView extends AbstractViewCenter {
|
|||
this.addObservables({
|
||||
login: '',
|
||||
password: '',
|
||||
totp: '',
|
||||
|
||||
loginError: false,
|
||||
passwordError: false,
|
||||
|
|
@ -59,7 +60,8 @@ class LoginAdminView extends AbstractViewCenter {
|
|||
}
|
||||
},
|
||||
name,
|
||||
pass
|
||||
pass,
|
||||
this.totp()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
SetSystemFoldersNotification
|
||||
} from 'Common/EnumsUser';
|
||||
|
||||
import { inFocus, pInt, isArray, isNonEmptyArray } from 'Common/Utils';
|
||||
import { inFocus, pInt, isArray, arrayLength } from 'Common/Utils';
|
||||
import { delegateRunOnDestroy } from 'Common/UtilsUser';
|
||||
import { encodeHtml, HtmlEditor } from 'Common/Html';
|
||||
|
||||
|
|
@ -308,7 +308,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
},
|
||||
|
||||
attachmentsInProcess: value => {
|
||||
if (this.attachmentsInProcessError() && isNonEmptyArray(value)) {
|
||||
if (this.attachmentsInProcessError() && arrayLength(value)) {
|
||||
this.attachmentsInProcessError(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -385,8 +385,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
if (!this.emptyToError() && !this.attachmentsInErrorError() && !this.attachmentsInProcessError()) {
|
||||
if (SettingsUserStore.replySameFolder()) {
|
||||
if (
|
||||
isArray(this.aDraftInfo) &&
|
||||
3 === this.aDraftInfo.length &&
|
||||
3 === arrayLength(this.aDraftInfo) &&
|
||||
null != this.aDraftInfo[2] &&
|
||||
this.aDraftInfo[2].length
|
||||
) {
|
||||
|
|
@ -400,7 +399,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
this.sendError(false);
|
||||
this.sending(true);
|
||||
|
||||
if (isArray(this.aDraftInfo) && 3 === this.aDraftInfo.length) {
|
||||
if (3 === arrayLength(this.aDraftInfo)) {
|
||||
const flagsCache = MessageFlagsCache.getFor(this.aDraftInfo[2], this.aDraftInfo[1]);
|
||||
if (flagsCache) {
|
||||
if ('forward' === this.aDraftInfo[0]) {
|
||||
|
|
@ -753,7 +752,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
* @param {Array} emails
|
||||
*/
|
||||
addEmailsTo(fKoValue, emails) {
|
||||
if (isNonEmptyArray(emails)) {
|
||||
if (arrayLength(emails)) {
|
||||
const value = fKoValue().trim(),
|
||||
values = emails.map(item => item ? item.toLine(false) : null)
|
||||
.validUnique();
|
||||
|
|
@ -806,11 +805,11 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
oMessageOrArray = oMessageOrArray || null;
|
||||
if (oMessageOrArray) {
|
||||
message =
|
||||
isArray(oMessageOrArray) && 1 === oMessageOrArray.length
|
||||
1 === arrayLength(oMessageOrArray)
|
||||
? oMessageOrArray[0]
|
||||
: !isArray(oMessageOrArray)
|
||||
? oMessageOrArray
|
||||
: null;
|
||||
: isArray(oMessageOrArray)
|
||||
? null
|
||||
: oMessageOrArray;
|
||||
}
|
||||
|
||||
this.oLastMessage = message;
|
||||
|
|
@ -826,15 +825,15 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
excludeEmail[identity.email()] = true;
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(aToEmails)) {
|
||||
if (arrayLength(aToEmails)) {
|
||||
this.to(this.emailArrayToStringLineHelper(aToEmails));
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(aCcEmails)) {
|
||||
if (arrayLength(aCcEmails)) {
|
||||
this.cc(this.emailArrayToStringLineHelper(aCcEmails));
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(aBccEmails)) {
|
||||
if (arrayLength(aBccEmails)) {
|
||||
this.bcc(this.emailArrayToStringLineHelper(aBccEmails));
|
||||
}
|
||||
|
||||
|
|
@ -899,7 +898,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
this.subject(sSubject);
|
||||
this.prepareMessageAttachments(message, lineComposeType);
|
||||
|
||||
this.aDraftInfo = isNonEmptyArray(aDraftInfo) && 3 === aDraftInfo.length ? aDraftInfo : null;
|
||||
this.aDraftInfo = 3 === arrayLength(aDraftInfo) ? aDraftInfo : null;
|
||||
this.sInReplyTo = message.sInReplyTo;
|
||||
this.sReferences = message.sReferences;
|
||||
break;
|
||||
|
|
@ -913,7 +912,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
this.subject(sSubject);
|
||||
this.prepareMessageAttachments(message, lineComposeType);
|
||||
|
||||
this.aDraftInfo = isNonEmptyArray(aDraftInfo) && 3 === aDraftInfo.length ? aDraftInfo : null;
|
||||
this.aDraftInfo = 3 === arrayLength(aDraftInfo) ? aDraftInfo : null;
|
||||
this.sInReplyTo = message.sInReplyTo;
|
||||
this.sReferences = message.sReferences;
|
||||
break;
|
||||
|
|
@ -1003,7 +1002,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
|
||||
this.setFocusInPopup();
|
||||
});
|
||||
} else if (isNonEmptyArray(oMessageOrArray)) {
|
||||
} else if (arrayLength(oMessageOrArray)) {
|
||||
oMessageOrArray.forEach(item => this.addMessageAsAttachment(item));
|
||||
|
||||
this.editor(editor => {
|
||||
|
|
@ -1024,7 +1023,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
}
|
||||
|
||||
const downloads = this.getAttachmentsDownloadsForUpload();
|
||||
if (isNonEmptyArray(downloads)) {
|
||||
if (arrayLength(downloads)) {
|
||||
Remote.messageUploadAttachments((iError, oData) => {
|
||||
if (!iError) {
|
||||
Object.entries(oData.Result).forEach(([tempName, id]) => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
|
||||
import { ComposeType } from 'Common/EnumsUser';
|
||||
|
||||
import { isNonEmptyArray, pInt } from 'Common/Utils';
|
||||
import { arrayLength, pInt } from 'Common/Utils';
|
||||
import { delegateRunOnDestroy, computedPaginatorHelper, showMessageComposer } from 'Common/UtilsUser';
|
||||
|
||||
import { Selector } from 'Common/Selector';
|
||||
|
|
@ -163,7 +163,7 @@ class ContactsPopupView extends AbstractViewPopup {
|
|||
bccEmails = null;
|
||||
|
||||
const aC = this.contactsCheckedOrSelected();
|
||||
if (isNonEmptyArray(aC)) {
|
||||
if (arrayLength(aC)) {
|
||||
aE = aC.map(oItem => {
|
||||
if (oItem) {
|
||||
const data = oItem.getNameAndEmailHelper(),
|
||||
|
|
@ -180,7 +180,7 @@ class ContactsPopupView extends AbstractViewPopup {
|
|||
aE = aE.filter(value => !!value);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(aE)) {
|
||||
if (arrayLength(aE)) {
|
||||
this.bBackToCompose = false;
|
||||
|
||||
hideScreenPopup(ContactsPopupView);
|
||||
|
|
@ -447,7 +447,7 @@ class ContactsPopupView extends AbstractViewPopup {
|
|||
let count = 0,
|
||||
list = [];
|
||||
|
||||
if (!iError && isNonEmptyArray(data.Result.List)) {
|
||||
if (!iError && arrayLength(data.Result.List)) {
|
||||
data.Result.List.forEach(item => {
|
||||
item = ContactModel.reviveFromJson(item);
|
||||
item && list.push(item);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import ko from 'ko';
|
|||
|
||||
import { Scope } from 'Common/Enums';
|
||||
import { getNotification, i18n } from 'Common/Translator';
|
||||
import { isNonEmptyArray } from 'Common/Utils';
|
||||
import { arrayLength } from 'Common/Utils';
|
||||
|
||||
import Remote from 'Remote/Admin/Fetch';
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class PluginPopupView extends AbstractViewPopup {
|
|||
this.readme(oPlugin.Readme);
|
||||
|
||||
const config = oPlugin.Config;
|
||||
if (isNonEmptyArray(config)) {
|
||||
if (arrayLength(config)) {
|
||||
this.configures(
|
||||
config.map(item => ({
|
||||
value: ko.observable(item[0]),
|
||||
|
|
|
|||
|
|
@ -838,8 +838,10 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
return false;
|
||||
});
|
||||
shortcuts.add('tab,arrowright', '', Scope.MessageList, () => {
|
||||
MessageUserStore.message() && AppUserStore.focusedState(Scope.MessageView);
|
||||
if (MessageUserStore.message()){
|
||||
AppUserStore.focusedState(Scope.MessageView);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
shortcuts.add('arrowleft', 'meta', Scope.MessageView, ()=>false);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
|
||||
import { doc, $htmlCL, leftPanelDisabled, keyScopeReal, moveAction, Settings } from 'Common/Globals';
|
||||
|
||||
import { isNonEmptyArray, inFocus } from 'Common/Utils';
|
||||
import { arrayLength, inFocus } from 'Common/Utils';
|
||||
import { mailToHelper, showMessageComposer } from 'Common/UtilsUser';
|
||||
|
||||
import { SMAudio } from 'Common/Audio';
|
||||
|
|
@ -78,7 +78,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
this.allowMessageListActions = Settings.capa(Capa.MessageListActions);
|
||||
|
||||
const attachmentsActions = Settings.app('attachmentsActions');
|
||||
this.attachmentsActions = ko.observableArray(isNonEmptyArray(attachmentsActions) ? attachmentsActions : []);
|
||||
this.attachmentsActions = ko.observableArray(arrayLength(attachmentsActions) ? attachmentsActions : []);
|
||||
|
||||
this.message = MessageUserStore.message;
|
||||
this.hasCheckedMessages = MessageUserStore.hasCheckedMessages;
|
||||
|
|
@ -176,11 +176,9 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
viewFromDkimStatusTitle:() => {
|
||||
const status = this.viewFromDkimData();
|
||||
if (isNonEmptyArray(status)) {
|
||||
if (status[0]) {
|
||||
if (arrayLength(status) && status[0]) {
|
||||
return status[1] || 'DKIM: ' + status[0];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
|
@ -541,8 +539,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
return false;
|
||||
}
|
||||
});
|
||||
// shortcuts.add('tab', 'shift', Scope.MessageView, (event, handler) => {
|
||||
shortcuts.add('tab', '', Scope.MessageView, () => {
|
||||
shortcuts.add('tab', 'shift', Scope.MessageView, () => {
|
||||
if (!this.fullScreenMode() && this.message() && SettingsUserStore.usePreviewPane()) {
|
||||
AppUserStore.focusedState(Scope.MessageList);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,10 +179,13 @@ trait Admin
|
|||
|
||||
$this->Logger()->AddSecret($sPassword);
|
||||
|
||||
$totp = $this->Config()->Get('security', 'admin_totp', '');
|
||||
|
||||
if (0 === strlen($sLogin) || 0 === strlen($sPassword) ||
|
||||
!$this->Config()->Get('security', 'allow_admin_panel', true) ||
|
||||
$sLogin !== $this->Config()->Get('security', 'admin_login', '') ||
|
||||
!$this->Config()->ValidatePassword($sPassword))
|
||||
!$this->Config()->ValidatePassword($sPassword)
|
||||
|| ($totp && !\SnappyMail\TOTP::Verify($totp, $this->GetActionParam('TOTP', ''))))
|
||||
{
|
||||
$this->loginErrorDelay();
|
||||
$this->LoggerAuthHelper(null, $this->getAdditionalLogParamsByUserLogin($sLogin, true));
|
||||
|
|
|
|||
|
|
@ -259,4 +259,34 @@ trait Contacts
|
|||
return $mResult;
|
||||
}
|
||||
|
||||
public function RawContactsVcf() : bool
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
\header('Content-Type: text/x-vcard; charset=UTF-8');
|
||||
\header('Content-Disposition: attachment; filename="contacts.vcf"', true);
|
||||
\header('Accept-Ranges: none', true);
|
||||
\header('Content-Transfer-Encoding: binary');
|
||||
|
||||
$this->oHttp->ServerNoCache();
|
||||
|
||||
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
||||
$this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'vcf') : false;
|
||||
}
|
||||
|
||||
public function RawContactsCsv() : bool
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
\header('Content-Type: text/csv; charset=UTF-8');
|
||||
\header('Content-Disposition: attachment; filename="contacts.csv"', true);
|
||||
\header('Accept-Ranges: none', true);
|
||||
\header('Content-Transfer-Encoding: binary');
|
||||
|
||||
$this->oHttp->ServerNoCache();
|
||||
|
||||
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
||||
$this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'csv') : false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,36 +112,6 @@ trait Raw
|
|||
return false;
|
||||
}
|
||||
|
||||
public function RawContactsVcf() : bool
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
\header('Content-Type: text/x-vcard; charset=UTF-8');
|
||||
\header('Content-Disposition: attachment; filename="contacts.vcf"', true);
|
||||
\header('Accept-Ranges: none', true);
|
||||
\header('Content-Transfer-Encoding: binary');
|
||||
|
||||
$this->oHttp->ServerNoCache();
|
||||
|
||||
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
||||
$this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'vcf') : false;
|
||||
}
|
||||
|
||||
public function RawContactsCsv() : bool
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
\header('Content-Type: text/csv; charset=UTF-8');
|
||||
\header('Content-Disposition: attachment; filename="contacts.csv"', true);
|
||||
\header('Accept-Ranges: none', true);
|
||||
\header('Content-Transfer-Encoding: binary');
|
||||
|
||||
$this->oHttp->ServerNoCache();
|
||||
|
||||
return $this->AddressBookProvider($oAccount)->IsActive() ?
|
||||
$this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'csv') : false;
|
||||
}
|
||||
|
||||
private function rawSmart(bool $bDownload, bool $bThumbnail = false) : bool
|
||||
{
|
||||
$sRawKey = (string) $this->GetActionParam('RawKey', '');
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ class Application extends \RainLoop\Config\AbstractConfig
|
|||
|
||||
'admin_login' => array('admin', 'Login and password for web admin panel'),
|
||||
'admin_password' => array(''),
|
||||
'admin_totp' => array(''),
|
||||
'allow_admin_panel' => array(true, 'Access settings'),
|
||||
'hide_x_mailer_header' => array(true),
|
||||
'admin_panel_host' => array(''),
|
||||
|
|
|
|||
98
snappymail/v/0.0.0/app/libraries/snappymail/totp.php
Normal file
98
snappymail/v/0.0.0/app/libraries/snappymail/totp.php
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace SnappyMail;
|
||||
|
||||
abstract class TOTP
|
||||
{
|
||||
public function Verify(string $sSecret, string $sCode) : bool
|
||||
{
|
||||
$key = static::Base32Decode($sSecret);
|
||||
$algo = 'SHA1'; // Google Authenticator doesn't support SHA256
|
||||
$digits = 6; // Google Authenticator doesn't support 8
|
||||
$modulo = \pow(10, $digits);
|
||||
$timeSlice = \floor(\time() / 30);
|
||||
$discrepancy = 1;
|
||||
for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
|
||||
// Pack time into binary string
|
||||
$counter = \str_pad(\pack('N*', $timeSlice + $i), 8, "\x00", STR_PAD_LEFT);
|
||||
// Hash it with users secret key
|
||||
$hm = \hash_hmac($algo, $counter, $key, true);
|
||||
// Unpak 4 bytes of the result, use last nipple of result as index/offset
|
||||
$value = \unpack('N', \substr($hm, (\ord(\substr($hm, -1)) & 0x0F), 4));
|
||||
// Only 32 bits
|
||||
$value = $value[1] & 0x7FFFFFFF;
|
||||
$value = \str_pad($value % $modulo, $digits, '0', STR_PAD_LEFT);
|
||||
if (\hash_equals($value, $sCode)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function CreateSecret() : string
|
||||
{
|
||||
$CHARS = \array_keys(static::$map);
|
||||
$length = 16;
|
||||
$secret = '';
|
||||
while (0 < $length--) {
|
||||
$secret .= $CHARS[\random_int(0,31)];
|
||||
}
|
||||
return $secret;
|
||||
}
|
||||
|
||||
protected static $map = array(
|
||||
'A' => 0, // ord 65
|
||||
'B' => 1,
|
||||
'C' => 2,
|
||||
'D' => 3,
|
||||
'E' => 4,
|
||||
'F' => 5,
|
||||
'G' => 6,
|
||||
'H' => 7,
|
||||
'I' => 8,
|
||||
'J' => 9,
|
||||
'K' => 10,
|
||||
'L' => 11,
|
||||
'M' => 12,
|
||||
'N' => 13,
|
||||
'O' => 14,
|
||||
'P' => 15,
|
||||
'Q' => 16,
|
||||
'R' => 17,
|
||||
'S' => 18,
|
||||
'T' => 19,
|
||||
'U' => 20,
|
||||
'V' => 21,
|
||||
'W' => 22,
|
||||
'X' => 23,
|
||||
'Y' => 24,
|
||||
'Z' => 25, // ord 90
|
||||
'2' => 26, // ord 50
|
||||
'3' => 27,
|
||||
'4' => 28,
|
||||
'5' => 29,
|
||||
'6' => 30,
|
||||
'7' => 31 // ord 55
|
||||
);
|
||||
|
||||
protected static function Base32Decode(string $data)
|
||||
{
|
||||
$data = \strtoupper(\rtrim($data, "=\x20\t\n\r\0\x0B"));
|
||||
$dataSize = \strlen($data);
|
||||
$buf = 0;
|
||||
$bufSize = 0;
|
||||
$res = '';
|
||||
for ($i = 0; $i < $dataSize; ++$i) {
|
||||
$c = $data[$i];
|
||||
if (isset(static::$map[$c])) {
|
||||
$buf = ($buf << 5) | static::$map[$c];
|
||||
$bufSize += 5;
|
||||
if ($bufSize > 7) {
|
||||
$bufSize -= 8;
|
||||
$res .= \chr(($buf & (0xff << $bufSize)) >> $bufSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<form action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError, 'submitting': submitRequest()}">
|
||||
<div class="controls" data-bind="css: {'error': loginError}">
|
||||
<div class="input-append">
|
||||
<input required="" type="text" class="input-block-level inputLogin"
|
||||
<input required="" type="text" class="input-block-level inputIcon"
|
||||
autofocus="" autocomplete="username" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-bind="textInput: login, disable: submitRequest"
|
||||
data-i18n="[placeholder]LOGIN/LABEL_LOGIN" />
|
||||
|
|
@ -15,15 +15,20 @@
|
|||
</div>
|
||||
<div class="controls" data-bind="css: {'error': passwordError}">
|
||||
<div class="input-append">
|
||||
<input required="" type="password" class="input-block-level inputPassword"
|
||||
<input required="" type="password" class="input-block-level inputIcon"
|
||||
autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-bind="textInput: password, disable: submitRequest"
|
||||
data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" />
|
||||
<span class="add-on" tabindex="-1"
|
||||
data-bind="command: submitCommand" data-i18n="[title]LOGIN/BUTTON_LOGIN">
|
||||
<i class="fontastic" data-bind="visible: '' === password()">🔑</i>
|
||||
<button type="submit" class="btn-submit-icon-wrp login-submit-icon fontastic" data-bind="visible: '' !== password()">❯</button>
|
||||
</span>
|
||||
<span class="add-on fontastic">🔑</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="input-append">
|
||||
<input type="text" pattern="[0-9]*" inputmode="numeric" class="input-block-level inputIcon"
|
||||
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-bind="textInput: totp, disable: submitRequest"
|
||||
data-i18n="[placeholder]LOGIN/LABEL_TOTP" />
|
||||
<button type="submit" class="add-on btn-submit-icon-wrp login-submit-icon fontastic" data-bind="command: submitCommand" data-i18n="[title]LOGIN/BUTTON_LOGIN">❯</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="plugin-Login-BottomControlGroup"></div>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
<tr><td data-i18n="SHORTCUTS_HELP/LABEL_PRINT"></td><td>Ctrl + P, ⌘ + P</td></tr>
|
||||
<tr><td data-i18n="SHORTCUTS_HELP/LABEL_EXIT_FULLSCREEN"></td><td>Esc, Close</td></tr>
|
||||
<tr><td data-i18n="SHORTCUTS_HELP/LABEL_CLOSE_MESSAGE"></td><td>Esc, Close</td></tr>
|
||||
<tr><td data-i18n="SHORTCUTS_HELP/LABEL_SWITCH_TO_LIST"></td><td>Tab, Shift + Tab, Esc</td></tr>
|
||||
<tr><td data-i18n="SHORTCUTS_HELP/LABEL_SWITCH_TO_LIST"></td><td>Shift + Tab, Esc</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
data-bind="submit: submitForm, css: {'errorAnimated': formError, 'submitting': submitRequest()}">
|
||||
<div class="controls" data-bind="css: {'error': emailError}">
|
||||
<div class="input-append">
|
||||
<input required="" type="text" class="input-block-level inputLogin" pattern="[^@\s]+(@[^\s]+)?" inputmode="email"
|
||||
<input required="" type="text" class="input-block-level inputIcon" pattern="[^@\s]+(@[^\s]+)?" inputmode="email"
|
||||
autofocus="" autocomplete="email" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-bind="textInput: email, disable: submitRequest"
|
||||
data-i18n="[placeholder]GLOBAL/EMAIL" />
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
</div>
|
||||
<div class="controls" data-bind="css: {'error': passwordError}">
|
||||
<div class="input-append">
|
||||
<input required="" type="password" class="input-block-level inputPassword"
|
||||
<input required="" type="password" class="input-block-level inputIcon"
|
||||
autocomplete="current-password" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-bind="textInput: password, disable: submitRequest"
|
||||
data-i18n="[placeholder]GLOBAL/PASSWORD" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div id="rl-sub-left" class="messageList g-ui-user-select-none"
|
||||
data-bind="css: {'message-selected': isMessageSelected, 'message-focused': !messageListFocused() }">
|
||||
data-bind="css: {'message-selected': isMessageSelected, 'focused': messageListFocused() }">
|
||||
<div class="toolbar">
|
||||
<div class="btn-toolbar">
|
||||
<a class="btn btn-thin-2 fontastic show-mobile" data-bind="click: hideLeft, visible: !leftPanelDisabled()">❮</a>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div id="rl-sub-right">
|
||||
<div class="messageView" data-bind="css: {'message-selected': isMessageSelected, 'message-focused': messageFocused}">
|
||||
<div class="messageView" data-bind="css: {'message-selected': isMessageSelected, 'focused': messageFocused}">
|
||||
<div class="toolbar top-toolbar g-ui-user-select-none">
|
||||
<div class="messageButtons btn-toolbar">
|
||||
<div class="btn-group" data-i18n="[title]GLOBAL/CLOSE">
|
||||
|
|
|
|||
|
|
@ -144,3 +144,7 @@
|
|||
.b-admin-about .rl-logo {
|
||||
filter: drop-shadow(0 0 1px #FFF);
|
||||
}
|
||||
|
||||
.b-folders .e-item a.focused {
|
||||
background-color: #111;
|
||||
}
|
||||
|
|
|
|||
1
vendors/bootstrap/less/navs.less
vendored
1
vendors/bootstrap/less/navs.less
vendored
|
|
@ -29,6 +29,7 @@
|
|||
// Give the tabs something to sit on
|
||||
.nav-tabs {
|
||||
border-bottom: 1px solid #ddd;
|
||||
white-space: nowrap;
|
||||
}
|
||||
// Make the list-items overlay the bottom border
|
||||
.nav-tabs > li {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue