Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions client/resources/css/darkmode.css
Original file line number Diff line number Diff line change
Expand Up @@ -2402,6 +2402,14 @@ body.dark-mode .zarafa-settings-category-rules,
body.dark-mode .icon_flag_Reminder,
body.dark-mode .icon_large_addressbook,
body.dark-mode .icon_large_refresh,
/* Undo history: the range which would be undone (see grommunio.css). */
body.dark-mode .x-menu .x-menu-list li.x-menu-list-item.zarafa-undo-item-included a {
background-color: var(--dm-hover) !important;
color: var(--dm-fg) !important;
}

body.dark-mode .icon_large_undo,
body.dark-mode .icon_large_redo,
body.dark-mode .icon_large_print,
body.dark-mode .icon_large_view,
body.dark-mode .icon_contact_distlist,
Expand Down
20 changes: 19 additions & 1 deletion client/resources/css/grommunio.css
Original file line number Diff line number Diff line change
Expand Up @@ -7069,4 +7069,22 @@ input[type="radio"]:focus-visible {
}

.x-tree-root-node {
min-width: 100%; }
min-width: 100%; }
/* Undo history: hovering an entry marks every entry which would be undone
with it, since clicking one undoes the whole range down to it. Matches the
active-item background so the range reads as one hovered block. */
.x-menu .x-menu-list li.x-menu-list-item.zarafa-undo-item-included a {
background-color: #f2f2f2 !important; }

/* Undo/redo main toolbar buttons */
.icon_large_undo {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSczMicgaGVpZ2h0PSczMicgdmlld0JveD0nLTEuNSAtMS41IDI3IDI3Jz48ZyBmaWxsPSdub25lJyBzdHJva2U9JyMwMDAnIHN0cm9rZS13aWR0aD0nMicgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cG9seWxpbmUgcG9pbnRzPScxIDQgMSAxMCA3IDEwJy8+PHBhdGggZD0nTTMuNTEgMTVhOSA5IDAgMSAwIDIuMTMtOS4zNkwxIDEwJy8+PC9nPjwvc3ZnPg==) !important;
background-repeat: no-repeat !important;
background-position: center center !important;
}

.icon_large_redo {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSczMicgaGVpZ2h0PSczMicgdmlld0JveD0nLTEuNSAtMS41IDI3IDI3Jz48ZyBmaWxsPSdub25lJyBzdHJva2U9JyMwMDAnIHN0cm9rZS13aWR0aD0nMicgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cG9seWxpbmUgcG9pbnRzPScyMyA0IDIzIDEwIDE3IDEwJy8+PHBhdGggZD0nTTIwLjQ5IDE1YTkgOSAwIDEgMS0yLjEzLTkuMzZMMjMgMTAnLz48L2c+PC9zdmc+) !important;
background-repeat: no-repeat !important;
background-position: center center !important;
}
93 changes: 93 additions & 0 deletions client/zarafa/common/KeyMapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,49 @@ Zarafa.common.KeyMapping = Ext.extend(Object, {
// not specifying settingsCfg as we already have an entry of opening item in all views
}];

var undoRedoKeys = [{
key: Ext.EventObject.Z,
ctrl: true,
alt: false,
shift: false,
// Don't stop the event: inside text inputs and the HTML editor
// Ctrl+Z must keep triggering the native text undo. The handler
// checks the event target itself.
stopEvent: false,
handler: this.onUndo,
scope: this,
settingsCfg: {
description: _('Undo the last action'),
category: _('All views')
},
basic: true
},{
key: Ext.EventObject.Y,
ctrl: true,
alt: false,
shift: false,
stopEvent: false,
handler: this.onRedo,
scope: this,
settingsCfg: {
description: _('Redo the last undone action'),
category: _('All views')
},
basic: true
},{
key: Ext.EventObject.Z,
ctrl: true,
alt: false,
shift: true,
stopEvent: false,
handler: this.onRedo,
scope: this
// not specifying settingsCfg as Ctrl+Y is already listed for redo
}];

Zarafa.core.KeyMapMgr.register('global', mainTabBar);
Zarafa.core.KeyMapMgr.register('global', mainToolbarKeys);
Zarafa.core.KeyMapMgr.register('global', undoRedoKeys);

