Add new "Move to folder" button

This commit is contained in:
RainLoop Team 2017-02-08 20:48:53 +03:00
parent 2c3881abc6
commit f0e9d1a6d5
14 changed files with 312 additions and 182 deletions

View file

@ -192,10 +192,23 @@ export const VIEW_MODELS = {
'settings-disabled': []
};
export const moveAction = ko.observable(false);
export const leftPanelDisabled = ko.observable(false);
export const leftPanelType = ko.observable('');
export const leftPanelWidth = ko.observable(0);
leftPanelDisabled.subscribe((value) => {
if (value && moveAction()) {
moveAction(false);
}
});
moveAction.subscribe((value) => {
if (value && leftPanelDisabled()) {
leftPanelDisabled(false);
}
});
// popups
export const popupVisibilityNames = ko.observableArray([]);

View file

@ -2,7 +2,7 @@
import _ from '_';
import {Focused, Capa, ClientSideKeyName, Magics} from 'Common/Enums';
import {leftPanelDisabled, leftPanelType, bMobileDevice} from 'Common/Globals';
import {$html, leftPanelDisabled, leftPanelType, moveAction, bMobileDevice} from 'Common/Globals';
import {pString, pInt, decodeURI, windowResizeCallback} from 'Common/Utils';
import {getFolderFromCacheList, getFolderFullNameRaw, getFolderInboxName} from 'Common/Cache';
import {i18n} from 'Common/Translator';
@ -141,6 +141,10 @@ class MailBoxUserScreen extends AbstractScreen
getApp().initHorizontalLayoutResizer(ClientSideKeyName.MessageListSize);
});
}
$html.on('click', '#rl-right', () => {
moveAction(false);
});
}
/**

View file

@ -42,6 +42,7 @@ class GeneralAdminSettings
this.allowLanguagesOnSettings = AppAdminStore.allowLanguagesOnSettings;
this.weakPassword = AppAdminStore.weakPassword;
this.newMoveToFolder = AppAdminStore.newMoveToFolder;
this.mainAttachmentLimit = ko.observable(pInt(settingsGet('AttachmentLimit')) / (Magics.BitLength1024 * Magics.BitLength1024)).extend({posInterer: 25});
@ -151,6 +152,12 @@ class GeneralAdminSettings
'AllowLanguagesOnSettings': boolToAjax(value)
});
});
this.newMoveToFolder.subscribe((value) => {
Remote.saveAdminConfig(null, {
'NewMoveToFolder': boolToAjax(value)
});
});
}, Magics.Time50ms);
}

View file

@ -8,6 +8,7 @@ class AbstractAppStore
constructor() {
this.allowLanguagesOnSettings = ko.observable(true);
this.allowLanguagesOnLogin = ko.observable(true);
this.newMoveToFolder = ko.observable(true);
this.interfaceAnimation = ko.observable(true);
@ -25,6 +26,7 @@ class AbstractAppStore
populate() {
this.allowLanguagesOnLogin(!!Settings.settingsGet('AllowLanguagesOnLogin'));
this.allowLanguagesOnSettings(!!Settings.settingsGet('AllowLanguagesOnSettings'));
this.newMoveToFolder(!!Settings.settingsGet('NewMoveToFolder'));
this.interfaceAnimation(!!Settings.settingsGet('InterfaceAnimation'));

View file

@ -1,7 +1,8 @@
import ko from 'ko';
import {Focused, KeyState} from 'Common/Enums';
import {keyScope} from 'Common/Globals';
import {keyScope, leftPanelDisabled} from 'Common/Globals';
import {isNonEmptyArray} from 'Common/Utils';
import * as Settings from 'Storage/Settings';
@ -17,17 +18,28 @@ class AppUserStore extends AbstractAppStore
this.focusedState = ko.observable(Focused.None);
const isMobile = Settings.appSettingsGet('mobile');
this.focusedState.subscribe((value) => {
switch (value)
{
case Focused.MessageList:
keyScope(KeyState.MessageList);
if (isMobile) {
leftPanelDisabled(true);
}
break;
case Focused.MessageView:
keyScope(KeyState.MessageView);
if (isMobile) {
leftPanelDisabled(true);
}
break;
case Focused.FolderList:
keyScope(KeyState.FolderList);
if (isMobile) {
leftPanelDisabled(false);
}
break;
default:
break;

View file

@ -4,6 +4,21 @@
.b-folders {
.move-action-content-wrapper {
z-index: -1;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
opacity: 0.05;
background-color: #fff;
background-size: 60px 60px;
background-image: linear-gradient(135deg, #000 25%, transparent 25%,
transparent 50%, #000 50%, #000 75%,
transparent 75%, transparent);
}
&.focused .b-content {
}

View file

@ -6,7 +6,7 @@ import key from 'key';
import {trim, isNormal, isArray, windowResize} from 'Common/Utils';
import {Capa, Focused, Layout, KeyState, EventKeyCode, Magics} from 'Common/Enums';
import {$html, leftPanelDisabled} from 'Common/Globals';
import {$html, leftPanelDisabled, moveAction} from 'Common/Globals';
import {mailBox, settings} from 'Common/Links';
import {setFolderHash} from 'Common/Cache';
@ -42,6 +42,8 @@ class FolderListMailBoxUserView extends AbstractViewNext
this.folderListSystem = FolderStore.folderListSystem;
this.foldersChanging = FolderStore.foldersChanging;
this.moveAction = moveAction;
this.foldersListWithSingleInboxRootFolder = FolderStore.foldersListWithSingleInboxRootFolder;
this.leftPanelDisabled = leftPanelDisabled;
@ -71,6 +73,7 @@ class FolderListMailBoxUserView extends AbstractViewNext
isMobile = Settings.appSettingsGet('mobile'),
fSelectFolder = (el, event, starred) => {
const isMove = moveAction();
if (isMobile)
{
leftPanelDisabled(true);
@ -86,24 +89,39 @@ class FolderListMailBoxUserView extends AbstractViewNext
const folder = ko.dataFor(el);
if (folder)
{
if (Layout.NoPreview === SettingsStore.layout())
if (isMove)
{
MessageStore.message(null);
}
if (folder.fullNameRaw === FolderStore.currentFolderFullNameRaw())
{
setFolderHash(folder.fullNameRaw, '');
}
if (starred)
{
setHash(mailBox(folder.fullNameHash, 1, 'is:flagged'));
moveAction(false);
getApp().moveMessagesToFolder(
FolderStore.currentFolderFullNameRaw(),
MessageStore.messageListCheckedOrSelectedUidsWithSubMails(),
folder.fullNameRaw,
false
);
}
else
{
setHash(mailBox(folder.fullNameHash));
if (Layout.NoPreview === SettingsStore.layout())
{
MessageStore.message(null);
}
if (folder.fullNameRaw === FolderStore.currentFolderFullNameRaw())
{
setFolderHash(folder.fullNameRaw, '');
}
if (starred)
{
setHash(mailBox(folder.fullNameHash, 1, 'is:flagged'));
}
else
{
setHash(mailBox(folder.fullNameHash));
}
}
AppStore.focusedState(Focused.MessageList);
}
};
@ -186,6 +204,7 @@ class FolderListMailBoxUserView extends AbstractViewNext
key('esc, tab, shift+tab, right', KeyState.FolderList, () => {
AppStore.focusedState(Focused.MessageList);
moveAction(false);
return false;
});

View file

@ -20,7 +20,8 @@ import {
import {
bMobileDevice,
popupVisibility,
leftPanelDisabled
leftPanelDisabled,
moveAction
} from 'Common/Globals';
import {
@ -76,6 +77,7 @@ class MessageListMailBoxUserView extends AbstractViewNext
this.iGoToUpUpOrDownDownTimeout = 0;
this.mobile = !!Settings.appSettingsGet('mobile');
this.newMoveToFolder = AppStore.newMoveToFolder;
this.allowReload = !!Settings.capa(Capa.Reload);
this.allowSearch = !!Settings.capa(Capa.Search);
@ -350,6 +352,30 @@ class MessageListMailBoxUserView extends AbstractViewNext
@command(canBeMovedHelper)
moveCommand() {} // eslint-disable-line no-empty-function
@command(canBeMovedHelper)
moveNewCommand(vm, event) {
if (this.newMoveToFolder() && this.mobileCheckedStateShow())
{
if (vm && event && event.preventDefault) {
event.preventDefault();
if (event.stopPropagation) {
event.stopPropagation();
}
}
if (moveAction())
{
AppStore.focusedState(Focused.MessageList);
moveAction(false);
}
else
{
AppStore.focusedState(Focused.FolderList);
moveAction(true);
}
}
}
hideLeft(item, event) {
event.preventDefault();
event.stopPropagation();
@ -869,7 +895,16 @@ class MessageListMailBoxUserView extends AbstractViewNext
{
// move
key('m', KeyState.MessageList, () => {
this.moveDropdownTrigger(true);
if (this.newMoveToFolder())
{
this.moveNewCommand();
}
else
{
this.moveDropdownTrigger(true);
}
return false;
});
}

View file

@ -1817,6 +1817,7 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$sLanguageAdmin = $oConfig->Get('webmail', 'language_admin', 'en');
$sTheme = $oConfig->Get('webmail', 'theme', 'Default');
$aResult['NewMoveToFolder'] = (bool) $oConfig->Get('interface', 'new_move_to_folder_button', true);
$aResult['AllowLanguagesOnSettings'] = (bool) $oConfig->Get('webmail', 'allow_languages_on_settings', true);
$aResult['AllowLanguagesOnLogin'] = (bool) $oConfig->Get('login', 'allow_languages_on_login', true);
$aResult['AttachmentLimit'] = ((int) $oConfig->Get('webmail', 'attachment_size_limit', 10)) * 1024 * 1024;
@ -3746,6 +3747,8 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack
$this->setConfigFromParams($oConfig, 'UseLocalProxyForExternalImages', 'labs', 'use_local_proxy_for_external_images', 'bool');
$this->setConfigFromParams($oConfig, 'NewMoveToFolder', 'interface', 'new_move_to_folder_button', 'bool');
$this->setConfigFromParams($oConfig, 'AllowLanguagesOnSettings', 'webmail', 'allow_languages_on_settings', 'bool');
$this->setConfigFromParams($oConfig, 'AllowLanguagesOnLogin', 'login', 'allow_languages_on_login', 'bool');
$this->setConfigFromParams($oConfig, 'AttachmentLimit', 'webmail', 'attachment_size_limit', 'int');

View file

@ -160,7 +160,8 @@ class Application extends \RainLoop\Config\AbstractConfig
'interface' => array(
'show_attachment_thumbnail' => array(true, ''),
'use_native_scrollbars' => array(false)
'use_native_scrollbars' => array(false),
'new_move_to_folder_button' => array(true)
),
'branding' => array(

View file

@ -26,6 +26,7 @@ en:
LABEL_ALLOW_LANGUAGES_ON_SETTINGS: "Allow language selection on settings screen"
LABEL_ALLOW_THEMES_ON_SETTINGS: "Allow theme selection on settings screen"
LABEL_ALLOW_BACKGROUND_ON_SETTINGS: "Allow background selection on settings screen"
LABEL_NEW_FOLDER_MOVE: "New \"move to folder\" button"
LABEL_SHOW_THUMBNAILS: "Show thumbnails (attachments)"
LABEL_ALLOW_GRAVATAR: "Allow Gravatar"
LEGEND_MAIN: "Main"

View file

@ -1,164 +1,171 @@
<div class="b-admin-general">
<div class="row" data-bind="visible: weakPassword">
<div class="alert alert-error span8" style="margin-top: 10px;">
<h4 data-i18n="TAB_GENERAL/ALERT_WARNING"></h4>
<br />
<div data-i18n="[html]TAB_GENERAL/HTML_ALERT_WEAK_PASSWORD"></div>
</div>
</div>
<div class="form-horizontal">
<div class="legend i18n i18n-animation" data-i18n="TAB_GENERAL/LEGEND_INTERFACE"></div>
<div class="control-group" style="margin-bottom: 10px;">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_LANGUAGE"></label>
<div class="controls">
<div class="flag-selector">
<span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + language().toLowerCase()" style=""></span>
</span>
<span class="flag-name" tabindex="0" data-bind="text: languageFullName, click: selectLanguage, onSpace: selectLanguage, onEnter: selectLanguage"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: languageTrigger }
}"></div>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_LANGUAGE_ADMIN"></label>
<div class="controls">
<div class="flag-selector">
<span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + languageAdmin().toLowerCase()" style=""></span>
</span>
<span class="flag-name" tabindex="0" data-bind="text: languageAdminFullName, click: selectLanguageAdmin, onSpace: selectLanguageAdmin, onEnter: selectLanguageAdmin"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: languageAdminTrigger }
}"></div>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_THEME"></label>
<div class="controls">
<div data-bind="component: {
name: 'Select',
params: {
options: themesOptions,
value: theme,
trigger: themeTrigger,
optionsText: 'optText',
optionsValue: 'optValue',
size: 2
}
}"></div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_LANGUAGES_ON_SETTINGS',
value: allowLanguagesOnSettings
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_THEMES_ON_SETTINGS',
value: capaThemes
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_BACKGROUND_ON_SETTINGS',
value: capaUserBackground
}
}"></div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_SHOW_THUMBNAILS',
value: capaAttachmentThumbnails,
inline: false
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_GRAVATAR',
value: capaGravatar,
inline: true
}
}"></div>
(<a class="g-ui-link" href="http://en.gravatar.com/" target="_blank">http://en.gravatar.com/</a>)
</div>
</div>
<div class="legend i18n i18n-animation" data-i18n="TAB_GENERAL/LEGEND_MAIN"></div>
<div class="control-group">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_ATTACHMENT_SIZE_LIMIT"></label>
<div class="controls">
<div data-bind="component: {
name: 'Input',
params: {
label: 'MB',
value: mainAttachmentLimit,
trigger: attachmentLimitTrigger,
size: 1
}
}"></div>
<br />
<br />
<span class="well well-small" data-bind="visible: '' !== uploadDataDesc">
<b>PHP:</b>
&nbsp;
<span data-bind="text: uploadDataDesc"></span>
</span>
&nbsp;&nbsp;&nbsp;
<a href="#" target="_blank" class="btn btn-thin" data-bind="link: phpInfoLink()">
<i class="icon-info"></i>
</a>
</div>
</div>
<br />
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_ADDITIONAL_ACCOUNTS',
value: capaAdditionalAccounts
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_IDENTITIES',
value: capaIdentities
}
}"></div>
</div>
</div>
<!--
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_TEMPLATES',
value: capaTemplates
}
}"></div>
</div>
</div>
-->
</div>
<div class="b-admin-general">
<div class="row" data-bind="visible: weakPassword">
<div class="alert alert-error span8" style="margin-top: 10px;">
<h4 data-i18n="TAB_GENERAL/ALERT_WARNING"></h4>
<br />
<div data-i18n="[html]TAB_GENERAL/HTML_ALERT_WEAK_PASSWORD"></div>
</div>
</div>
<div class="form-horizontal">
<div class="legend i18n i18n-animation" data-i18n="TAB_GENERAL/LEGEND_INTERFACE"></div>
<div class="control-group" style="margin-bottom: 10px;">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_LANGUAGE"></label>
<div class="controls">
<div class="flag-selector">
<span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + language().toLowerCase()" style=""></span>
</span>
<span class="flag-name" tabindex="0" data-bind="text: languageFullName, click: selectLanguage, onSpace: selectLanguage, onEnter: selectLanguage"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: languageTrigger }
}"></div>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_LANGUAGE_ADMIN"></label>
<div class="controls">
<div class="flag-selector">
<span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + languageAdmin().toLowerCase()" style=""></span>
</span>
<span class="flag-name" tabindex="0" data-bind="text: languageAdminFullName, click: selectLanguageAdmin, onSpace: selectLanguageAdmin, onEnter: selectLanguageAdmin"></span>
&nbsp;&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: languageAdminTrigger }
}"></div>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_THEME"></label>
<div class="controls">
<div data-bind="component: {
name: 'Select',
params: {
options: themesOptions,
value: theme,
trigger: themeTrigger,
optionsText: 'optText',
optionsValue: 'optValue',
size: 2
}
}"></div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_LANGUAGES_ON_SETTINGS',
value: allowLanguagesOnSettings
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_THEMES_ON_SETTINGS',
value: capaThemes
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_BACKGROUND_ON_SETTINGS',
value: capaUserBackground
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_NEW_FOLDER_MOVE',
value: newMoveToFolder
}
}"></div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_SHOW_THUMBNAILS',
value: capaAttachmentThumbnails,
inline: false
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_GRAVATAR',
value: capaGravatar,
inline: true
}
}"></div>
(<a class="g-ui-link" href="http://en.gravatar.com/" target="_blank">http://en.gravatar.com/</a>)
</div>
</div>
<div class="legend i18n i18n-animation" data-i18n="TAB_GENERAL/LEGEND_MAIN"></div>
<div class="control-group">
<label class="control-label" data-i18n="TAB_GENERAL/LABEL_ATTACHMENT_SIZE_LIMIT"></label>
<div class="controls">
<div data-bind="component: {
name: 'Input',
params: {
label: 'MB',
value: mainAttachmentLimit,
trigger: attachmentLimitTrigger,
size: 1
}
}"></div>
<br />
<br />
<span class="well well-small" data-bind="visible: '' !== uploadDataDesc">
<b>PHP:</b>
&nbsp;
<span data-bind="text: uploadDataDesc"></span>
</span>
&nbsp;&nbsp;&nbsp;
<a href="#" target="_blank" class="btn btn-thin" data-bind="link: phpInfoLink()">
<i class="icon-info"></i>
</a>
</div>
</div>
<br />
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_ADDITIONAL_ACCOUNTS',
value: capaAdditionalAccounts
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_IDENTITIES',
value: capaIdentities
}
}"></div>
</div>
</div>
<!--
<div class="control-group">
<div class="controls">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_TEMPLATES',
value: capaTemplates
}
}"></div>
</div>
</div>
-->
</div>
</div>

View file

@ -16,6 +16,7 @@
<div class="b-folders-system" data-bind="template: { name: 'MailFolderListSystemItem', foreach: folderListSystem }"></div>
<hr class="b-list-delimiter" />
<div class="b-folders-user" data-bind="template: { name: 'MailFolderListItem', foreach: folderList }"></div>
<div class="move-action-content-wrapper" data-bind="visible: moveAction"></div>
</div>
</div>
</div>

View file

@ -28,6 +28,7 @@
</a>
</div>
<div class="btn-group" data-bind="visible: allowReload && mobileCheckedStateHide()">&nbsp;</div>
<!-- ko if: !newMoveToFolder() -->
<div class="btn-group dropdown colored-toggle hide-on-mobile" data-bind="visible: allowMessageListActions, registrateBootstrapDropdown: true, openDropdownTrigger: moveDropdownTrigger">
<a id="move-dropdown-id" href="#" tabindex="-1" class="btn single btn-dark-disabled-border dropdown-toggle buttonMove" data-toggle="dropdown" data-tooltip-join="top" data-bind="command: moveCommand, tooltip: 'MESSAGE_LIST/BUTTON_MOVE_TO'">
<i class="icon-copy visible-on-ctrl-btn"></i>
@ -46,6 +47,15 @@
<!-- /ko -->
</ul>
</div>
<!-- /ko -->
<!-- ko if: newMoveToFolder() -->
<div class="btn-group" data-bind="visible: allowMessageListActions && mobileCheckedStateShow()">
<a id="move-dropdown-id" href="#" tabindex="-1" class="btn single btn-dark-disabled-border buttonMove" data-tooltip-join="top" data-bind="command: moveNewCommand, tooltip: 'MESSAGE_LIST/BUTTON_MOVE_TO'">
<i class="icon-copy visible-on-ctrl-btn"></i>
<i class="icon-folder hidden-on-ctrl-btn"></i>
</a>
</div>
<!-- /ko -->
<div class="btn-group" data-bind="visible: allowMessageListActions && mobileCheckedStateHide()">&nbsp;</div>
<div class="btn-group" data-bind="visible: allowMessageListActions && mobileCheckedStateShow()">
<a class="btn first btn-dark-disabled-border button-archive" data-tooltip-join="top"