From: Michael M Slusarz Date: Tue, 15 Dec 2009 01:27:21 +0000 (-0700) Subject: Request #8223: Add keyboard shortcut to move to next unseen message X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=34090faab8c59bf3af4e88cde323f64bd800c839;p=horde.git Request #8223: Add keyboard shortcut to move to next unseen message --- diff --git a/imp/ajax.php b/imp/ajax.php index dcbd5cbae..240930bdc 100644 --- a/imp/ajax.php +++ b/imp/ajax.php @@ -94,8 +94,9 @@ function _getListMessages($mbox, $change) } else { $search = Horde_Serialize::unserialize($search, Horde_Serialize::JSON); $args += array( - 'search_uid' => $search->imapuid, - 'search_view' => $search->view, + 'search_uid' => isset($search->imapuid) ? $search->imapuid : null, + 'search_unseen' => isset($search->unseen) ? $search->unseen : null, + 'search_view' => isset($search->view) ? $search->view : null ); } diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES index 8596e32d3..d221d785c 100644 --- a/imp/docs/CHANGES +++ b/imp/docs/CHANGES @@ -3,6 +3,7 @@ v5.0-git -------- [mms] Add auto-save draft to IMP (Request #7656). +[mms] Add keyboard shortcut to move to next unseen message (Request #8223). [mms] Add hook to dynamically change mailbox label (Request #6734). [mms] Improved address expansion in MIMP. [mms] Load folders on-demand in sidebar (DIMP). diff --git a/imp/js/DimpBase.js b/imp/js/DimpBase.js index 9f1dc290b..96676c799 100644 --- a/imp/js/DimpBase.js +++ b/imp/js/DimpBase.js @@ -1274,7 +1274,7 @@ var DimpBase = { } sel = this.viewport.createSelection('dataob', r); - unseen = Number($(this.getFolderId(r.view)).retrieve('u')); + unseen = this.getUnseenCount(r.view); unseen += setflag ? -1 : 1; this.updateFlag(sel, '\\seen', setflag); @@ -1282,6 +1282,11 @@ var DimpBase = { this.updateUnseenStatus(r.view, unseen); }, + getUnseenCount: function(mbox) + { + return Number($(this.getFolderId(mbox)).retrieve('u')); + }, + updateUnseenStatus: function(mbox, unseen) { if (this.viewport) { @@ -1528,7 +1533,8 @@ var DimpBase = { /* Keydown event handler */ keydownHandler: function(e) { - var co, form, h, pp, ps, r, row, rowoff, sel, + var all, cnt, co, form, h, need, pp, ps, r, row, rownum, rowoff, sel, + tmp, vsel, elt = e.element(), kc = e.keyCode || e.charCode; @@ -1672,6 +1678,79 @@ var DimpBase = { e.stop(); } break; + + case 78: // N + case 110: // n + if (e.shiftKey && !this.isSearch(this.folder)) { + cnt = this.getUnseenCount(this.folder); + if (Object.isUndefined(cnt) || cnt) { + vsel = this.viewport.getSelection(); + row = vsel.search({ flag: { include: '\\seen' } }).get('rownum'); + all = (vsel.size() == this.viewport.getMetaData('total_rows')); + + if (all || + (!Object.isUndefined(cnt) && row.size() == cnt)) { + // Here we either have the entire mailbox in buffer, + // or all unseen messages are in the buffer. + if (sel.size()) { + tmp = sel.get('rownum').last(); + if (tmp) { + rownum = row.detect(function(r) { + return tmp < r; + }); + } + } else { + rownum = tmp = row.first(); + } + } else { + // Here there is no guarantee that the next unseen + // message will appear in the current buffer. Need to + // determine if any gaps are between last selected + // message and next unseen message in buffer. + vsel = vsel.get('rownum'); + + if (sel.size()) { + // We know that the selected rows are in the + // buffer. + tmp = sel.get('rownum').last(); + } else if (vsel.include(1)) { + // If no selected rows, start searching from the + // first entry. + tmp = 0; + } else { + // First message is not in current buffer. + need = true; + } + + if (!need) { + rownum = vsel.detect(function(r) { + if (r > tmp) { + if (++tmp != r) { + // We have found a gap. + need = true; + throw $break; + } + return row.include(tmp); + } + }); + + if (!need && !rownum) { + need = (tmp !== this.viewport.getMetaData('total_rows')); + } + } + + if (need) { + this.viewport.select(null, { search: { unseen: 1 } }); + } + } + + if (rownum) { + this.moveSelected(rownum, true); + } + } + e.stop(); + } + break; } }, diff --git a/imp/js/ViewPort.js b/imp/js/ViewPort.js index 43de85ea1..ec0e9e401 100644 --- a/imp/js/ViewPort.js +++ b/imp/js/ViewPort.js @@ -212,8 +212,14 @@ var ViewPort = Class.create({ this.page_size = ps; } - if (this.view && (view != this.view)) { + if (this.view) { if (!background) { + // If this is the same view, send an ajax request with the + // given parameters. + if (view == this.view) { + return this._fetchBuffer({ search: search }); + } + // Need to store current buffer to save current offset buffer = this._getBuffer(); buffer.setMetaData({ offset: this.currentOffset() }, true); @@ -497,6 +503,7 @@ var ViewPort = Class.create({ // // OPTIONAL: // background: (boolean) Do fetch in background + // callback: (function) A callback to run when the request is complete // initial: (boolean) Is this the initial access to this view? // nearing: (string) TODO [only used w/offset] // params: (object) Parameters to add to outgoing URL @@ -589,6 +596,12 @@ var ViewPort = Class.create({ params.update({ slice: rlist.first() + ':' + rlist.last() }); } + if (opts.callback) { + tmp = b.getMetaData('callback') || $H(); + tmp.set(r_id, opts.callback); + b.setMetaData({ callback: tmp }, true); + } + if (!opts.background) { this.active_req = r_id; this._handleWait(); @@ -744,7 +757,7 @@ var ViewPort = Class.create({ this.isbusy = true; this._clearWait(); - var offset, + var callback, offset, buffer = this._getBuffer(r.view), llist = buffer.getMetaData('llist') || $H(); @@ -766,8 +779,15 @@ var ViewPort = Class.create({ if (r.requestid && r.requestid == this.active_req) { this.active_req = null; + callback = buffer.getMetaData('callback'); offset = buffer.getMetaData('req_offset'); - buffer.setMetaData({ req_offset: undefined }); + + if (callback && callback.get(r.requestid)) { + callback.get(r.requestid)(r); + callback.unset(r.requestid); + } + + buffer.setMetaData({ callback: undefined, req_offset: undefined }, true); if (this.opts.onEndFetch) { this.opts.onEndFetch(); @@ -1108,9 +1128,18 @@ var ViewPort = Class.create({ return this.createSelection('uid', buffer ? buffer.getAllUIDs() : [], view); }, - // vs = (Viewport_Selection | array) A Viewport_Selection object -or-, if + getMissing: function(view) + { + var buffer = this._getBuffer(view); + if (!buffer) { + return ''; + } + return this.createSelection('uid', buffer ? buffer.getAllUIDs() : [], view); + }, + + // vs = (Viewport_Selection | array) A Viewport_Selection object -or- if // opts.range is set, an array of row numbers. - // opts = (object) TODO [add, range] + // opts = (object) TODO [add, range, search] select: function(vs, opts) { opts = opts || {}; @@ -1118,6 +1147,21 @@ var ViewPort = Class.create({ var b = this._getBuffer(), sel, slice; + if (opts.search) { + if (this.opts.onFetch) { + this.opts.onFetch(); + } + + return this._fetchBuffer({ + callback: function(r) { + if (r.rownum) { + this.select(this.createSelection('rownum', [ r.rownum ]), { add: opts.add, range: opts.range }); + } + }.bind(this), + search: opts.search + }); + } + if (opts.range) { slice = this.createSelection('rownum', vs); if (vs.size() != slice.size()) { @@ -1125,8 +1169,7 @@ var ViewPort = Class.create({ this.opts.onFetch(); } - this._ajaxRequest({ rangeslice: 1, slice: vs.min() + ':' + vs.size() }); - return; + return this._ajaxRequest({ rangeslice: 1, slice: vs.min() + ':' + vs.size() }); } vs = slice; } diff --git a/imp/lib/Views/ListMessages.php b/imp/lib/Views/ListMessages.php index f0ef826b2..76aa51539 100644 --- a/imp/lib/Views/ListMessages.php +++ b/imp/lib/Views/ListMessages.php @@ -95,42 +95,6 @@ class IMP_Views_ListMessages $result->cacheid = $imp_mailbox->getCacheID(); $result->totalrows = $msgcount; - /* If this is the initial request for a mailbox, figure out the - * starting location based on user's preferences. */ - $rownum = $args['initial'] - ? intval($imp_mailbox->mailboxStart($msgcount)) - : null; - - /* Determine the row slice to process. */ - if (isset($args['search_uid']) || !is_null($rownum)) { - if (is_null($rownum)) { - $rownum = 1; - foreach (array_keys($sorted_list['s'], $args['search_uid']) as $val) { - if (empty($sorted_list['m'][$val]) || - ($sorted_list['m'][$val] == $args['search_mbox'])) { - $rownum = $val; - break; - } - } - } - - $slice_start = $rownum - $args['before']; - $slice_end = $rownum + $args['after']; - if ($slice_start < 1) { - $slice_end += abs($slice_start) + 1; - } elseif ($slice_end > $msgcount) { - $slice_start -= $slice_end - $msgcount; - } - - $result->rownum = $rownum; - } else { - $slice_start = $args['slice_start']; - $slice_end = $args['slice_end']; - } - - $slice_start = max(1, $slice_start); - $slice_end = min($msgcount, $slice_end); - /* Mail-specific viewport information. */ $md = &$result->metadata; if (!IMP::threadSortAvailable($mbox)) { @@ -198,6 +162,53 @@ class IMP_Views_ListMessages $cached = array_flip($cached); } + if (isset($args['search_unseen'])) { + /* Do an unseen search. We know what messages the browser + * doesn't have based on $cached. Thus, search for the first + * unseen message not located in $cached. */ + $unseen_search = $imp_mailbox->unseenMessages(Horde_Imap_Client::SORT_RESULTS_MATCH, true); + if (!($uid_search = array_diff($unseen_search['match'], array_keys($cached)))) { + return $result; + } + $rownum = array_search(reset($uid_search), $sorted_list['s']); + } else { + /* If this is the initial request for a mailbox, figure out the + * starting location based on user's preferences. */ + $rownum = $args['initial'] + ? intval($imp_mailbox->mailboxStart($msgcount)) + : null; + } + + /* Determine the row slice to process. */ + if (isset($args['search_uid']) || !is_null($rownum)) { + if (is_null($rownum)) { + $rownum = 1; + foreach (array_keys($sorted_list['s'], $args['search_uid']) as $val) { + if (empty($sorted_list['m'][$val]) || + ($sorted_list['m'][$val] == $args['search_mbox'])) { + $rownum = $val; + break; + } + } + } + + $slice_start = $rownum - $args['before']; + $slice_end = $rownum + $args['after']; + if ($slice_start < 1) { + $slice_end += abs($slice_start) + 1; + } elseif ($slice_end > $msgcount) { + $slice_start -= $slice_end - $msgcount; + } + + $result->rownum = $rownum; + } else { + $slice_start = $args['slice_start']; + $slice_end = $args['slice_end']; + } + + $slice_start = max(1, $slice_start); + $slice_end = min($msgcount, $slice_end); + /* Generate the message list and the UID -> rownumber list. */ $data = $msglist = $rowlist = array(); foreach (range($slice_start, $slice_end) as $key) { diff --git a/imp/templates/index/index-dimp.inc b/imp/templates/index/index-dimp.inc index d1576faef..6b972bda6 100644 --- a/imp/templates/index/index-dimp.inc +++ b/imp/templates/index/index-dimp.inc @@ -210,6 +210,7 @@ function _simpleButton($id, $text, $image) + / :
/ :
: ' . _("Shift") . ' + ' . _("Del") . '') ?>
+ + :
:
+ :