Zarafa.core.KeyMapMgr.register('grid', selectionKey);
Zarafa.core.KeyMapMgr.register('view.mapimessage', selectionKey);
Expand Down Expand Up @@ -343,6 +384,58 @@ Zarafa.common.KeyMapping = Ext.extend(Object, {
refreshButton.handler.call(refreshButton.scope);
},

/**
* Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
* when the user wants to undo the last action. The event is ignored when
* it originates from a text input, so the native text undo keeps working.
* @param {Number} key Key code
* @param {Ext.EventObject} event The event
* @param {Ext.Component} component The component on which key event is fired.
*/
onUndo: function(key, event, component)
{
if (this.isTextEditingTarget(event)) {
return;
}
event.stopEvent();
container.getUndoManager().undo();
},

/**
* Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
* when the user wants to redo the last undone action. The event is ignored
* when it originates from a text input.
* @param {Number} key Key code
* @param {Ext.EventObject} event The event
* @param {Ext.Component} component The component on which key event is fired.
*/
onRedo: function(key, event, component)
{
if (this.isTextEditingTarget(event)) {
return;
}
event.stopEvent();
container.getUndoManager().redo();
},

/**
* Check whether a key event originates from an element in which the user
* is editing text (input, textarea or contenteditable element). Undo/redo
* shortcuts must not be intercepted there.
* @param {Ext.EventObject} event The event to check
* @return {Boolean} True when the event targets a text editing element
* @private
*/
isTextEditingTarget: function(event)
{
var target = event.getTarget();
if (!target) {
return false;
}
var nodeName = target.nodeName ? target.nodeName.toLowerCase() : '';
return nodeName === 'input' || nodeName === 'textarea' || target.isContentEditable === true;
},

/**
* Event handler for the keydown event of the {@link Zarafa.core.KeyMap KeyMap}
* when the user wants to switch between {@link Zarafa.core.Context Contexts}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ Zarafa.common.categories.dialogs.CategoriesContentPanel = Ext.extend(Zarafa.core
}, this);

if (this.autoSave) {
// Record the category change in the undo history. This must be
// done explicitly since ShadowStore saves are not announced
// through the IPMStoreMgr.
container.getUndoManager().capturePropertyGesture(this.record);
this.record[0].getStore().save(this.record);
}

Expand Down
9 changes: 8 additions & 1 deletion client/zarafa/common/flags/dialogs/CustomFlagContentPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,15 @@ Zarafa.common.flags.dialogs.CustomFlagContentPanel = Ext.extend(Zarafa.core.ui.C
record.set(property, flagProperties[property]);
}
record.endEdit();
record.save();
}, this);

if (!Ext.isEmpty(this.record)) {
// Record the flag change in the undo history. This must be done
// explicitly since ShadowStore saves are not announced through
// the IPMStoreMgr.
container.getUndoManager().capturePropertyGesture(this.record);
this.record[0].getStore().save(this.record);
}
this.close();
},

Expand Down
12 changes: 11 additions & 1 deletion client/zarafa/common/flags/ui/FlagsMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,18 @@ Zarafa.common.flags.ui.FlagsMenu = Ext.extend(Zarafa.core.ui.menu.ConditionalMen
record.set(property, flagProperties[property]);
}
record.endEdit();
record.save();
}, this);

if (Ext.isEmpty(records)) {
return;
}

// Record the flag change in the undo history. This must be done
// explicitly since ShadowStore saves are not announced through the
// IPMStoreMgr. Saving all records in one batch makes the whole
// gesture one undo entry.
container.getUndoManager().capturePropertyGesture(records);
records[0].getStore().save(records);
}
});

Expand Down
10 changes: 10 additions & 0 deletions client/zarafa/core/Container.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,16 @@ Zarafa.core.Container = Ext.extend(Ext.util.Observable, {
return this.notifier || (this.notifier = new Zarafa.core.ui.notifier.Notifier());
},

/**
* Returns the {@link Zarafa.core.data.UndoManager UndoManager} instance which
* records undoable user actions and performs undo/redo operations.
* @return {Zarafa.core.data.UndoManager} the UndoManager instance.
*/
getUndoManager: function()
{
return this.undoManager || (this.undoManager = new Zarafa.core.data.UndoManager());
},

/**
* Returns the application main panel.
* @return {Zarafa.core.ui.MainViewport} the application main panel.
Expand Down
26 changes: 26 additions & 0 deletions client/zarafa/core/data/ProxyResponseHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,32 @@ Zarafa.core.data.ProxyResponseHandler = Ext.extend(Zarafa.core.data.AbstractResp
return ret;
},

/**
* Handles the 'success' response. Normally this response carries no data,
* but for operations for which the client requested undo tracking (via the
* 'track_new_entryids' message action) the server includes an 'undo'
* object describing the new location of the affected items. This object
* is placed on the {@link #sendRecords} as 'undoResponse' so the
* {@link Zarafa.core.data.UndoManager UndoManager} can pick it up after
* the write has completed.
* @param {Object} response The response object belonging to the given command.
*/
doSuccess: function(response)
{
if (Ext.isEmpty(this.sendRecords)) {
return;
}
Ext.each(this.sendRecords, function(record) {
if (response && response.undo) {
record.undoResponse = response.undo;
} else {
// Never leave a stale mapping from an earlier operation on
// the record, or a later untracked save could pick it up.
delete record.undoResponse;
}
});
},

/**
* Handles the 'error' response. This means that the Request has failed
* due to a problem on the PHP-side. This will fire the {@link Ext.data.DataProxy#exception exception}
Expand Down
Loading