} 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
);
}
--------
[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).
}
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);
this.updateUnseenStatus(r.view, unseen);
},
+ getUnseenCount: function(mbox)
+ {
+ return Number($(this.getFolderId(mbox)).retrieve('u'));
+ },
+
updateUnseenStatus: function(mbox, unseen)
{
if (this.viewport) {
/* 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;
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;
}
},
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);
//
// 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
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();
this.isbusy = true;
this._clearWait();
- var offset,
+ var callback, offset,
buffer = this._getBuffer(r.view),
llist = buffer.getMetaData('llist') || $H();
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();
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 || {};
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()) {
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;
}
$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)) {
$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) {
<span class="kbd"><?php echo _("Alt") ?></span> + <span class="kbd"><?php echo _("PgUp") ?></span> / <span class="kbd"><?php echo _("PgDown") ?></span> : <?php echo _("Scroll up/down through the display of the previewed message.") ?><br />
<span class="kbd"><?php echo _("Home") ?></span> / <span class="kbd"><?php echo _("End") ?></span> : <?php echo _("Move to the beginning/end of the message list.") ?><br />
<span class="kbd"><?php echo _("Del") ?></span> : <?php echo _("Delete the currently selected message(s).") ?> <?php printf(_("%s will delete the current message and move to the next message if a single message is selected."), '<span class="kbd">' . _("Shift") . '</span> + <span class="kbd">' . _("Del") . '</span>') ?><br />
+ <span class="kbd"><?php echo _("Shift") ?></span> + <span class="kbd"><?php echo _("N") ?></span> : <?php echo _("Move the the next unseen message (non-search folders only).") ?></span><br />
<span class="kbd"><?php echo _("Enter") ?></span> : <?php echo _("Open message in a popup window.") ?><br />
<span class="kbd"><?php echo _("Ctrl") ?></span> + <span class="kbd"><?php echo 'A' ?></span> : <?php echo _("Select all messages in the current mailbox.") ?><br />
</div>