Merge DIMP with IMP.
authorMichael M Slusarz <slusarz@curecanti.org>
Mon, 1 Dec 2008 06:35:28 +0000 (23:35 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Mon, 1 Dec 2008 22:28:05 +0000 (15:28 -0700)
None of this works yet.

166 files changed:
imp/compose-dimp.php [new file with mode: 0644]
imp/config/conf.xml
imp/config/hooks.php.dist
imp/config/menu.php.dist
imp/config/portal.php.dist [new file with mode: 0644]
imp/config/prefs.php.dist
imp/docs/CREDITS
imp/docs/INSTALL
imp/imp-dimp.php [new file with mode: 0644]
imp/imple.php
imp/index-dimp.php [new file with mode: 0644]
imp/js/ContextSensitive.js [new file with mode: 0644]
imp/js/DimpBase.js [new file with mode: 0644]
imp/js/DimpCore.js [new file with mode: 0644]
imp/js/DimpSlider.js [new file with mode: 0644]
imp/js/ViewPort.js [new file with mode: 0644]
imp/js/compose-dimp.js [new file with mode: 0644]
imp/js/dragdrop.js [new file with mode: 0644]
imp/js/fullmessage-dimp.js [new file with mode: 0644]
imp/js/src/ContextSensitive.js [new file with mode: 0644]
imp/js/src/DimpBase.js [new file with mode: 0644]
imp/js/src/DimpCore.js [new file with mode: 0644]
imp/js/src/DimpSlider.js [new file with mode: 0644]
imp/js/src/ViewPort.js [new file with mode: 0644]
imp/js/src/compose-dimp.js [new file with mode: 0644]
imp/js/src/dragdrop.js [new file with mode: 0644]
imp/js/src/fullmessage-dimp.js [new file with mode: 0644]
imp/lib/Block/foldersummary.php [new file with mode: 0644]
imp/lib/Block/newmail.php [new file with mode: 0644]
imp/lib/DIMP.php [new file with mode: 0644]
imp/lib/Notification/Listener/status-dimp.php [new file with mode: 0644]
imp/lib/Views/Compose.php [new file with mode: 0644]
imp/lib/Views/ListMessages.php [new file with mode: 0644]
imp/lib/Views/ShowMessage.php [new file with mode: 0644]
imp/lib/api.php
imp/lib/base.php
imp/message-dimp.php [new file with mode: 0644]
imp/templates/chunks/compose.php [new file with mode: 0644]
imp/templates/chunks/message.php [new file with mode: 0644]
imp/templates/imp/compose.html [new file with mode: 0644]
imp/templates/imp/portal.html [new file with mode: 0644]
imp/templates/index/index.inc [new file with mode: 0644]
imp/templates/javascript/mailbox.js [new file with mode: 0644]
imp/themes/bluewhite/screen-dimp.css [new file with mode: 0644]
imp/themes/graphics/add_contact.png [new file with mode: 0644]
imp/themes/graphics/arrow_collapsed.png [new file with mode: 0644]
imp/themes/graphics/arrow_expanded.png [new file with mode: 0644]
imp/themes/graphics/backhead_orderby.png [new file with mode: 0644]
imp/themes/graphics/backhead_r.png [new file with mode: 0644]
imp/themes/graphics/backhead_s2.png [new file with mode: 0644]
imp/themes/graphics/backhead_shadow.png [new file with mode: 0644]
imp/themes/graphics/blacklist.png [new file with mode: 0644]
imp/themes/graphics/checkbox_off.png [new file with mode: 0755]
imp/themes/graphics/checkbox_on.png [new file with mode: 0755]
imp/themes/graphics/checkbox_over.png [new file with mode: 0644]
imp/themes/graphics/checkmail.png [new file with mode: 0644]
imp/themes/graphics/checkmail_menu.png [new file with mode: 0644]
imp/themes/graphics/compose_menu.png [new file with mode: 0644]
imp/themes/graphics/delete_menu.png [new file with mode: 0644]
imp/themes/graphics/dimp.png [new file with mode: 0644]
imp/themes/graphics/drafts.png [new file with mode: 0644]
imp/themes/graphics/drafts_menu.png [new file with mode: 0644]
imp/themes/graphics/dragHandle.png [new file with mode: 0644]
imp/themes/graphics/error.png [new file with mode: 0644]
imp/themes/graphics/folder.png [new file with mode: 0644]
imp/themes/graphics/folder_create.png [new file with mode: 0644]
imp/themes/graphics/folder_drafts.png [new file with mode: 0644]
imp/themes/graphics/folder_inbox.png [new file with mode: 0644]
imp/themes/graphics/folder_minus.png [new file with mode: 0644]
imp/themes/graphics/folder_plus.png [new file with mode: 0644]
imp/themes/graphics/folder_sent.png [new file with mode: 0644]
imp/themes/graphics/folder_spam.png [new file with mode: 0644]
imp/themes/graphics/folder_trash.png [new file with mode: 0644]
imp/themes/graphics/forward.png [new file with mode: 0644]
imp/themes/graphics/forward_menu.png [new file with mode: 0644]
imp/themes/graphics/ham.png [new file with mode: 0644]
imp/themes/graphics/ham_menu.png [new file with mode: 0644]
imp/themes/graphics/ico_message_off.png [new file with mode: 0644]
imp/themes/graphics/ie6_or_less-dimp.css [new file with mode: 0644]
imp/themes/graphics/ie7-dimp.css [new file with mode: 0644]
imp/themes/graphics/key_down.png [new file with mode: 0644]
imp/themes/graphics/key_up.png [new file with mode: 0644]
imp/themes/graphics/logo.png [new file with mode: 0644]
imp/themes/graphics/logout.png [new file with mode: 0644]
imp/themes/graphics/mail_forwarded.png
imp/themes/graphics/message.png [new file with mode: 0644]
imp/themes/graphics/message_source.png [new file with mode: 0644]
imp/themes/graphics/newwin.png [new file with mode: 0644]
imp/themes/graphics/plus_menu.png [new file with mode: 0644]
imp/themes/graphics/preview.png [new file with mode: 0644]
imp/themes/graphics/print-dimp.css [new file with mode: 0644]
imp/themes/graphics/reply.png [new file with mode: 0644]
imp/themes/graphics/reply_menu.png [new file with mode: 0644]
imp/themes/graphics/replyall.png [new file with mode: 0644]
imp/themes/graphics/sbcursor_bottom.png [new file with mode: 0644]
imp/themes/graphics/sbcursor_top.png [new file with mode: 0644]
imp/themes/graphics/screen-dimp.css [new file with mode: 0644]
imp/themes/graphics/scroller.png [new file with mode: 0644]
imp/themes/graphics/scroller_back.png [new file with mode: 0644]
imp/themes/graphics/select.png [new file with mode: 0644]
imp/themes/graphics/sortdown.png [new file with mode: 0644]
imp/themes/graphics/sortup.png [new file with mode: 0644]
imp/themes/graphics/spam.png [new file with mode: 0644]
imp/themes/graphics/spam_menu.png [new file with mode: 0644]
imp/themes/graphics/spellcheck_menu.png [new file with mode: 0644]
imp/themes/graphics/success.png [new file with mode: 0644]
imp/themes/graphics/tick.png [new file with mode: 0644]
imp/themes/graphics/warning.png [new file with mode: 0644]
imp/themes/graphics/whitelist.png [new file with mode: 0644]
imp/themes/silver/graphics/add_contact.png [new file with mode: 0644]
imp/themes/silver/graphics/arrow_collapsed.png [new file with mode: 0644]
imp/themes/silver/graphics/arrow_expanded.png [new file with mode: 0644]
imp/themes/silver/graphics/backhead_orderby.png [new file with mode: 0644]
imp/themes/silver/graphics/backhead_r.png [new file with mode: 0644]
imp/themes/silver/graphics/backhead_s2.png [new file with mode: 0644]
imp/themes/silver/graphics/backhead_shadow.png [new file with mode: 0644]
imp/themes/silver/graphics/blacklist.png [new file with mode: 0644]
imp/themes/silver/graphics/checkmail.png [new file with mode: 0644]
imp/themes/silver/graphics/checkmail_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/compose.png
imp/themes/silver/graphics/compose_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/delete.png [new file with mode: 0644]
imp/themes/silver/graphics/delete_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/dimp.png [new file with mode: 0644]
imp/themes/silver/graphics/drafts.png [new file with mode: 0644]
imp/themes/silver/graphics/drafts_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/dragHandle.png [new file with mode: 0644]
imp/themes/silver/graphics/error.png [new file with mode: 0644]
imp/themes/silver/graphics/folder.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_create.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_delete.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_drafts.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_edit.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_inbox.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_minus.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_open.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_plus.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_sent.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_spam.png [new file with mode: 0644]
imp/themes/silver/graphics/folder_trash.png [new file with mode: 0644]
imp/themes/silver/graphics/forward.png [new file with mode: 0644]
imp/themes/silver/graphics/forward_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/ham.png [new file with mode: 0644]
imp/themes/silver/graphics/ham_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/logo.png [new file with mode: 0644]
imp/themes/silver/graphics/logout.png [new file with mode: 0644]
imp/themes/silver/graphics/message.png [new file with mode: 0644]
imp/themes/silver/graphics/message_source.png [new file with mode: 0644]
imp/themes/silver/graphics/newwin.png [new file with mode: 0644]
imp/themes/silver/graphics/plus_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/preview.png [new file with mode: 0644]
imp/themes/silver/graphics/reply.png [new file with mode: 0644]
imp/themes/silver/graphics/reply_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/replyall.png [new file with mode: 0644]
imp/themes/silver/graphics/select.png [new file with mode: 0644]
imp/themes/silver/graphics/sortdown.png [new file with mode: 0644]
imp/themes/silver/graphics/sortup.png [new file with mode: 0644]
imp/themes/silver/graphics/spam.png [new file with mode: 0644]
imp/themes/silver/graphics/spam_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/spellcheck_menu.png [new file with mode: 0644]
imp/themes/silver/graphics/success.png [new file with mode: 0644]
imp/themes/silver/graphics/tick.png [new file with mode: 0644]
imp/themes/silver/graphics/warning.png [new file with mode: 0644]
imp/themes/silver/graphics/whitelist.png [new file with mode: 0644]
imp/themes/silver/screen-dimp.css [new file with mode: 0644]
imp/themes/tango-blue/screen-dimp.css [new file with mode: 0644]

diff --git a/imp/compose-dimp.php b/imp/compose-dimp.php
new file mode 100644 (file)
index 0000000..bcd6741
--- /dev/null
@@ -0,0 +1,347 @@
+<?php
+/**
+ * DIMP Compose page.
+ *
+ * List of potential parameters:
+ *   'popup' - Explicitly mark window as popup. Needed if compose page is
+ *             opened from a page other than the base DIMP page.
+ *   TODO
+ *
+ * $Horde: dimp/compose.php,v 1.118 2008/08/05 05:48:48 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ */
+
+function _removeAutoSaveDraft($index)
+{
+    if (empty($index)) {
+        return;
+    }
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    $idx_array = array($index . IMP_IDX_SEP . IMP::folderPref($GLOBALS['prefs']->getValue('drafts_folder'), true));
+    $imp_message->delete($idx_array, true);
+}
+
+$load_imp = true;
+@define('DIMP_BASE', dirname(__FILE__));
+require_once DIMP_BASE . '/lib/base.php';
+require_once 'Horde/Identity.php';
+
+/* The headers of the message. */
+$header = array();
+foreach (array('to', 'cc', 'bcc', 'subject', 'in_reply_to', 'references') as $v) {
+    $header[$v] = rawurldecode(Util::getFormData($v, ''));
+}
+
+$action = Util::getFormData('action');
+$get_sig = true;
+$msg = '';
+
+$identity = &Identity::singleton(array('imp', 'imp'));
+if (!$prefs->isLocked('default_identity')) {
+    $identity_id = Util::getFormData('identity');
+    if ($identity_id !== null) {
+        $identity->setDefault($identity_id);
+    }
+}
+
+/* Set the current time zone. */
+NLS::setTimeZone();
+
+/* Initialize the IMP_Compose:: object. */
+require_once IMP_BASE . '/lib/Compose.php';
+$imp_compose = &IMP_Compose::singleton(Util::getFormData('composeCache'));
+
+/* Init IMP_UI_Compose:: object. */
+require_once IMP_BASE . '/lib/UI/Compose.php';
+$imp_ui = new IMP_UI_Compose();
+
+if (count($_POST)) {
+    $result = new stdClass;
+    $result->action = $action;
+    $result->success = false;
+
+    /* Update the file attachment information. */
+    if ($action == 'add_attachment') {
+        if ($_SESSION['imp']['file_upload'] &&
+            $imp_compose->addFilesFromUpload('file_')) {
+            $info = DIMP::getAttachmentInfo($imp_compose);
+            $result->success = true;
+            $result->info = end($info);
+            $result->imp_compose = $imp_compose->getMessageCacheId();
+        }
+        IMP::sendHTTPResponse(DIMP::prepareResponse($result, true, false), 'js-json');
+        exit;
+    }
+
+    /* Set the default charset. */
+    $charset = NLS::getEmailCharset();
+    if (!$prefs->isLocked('sending_charset')) {
+        $charset = Util::getFormData('charset', $charset);
+    }
+
+    switch ($action) {
+    case 'auto_save_draft':
+    case 'save_draft':
+        /* Set up the From address based on the identity. */
+        $from = $identity->getFromLine(null, Util::getFormData('from'));
+        if (is_a($from, 'PEAR_Error')) {
+            $notification->push($from);
+            break;
+        }
+        $header['from'] = $from;
+
+        /* Save the draft. */
+        $res = $imp_compose->saveDraft($header, Util::getFormData('message', ''), NLS::getCharset(), Util::getFormData('html'));
+        if (is_a($res, 'PEAR_Error')) {
+            $notification->push($res->getMessage(), 'horde.error');
+        } else {
+            $result->success = true;
+
+            /* Delete existing draft. */
+            _removeAutoSaveDraft(Util::getFormData('draft_index'));
+
+            if ($action == 'auto_save_draft') {
+                /* Just update the last draft index so subsequent
+                 * drafts are properly replaced. */
+                $result->draft_index = (int)$imp_compose->saveDraftIndex();
+            } else {
+                $notification->push($res);
+            }
+        }
+        break;
+
+    case 'send_message':
+        $from = $identity->getFromLine(null, Util::getFormData('from'));
+        if (is_a($from, 'PEAR_Error')) {
+            $notification->push($from);
+            break;
+        }
+        $header['from'] = $from;
+        $header['replyto'] = $identity->getValue('replyto_addr');
+
+        $header['to'] = $imp_ui->getAddressList(Util::getFormData('to'), Util::getFormData('to_list'), Util::getFormData('to_field'), Util::getFormData('to_new'));
+        if ($prefs->getValue('compose_cc')) {
+            $header['cc'] = $imp_ui->getAddressList(Util::getFormData('cc'), Util::getFormData('cc_list'), Util::getFormData('cc_field'), Util::getFormData('cc_new'));
+        }
+        if ($prefs->getValue('compose_bcc')) {
+            $header['bcc'] = $imp_ui->getAddressList(Util::getFormData('bcc'), Util::getFormData('bcc_list'), Util::getFormData('bcc_field'), Util::getFormData('bcc_new'));
+        }
+
+        $message = Util::getFormData('message');
+        $html = Util::getFormData('html');
+
+        $result->reply_type = Util::getFormData('reply_type');
+        $result->index = Util::getFormData('index');
+        $result->reply_folder = Util::getFormData('folder');
+
+        /* Use IMP_Tree to determine whether the sent mail folder was
+         * created. */
+        require_once IMP_BASE . '/lib/IMAP/Tree.php';
+        $imptree = &IMP_Tree::singleton();
+        $imptree->eltDiffStart();
+
+        /* Create the DIMP User-Agent string. */
+        require_once DIMP_BASE . '/lib/version.php';
+        $useragent = 'Dynamic Internet Messaging Program (DIMP) ' . DIMP_VERSION;
+
+        $options = array(
+            'save_sent' => (($prefs->isLocked('save_sent_mail'))
+                            ? $identity->getValue('save_sent_mail')
+                            : (bool)Util::getFormData('save_sent_mail')),
+            'sent_folder' => $identity->getValue('sent_mail_folder'),
+            'save_attachments' => Util::getFormData('save_attachments_select'),
+            'reply_type' => $result->reply_type,
+            'reply_index' => $result->index . IMP_IDX_SEP . $result->reply_folder,
+            'readreceipt' => Util::getFormData('request_read_receipt'),
+            'useragent' => $useragent
+        );
+        $sent = $imp_compose->buildAndSendMessage($message, $header, $charset, $html, $options);
+
+        if (is_a($sent, 'PEAR_Error')) {
+            $notification->push($sent, 'horde.error');
+            break;
+        }
+        $result->success = true;
+
+        /* Remove any auto-saved drafts. */
+        if ($prefs->getValue('auto_save_drafts') ||
+            $prefs->getValue('auto_delete_drafts')) {
+            _removeAutoSaveDraft(Util::getFormData('draft_index'));
+            $result->draft_delete = true;
+        }
+
+        if ($sent && $prefs->getValue('compose_confirm')) {
+            $notification->push(_("Message sent successfully."), 'horde.success');
+        }
+
+        $res = DIMP::getFolderResponse($imptree);
+        if (!empty($res)) {
+            $result->folder = $res['a'][0];
+        }
+    }
+
+    IMP::sendHTTPResponse(DIMP::prepareResponse($result, !$result->success || !Util::getFormData('nonotify'), false), 'json');
+    exit;
+}
+
+/* Attach spellchecker & auto completer. */
+require_once DIMP_BASE . '/lib/Dimple.php';
+$imp_ui->attachAutoCompleter('Dimple', array('to', 'cc', 'bcc'));
+$imp_ui->attachSpellChecker('dimp');
+
+$type = Util::getFormData('type');
+$index = Util::getFormData('uid');
+$folder = Util::getFormData('folder');
+$show_editor = false;
+$title = _("New Message");
+
+if (in_array($type, array('reply', 'reply_all', 'reply_list', 'forward_all', 'forward_body', 'forward_attachments', 'resume'))) {
+    if (!$index || !$folder) {
+        $type = 'new';
+    }
+
+    require_once IMP_BASE . '/lib/MIME/Contents.php';
+    $imp_contents = &IMP_Contents::singleton($index . IMP_IDX_SEP . $folder);
+    if (is_a($imp_contents, 'PEAR_Error')) {
+        $notification->push(_("Requested message not found."), 'horde.error');
+        $index = $folder = null;
+        $type = 'new';
+    }
+}
+
+switch ($type) {
+case 'reply':
+case 'reply_all':
+case 'reply_list':
+    $reply_msg = $imp_compose->replyMessage($type, $imp_contents, Util::getFormData('to'));
+    $msg = $reply_msg['body'];
+    $header = $reply_msg['headers'];
+    $header['replytype'] = 'reply';
+
+    if ($type == 'reply') {
+        $title = _("Reply:");
+    } elseif ($type == 'reply_all') {
+        $title = _("Reply to All:");
+    } elseif ($type == 'reply_list') {
+        $title = _("Reply to List:");
+    }
+    $title .= ' ' . $header['subject'];
+
+    if ($reply_msg['format'] == 'html') {
+        $show_editor = true;
+    }
+
+    if (!$prefs->isLocked('default_identity') && !is_null($reply_msg['identity'])) {
+        $identity->setDefault($reply_msg['identity']);
+    }
+    break;
+
+case 'forward_all':
+case 'forward_body':
+case 'forward_attachments':
+    $fwd_msg = $imp_ui->getForwardData($imp_compose, $imp_contents, $type, $index . IMP_IDX_SEP . $folder);
+    if ($type == 'forward_all') {
+        $msg = '';
+    } else {
+        $msg = $fwd_msg['body'];
+    }
+    $header = $fwd_msg['headers'];
+    $header['replytype'] = 'forward';
+    $title = $header['title'];
+    if ($fwd_msg['format'] == 'html') {
+        $show_editor = true;
+    }
+    $type = 'forward';
+
+    if (!$prefs->isLocked('default_identity') && !is_null($fwd_msg['identity'])) {
+        $identity->setDefault($fwd_msg['identity']);
+    }
+    break;
+
+case 'resume':
+    $result = $imp_compose->resumeDraft($index . IMP_IDX_SEP . $folder);
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push($result->getMessage(), 'horde.error');
+    } else {
+        if ($result['mode'] == 'html') {
+            $show_editor = true;
+        }
+        $msg = $result['msg'];
+        if (!is_null($result['identity']) &&
+            !$prefs->isLocked('default_identity')) {
+            $identity->setDefault($result['identity']);
+        }
+        $header = array_merge($header, $result['header']);
+    }
+    $get_sig = false;
+    break;
+
+case 'new':
+    $rte = ($browser->hasFeature('rte') && $prefs->getValue('compose_html'));
+    if ($rte) {
+        $show_editor = true;
+    }
+    break;
+}
+
+$sig = $identity->getSignature();
+if ($get_sig && !empty($sig)) {
+    if ($show_editor) {
+        $sig = '<p><!--begin_signature-->' . $imp_compose->text2html(trim($sig)) . '<!--end_signature--></p>';
+    }
+
+    $msg = ($identity->getValue('sig_first'))
+        ? "\n" . $sig . $msg
+        : $msg . "\n" . $sig;
+}
+
+$args = array(
+    'folder' => $folder,
+    'index' => $index,
+    'composeCache' => $imp_compose->getCacheId(),
+    'qreply' => false,
+);
+
+require_once IMP_BASE . '/lib/Template.php';
+$t = new IMP_Template(DIMP_TEMPLATES . '/imp/');
+$t->setOption('gettext', true);
+$t->set('title', $title);
+$t->set('closelink', Horde::img('close.png', 'X', array('id' => 'compose_close'), $registry->getImageDir('horde')));
+
+$compose_result = DIMP_Views_Compose::showCompose($args);
+$t->set('compose_html', $compose_result['html']);
+
+/* Javscript variables to be set immediately. */
+$compose_result['js'][] = 'DIMP.conf_compose.show_editor = ' . intval($show_editor);
+if (Util::getFormData('popup')) {
+    $compose_result['js'][] = 'DIMP.conf_compose.popup = true';
+}
+IMP::addInlineScript($compose_result['js']);
+
+/* Some actions, like adding forwards, may return error messages so explicitly
+ * display those messages now. */
+IMP::addInlineScript(array(DIMP::notify()), 'dom');
+
+/* Javascript to be run on window load. */
+require_once 'Horde/Serialize.php';
+$compose_result['js_onload'][] = 'DimpCompose.fillForm(' . Horde_Serialize::serialize($msg, SERIALIZE_JSON) . ', ' . Horde_Serialize::serialize($header, SERIALIZE_JSON) . ', "' . (($type == 'new' || $type == 'forward') ? 'to' : 'message') . '", true)';
+IMP::addInlineScript($compose_result['js_onload'], 'load');
+
+$scripts = array(
+    array('compose.js', 'dimp', true)
+);
+
+DIMP::header(_("Message Composition"), $scripts);
+echo $t->fetch('compose.html');
+IMP::includeScriptFiles();
+IMP::outputInlineScript();
+echo $compose_result['jsappend'];
+echo "</body>\n</html>";
index 36cadbb..cec9f2c 100644 (file)
    them?">20</configinteger>
   </configsection>
  </configtab>
+
+ <configtab name="dimp" desc="Dynamic View (dimp) Options">
+  <configheader>Custom Hooks</configheader>
+  <configsection name="hooks">
+   <configboolean name="mailboxarray" required="false" desc="Should we
+   run a custom function in which array elements can be added to use in the
+   template file, while processing the message lists? If so, make sure
+   you define _dimp_hook_mailboxarray() in config/hooks.php. The elements
+   will be available as template tags in the dimp/templates/imp/mailbox.html
+   template file.">false</configboolean>
+   <configboolean name="previewview" required="false" desc="Should we
+   run a custom function in which array elements can be modified, and javascript
+   code be provided to dynamically update page elements in the preview pane?
+   If so, make sure you define _dimp_hook_previewview() in config/hooks.php. The
+   sample documentation in that file can better explain what can be accomplished
+   with this hook.">false</configboolean>
+   <configboolean name="messageview" required="false" desc="Should we
+   run a custom function in which array elements can be added to use in the
+   template file, while displaying the message text? If so, make sure
+   you define _dimp_hook_messageview() in config/hooks.php. The elements
+   will be available as template tags in the dimp/templates/imp/message.html
+   template file.">false</configboolean>
+   <configboolean name="addressformatting" required="false" desc="Should we
+   run a custom function that is used to format email addresses in message
+   headers? If so, make sure you define _dimp_hook_addressformatting() in
+   config/hooks.php.">false</configboolean>
+   <configboolean name="msglist_format" required="false" desc="Should we use
+   a custom function to provide additional information/custom formatting of
+   messages in the mailbox message list? If so, make sure you define
+  _dimp_hook_msglist_format() in config/hooks.php.">false</configboolean>
+  </configsection>
+
+  <configlist name="css_files" required="false" desc="A list of additional CSS
+  files to load from the dimp/themes/ directory."/>
+
+  <configheader>JavaScript Settings</configheader>
+  <configsection name="js">
+   <configboolean name="debug" required="false" desc="Turn on javascript
+   debugging?  Will cause popup alert windows to appear on various errors.
+   This should be turned off for all production servers.">false</configboolean>
+  </configsection>
+
+  <configheader>ViewPort Settings</configheader>
+  <configsection name="viewport">
+   <configinteger name="buffer_pages" desc="The number of viewable pages to
+   send to the browser per server access when listing
+   messages.">5</configinteger>
+   <configinteger name="limit_factor" desc="When browsing through a message
+   list, if a user comes within this percentage of the end of the current
+   cached viewport, the browser will send a background request to the server
+   to retrieve the next slice of the message list.">35</configinteger>
+   <configinteger name="viewport_wait" desc="How long, in seconds, to wait
+   before displaying an informational message to users that the message list is
+   still being built. Set to 0 to disable messages.">12</configinteger>
+   <configboolean name="background_inbox" required="false" desc="On initial
+   login to the portal page, load the INBOX in the
+   background?">true</configboolean>
+  </configsection>
+
+  <configheader>Search Settings</configheader>
+  <configsection name="search">
+   <configboolean name="search_all" desc="Allow user to search all mailboxes at
+   a single time? Unless your mail server is optimized to perform searches,
+   enabling this can cause massive server load if a user has a large
+   mailbox.">false</configboolean>
+  </configsection>
+
+  <configsection name="menu" desc="Menu Settings">
+   <configheader>Menu Settings</configheader>
+   <configmultienum name="apps" desc="Select any applications that should be
+   linked in DIMP's menu">
+    <values>
+     <configspecial name="list-horde-apps" />
+    </values>
+   </configmultienum>
+  </configsection>
+ </configtab>
 </configuration>
index 00686e2..0fc0556 100644 (file)
@@ -555,3 +555,121 @@ if (!function_exists('_imp_hook_quota')) {
         return array($quota[1] * 1024, $quota[2] * 1024);
     }
 }
+
+
+
+// This is an example hook function for the DIMP mailbox view. This function
+// is called for every message and allows additional information to be added
+// to the array that is passed to the mailbox display template -
+// dimp/templates/javascript/mailbox.js.  The current entry array is passed
+// in, the value returned should be the altered array to use in the
+// template. If you are going to add new columns, you also have to update
+// dimp/templates/index/index.inc to contain the new field in the header and
+// dimp/themes/screen.css to specify the column width.
+
+// if (!function_exists('_dimp_hook_mailboxarray')) {
+//     function _dimp_hook_mailboxarray($msg, $ob) {
+//         $msg['foo'] = true;
+//         return $msg;
+//     }
+// }
+
+// This is an example hook function for the DIMP message view.  This function
+// allows additional information to be added to the array that is passed to
+// the message text display template - dimp/templates/chunks/message.html.
+// The current entry array is passed in (see the showMessage() function in
+// dimp/lib/Views/ShowMessage.php for the format). The value returned should
+// be the altered array to use in the template. See the showMessage() function
+// in dimp/lib/Views/ShowMessage for the base values contained in the original
+// passed-in array.
+
+// if (!function_exists('_dimp_hook_messageview')) {
+//     function _dimp_hook_messageview($msg) {
+//         // Ex.: Add a new foo variable
+//         $msg['foo'] = '<div class="foo">BAR</div>';
+//         return $msg;
+//     }
+// }
+// This is an example hook function for the DIMP preview view.  This function
+// allows additional information to be added to the preview view and its
+// corresponding template - dimp/templates/index/index.inc. The current entry
+// array is passed in (see the showMessage() function in
+// dimp/lib/Views/ShowMessage.php for the format). Since the preview pane is
+// dynamically updated via javascript, all updates other than the base
+// entries must be provided in javascript code to be run at update time. The
+// expected return is a 2 element array - the first element is the original
+// array with any changes made to the initial data. The second element is an
+// array of javascript commands, one command per array value.
+
+// if (!function_exists('_dimp_hook_previewview')) {
+//     function _dimp_hook_previewview($msg) {
+//         // Ex.: Alter the subject
+//         $msg['subject'] .= 'test';
+//
+//         // Ex.: Update the DOM ID 'foo' with the value 'bar'. 'foo' needs
+//         //      to be manually added to the HTML template.
+//         $js_code = array(
+//             "$('foo').update('bar')"
+//         );
+//
+//         return array($msg, $js_code);
+//     }
+// }
+
+// This is an example hook function for the address formatting in email
+// message headers. The argument passed to the function is an object with the
+// following possible properties:
+// 'address'   -  Full address
+// 'display'   -  Display address
+// 'host'      -  Host name
+// 'inner'     -  Trimmed, bare address
+// 'personal'  -  Personal string
+// The return value is the raw string to display for that address. This value
+// must be properly escaped (i.e. htmlspecialchars() used on the portions of
+// the string where appropriate).
+
+// if (!function_exists('_dimp_hook_addressformatting')) {
+//     function _dimp_hook_addressformatting($ob) {
+//         return empty($ob->personal) ? $ob->address : $ob->personal;
+//     }
+// }
+
+// This is an example hook function for displaying additional message
+// information in the message listing screen for a mailbox.  This example hook
+// will add a icon if the message contains attachments and will change the
+// display of the message entry based on the X-Priority header.
+
+// if (!function_exists('_dimp_hook_msglist_format')) {
+//     function _dimp_hook_msglist_format($mailbox, $uid)
+//     {
+//         // Required return (array):
+//         //   'atc'   - Attachment type (either 'signed', 'encrypted', or
+//         //             'attachment').
+//         //   'class' - An array of CSS classnames that will be added to
+//         //             the row.
+//         $ret = array('atc' => '', 'class' => array());
+//
+//         require_once IMP_BASE . '/lib/IMAP/MessageCache.php';
+//         $cache = &IMP_MessageCache::singleton();
+//         $cache_entry = $cache->retrieve($mailbox, array($uid), 8 | 32);
+//         $ob = reset($cache_entry);
+//
+//         // Add attachment information
+//         require_once IMP_BASE . '/lib/UI/Mailbox.php';
+//         $imp_ui = new IMP_UI_Mailbox();
+//         $ret['atc'] = $imp_ui->getAttachmentType($ob->structure);
+//
+//         // Add xpriority information
+//         switch ($ob->header->getXpriority()) {
+//         case 'high':
+//             $ret['class'][] = 'important';
+//             break;
+//
+//         case 'low':
+//             $ret['class'][] = 'unimportant';
+//             break;
+//         }
+//
+//         return $ret;
+//     }
+// }
index 59ec434..dd9f84c 100644 (file)
@@ -10,6 +10,7 @@
  *
  * These attributes are optional:
  *
+ *  'action'    The javascript code for the menu item (dimp only).
  *  'icon'      The filename of an icon to use for the menu item.
  *  'icon_path' The path to the icon if it doesn't exist in the graphics/
  *              directory.
@@ -24,7 +25,8 @@
  *      'icon' =>       'example.png',
  *      'icon_path' =>  'http://www.example.com/images/',
  *      'target' =>     '_blank',
- *      'onclick' =>    ''
+ *      'onclick' =>    '',
+ *      'action' =>     ''
  *  );
  *
  * You can also add a "separator" (a spacer) between menu items.  To add a
diff --git a/imp/config/portal.php.dist b/imp/config/portal.php.dist
new file mode 100644 (file)
index 0000000..ab5d291
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * DIMP portal configuration page.
+ *
+ * Format: An array named $dimp_block_list
+ *         KEY: Block label text
+ *         VALUE: An array with the following entries:
+ *                'ob' => The Horde_Block object to display
+ *
+ *                These entries are optional and will only be used if you need
+ *                to customize the portal output:
+ *                'class' => A CSS class to assign to the containing block.
+ *                           Defaults to "headerbox".
+ *                'domid' => A DOM ID to assign to the containing block
+ *                'tag' => A tag name to add to the template array. Allows
+ *                         the use of <if:block.tag> in custom template files.
+ *
+ * $Horde: dimp/config/portal.php.dist,v 1.7 2008/08/22 04:19:29 slusarz Exp $
+ */
+
+require_once 'Horde/Block.php';
+require_once 'Horde/Block/Collection.php';
+$collection = new Horde_Block_Collection();
+$dimp_block_list = array();
+
+// Show a folder summary of the mailbox.  All polled folders are displayed.
+require_once DIMP_BASE . '/lib/Block/foldersummary.php';
+$dimp_block_list[_("Folder Summary")] = array(
+    'ob' => new Horde_Block_dimp_foldersummary(array())
+);
+
+// Alternate DIMP block - shows details of 'msgs_shown' number of the most
+// recent unseen messages.
+//require_once DIMP_BASE . '/lib/Block/newmail.php';
+//$dimp_block_list[_("Newest Unseen Messages")] = array(
+//    'ob' => new Horde_Block_dimp_newmail(array('msgs_shown' => 3))
+//);
+
+// Show a contact search box.
+// Must include 'turba' in $conf['menu']['apps']
+$dimp_block_list[$collection->getName('turba', 'minisearch')] = array(
+    'ob' => $collection->getBlock('turba', 'minisearch', array())
+);
+
+// Display calendar events
+// Must include 'kronolith' in $conf['menu']['apps']
+$dimp_block_list[$collection->getName('kronolith', 'summary')] = array(
+    'ob' => $collection->getBlock('kronolith', 'summary', array())
+);
index 42a4e2f..9a6cf6c 100644 (file)
@@ -7,7 +7,7 @@
 require_once dirname(__FILE__) . '/../lib/IMP.php';
 require_once dirname(__FILE__) . '/../lib/IMAP.php';
 require_once 'Horde/Imap/Client.php';
-$is_pop3 = isset($_SESSION['imp']) && $_SESSION['imp']['protocol'] == 'pop';
+$is_pop3 = isset($_SESSION['imp']) && ($_SESSION['imp']['protocol'] == 'pop');
 
 $prefGroups['identities'] = array(
     'column' => _("General Options"),
@@ -194,6 +194,13 @@ $prefGroups['mimp'] = array(
     'members' => array('mimp_preview_msg')
 );
 
+$prefGroups['dimp'] = array(
+    'column' => _("Other Options"),
+    'label' => _("DIMP Options"),
+    'desc' => _("Configure DIMP Options."),
+    'members' => array('login_view', 'show_preview')
+);
+
 
 // Personal Information preferences
 
@@ -1446,6 +1453,25 @@ $_prefs['mimp_preview_msg'] = array(
     'desc' => _("Display only the first 250 characters of a message initially?")
 );
 
+// Login preferences (dimp)
+$_prefs['login_view'] = array(
+    'value' => 'portal',
+    'locked' => false,
+    'shared' => false,
+    'type' => 'enum',
+    'enum' => array('portal' => _("Portal"),
+                    'inbox' => _("Inbox")),
+    'desc' => _("The page to view immediately after login in DIMP.")
+);
+
+// Other Implicit DIMP preferences
+$_prefs['show_preview'] = array(
+    'value' => true,
+    'locked' => false,
+    'shared' => false,
+    'type' => 'implicit',
+);
+
 // Other entries (used internally in IMP)
 
 // virtual inbox identifier
index c9e686a..3715a6d 100644 (file)
@@ -1,3 +1,11 @@
+==================
+ Acknowledgements
+==================
+
+Development of DIMP sponsored by Portugal Telecom's SAPO division
+(http://softwarelivre.sapo.pt/).
+
+
 ======================
  IMP Development Team
 ======================
index fd2dc49..0834d9c 100644 (file)
@@ -2,8 +2,6 @@
  Installing IMP 5.0
 ====================
 
-:Last update:   $Date: 2008/09/25 08:01:51 $
-:Revision:      $Revision: 1.95 $
 :Contact:       imp@lists.horde.org
 
 .. contents:: Contents
@@ -325,6 +323,21 @@ The following items are not required, but are strongly **RECOMMENDED**:
    create the information using other server variables, but this process is
    slower and less reliable.
 
+8. PHP version 5.2.5+ (for dimp).
+
+   If using the PHP built-in JSON encoder/decoder (highly recommended as it is
+   hundreds of times faster than the fallback PHP code-based version), you
+   should be using the latest version of PHP. The PECL json module is badly
+   out-of-date (as of version 1.2.1) and does not contain many bug fixes
+   for handling message data that contains invalid characters. Using older
+   versions of the JSON encoder may result in the JSON output to the browser
+   being prematurely terminated, resulting in incomplete or blank screens.
+
+   If you still see this problem after upgrading PHP, your server may have
+   incorrect locale information.  See `Bug #5955`_ for further information.
+
+.. _`Bug #5955`: http://bugs.horde.org/ticket/?id=5955#c13
+
 
 Installing IMP
 ==============
@@ -546,6 +559,42 @@ Configuring IMP
    IMP's setup screen (``Administration/Setup/Webmail/Mailbox and Fetchmail``.
 
 
+DIMP Troubleshooting
+====================
+
+DIMP differs from most traditional Horde applications in that it requires
+javascript support and, in fact, javascript performs the bulk of the page
+display.  As such, debugging DIMP is more complex than with other Horde
+applications.
+
+If you run into problems with DIMP, first follow the troubleshooting steps
+for both Horde and IMP - namely checking PHP error logs and Horde debug logs,
+to determine if the problem is located there.  Since DIMP uses Horde and IMP
+code extensively on the server side, most server-based errors will be logged
+in the traditional manner.
+
+Only if traditional debugging is unsuccessful will you need to move to
+javascript debugging.  It is highly recommended to use Mozilla Firefox with
+the `Firebug`_ extension installed in order to better track javascript errors
+- it is what the developers use and makes deciphering error codes and error
+line numbers much easier.  Next, it is also recommended to set the ``debug``
+parameter in ``dimp/conf/conf.php`` to ``true`` (no quotes) - this will popup
+javascript exceptions that may not be able to be caught by Firebug.  Next you
+will want to turn off javascript caching, if on, in ``dimp/conf/conf.php``.
+Finally, you need to change ``horde/conf/registry.php`` to serve the
+javascript files from the ``js/src/`` directory rather than the ``js/``
+directory (we compress javascript files to reduce network load, but this
+results in all javascript errors occurring on line 1 which is not very useful
+to diagnose the problem).
+
+If you do find a javascript error, it would be great if you could fix the
+issue and provide a patch :)  Absent that, before reporting to the mailing
+list, IRC room, or bug tracker make sure you have a valid javascript error,
+the file the error is being caused in, the line number of the error, and a
+reliable way to reproduce the error.  Developers/other interested folks will
+be much more likely to help you if all this information is provided.
+
+
 Obtaining Support
 =================
 
@@ -581,3 +630,4 @@ The IMP team
 .. _`horde/docs/SECURITY`: ../../horde/docs/?f=SECURITY.html
 .. _`horde/docs/TRANSLATIONS`: ../../horde/docs/?f=TRANSLATIONS.html
 .. _`File Uploads`: http://wiki.horde.org/FAQ/Admin/FileUploads
+.. _`Firebug`: http://www.getfirebug.com/
diff --git a/imp/imp-dimp.php b/imp/imp-dimp.php
new file mode 100644 (file)
index 0000000..6807f12
--- /dev/null
@@ -0,0 +1,720 @@
+<?php
+/**
+ * imp.php - performs an AJAX-requested action and returns the DIMP-specific
+ * JSON object
+ *
+ * $Horde: dimp/imp.php,v 1.234 2008/09/05 17:42:13 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+function _generateDeleteResult($folder, $indices, $change)
+{
+    $result = new stdClass;
+    $result->folder = $folder;
+    $result->uids = IMP::toRangeString($indices);
+    $result->remove = ($GLOBALS['prefs']->getValue('hide_deleted') ||
+                       $GLOBALS['prefs']->getValue('use_trash'));
+    $result->cacheid = _cacheID($folder);
+
+    if ($change) {
+        $result->viewport = _getListMessages($folder, true);
+    }
+
+    $poll = _getPollInformation($folder);
+    if (!empty($poll)) {
+        $result->poll = $poll;
+    }
+
+    return $result;
+}
+
+function _cacheID($folder)
+{
+    require_once IMP_BASE . '/lib/Mailbox.php';
+    $imp_mailbox = &IMP_Mailbox::singleton($folder);
+    return $imp_mailbox->getCacheId();
+}
+
+function _changed($folder, $compare, $indices = array(), $nothread = false)
+{
+    if ($GLOBALS['imp_search']->isVFolder($folder) ||
+        (_cacheID($folder) != $compare)) {
+        return true;
+    }
+
+    return (!empty($indices) &&
+            (!$nothread && _threadUidChanged($folder, $indices)));
+}
+
+function _threadUidChanged($folder, $indices)
+{
+    $sort = IMP::getSort($folder);
+    if ($sort['by'] == SORTTHREAD) {
+        require_once IMP_BASE . '/lib/Mailbox.php';
+        foreach ($indices as $mbox => $mbox_array) {
+            $imp_mailbox = &IMP_Mailbox::singleton($mbox);
+            $threadob = $imp_mailbox->getThreadOb();
+            foreach ($mbox_array as $val) {
+                if ($threadob->getThreadBase($val) !== false) {
+                    return true;
+                }
+            }
+        }
+    }
+
+    return false;
+}
+
+function _getListMessages($folder, $change)
+{
+    $args = array(
+        'cacheid' => Util::getPost('cacheid'),
+        'filter' => Util::getPost('filter'),
+        'folder' => $folder,
+        'searchfolder' => Util::getPost('searchfolder'),
+        'searchmsg' => Util::getPost('searchmsg'),
+    );
+
+    $search = Util::getPost('search');
+    if (!empty($search)) {
+        require_once 'Horde/Serialize.php';
+        $search = Horde_Serialize::unserialize($search, SERIALIZE_JSON);
+        $args += array(
+            'search_uid' => $search->imapuid,
+            'search_before' => intval(Util::getPost('search_before')),
+            'search_after' => intval(Util::getPost('search_size'))
+        );
+    } else {
+        $args += array(
+            'slice_rownum' => intval(Util::getPost('rownum')),
+            'slice_start' => intval(Util::getPost('slice_start')),
+            'slice_end' => intval(Util::getPost('slice_end'))
+        );
+    }
+
+    require_once DIMP_BASE . '/lib/Views/ListMessages.php';
+    $list_msg = new DIMP_Views_ListMessages();
+    $res = $list_msg->ListMessages($args);
+    // TODO: This can potentially be optimized for arrival time sort - if the
+    // cache ID changes, we know the changes must occur at end of mailbox.
+    if (Util::getPost('purge') || $change) {
+        $res->update = true;
+    }
+    $res->request_id = Util::getPost('request_id');
+    return $res;
+}
+
+function _getIdxString($indices)
+{
+    $i = each($indices);
+    return reset($i['value']) . IMP_IDX_SEP . $i['key'];
+}
+
+function _getPollInformation($mbox)
+{
+    require_once IMP_BASE . '/lib/IMAP/Tree.php';
+    $imptree = &IMP_Tree::singleton();
+    $elt = $imptree->get($mbox);
+    if ($imptree->isPolled($elt)) {
+        $info = $imptree->getElementInfo($mbox);
+        return array($mbox => isset($info['unseen']) ? $info['unseen'] : 0);
+    }
+    return array();
+}
+
+// Need to load Util:: to give us access to Util::getPathInfo().
+if (!defined('HORDE_BASE')) {
+    define('HORDE_BASE', dirname(__FILE__) . '/..');
+}
+require_once HORDE_BASE . '/lib/core.php';
+$action = basename(Util::getPathInfo());
+if (empty($action)) {
+    // This is the only case where we really don't return anything, since
+    // the frontend can be presumed not to make this request on purpose.
+    // Other missing data cases we return a response of boolean false.
+    exit;
+}
+
+// The following actions do not need write access to the session and
+// should be opened read-only for performance reasons.
+if (in_array($action, array('chunkContent', 'Html2Text', 'Text2Html', 'GetReplyData'))) {
+    $session_control = 'readonly';
+}
+
+$authentication = 'none';
+$dimp_logout = ($action == 'LogOut');
+$load_imp = true;
+$session_timeout = 'json';
+@define('AUTH_HANDLER', true);
+@define('DIMP_BASE', dirname(__FILE__));
+require_once DIMP_BASE . '/lib/base.php';
+
+// Process common request variables.
+$folder = Util::getPost('folder');
+$indices = IMP::parseRangeString(Util::getPost('uid'));
+$cacheid = Util::getPost('cacheid');
+
+// Open an output buffer to ensure that we catch errors that might break JSON
+// encoding.
+ob_start();
+
+$result = false;
+
+switch ($action) {
+case 'CreateFolder':
+case 'CreateSubfolder':
+    if (empty($folder)) {
+        break;
+    }
+
+    $parent = Util::getPost('parent');
+    if (($action == 'CreateSubfolder') && !$parent) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/IMAP/Tree.php';
+    $imptree = &IMP_Tree::singleton();
+    $imptree->eltDiffStart();
+
+    require_once IMP_BASE . '/lib/Folder.php';
+    $imp_folder = &IMP_Folder::singleton();
+
+    $new = String::convertCharset($folder, NLS::getCharset(), 'UTF7-IMAP');
+    $new = $imptree->createMailboxName($parent, $new);
+    if (is_a($new, 'PEAR_Error')) {
+        $notification->push($new, 'horde.error');
+        $result = false;
+    } else {
+        $result = $imp_folder->create($new, $prefs->getValue('subscribe'));
+        if ($result) {
+            $result = DIMP::getFolderResponse($imptree);
+        }
+    }
+    break;
+
+case 'DeleteFolder':
+    if (empty($folder)) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/IMAP/Tree.php';
+    $imptree = &IMP_Tree::singleton();
+    $imptree->eltDiffStart();
+
+    require_once IMP_BASE . '/lib/Folder.php';
+    $imp_folder = &IMP_Folder::singleton();
+    $result = $imp_folder->delete(array($folder));
+    if ($result) {
+        $result = DIMP::getFolderResponse($imptree);
+    }
+    break;
+
+case 'RenameFolder':
+    $old = Util::getPost('old_name');
+    $new_parent = Util::getPost('new_parent');
+    $new = Util::getPost('new_name');
+    if (!$old || !$new) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/IMAP/Tree.php';
+    $imptree = &IMP_Tree::singleton();
+    $imptree->eltDiffStart();
+
+    require_once IMP_BASE . '/lib/Folder.php';
+    $imp_folder = &IMP_Folder::singleton();
+
+    $new = $imptree->createMailboxName($new_parent, $new);
+    if (is_a($new, 'PEAR_Error')) {
+        $notification->push($new, 'horde.error');
+        $result = false;
+    } else {
+        require_once 'Horde/String.php';
+        $new = String::convertCharset($new, NLS::getCharset(), 'UTF7-IMAP');
+        if ($old != $new) {
+            $result = $imp_folder->rename($old, $new);
+            if ($result) {
+                $result = DIMP::getFolderResponse($imptree);
+            }
+        }
+    }
+    break;
+
+case 'EmptyFolder':
+    if (empty($folder)) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    $imp_message->emptyMailbox(array($folder));
+    $result = new stdClass;
+    $result->mbox = $folder;
+    break;
+
+case 'MarkFolderSeen':
+case 'MarkFolderUnseen':
+    if (empty($folder)) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    $result = $imp_message->flagAllInMailbox(array('seen'),
+                                             array($folder),
+                                             $action == 'MarkFolderSeen');
+    if ($result) {
+        $result = new stdClass;
+        $result->mbox = $folder;
+
+        $poll = _getPollInformation($folder);
+        if (!empty($poll)) {
+            $result->poll = array($folder => $poll[$folder]['u']);
+        }
+    }
+    break;
+
+case 'ListFolders':
+    require_once IMP_BASE . '/lib/IMAP/Tree.php';
+    $imptree = &IMP_Tree::singleton();
+    $result = DIMP::getFolderResponse($imptree, array('a' => $imptree->folderList(IMPTREE_FLIST_CONTAINER | IMPTREE_FLIST_VFOLDER), 'c' => array(), 'd' => array()));
+    break;
+
+case 'PollFolders':
+    $result = new stdClass;
+
+    require_once IMP_BASE . '/lib/Fetchmail.php';
+    $fm_account = new IMP_Fetchmail_Account();
+    $fm_count = $fm_account->count();
+    if (!empty($fm_count)) {
+        IMP_Fetchmail::fetchMail(range(0, $fm_count - 1));
+    }
+
+    require_once IMP_BASE . '/lib/IMAP/Tree.php';
+    $imptree = &IMP_Tree::singleton();
+
+    $result->poll = array();
+    foreach ($imptree->getPollList(true) as $val) {
+        if ($info = $imptree->getElementInfo($val)) {
+            $result->poll[$val] = $info['unseen'];
+        }
+    }
+
+    if (!empty($folder) && _changed($folder, $cacheid)) {
+        $result->viewport = _getListMessages($folder, true);
+    }
+
+    if (isset($_SESSION['imp']['quota']) &&
+        is_array($_SESSION['imp']['quota'])) {
+        $quotadata = IMP::quotaData(false);
+        if (!empty($quotadata)) {
+            $result->quota = array('p' => round($quotadata['percent']), 'm' => $quotadata['message']);
+        }
+    }
+    break;
+
+case 'ListMessages':
+    if (empty($folder)) {
+        break;
+    }
+
+    /* Change sort preferences if necessary. */
+    $sortby = Util::getPost('sortby');
+    $sortdir = Util::getPost('sortdir');
+    if (!is_null($sortby) || !is_null($sortdir)) {
+        IMP::setSort($sortby, $sortdir, $folder);
+    }
+
+    $result = new stdClass;
+
+    if (Util::getPost('rangeslice')) {
+        require_once DIMP_BASE . '/lib/Views/ListMessages.php';
+        $list_msg = new DIMP_Views_ListMessages();
+        $result->viewport = $list_msg->getSlice($folder, intval(Util::getPost('start')) - 1, intval(Util::getPost('length')));
+        $result->viewport->request_id = Util::getPost('request_id');
+        $result->viewport->type = 'slice';
+    } else {
+        $changed = _changed($folder, $cacheid);
+        if (!Util::getPost('checkcache') || $changed) {
+            $result->viewport = _getListMessages($folder, $changed);
+        }
+    }
+    break;
+
+case 'MoveMessage':
+case 'CopyMessage':
+    $to = Util::getPost('tofld');
+    if (!$to || empty($indices)) {
+        break;
+    }
+
+    if ($action == 'MoveMessage') {
+        $change = _changed($folder, $cacheid, $indices);
+    }
+
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    $result = $imp_message->copy($to,
+                                 $action == 'MoveMessage'
+                                     ? IMP_MESSAGE_MOVE
+                                     : IMP_MESSAGE_COPY,
+                                 $indices);
+    if ($result) {
+        if ($action == 'MoveMessage') {
+            $result = _generateDeleteResult($folder, $indices, $change);
+            // Need to manually set remove to 'true' since we want to remove
+            // message from the list no matter the current pref settings.
+            $result->remove = true;
+        }
+
+        // Update poll information for destination folder if necessary.
+        // Poll information for current folder will be added by
+        // _generateDeleteResult() call above.
+        $poll = _getPollInformation($to);
+        if (!empty($poll)) {
+            if (!isset($result->poll)) {
+                $result->poll = array();
+            }
+            $result->poll = array_merge($result->poll, $poll);
+        }
+    }
+    break;
+
+case 'MarkMessage':
+    $flag = Util::getPost('messageFlag');
+    if (!$flag || empty($indices)) {
+        break;
+    }
+    if ($flag[0] == '-') {
+        $flag = substr($flag, 1);
+        $set = false;
+    } else {
+        $set = true;
+    }
+
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    $result = $imp_message->flag(array($flag), $indices, $set);
+    if ($result) {
+        $result = new stdClass;
+    }
+    break;
+
+case 'DeleteMessage':
+case 'UndeleteMessage':
+    if (empty($indices)) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    if ($action == 'DeleteMessage') {
+        $change = _changed($folder, $cacheid, $indices, !$prefs->getValue('hide_deleted') && !$prefs->getValue('use_trash'));
+        $result = $imp_message->delete($indices);
+        if ($result) {
+            $result = _generateDeleteResult($folder, $indices, $change);
+        }
+    } else {
+        $result = $imp_message->undelete($indices);
+        if ($result) {
+            $result = new stdClass;
+        }
+    }
+    break;
+
+case 'AddContact':
+    $email = Util::getPost('email');
+    $name = Util::getPost('name');
+    // Allow $name to be empty.
+    if (empty($email)) {
+        break;
+    }
+
+    $result = IMP::addAddress($email, $name);
+    if (is_a($result, 'PEAR_Error')) {
+        $notification->push($result, 'horde.error');
+        $result = false;
+    } else {
+        $result = true;
+        $notification->push(sprintf(_("%s was successfully added to your address book."), $name ? $name : $email), 'horde.success');
+    }
+    break;
+
+case 'ReportSpam':
+case 'ReportHam':
+    $change = _changed($folder, $cacheid, $indices);
+    require_once IMP_BASE . '/lib/Spam.php';
+    $spam = new IMP_Spam();
+    $result = $spam->reportSpam($indices,
+                                $action == 'ReportSpam' ? 'spam' : 'notspam');
+    if ($result) {
+        $result = _generateDeleteResult($folder, $indices, $change);
+        // If $result is non-zero, then we know the message has been removed
+        // from the current mailbox.
+        $result->remove = true;
+    }
+    break;
+
+case 'Blacklist':
+case 'Whitelist':
+    if (empty($indices)) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/Filter.php';
+    $imp_filter = new IMP_Filter();
+    if ($action == 'Whitelist') {
+        $imp_filter->whitelistMessage($indices, false);
+    } else {
+        $change = _changed($folder, $cacheid, $indices);
+        if ($imp_filter->blacklistMessage($indices, false)) {
+            $result = _generateDeleteResult($folder, $indices, $change);
+        }
+    }
+    break;
+
+case 'ShowPreview':
+    if (count($indices) != 1) {
+        break;
+    }
+
+    $ptr = each($indices);
+    $args = array(
+        'folder' => $ptr['key'],
+        'index' => reset($ptr['value']),
+        'preview' => true,
+    );
+
+    require_once DIMP_BASE . '/lib/Views/ShowMessage.php';
+    $show_msg = new DIMP_Views_ShowMessage();
+    $result = (object) $show_msg->showMessage($args);
+    break;
+
+case 'Html2Text':
+    require_once 'Horde/Text/Filter.php';
+    $result = new stdClass;
+    // Need to replace line endings or else IE won't display line endings
+    // properly.
+    $result->text = str_replace("\n", "\r\n", Text_Filter::filter(Util::getPost('text'), 'html2text'));
+    break;
+
+case 'Text2Html':
+    require_once 'Horde/Text/Filter.php';
+    $result = new stdClass;
+    $result->text = Text_Filter::filter(Util::getPost('text'), 'text2html', array('parselevel' => TEXT_HTML_MICRO_LINKURL, 'class' => null, 'callback' => null));
+    break;
+
+case 'GetForwardData':
+    require_once IMP_BASE . '/lib/Compose.php';
+    require_once IMP_BASE . '/lib/MIME/Contents.php';
+    require_once IMP_BASE . '/lib/UI/Compose.php';
+    require_once 'Horde/MIME/Message.php';
+    $header = array();
+    $msg = $header = null;
+    $idx_string = _getIdxString($indices);
+
+    $imp_compose = &IMP_Compose::singleton(Util::getPost('imp_compose'));
+    $imp_contents = &IMP_Contents::singleton($idx_string);
+    $imp_ui = new IMP_UI_Compose();
+    $fwd_msg = $imp_ui->getForwardData($imp_compose, $imp_contents, Util::getPost('type'), $idx_string);
+    $header = $fwd_msg['headers'];
+    $header['replytype'] = 'forward';
+
+    $result = new stdClass;
+    // Can't open read-only since we need to store the message cache id.
+    $result->imp_compose = $imp_compose->getMessageCacheId();
+    $result->fwd_list = DIMP::getAttachmentInfo($imp_compose);
+    $result->body = $fwd_msg['body'];
+    $result->header = $header;
+    $result->format = $fwd_msg['format'];
+    $result->identity = $fwd_msg['identity'];
+    break;
+
+case 'GetReplyData':
+    require_once IMP_BASE . '/lib/Compose.php';
+    require_once IMP_BASE . '/lib/MIME/Contents.php';
+    $imp_compose = &IMP_Compose::singleton(Util::getPost('imp_compose'));
+    $imp_contents = &IMP_Contents::singleton(_getIdxString($indices));
+    $reply_msg = $imp_compose->replyMessage(Util::getPost('type'), $imp_contents);
+    $header = $reply_msg['headers'];
+    $header['replytype'] = 'reply';
+
+    $result = new stdClass;
+    $result->format = $reply_msg['format'];
+    $result->body = $reply_msg['body'];
+    $result->header = $header;
+    $result->identity = $reply_msg['identity'];
+    break;
+
+case 'DeleteDraft':
+    $index = Util::getPost('index');
+    if (empty($indices)) {
+        break;
+    }
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    $idx_array = array($index . IMP_IDX_SEP . IMP::folderPref($prefs->getValue('drafts_folder'), true));
+    $imp_message->delete($idx_array, true);
+    break;
+
+case 'DeleteAttach':
+    $atc = Util::getPost('atc_indices');
+    if (!is_null($atc)) {
+        require_once IMP_BASE . '/lib/Compose.php';
+        $imp_compose = &IMP_Compose::singleton(Util::getPost('imp_compose'));
+        $imp_compose->deleteAttachment($atc);
+    }
+    break;
+
+case 'ShowPortal':
+    // Load the block list. Blocks are located in $dimp_block_list.
+    // KEY: Block label  VALUE: Horde_Block object
+    require DIMP_BASE . '/config/portal.php';
+
+    $blocks = $linkTags = array();
+    $css_load = array('dimp' => true);
+    foreach ($dimp_block_list as $title => $block) {
+        if (is_a($block['ob'], 'Horde_Block')) {
+            $app = $block['ob']->getApp();
+            $content = ((empty($css_load[$app])) ? Horde::styleSheetLink($app, '', false) : '') . $block['ob']->getContent();
+            $css_load[$app] = true;
+            // Don't do substitutions on our own blocks.
+            if ($app != 'dimp') {
+                $content = preg_replace('/<a href="([^"]+)"/',
+                                        '<a onclick="DimpBase.go(\'app:' . $app . '\', \'$1\');return false"',
+                                        $content);
+                if (preg_match_all('/<link .*?rel="stylesheet".*?\/>/',
+                                   $content, $links)) {
+                    $content = str_replace($links[0], '', $content);
+                    foreach ($links[0] as $link) {
+                        if (preg_match('/href="(.*?)"/', $link, $href)) {
+                            $linkOb = new stdClass;
+                            $linkOb->href = $href[1];
+                            if (preg_match('/media="(.*?)"/', $link, $media)) {
+                                $linkOb->media = $media[1];
+                            }
+                            $linkTags[] = $linkOb;
+                        }
+                    }
+                }
+            }
+            if (!empty($content)) {
+                $entry = array(
+                    'app' => $app,
+                    'content' => $content,
+                    'title' => $title,
+                    'class' => empty($block['class']) ? 'headerbox' : $block['class'],
+                );
+                if (!empty($block['domid'])) {
+                    $entry['domid'] = $block['domid'];
+                }
+                if (!empty($block['tag'])) {
+                    $entry[$block['tag']] = true;
+                }
+                $blocks[] = $entry;
+            }
+        }
+    }
+
+    $result = new stdClass;
+    $result->portal = '';
+    if (!empty($blocks)) {
+        require_once IMP_BASE . '/lib/Template.php';
+        $t = new IMP_Template(DIMP_TEMPLATES . '/imp/');
+        $t->set('block', $blocks);
+        $result->portal = $t->fetch('portal.html');
+    }
+    $result->linkTags = $linkTags;
+    break;
+
+case 'chunkContent':
+    $chunk = basename(Util::getPost('chunk'));
+    if (!empty($chunk)) {
+        $result = new stdClass;
+        $result->chunk = Util::bufferOutput('include', DIMP_TEMPLATES . '/chunks/' . $chunk . '.php');
+    }
+    break;
+
+case 'PurgeDeleted':
+    $change = _changed($folder, $cacheid, $indices);
+    if (!$change) {
+        $sort = IMP::getSort($folder);
+        $change = ($sort['by'] == SORTTHREAD);
+    }
+    require_once IMP_BASE . '/lib/Message.php';
+    $imp_message = &IMP_Message::singleton();
+    $expunged = $imp_message->expungeMailbox(array($folder => 1));
+    if (!empty($expunged[$folder])) {
+        $expunge_count = count($expunged[$folder]);
+        $display_folder = IMP::displayFolder($folder);
+        if ($expunge_count == 1) {
+            $notification->push(sprintf(_("1 message was purged from \"%s\"."),  $display_folder), 'horde.success');
+        } else {
+            $notification->push(sprintf(_("%s messages were purged from \"%s\"."), $expunge_count, $display_folder), 'horde.success');
+        }
+        $result = _generateDeleteResult($folder, $expunged, $change);
+        // Need to manually set remove to 'true' since we want to remove
+        // message from the list no matter the current pref settings.
+        $result->remove = true;
+    }
+    break;
+
+case 'ModifyPollFolder':
+    if (empty($folder)) {
+        break;
+    }
+
+    $add = Util::getPost('add');
+
+    require_once IMP_BASE . '/lib/IMAP/Tree.php';
+    $imptree = &IMP_Tree::singleton();
+
+    $result = new stdClass;
+    $result->add = (bool) $add;
+    $result->folder = $folder;
+
+    if ($add) {
+        $imptree->addPollList($folder);
+        if ($info = $imptree->getElementInfo($folder)) {
+            $result->poll = array($folder => $info['unseen']);
+        }
+    } else {
+        $imptree->removePollList($folder);
+    }
+    break;
+
+case 'SendMDN':
+    $index = Util::getPost('index');
+    if (empty($folder) || empty($index)) {
+        break;
+    }
+
+    /* Get the IMP_Headers:: object. */
+    require_once IMP_BASE . '/lib/IMAP/MessageCache.php';
+    $msg_cache = &IMP_MessageCache::singleton();
+    $cache_entry = $msg_cache->retrieve($folder, array($index), 32);
+    $ob = reset($cache_entry);
+    if ($ob === false) {
+        break;
+    }
+
+    require_once IMP_BASE . '/lib/UI/Message.php';
+    $imp_ui = new IMP_UI_Message();
+    $imp_ui->MDNCheck($ob->header, true);
+    break;
+}
+
+// Clear the output buffer that we started above, and log any unexpected
+// output at a DEBUG level.
+$errors = ob_get_clean();
+if ($errors) {
+    Horde::logMessage('DIMP: unexpected output: ' .
+                      $errors, __FILE__, __LINE__, PEAR_LOG_DEBUG);
+}
+
+// Send the final result.
+IMP::sendHTTPResponse(DIMP::prepareResponse($result), 'json');
index 4888274..b147f63 100644 (file)
@@ -8,21 +8,42 @@
  * @author Michael Slusarz <slusarz@horde.org>
  */
 
+// As of right now, imples don't need read/write session access.
+$session_control = 'readonly';
+$session_timeout = 'none';
+
 @define('IMP_BASE', dirname(__FILE__));
 $authentication = 'horde';
 require_once IMP_BASE . '/lib/base.php';
 
-if (!($path = Util::getFormData('imple'))) {
-    exit;
-}
-if ($path[0] == '/') {
-    $path = substr($path, 1);
-}
-$path = explode('/', $path);
-$impleName = array_shift($path);
+if ($_SESSION['imp']['view'] == 'dimp') {
+    $path_info = Util::getPathInfo();
+    if (empty($path_info)) {
+        IMP::sendHTTPResponse(new stdClass(), 'json');
+    }
+
+    if ($path_info[0] == '/') {
+        $path_info = substr($path_info, 1);
+    }
+    $path = explode('/', $path_info);
+    $impleName = array_shift($path);
 
-if (!($imple = IMP_Imple::factory($impleName))) {
-    exit;
+    if (!($imple = IMP_Imple::factory($impleName))) {
+        IMP::sendHTTPResponse(new stdClass(), 'json');
+    }
+} else {
+    if (!($path = Util::getFormData('imple'))) {
+        exit;
+    }
+    if ($path[0] == '/') {
+        $path = substr($path, 1);
+    }
+    $path = explode('/', $path);
+    $impleName = array_shift($path);
+
+    if (!($imple = IMP_Imple::factory($impleName))) {
+        exit;
+    }
 }
 
 $args = array();
diff --git a/imp/index-dimp.php b/imp/index-dimp.php
new file mode 100644 (file)
index 0000000..21062f2
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+/**
+ * $Horde: dimp/index.php,v 1.72 2008/09/29 17:30:19 chuck Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ */
+
+@define('DIMP_BASE', dirname(__FILE__));
+$dimp_configured = (is_readable(DIMP_BASE . '/config/conf.php') &&
+                    is_readable(DIMP_BASE . '/config/portal.php') &&
+                    is_readable(DIMP_BASE . '/config/prefs.php'));
+
+if (!$dimp_configured) {
+    require DIMP_BASE . '/../lib/Test.php';
+    Horde_Test::configFilesMissing('DIMP', DIMP_BASE,
+                                   array('conf.php', 'prefs.php'),
+                                   array('portal.php' => 'This file controls the blocks that appear in DIMP\'s portal'));
+}
+
+$load_imp = true;
+require_once DIMP_BASE . '/lib/base.php';
+
+$scripts = array(
+    array('DimpBase.js', 'dimp', true),
+    array('ContextSensitive.js', 'dimp', true),
+    array('ViewPort.js', 'dimp', true),
+    array('dragdrop.js', 'dimp', true),
+    array('dhtmlHistory.js', 'horde', true),
+    array('redbox.js', 'horde', true),
+    array('mailbox.js', 'dimp'),
+    array('DimpSlider.js', 'dimp', true),
+    array('unblockImages.js', 'imp', true)
+);
+
+/* Get site specific menu items. */
+$js_code = $site_menu = array();
+if (is_readable(DIMP_BASE . '/config/menu.php')) {
+    include DIMP_BASE . '/config/menu.php';
+}
+
+/* Add the site specific javascript now. */
+if (!empty($site_menu)) {
+    foreach ($site_menu as $key => $menu_item) {
+        if ($menu_item == 'separator') {
+            continue;
+        }
+        $js_code[] = 'DimpCore.clickObserveHandler({ d: $(\'menu' . $key . '\'), f: function() { ' . $menu_item['action'] . ' } })';
+        $js_code[] = 'DimpCore.clickObserveHandler({ d: $(\'tab' . $key . '\'), f: function() { ' . $menu_item['action'] . ' } })';
+    }
+}
+
+IMP::addInlineScript($js_code, true);
+DIMP::header('', $scripts);
+
+/* Get application folders list. */
+$application_folders = array();
+foreach (DIMP::menuList() as $app) {
+    if ($registry->get('status', $app) != 'inactive' &&
+        $registry->hasPermission($app, PERMS_SHOW)) {
+        $application_folders[] = array(
+            'name' => htmlspecialchars($registry->get('name', $app)),
+            'icon' => $registry->get('icon', $app),
+            'app' => rawurlencode($app)
+        );
+    }
+}
+
+echo "<body>\n";
+require DIMP_TEMPLATES . '/index/index.inc';
+IMP::includeScriptFiles();
+IMP::outputInlineScript();
+$notification->notify(array('listeners' => array('javascript')));
+echo "</body>\n</html>";
diff --git a/imp/js/ContextSensitive.js b/imp/js/ContextSensitive.js
new file mode 100644 (file)
index 0000000..2dbe020
--- /dev/null
@@ -0,0 +1 @@
+var ContextSensitive=Class.create({initialize:function(){this.current=this.target=null;this.elements=$H();document.observe("contextmenu",this.rightClickHandler.bindAsEventListener(this));document.observe("click",this.leftClickHandler.bindAsEventListener(this));document.observe(Prototype.Browser.Gecko?"DOMMouseScroll":"mousescroll",this.close.bind(this))},addElement:function(D,C,A){var B=Boolean(A.left);if(D&&!this.validElement(D,B)){this.elements.set(D+Number(B),new ContextSensitive.Element(D,C,A))}},removeElement:function(A){this.elements.unset(A+"0");this.elements.unset(A+"1")},close:function(A){if(this.current){if(A){this.current.hide()}else{Effect.Fade(this.current,{duration:0.2})}this.current=this.target=null}},element:function(){return this.target},currentmenu:function(){if(this.current&&this.current.visible()){return this.current}},validElement:function(B,A){return this.elements.get(B+Number(Boolean(A)))},disable:function(D,C,A){var B=this.validElement(D,C);if(B){B.disable=A}},leftClickHandler:function(A){if(A.isRightClick()){return}this.rightClickHandler(A,true)},rightClickHandler:function(B,A){if(this.trigger(B.element(),A,B.pointerX(),B.pointerY())){B.stop()}},trigger:function(F,E,H,G){var J,B,D,C,K,I,A;[F].concat(F.ancestors()).find(function(L){J=this.validElement(L.id,E);return J},this);if(!J||J.disable){this.close();return false}B=$(J.ctx);if(!B){this.close();return false}else{if(E&&B==this.current){return false}}this.current=B;this.target=$(J.id);D=J.opts.offset;if(!D&&(Object.isUndefined(H)||Object.isUndefined(G))){D=F.id}D=$(D);if(D){C=D.viewportOffset();A=document.viewport.getScrollOffsets();H=C[0]+A.left;G=C[1]+D.getHeight()+A.top}I=document.viewport.getDimensions();K=B.getDimensions();if((G+K.height)>I.height){G=I.height-K.height-10}if((H+K.width)>I.width){H=I.width-K.width-10}if(J.opts.onShow){J.opts.onShow(J)}Effect.Appear(B.setStyle({left:H+"px",top:G+"px"}),{duration:0.2});return true}});ContextSensitive.Element=Class.create({initialize:function(C,B,A){this.id=C;this.ctx=B;this.opts=A;this.opts.left=Boolean(A.left);this.disable=false}});
\ No newline at end of file
diff --git a/imp/js/DimpBase.js b/imp/js/DimpBase.js
new file mode 100644 (file)
index 0000000..532afb4
--- /dev/null
@@ -0,0 +1 @@
+var DimpBase={bcache:$H(),lastrow:-1,mo_sidebar:{},pivotrow:-1,ppcache:{},ppfifo:[],showPreview:DIMP.conf.preview_pref,sfiltersfolder:$H({sf_all:"all",sf_current:"current"}),sfilters:$H({sf_msgall:"msgall",sf_from:"from",sf_to:"to",sf_subject:"subject"}),_select:function(C,A){var B=C.get("rownum");if(B.size()==1){this.lastrow=this.pivotrow=B.first()}this.toggleButtons();if($("previewPane").visible()){if(A.right){this.clearPreviewPane()}else{if(A.delay){(this.bcache.get("initPP")||this.bcache.set("initPP",this.initPreviewPane.bind(this))).delay(A.delay)}else{this.initPreviewPane()}}}},_deselect:function(D,B){var C=this.viewport.getSelected(),A=C.size();if(!A){this.lastrow=this.pivotrow=-1}this.toggleButtons();if(B.right||!A){this.clearPreviewPane()}else{if((A==1)&&$("previewPane").visible()){this._loadPreview(C.get("dataob").first())}}},msgSelect:function(G,C){var B,E=this.viewport.createSelection("domid",G),A=E.get("rownum").first(),D=this.isSelected("domid",G),F=this.selectedCount();this.lastrow=A;$("msgList_filter").blur();if(C.shift){if(F){if(!D||F!=1){B=[A,this.pivotrow];this.viewport.select($A($R(B.min(),B.max())),{range:true})}return}}else{if(C.ctrl){this.pivotrow=A;if(D){this.viewport.deselect(E,{right:C.right});return}else{if(C.right||F){this.viewport.select(E,{add:true,right:C.right});return}}}}this.viewport.select(E,{right:C.right})},selectAll:function(){this.viewport.select($A($R(1,this.viewport.getMetaData("total_rows"))),{range:true})},isSelected:function(B,A){return this.viewport.getSelected().contains(B,A)},selectedCount:function(){return(this.viewport)?this.viewport.getSelected().size():0},resetSelected:function(){if(this.viewport){this.viewport.deselect(this.viewport.getSelected(),{clearall:true})}this.toggleButtons();this.clearPreviewPane()},moveSelected:function(A,G){var E,D,F,B,C;if(G){if(!this.viewport.getMetaData("total_rows")){return}E=A}else{if(A==0){return}C=this.viewport.getSelected();switch(C.size()){case 0:E=this.viewport.currentOffset()+1;break;case 1:D=C.get("dataob").first();E=D.rownum+A;break;default:C=C.get("rownum");E=(A>0?C.max():C.min())+A;break}E=(A>0)?Math.min(E,this.viewport.getMetaData("total_rows")):Math.max(E,1)}F=this.viewport.createSelection("rownum",E);if(F.size()){B=F.get("dataob").first();if(!D||B.imapuid!=D.imapuid){this.viewport.scrollTo(B.rownum);this.viewport.select(F,{delay:0.3})}}else{this.offset=E;this.viewport.requestContentRefresh(E-1)}},go:function(E,B){var D,A,C;if(E.startsWith("compose:")){return}if(E.startsWith("msg:")){C=E.indexOf(":",4);A=E.substring(4,C);this.uid=E.substring(C+1);E="folder:"+A}if(E.startsWith("folder:")){A=E.substring(7);if(this.folder!=A||!$("dimpmain_folder").visible()){this.highlightSidebar(this.getFolderId(A));if(!$("dimpmain_folder").visible()){$("dimpmain_portal").hide();$("dimpmain_folder").show()}if(!Object.isUndefined(this.folder)){this._addHistory(E)}}this.loadFolder(A);return}this.folder=null;$("dimpmain_folder").hide();$("dimpmain_portal").update(DIMP.text.loading).show();if(E.startsWith("app:")){D=E.substr(4);if(D=="imp"||D=="dimp"){this.go("folder:INBOX");return}this.highlightSidebar("app"+D);this._addHistory(E,B);if(B){this.iframeContent(E,B)}else{if(DIMP.conf.app_urls[D]){this.iframeContent(E,DIMP.conf.app_urls[D])}}return}switch(E){case"portal":this.highlightSidebar("appportal");this._addHistory(E);DimpCore.setTitle(DIMP.text.portal);DimpCore.doAction("ShowPortal",{},null,this.bcache.get("portalC")||this.bcache.set("portalC",this._portalCallback.bind(this)));break;case"options":this.highlightSidebar("appoptions");this._addHistory(E);DimpCore.setTitle(DIMP.text.prefs);this.iframeContent(E,DIMP.conf.prefs_url);break}},_addHistory:function(B,A){if(Horde.dhtmlHistory.getCurrentLocation()!=B){Horde.dhtmlHistory.add(B,A)}},highlightSidebar:function(B){if($("foldersLoading").visible()){this.highlightSidebar.bind(this,B).defer();return}$("sidebarPanel").select(".on").invoke("removeClassName","on");var A=$(B);if(!A){return}if(!A.match("LI")){A=A.up();if(!A){return}}A.addClassName("on");A.ancestors().find(function(C){if(C.hasClassName("subfolders")){this._toggleSubFolder(C.id.substring(3),"exp")}else{return(C.id=="foldersSidebar")}},this)},iframeContent:function(B,D){if(B===null){B=D}var A=$("dimpmain_portal"),C;if(!A){DimpCore.showNotifications([{type:"horde.error",message:"Bad portal!"}]);return}C=new Element("IFRAME",{id:"iframe"+B,className:"iframe",frameBorder:0,src:D});this._resizeIE6Iframe(C);if(B=="options"){C.observe("load",function(){$("iframeoptions").contentWindow.document.getElementById("menu").style.display="none"})}A.insert(C)},msgWindow:function(B){this.updateUnseenUID(B,0);var A=DIMP.conf.message_url;A+=(A.include("?")?"&":"?")+$H({folder:B.view,uid:B.imapuid}).toQueryString();DimpCore.popupWindow(A,"msgview"+B.view+B.imapuid)},composeMailbox:function(A){var B=this.viewport.getSelected();if(!B.size()){return}B.get("dataob").each(function(C){DimpCore.compose(A,{folder:C.view,uid:C.imapuid})})},loadFolder:function(B,A){if(!this.viewport){this._createViewPort()}if(!A){this.resetSelected();if(this.folder==B){this.searchfilterClear(false);return}this.searchfilterClear(true);$("folderName").update(DIMP.text.loading);$("msgHeader").update();this.folderswitch=true;this.folder=B}this.viewport.loadView(B,{folder:B},this.uid?{imapuid:this.uid,view:B}:null,A)},_createViewPort:function(){var B=$("msgList_filter"),A=this.setMessageListTitle.bind(this);this.viewport=new ViewPort({content_container:"msgList",empty_container:"msgList_empty",error_container:"msgList_error",fetch_action:"ListMessages",template:this.message_list_template,buffer_pages:DIMP.conf.buffer_pages,limit_factor:DIMP.conf.limit_factor,viewport_wait:DIMP.conf.viewport_wait,show_split_pane:this.showPreview,split_pane:"previewPane",splitbar:"splitBar",content_class:"msglist",row_class:"msgRow",selected_class:"selectedRow",ajaxRequest:DimpCore.doAction.bind(DimpCore),norows:true,onScrollIdle:A,onSlide:A,onViewChange:function(){DimpCore.addGC(this.viewport.visibleRows())}.bind(this),onContent:function(F){var E,D,C=((this.viewport.getMetaData("sortby")==DIMP.conf.sortthread)&&this.viewport.getMetaData("thread"));if(this.viewport.isFiltering()){D=this.sfilters.get(this._getSearchfilterField()).capitalize();E=new RegExp("("+$F("msgList_filter")+")","i")}F.get("dataob").each(function(K){var H,I,G,J=$(K.domid);if(C&&C[K.imapuid]){H=J.down(".msgSubject");I=H.cloneNode(false);G=C[K.imapuid];$R(0,G.length,true).each(function(L){I.insert($($("thread_img_"+G.charAt(L)).cloneNode(false)).writeAttribute("id",""))});H.replace(I.insert(H.getText().escapeHTML()))}if(K.atc){J.down(".msgSize").insert({top:$($("atc_img_"+K.atc).cloneNode(false)).writeAttribute("id","")})}DimpCore.addMouseEvents({id:K.domid,type:K.menutype});if(D=="From"||D=="Subject"){H=J.down(".msg"+D);H.update(H.getText().escapeHTML().gsub(E,'<span class="searchMatch">#{1}</span>'))}},this);this.setMessageListTitle()}.bind(this),onComplete:function(){var E,C,D=this.viewport.getMetaData("label");if(this.uid){E=this.viewport.getViewportSelection().search({imapuid:{equal:[this.uid]},view:{equal:[this.folder]}});if(E.size()){this.viewport.scrollTo(E.get("rownum").first());this.viewport.select(E)}}else{if(this.offset){this.viewport.select(this.viewport.createSelection("rownum",this.offset))}}this.offset=this.uid=null;D=this.viewport.getMetaData("label");if(D){$("folderName").update(D)}if(this.folderswitch){this.folderswitch=false;if(this.folder==DIMP.conf.spam_folder){if(!DIMP.conf.spam_spamfolder&&DimpCore.buttons.indexOf("button_spam")!=-1){[$("button_spam").up(),$("ctx_message_spam")].invoke("hide")}if(DimpCore.buttons.indexOf("button_ham")!=-1){[$("button_ham").up(),$("ctx_message_ham")].invoke("show")}}else{if(DimpCore.buttons.indexOf("button_spam")!=-1){[$("button_spam").up(),$("ctx_message_spam")].invoke("show")}if(DimpCore.buttons.indexOf("button_ham")!=-1){if(DIMP.conf.ham_spamfolder){[$("button_ham").up(),$("ctx_message_ham")].invoke("hide")}else{[$("button_ham").up(),$("ctx_message_ham")].invoke("show")}}}}else{if(this.filtertoggle){if(this.filtertoggle==1&&this.viewport.getMetaData("sortby")==DIMP.conf.sortthread){C=DIMP.conf.sortdate}this.filtertoggle=0}}this.setSortColumns(C);if(this.viewport.isFiltering()){this.resetSelected()}else{this.setFolderLabel(this.folder,this.viewport.getMetaData("unseen")||0)}this.updateTitle()}.bind(this),onFetch:this.msgListLoading.bind(this,true),onEndFetch:this.msgListLoading.bind(this,false),onWait:function(){if($("dimpmain_folder").visible()){DimpCore.showNotifications([{type:"horde.warning",message:DIMP.text.listmsg_wait}])}},onFail:function(){if($("dimpmain_folder").visible()){DimpCore.showNotifications([{type:"horde.error",message:DIMP.text.listmsg_timeout}])}this.msgListLoading(false)}.bind(this),onFirstContent:function(){this.clearPreviewPane();$("msgList").observe("dblclick",this._handleMsgListDblclick.bindAsEventListener(this))}.bind(this),onClearRows:function(C){C.each(function(D){var E=$(D).down("div.msCheck");if(E){DimpCore.addGC(E)}if(D.id){DimpCore.removeMouseEvents(D)}})},onBeforeResize:function(){var C=this.viewport.getSelected();this.isvisible=(C.size()==1)&&(this.viewport.isVisible(C.get("rownum").first())==0)}.bind(this),onAfterResize:function(){if(this.isvisible){this.viewport.scrollTo(this.viewport.getSelected().get("rownum").first())}}.bind(this),selectCallback:this._select.bind(this),deselectCallback:this._deselect.bind(this)});if(!this.showPreview){$("msgList").addClassName("msglistNoPreview")}this.viewport.addFilter("ListMessages",this._addSearchfilterParams.bind(this));B.observe("keyup",this._searchfilterOnKeyup.bind(this));B.observe("focus",this._searchfilterOnFocus.bind(this));B.observe("blur",this._searchfilterOnBlur.bind(this));B.addClassName("msgFilterDefault")},_addMouseEvents:function(B,C){var A;switch(C.type){case"draft":case"message":new Drag(C.id,this._msgDragConfig);A=$(C.id).down("div.msCheck");if(A.visible()){A.observe("mousedown",this.bcache.get("handleMLC")||this.bcache.set("handleMLC",this._handleMsgListCheckbox.bindAsEventListener(this)));A.observe("contextmenu",Event.stop)}break;case"container":case"folder":new Drag(C.id,this._folderDragConfig);break;case"special":C.type="folder";break;case"vcontainer":case"virtual":$(C.id).observe("contextmenu",Event.stop);break}C.onShow=this.bcache.get("onMS")||this.bcache.set("onMS",this._onMenuShow.bind(this));B(C)},_removeMouseEvents:function(B,A){var C,D=$(A).readAttribute("id");if(D&&(C=DragDrop.Drags.get_drag(D))){C.destroy()}B(A)},_onMenuShow:function(A){var E,C,B,D;switch(A.ctx){case"ctx_folder":E=$("ctx_folder_create","ctx_folder_rename","ctx_folder_delete");C=DimpCore.DMenu.element();if(C.readAttribute("mbox")=="INBOX"){E.invoke("hide")}else{if(DIMP.conf.fixed_folders.indexOf(C.readAttribute("mbox"))!=-1){E.shift();E.invoke("hide")}else{E.invoke("show")}}if(C.hasAttribute("u")){$("ctx_folder_poll").hide();$("ctx_folder_nopoll").show()}else{$("ctx_folder_poll").show();$("ctx_folder_nopoll").hide()}break;case"ctx_message":[$("ctx_message_reply_list")].invoke(this.viewport.createSelection("domid",A.id).get("dataob").first().listmsg?"show":"hide");break;case"ctx_reply":D=this.viewport.getSelected();if(D.size()==1){B=D.get("dataob").first()}[$("ctx_reply_reply_list")].invoke(B&&B.listmsg?"show":"hide");break;case"ctx_otheractions":$("oa_seen","oa_unseen","oa_flagged","oa_clear","oa_sep1","oa_blacklist","oa_whitelist","oa_sep2").compact().invoke(this.viewport.getSelected().size()?"show":"hide");break}return true},_onResize:function(B,A){if(this.viewport){this.viewport.onResize(B,A)}this._resizeIE6()},_handleMsgListDblclick:function(B){var A=this._getMsgRow(B),C;if(!A){return}C=this.viewport.createSelection("domid",A.id).get("dataob").first();C.draft?DimpCore.compose("resume",{folder:C.view,uid:C.imapuid}):this.msgWindow(C);B.stop()},_handleMsgListCheckbox:function(B){var A=this._getMsgRow(B);if(!A){return}this.msgSelect(A.readAttribute("id"),{ctrl:true,right:true});B.stop()},_getMsgRow:function(A){A=A.element();if(A&&!A.hasClassName("msgRow")){A=A.up(".msgRow")}return A},updateTitle:function(){var B,A,C;if(this.viewport.isFiltering()){A=DIMP.text.search+" :: "+this.viewport.getMetaData("total_rows")+" "+DIMP.text.resfound}else{B=$(this.getFolderId(this.folder));if(B){C=B.readAttribute("u");A=B.readAttribute("l");if(C>0){A+=" ("+C+")"}}else{A=this.viewport.getMetaData("label")}}DimpCore.setTitle(A)},sort:function(D){if(this.viewport.getMetaData("sortlimit")){return}var B,C,A=D.element();if(!A.hasAttribute("sortby")){A=A.up("[sortby]");if(!A){return}}C=parseInt(A.readAttribute("sortby"),10);if(C==this.viewport.getMetaData("sortby")){B={sortdir:(this.viewport.getMetaData("sortdir")?0:1)};this.viewport.setMetaData({sortdir:B.sortdir})}else{B={sortby:C};this.viewport.setMetaData({sortby:B.sortby})}this.setSortColumns(C);this.viewport.reload(B)},setSortColumns:function(C){var B,A=$("msglistHeader");if(Object.isUndefined(C)){C=this.viewport.getMetaData("sortby")}B=A.down("small[sortby="+C+"]");if(B&&B.up().visible()){B.up(1).childElements().invoke("toggle")}B=A.down("div.msgFrom a");if((this.viewport.isFiltering()&&this.fspecial)||this.viewport.getMetaData("special")){B.hide().next().show()}else{B.show().next().hide()}B=A.down("div.msgSubject a");if(this.viewport.isFiltering()||this.viewport.getMetaData("nothread")||this.viewport.getMetaData("sortlimit")){B.show().next().hide();B.down().hide()}else{B.down().show()}A.childElements().invoke("removeClassName","sortup").invoke("removeClassName","sortdown");A.down("div a[sortby="+C+"]").up().addClassName(this.viewport.getMetaData("sortdir")?"sortup":"sortdown")},togglePreviewPane:function(){this.showPreview=!this.showPreview;$("previewtoggle").setText(this.showPreview?DIMP.text.hide_preview:DIMP.text.show_preview);[$("msgList")].invoke(this.showPreview?"removeClassName":"addClassName","msglistNoPreview");new Ajax.Request(DimpCore.addSID(DIMP.conf.URI_PREFS),{parameters:{app:"dimp",pref:"show_preview",value:this.showPreview?1:0}});this.viewport.showSplitPane(this.showPreview);if(this.showPreview){this.initPreviewPane()}},_loadPreview:function(C){var A=$("previewPane"),B;if(!A.visible()){return}if(this.pp&&this.pp==C){return}this.pp=C;if(this.ppfifo.indexOf(C.vp_id)!=-1){return this._loadPreviewCallback(this.ppcache[C.vp_id])}B=A.positionedOffset();$("msgLoading").setStyle({position:"absolute",top:(B.top+10)+"px",left:(B.left+10)+"px"}).show();DimpCore.doAction("ShowPreview",{},this.viewport.createSelection("dataob",C),this.bcache.get("loadPC")||this.bcache.set("loadPC",this._loadPreviewCallback.bind(this)))},_loadPreviewCallback:function(resp){var row,search,tmp,tmp2,pm=$("previewMsg"),r=resp.response,t=$("msgHeadersContent").down("THEAD");if(!r.error){search=this.viewport.getViewportSelection(r.view).search({vp_id:{equal:[r.uid]}});if(search.size()){row=search.get("dataob").first();this.updateUnseenUID(row,0)}}if(this.pp&&this.pp.vp_id!=r.uid){return}if(r.error||this.viewport.getSelected().size()!=1){if(r.error){DimpCore.showNotifications([{type:r.errortype,message:r.error}])}this.clearPreviewPane();return}this._expirePPCache([r.uid]);this.ppcache[r.uid]=resp;this.ppfifo.push(r.uid);DimpCore.removeAddressLinks(pm);DIMP.conf.msg_index=r.index;DIMP.conf.msg_folder=r.folder;tmp=pm.select(".subject");tmp.invoke("update",r.subject);switch(r.priority){case"high":case"low":tmp.invoke("insert",{top:$($(r.priority+"_priority_img").cloneNode(false)).writeAttribute("id",false)});break}$("msgHeadersColl").select(".date").invoke("update",r.minidate);$("msgHeaderDate").select(".date").invoke("update",r.fulldate);["from","to","cc"].each(function(a){if(r[a]){(a=="from"?pm.select("."+a):[t.down("."+a)]).each(function(elt){elt.replace(DimpCore.buildAddressLinks(r[a],elt.cloneNode(false)))})}[$("msgHeader"+a.capitalize())].invoke(r[a]?"show":"hide")});$("toggleHeaders").select(".attachmentImage").invoke(r.atc_label?"show":"hide");if(r.atc_label){tmp=$("msgAtc").show().down(".label");tmp2=$("partlist");tmp2.hide().previous().update(r.atc_label+" "+r.atc_download);if(r.atc_list){$("partlist_col").show();$("partlist_exp").hide();tmp.down().hide().next().show();tmp2.update(r.atc_list)}else{tmp.down().show().next().hide()}}else{$("msgAtc").hide()}$("msgBody").down().update(r.msgtext);$("msgLoading","previewInfo").invoke("hide");$("previewPane").scrollTop=0;pm.show();if(r.js){eval(r.js.join(";"))}this._addHistory("msg:"+row.view+":"+row.imapuid)},initPreviewPane:function(){var A=this.viewport.getSelected();if(A.size()!=1){this.clearPreviewPane()}else{this._loadPreview(A.get("dataob").first())}},clearPreviewPane:function(){$("msgLoading","previewMsg").invoke("hide");$("previewInfo").show();this.pp=null},_expirePPCache:function(A){this.ppfifo=this.ppfifo.without(A);A.each(function(B){delete this.ppcache[B]},this);if(this.ppfifo.size()>20){delete this.ppcache[this.ppfifo.shift()]}},updateUnseenUID:function(B,E){var C,D,A;if(!B.bg){return false}A=B.bg.indexOf("unseen")!=-1;if((E&&A)||(!E&&!A)){return false}C=this.viewport.createSelection("dataob",B);D=parseInt($(this.getFolderId(B.view)).readAttribute("u"),10);if(E){this.viewport.updateFlag(C,"unseen",true);++D}else{this.viewport.updateFlag(C,"unseen",false);--D}this.updateUnseenStatus(B.view,D)},updateUnseenStatus:function(B,A){if(this.viewport){this.viewport.setMetaData({unseen:A},B)}this.setFolderLabel(B,A);if(this.folder==B){this.updateTitle()}},setMessageListTitle:function(){var B,A=this.viewport.getMetaData("total_rows");if(A>0){B=this.viewport.currentOffset();$("msgHeader").update(DIMP.text.messages+" "+(B+1)+" - "+(Math.min(B+this.viewport.getPageSize(),A))+" "+DIMP.text.of+" "+A)}else{$("msgHeader").update(DIMP.text.nomessages)}},setFolderLabel:function(B,C){var A,D=this.getFolderId(B);A=$(D);if(!A||!A.hasAttribute("u")){return}C=parseInt(C);A.writeAttribute("u",C);if(B=="INBOX"&&window.fluid){window.fluid.setDockBadge(C?C:"")}$(D+"_label").update((C>0)?new Element("STRONG").insert(A.readAttribute("l")).insert("&nbsp;").insert(new Element("SPAN",{className:"count",dir:"ltr"}).insert("("+C+")")):A.readAttribute("l"))},getFolderId:function(A){return"fld"+decodeURIComponent(A).replace(/_/g,"__").replace(/\W/g,"_")},getSubFolderId:function(A){return"sub"+A},pollFolders:function(){this.setPollFolders();var A={};if(this.folder&&$("dimpmain_folder").visible()){A=this.viewport.addRequestParams({})}$("checkmaillink").down("A").update("["+DIMP.text.check+"]");DimpCore.doAction("PollFolders",A,null,this.bcache.get("pollFC")||this.bcache.set("pollFC",this._pollFoldersCallback.bind(this)))},_pollFoldersCallback:function(B){var C,A;B=B.response;if(B.poll){A=this;$H(B.poll).each(function(D){A.updateUnseenStatus(D.key,D.value)})}if(B.quota){C=$("quota").cleanWhitespace();C.setText(B.quota.m);C.down("SPAN.used IMG").writeAttribute({width:99-B.quota.p})}$("checkmaillink").down("A").update(DIMP.text.getmail)},setPollFolders:function(){if(DIMP.conf.refresh_time){if(this.pollPE){this.pollPE.stop()}this.pollPE=new PeriodicalExecuter(this.pollFolders.bind(this),DIMP.conf.refresh_time)}},_portalCallback:function(B){if(B.response.linkTags){var A=$$("HEAD").first();B.response.linkTags.each(function(C){var D=new Element("LINK",{type:"text/css",rel:"stylesheet",href:C.href});if(C.media){D.media=C.media}A.insert(D)})}$("dimpmain_portal").update(B.response.portal);$("dimpmain_portal").select("h1.header a").each(this.bcache.get("portalClkLink")||this.bcache.set("portalClkLink",function(C){C.observe("click",function(D,E){this.go("app:"+E.readAttribute("app"));D.stop()}.bindAsEventListener(this,C))}.bind(this)))},_searchfilterOnKeyup:function(){if(this.searchobserve){clearTimeout(this.searchobserve)}this.searchobserve=(this.bcache.get("searchfilterR")||this.bcache.set("searchfilterR",this.searchfilterRun.bind(this))).delay(0.5)},searchfilterRun:function(){if(!this.viewport.isFiltering()){this.filtertoggle=1;this.fspecial=this.viewport.getMetaData("special")}this.viewport.runFilter($F("msgList_filter"))},_searchfilterOnFocus:function(){var A=$("qoptions").up();if($("msgList_filter").hasClassName("msgFilterDefault")){this._setFilterText(false)}if(!A.visible()){$("sf_current").update(this.viewport.getMetaData("label"));this._setSearchfilterParams(this.viewport.getMetaData("special")?"to":"from","msg");this._setSearchfilterParams("current","folder");$(document.documentElement).setStyle({overflowY:"hidden"});Effect.SlideDown(A,{duration:0.5,afterFinish:function(){this._onResize(false,true);$(document.documentElement).setStyle({overflowY:"auto"})}.bind(this)})}},_searchfilterOnBlur:function(){if(!$F("msgList_filter")){this._setFilterText(true)}},searchfilterClear:function(A){var B=$("qoptions").up();if(!B.visible()){return}if(this.searchobserve){clearTimeout(this.searchobserve);this.searchobserve=null}this._setFilterText(true);Effect.SlideUp(B,{duration:0.5,afterFinish:this._onResize.bind(this,A)});this.filtertoggle=2;this.resetSelected();this.viewport.stopFilter(A)},_setFilterText:function(B){var A=$("msgList_filter");if(B){A.setValue(DIMP.text.search);A.addClassName("msgFilterDefault")}else{A.setValue("");A.removeClassName("msgFilterDefault")}},_setSearchfilterParams:function(C,A){var B=(A=="folder")?this.sfiltersfolder:this.sfilters;B.keys().each(function(D){$(D).writeAttribute("className",(C==B.get(D))?"qselected":"")})},updateSearchfilter:function(B,A){this._setSearchfilterParams(B,A);if($F("msgList_filter")){this.viewport.runFilter()}},_addSearchfilterParams:function(){var A=this.sfiltersfolder.keys().find(function(B){return $(B).hasClassName("qselected")});return{searchfolder:this.sfiltersfolder.get(A),searchmsg:this.sfilters.get(this._getSearchfilterField())}},_getSearchfilterField:function(){return this.sfilters.keys().find(function(A){return $(A).hasClassName("qselected")})},toggleButtons:function(){var A=(this.selectedCount()==0);DimpCore.buttons.each(function(B){var C=$(B);if(C){[C.up()].invoke(A?"addClassName":"removeClassName","disabled");DimpCore.DMenu.disable(B+"_img",true,A)}})},_folderDropHandler:function(C,D,H){var G,F,E,B=C.readAttribute("mbox"),A=C.readAttribute("ftype");if(D.hasClassName("folder")){G=(C==$("dropbase"));if(G||(A!="special"&&!this.isSubfolder(D,C))){DimpCore.doAction("RenameFolder",{old_name:D.readAttribute("mbox"),new_parent:G?"":B,new_name:D.readAttribute("l")},null,this.bcache.get("folderC")||this.bcache.set("folderC",this._folderCallback.bind(this)))}}else{if(A!="container"){F=this.viewport.getSelected();if(F.size()){E=F}else{if(D.readAttribute("mbox")!=B){E=this.viewport.createSelection("domid",D.id)}}if(E.size()){if(H.ctrlKey){DimpCore.doAction("CopyMessage",this.viewport.addRequestParams({tofld:B}),E,this.bcache.get("pollFC")||this.bcache.set("pollFC",this._pollFoldersCallback.bind(this)))}else{if(this.folder!=B){this.viewport.updateFlag(E,"deletedmsg",true);DimpCore.doAction("MoveMessage",this.viewport.addRequestParams({tofld:B}),E,this.bcache.get("deleteC")||this.bcache.set("deleteC",this._deleteCallback.bind(this)))}}}}}},_dragCaption:function(){var A=this.selectedCount();return A+" "+(A==1?DIMP.text.message:DIMP.text.messages)},_keydownHandler:function(E){if(!$("dimpmain_folder").visible()||E.findElement("FORM")||RedBox.overlayVisible()){return}var H,G,B,F,D,A=E.keyCode||E.charCode,C=this.viewport.getSelected();switch(A){case Event.KEY_DELETE:case Event.KEY_BACKSPACE:if(C.size()==1){B=C.get("dataob").first();if(E.shiftKey){this.moveSelected(B.rownum+((B.rownum==this.viewport.getMetaData("total_rows"))?-1:1),true)}this.flag("deleted",B)}else{this.flag("deleted")}E.stop();break;case Event.KEY_UP:case Event.KEY_DOWN:if(E.shiftKey&&this.lastrow!=-1){F=this.viewport.createSelection("rownum",this.lastrow+((A==Event.KEY_UP)?-1:1));if(F.size()){F=F.get("dataob").first();this.viewport.scrollTo(F.rownum);this.msgSelect(F.domid,{shift:true})}}else{this.moveSelected(A==Event.KEY_UP?-1:1)}E.stop();break;case Event.KEY_PAGEUP:case Event.KEY_PAGEDOWN:if(!E.ctrlKey&&!E.shiftKey&&!E.altKey&&!E.metaKey){G=this.viewport.getPageSize()-1;move=G*(A==Event.KEY_PAGEUP?-1:1);if(C.size()==1){H=this.viewport.currentOffset();D=C.get("rownum").first()-1;switch(A){case Event.KEY_PAGEUP:if(H!=D){move=H-D}break;case Event.KEY_PAGEDOWN:if((H+G)!=D){move=H+G-D}break}}this.moveSelected(move);E.stop()}break;case Event.KEY_HOME:case Event.KEY_END:this.moveSelected(A==Event.KEY_HOME?1:this.viewport.getMetaData("total_rows"),true);E.stop();break;case Event.KEY_RETURN:if(!E.element().match("input")){if(C.size()==1){this.msgWindow(C.get("dataob").first())}}E.stop();break;case 65:case 97:if(E.ctrlKey){this.selectAll();E.stop()}break}},renameFolder:function(A){if(Object.isUndefined(A)){return}A=$(A);var B=this._createFolderForm(function(C){this._folderAction(A,C,"rename");return false}.bindAsEventListener(this),DIMP.text.rename_prompt);B.down("input").setValue(A.readAttribute("l"))},createBaseFolder:function(){this._createFolderForm(function(A){this._folderAction("",A,"create");return false}.bindAsEventListener(this),DIMP.text.create_prompt)},createSubFolder:function(A){if(Object.isUndefined(A)){return false}this._createFolderForm(function(B){this._folderAction($(A),B,"createsub");return false}.bindAsEventListener(this),DIMP.text.createsub_prompt)},_createFolderForm:function(A,B){var C=new Element("FORM",{action:"#",id:"RB_folder"}).insert(new Element("P").insert(B)).insert(new Element("INPUT",{type:"text",size:15})).insert(new Element("INPUT",{type:"button",className:"button",value:DIMP.text.ok}).observe("click",A)).insert(new Element("INPUT",{type:"button",className:"button",value:DIMP.text.cancel}).observe("click",this.bcache.get("closeRB")||this.bcache.set("closeRB",this._closeRedBox.bind(this)))).observe("keydown",function(D){if((D.keyCode||D.charCode)==Event.KEY_RETURN){D.stop();A(D)}});RedBox.overlay=true;RedBox.onDisplay=Form.focusFirstElement.curry(C);RedBox.showHtml(C);return C},_closeRedBox:function(){var A=RedBox.getWindowContents();DimpCore.addGC([A,A.descendants()].flatten());RedBox.close()},_folderAction:function(B,D,G){this._closeRedBox();var C,F,E,A=D.findElement("form");E=$F(A.down("input"));if(E){switch(G){case"rename":if(B.readAttribute("l")!=E){C="RenameFolder";F={old_name:B.readAttribute("mbox"),new_parent:B.up().hasClassName("folderlist")?"":B.up(1).previous().readAttribute("mbox"),new_name:E}}break;case"create":case"createsub":C="CreateFolder";F={folder:E};if(G=="createsub"){F.parent=B.readAttribute("mbox")}break}if(C){DimpCore.doAction(C,F,null,this.bcache.get("folderC")||this.bcache.set("folderC",this._folderCallback.bind(this)))}}},_folderCallback:function(A){A=A.response;if(A.d){A.d.each(this.bcache.get("deleteFolder")||this.bcache.set("deleteFolder",this.deleteFolder.bind(this)))}if(A.c){A.c.each(this.bcache.get("changeFolder")||this.bcache.set("changeFolder",this.changeFolder.bind(this)))}if(A.a){A.a.each(this.bcache.get("createFolder")||this.bcache.set("createFolder",this.createFolder.bind(this)))}},_deleteCallback:function(C){var A,B=[];this.msgListLoading(false);this._pollFoldersCallback(C);C=C.response;if(!C.uids||C.folder!=this.folder){return}$H(DimpCore.parseRangeString(C.uids)).each(function(D){D.value.each(function(E){B.push(E+D.key)})});A=this.viewport.getViewportSelection().search({vp_id:{equal:B}});if(A.size()){if(C.remove){this.viewport.remove(A,{cacheid:C.cacheid,noupdate:C.viewport});this._expirePPCache(A.get("uid"))}else{this.viewport.updateFlag(A,"deletedmsg",true)}}},_emptyFolderCallback:function(A){if(A.response.mbox){if(this.folder==A.response.mbox){this.viewport.reload();this.clearPreviewPane()}this.setFolderLabel(A.response.mbox,0)}},_flagAllCallback:function(A){if(A.response.mbox){this.setFolderLabel(A.response.mbox,A.response.u)}},_folderLoadCallback:function(B){this._folderCallback(B);var D=$("specialfolders","normalfolders").compact(),C=$("normalfolders"),A=C.getStyle("max-height");D.invoke("observe","click",this._handleFolderMouseEvent.bindAsEventListener(this,"click"));D.invoke("observe","mouseover",this._handleFolderMouseEvent.bindAsEventListener(this,"over"));if(DIMP.conf.is_ie6){D.invoke("observe","mouseout",this._handleFolderMouseEvent.bindAsEventListener(this,"out"))}$("foldersLoading").hide();$("foldersSidebar").show();if(A!==null||(Prototype.Browser.IE&&Object.isUndefined(A)&&(C.getStyle("height")=="0px"))){this._sizeFolderlist();Event.observe(window,"resize",this._sizeFolderlist.bind(this))}},_handleFolderMouseEvent:function(E,D){var C,B=E.element(),A=B.up(".folder")||B.up(".custom");if(!A){return}C=A.readAttribute("ftype");switch(D){case"over":if(DIMP.conf.is_ie6){A.addClassName("over")}if(C&&!this.mo_sidebar[A.id]){DimpCore.addMouseEvents({id:A.id,type:C});this.mo_sidebar[A.id]=1}break;case"out":A.removeClassName("over");break;case"click":if(B.hasClassName("exp")||B.hasClassName("col")){this._toggleSubFolder(A.id,"tog")}else{switch(C){case"container":case"vcontainer":E.stop();break;case"folder":case"special":case"virtual":E.stop();return this.go("folder:"+A.readAttribute("mbox"));break}}break}},_toggleSubFolder:function(C,D){C=$(C);var B={duration:0.2},A=$(this.getSubFolderId(C.id));if(A&&(D=="tog"||(D=="exp"&&!A.visible())||(D=="col"&&A.visible()))){if(C.descendantOf("specialfolders")){B.afterFinish=this._sizeFolderlist}C.firstDescendant().writeAttribute({className:A.visible()?"exp":"col"});if(A.visible()){Effect.BlindUp(A,B)}else{Effect.BlindDown(A,B)}}},createFolder:function(C){var B,F,I,G,H,E=this.getFolderId(C.m),A=decodeURIComponent(C.m),D=this.getSubFolderId(E),J=$(D);I=new Element("LI",{className:"folder",id:E,l:C.l,mbox:A,ftype:(C.v?(C.co?"vcontainer":"virtual"):(C.co?"container":(C.s?"special":"folder")))});B=new Element("DIV",{className:C.cl||"base",id:E+"_div"});if(C.i){B.update(C.i)}if(C.ch){B.writeAttribute({className:"exp"}).observe("mouseover",this.bcache.get("mo_folder")||this.bcache.set("mo_folder",function(K){K=K.element();if(DragDrop.Drags.drag&&K.hasClassName("exp")){this._toggleSubFolder(K.up(),"exp")}}.bindAsEventListener(this)))}I.insert(B).insert(new Element("A",{id:E+"_label",title:C.l}).insert(C.l));if(J){if(J.insert({before:I}).visible()){B.removeClassName("exp").addClassName("col")}}else{if(C.s){H=$("specialfolders")}else{H=$(this.getSubFolderId(this.getFolderId(C.pa)));H=(H)?H.down("UL"):$("normalfolders")}G=A.toLowerCase();F=H.childElements().find(function(K){var L=K.readAttribute("mbox");return L&&(!C.s||L!="INBOX")&&(G<L.toLowerCase())});if(F){F.insert({before:I})}else{H.insert(I)}if(C.ch){I.insert({after:new Element("LI",{className:"subfolders",id:D}).insert(new Element("UL")).hide()})}}if(!C.v){new Drop(I,this._folderDropConfig)}if(C.po){I.writeAttribute("u","");this.setFolderLabel(A,C.u)}},deleteFolder:function(A){var B=decodeURIComponent(A),C;if(this.folder==B){this.go("folder:INBOX")}C=this.getFolderId(A);this.deleteFolderElt(C,true);Effect.Fade(C,{afterFinish:function(D){try{DimpCore.addGC(D.element.remove())}catch(E){DimpCore.debug("deleteFolder",E)}}})},changeFolder:function(B){var D=this.getFolderId(B.m),A=$(D+"_div"),C=A&&A.hasClassName("col");this.deleteFolderElt(D,!B.ch);if(B.co&&this.folder==B.m){this.go("folder:INBOX")}$(D).remove();this.createFolder(B);if(B.ch&&C){A.removeClassName("exp").addClassName("col")}},deleteFolderElt:function(D,B){var C=$(D),A;DimpCore.addGC($(C,D+"_div",D+"_label"));if(B){A=$(this.getSubFolderId(D));if(A){A.remove()}}[DragDrop.Drags.get_drag(D),DragDrop.Drops.get_drop(D)].compact().invoke("destroy");DimpCore.removeMouseEvents(C);delete this.mo_sidebar[D];if(this.viewport){this.viewport.deleteView(D)}},_sizeFolderlist:function(){var A=$("normalfolders");A.setStyle({height:(document.viewport.getHeight()-A.cumulativeOffset()[1]-10)+"px"})},flag:function(F,D,E){var C,B,H,G=[],A=1;if(D){if(Object.isUndefined(E)){H=this.viewport.createSelection("dataob",D)}else{H=this.viewport.getViewportSelection().search({imapuid:{equal:[D]},view:{equal:[E]}});if(!H.size()&&E!=this.folder){H=this.viewport.getViewportSelection(E).search({imapuid:{equal:[D]}})}}}else{H=this.viewport.getSelected()}switch(F){case"allUnseen":case"allSeen":DimpCore.doAction((F=="allUnseen")?"MarkFolderUnseen":"MarkFolderSeen",{folder:E},null,this.bcache.get("flagAC")||this.bcache.set("flagAC",this._flagAllCallback.bind(this)));if(E==this.folder){this.viewport.updateFlag(this.createSelection("rownum",$A($R(1,this.viewport.getMetaData("total_rows")))),"unseen",F=="allUnseen")}break;case"deleted":case"undeleted":case"spam":case"ham":case"blacklist":case"whitelist":if(!H.size()){break}if(F=="deleted"){H=H.search({isdel:{not:[true]}});if(!H.size()){break}H.set({isdel:true})}B=this.viewport.addRequestParams({});if(F=="deleted"||F=="undeleted"){this.viewport.updateFlag(H,"deletedmsg",F=="deleted")}if(F=="undeleted"){DimpCore.doAction("UndeleteMessage",B,H)}else{C={deleted:"DeleteMessage",spam:"ReportSpam",ham:"ReportHam",blacklist:"Blacklist",whitelist:"Whitelist"};DimpCore.doAction(C[F],B,H,this.bcache.get("deleteC")||this.bcache.set("deleteC",this._deleteCallback.bind(this)),{asynchronous:!(D&&E)});if(F=="spam"||F=="ham"){this.msgListLoading(true)}}break;case"unseen":case"seen":if(!H.size()){break}B={folder:this.folder,messageFlag:"-seen"};if(F=="seen"){A=0;B.messageFlag="seen"}G=H.get("dataob");if(G.size()){G.each(function(I){this.updateUnseenUID(I,A)},this);DimpCore.doAction("MarkMessage",B,this.viewport.createSelection("dataob",G))}break;case"flagged":case"clear":if(!H.size()){break}B={folder:this.folder,messageFlag:((F=="flagged")?"flagged":"-flagged")};this.viewport.updateFlag(H,"flagged",F=="flagged");DimpCore.doAction("MarkMessage",B,H);break;case"answered":this.viewport.updateFlag(H,"answered",true);this.viewport.updateFlag(H,"flagged",false);break}},purgeDeleted:function(){DimpCore.doAction("PurgeDeleted",this.viewport.addRequestParams({}),null,this.bcache.get("deleteC")||this.bcache.set("deleteC",this._deleteCallback.bind(this)))},modifyPollFolder:function(A,B){DimpCore.doAction("ModifyPollFolder",{folder:A,add:(B)?1:0},null,this.bcache.get("modifyPFC")||this.bcache.set("modifyPFC",this._modifyPollFolderCallback.bind(this)))},_modifyPollFolderCallback:function(A){A=A.response;var B=A.folder,D,C={response:{poll:{}}};D=$(this.getFolderId(B));if(A.add){C.response.poll[B]=A.poll.u;D.writeAttribute("u",0)}else{C.response.poll[B]=0}this._pollFoldersCallback(C);if(!A.add){D.removeAttribute("u")}},msgListLoading:function(A){var B;if(this.fl_visible!=A){this.fl_visible=A;if(A){B=$("msgList").positionedOffset();$("folderLoading").setStyle({position:"absolute",top:(B.top+10)+"px",left:(B.left+10)+"px"});Effect.Appear("folderLoading",{duration:0.2});$(document.body).setStyle({cursor:"progress"})}else{Effect.Fade("folderLoading",{duration:0.2});$(document.body).setStyle({cursor:"default"})}}},isSubfolder:function(B,C){var A=$(this.getSubFolderId(B.readAttribute("id")));return A&&C.descendantOf(A)},_onLoad:function(){var B,D=DimpCore.clickObserveHandler,A=DimpCore.DMenu;if(Horde.dhtmlHistory.initialize()){Horde.dhtmlHistory.addListener(this.go.bind(this))}if(!Horde.dhtmlHistory.getCurrentLocation()){if(DIMP.conf.login_view=="inbox"){this.go("folder:INBOX")}else{this.go("portal");if(DIMP.conf.background_inbox){this.loadFolder("INBOX",true)}}}this._setFilterText(true);DimpCore.addPopdown("button_reply","reply");A.disable("button_reply_img",true,true);DimpCore.addPopdown("button_forward","forward");A.disable("button_forward_img",true,true);DimpCore.addPopdown("button_other","otheractions");B=$("logo");if(B.visible()){D({d:B.down("a"),f:this.go.bind(this,"portal")})}D({d:$("composelink"),f:DimpCore.compose.bind(DimpCore,"new")});D({d:$("checkmaillink"),f:this.pollFolders.bind(this)});["portal","options"].each(function(C){var E=$("app"+C);if(E){D({d:E,f:this.go.bind(this,C)})}},this);B=$("applogout");if(B){D({d:B,f:DimpCore.logout.bind(DimpCore)})}B=$("applicationfolders");if(B){B.select("li.custom a").each(function(C){D({d:C,f:this.go.bind(this,"app:"+C.readAttribute("app"))})},this)}D({d:$("newfolder"),f:this.createBaseFolder.bind(this)});new Drop("dropbase",this._folderDropConfig);B=$("hometab");if(B){D({d:B,f:this.go.bind(this,"portal")})}$("tabbar").select("a.applicationtab").each(function(C){D({d:C,f:this.go.bind(this,"app:"+C.readAttribute("app"))})},this);D({d:$("button_reply"),f:this.composeMailbox.bind(this,"reply"),ns:true});D({d:$("button_forward"),f:this.composeMailbox.bind(this,DIMP.conf.forward_default),ns:true});["spam","ham","deleted"].each(function(C){var E=$("button_"+C);if(E){D({d:E,f:this.flag.bind(this,C)})}},this);D({d:$("button_compose").down("A"),f:DimpCore.compose.bind(DimpCore,"new")});D({d:$("button_other"),f:function(C){A.trigger(C.findElement("A").next(),true)},p:true});D({d:$("qoptions").down(".qclose a"),f:this.searchfilterClear.bind(this,false)});["all","current"].each(function(C){var E=$("sf_"+C);if(E){D({d:E,f:this.updateSearchfilter.bind(this,C,"folder")})}},this);["msgall","from","to","subject"].each(function(C){D({d:$("sf_"+C),f:this.updateSearchfilter.bind(this,C,"msg")})},this);D({d:$("msglistHeader"),f:this.sort.bind(this),p:true});D({d:$("ctx_folder_create"),f:function(){this.createSubFolder(A.element())}.bind(this),ns:true});D({d:$("ctx_folder_rename"),f:function(){this.renameFolder(A.element())}.bind(this),ns:true});D({d:$("ctx_folder_empty"),f:function(){var C=A.element().readAttribute("mbox");A.close(true);if(window.confirm(DIMP.text.empty_folder)){DimpCore.doAction("EmptyFolder",{folder:C},null,this._emptyFolderCallback.bind(this))}}.bind(this),ns:true});D({d:$("ctx_folder_delete"),f:function(){var C=A.element().readAttribute("mbox");A.close(true);if(window.confirm(DIMP.text.delete_folder)){DimpCore.doAction("DeleteFolder",{folder:C},null,this.bcache.get("folderC")||this.bcache.set("folderC",this._folderCallback.bind(this)))}}.bind(this),ns:true});["ctx_folder_seen","ctx_folder_unseen"].each(function(C){D({d:$(C),f:function(E){this.flag(E,null,A.element().readAttribute("mbox"))}.bind(this,C=="ctx_folder_seen"?"allSeen":"allUnseen"),ns:true})},this);["ctx_folder_poll","ctx_folder_nopoll"].each(function(C){D({d:$(C),f:function(E){this.modifyPollFolder(A.element().readAttribute("mbox"),E)}.bind(this,C=="ctx_folder_poll"),ns:true})},this);D({d:$("ctx_container_create"),f:function(){this.createSubFolder(A.element())}.bind(this),ns:true});D({d:$("ctx_container_rename"),f:function(){this.renameFolder(A.element())}.bind(this),ns:true});["reply","reply_all","reply_list","forward_all","forward_body","forward_attachments"].each(function(C){D({d:$("ctx_message_"+C),f:this.composeMailbox.bind(this,C),ns:true})},this);["seen","unseen","flagged","clear","spam","ham","blacklist","whitelist","deleted","undeleted"].each(function(C){var E=$("ctx_message_"+C);if(E){D({d:E,f:this.flag.bind(this,C),ns:true})}},this);D({d:$("ctx_draft_resume"),f:this.composeMailbox.bind(this,"resume")});["flagged","clear","deleted","undeleted"].each(function(C){var E=$("ctx_draft_"+C);if(E){D({d:E,f:this.flag.bind(this,C),ns:true})}},this);["reply","reply_all","reply_list"].each(function(C){D({d:$("ctx_reply_"+C),f:this.composeMailbox.bind(this,C),ns:true})},this);["forward_all","forward_body","forward_attachments"].each(function(C){D({d:$("ctx_forward_"+C),f:this.composeMailbox.bind(this,C),ns:true})},this);D({d:$("previewtoggle"),f:this.togglePreviewPane.bind(this),ns:true});["seen","unseen","flagged","clear","blacklist","whitelist"].each(function(C){var E=$("oa_"+C);if(E){D({d:E,f:this.flag.bind(this,C),ns:true})}},this);D({d:$("oa_selectall"),f:this.selectAll.bind(this),ns:true});B=$("oa_purge_deleted");if(B){D({d:B,f:this.purgeDeleted.bind(this),ns:true})}$("toggleHeaders").select("A").each(function(C){D({d:C,f:function(){[C.up().select("A"),$("msgHeadersColl","msgHeaders")].flatten().invoke("toggle")},ns:true})});$("msg_newwin","msg_newwin_options").compact().each(function(C){D({d:C,f:function(){this.msgWindow(this.viewport.getViewportSelection().search({imapuid:{equal:[DIMP.conf.msg_index]},view:{equal:[DIMP.conf.msg_folder]}}).get("dataob").first())}.bind(this)})},this);DimpCore.messageOnLoad();this._resizeIE6()},_resizeIE6:function(){if(DIMP.conf.is_ie6){var A=parseInt($("sidebarPanel").getStyle("width"),10),B=document.viewport.getWidth()-A-30;$("normalfolders").setStyle({width:A+"px"});$("dimpmain").setStyle({width:B+"px"});$("msglist").setStyle({width:(B-5)+"px"});$("msgBody").setStyle({width:(B-25)+"px"});A=$("dimpmain_portal").down("IFRAME");if(A){this._resizeIE6Iframe(A)}}},_resizeIE6Iframe:function(A){if(DIMP.conf.is_ie6){A.setStyle({width:$("dimpmain").getStyle("width"),height:(document.viewport.getHeight()-20)+"px"})}}};DimpBase._msgDragConfig={scroll:"normalfolders",threshold:5,caption:DimpBase._dragCaption.bind(DimpBase),onStart:function(C,B){var A={right:B.isRightClick()},D=C.element.id;C.selectIfNoDrag=false;if(!A.right&&(B.ctrlKey||B.metaKey)){this.msgSelect(D,$H({ctrl:true}).merge(A).toObject())}else{if(B.shiftKey){this.msgSelect(D,$H({shift:true}).merge(A).toObject())}else{if(this.isSelected("domid",D)){if(!A.right&&this.selectedCount()){C.selectIfNoDrag=true}}else{this.msgSelect(D,A)}}}}.bind(DimpBase),onEnd:function(B,A){if(B.selectIfNoDrag&&!B.wasDragged){this.msgSelect(B.element.id,{right:A.isRightClick()})}}.bind(DimpBase)};DimpBase._folderDragConfig={ghosting:true,offset:{x:5,y:5},scroll:"normalfolders",threshold:5,onDrag:function(B,A){if(!B.wasDragged){$("newfolder").hide();$("dropbase").show();B.ghost.removeClassName("on")}},onEnd:function(B,A){if(B.wasDragged){$("newfolder").show();$("dropbase").hide()}}};DimpBase._folderDropConfig={hoverclass:"dragdrop",caption:function(D,E,F){var A,G=E.readAttribute("l"),C=D.readAttribute("ftype"),B=D.readAttribute("l");if(D==$("dropbase")){return DIMP.text.moveto.replace(/%s/,G).replace(/%s/,DIMP.text.baselevel)}else{A=(F.ctrlKey)?DIMP.text.copyto:DIMP.text.moveto;if(E.hasClassName("folder")){return(C!="special"&&!this.isSubfolder(E,D))?A.replace(/%s/,G).replace(/%s/,B):""}else{return C!="container"?A.replace(/%s/,this._dragCaption()).replace(/%s/,B):""}}}.bind(DimpBase),onDrop:DimpBase._folderDropHandler.bind(DimpBase)};document.observe("dom:loaded",function(){$("dimpLoading").hide();$("dimpPage").show();DimpCore.doAction("ListFolders",{},null,DimpBase._folderLoadCallback.bind(DimpBase));DimpBase._onLoad();if(!DIMP.conf.search_all){DimpBase.sfiltersfolder.unset("sf_all")}DimpBase.setPollFolders();document.observe("keydown",DimpBase._keydownHandler.bind(DimpBase));Event.observe(window,"resize",DimpBase._onResize.bind(DimpBase));if(DIMP.conf.is_ie6){document.observe("selectstart",Event.stop);$("dimpbarActions","serviceActions","applicationfolders").compact().invoke("select","LI").flatten().compact().each(function(A){A.observe("mouseover",A.addClassName.curry("over")).observe("mouseout",A.removeClassName.curry("over"))})}});DimpCore.onDoActionComplete=function(A){if(DimpBase.viewport&&A.response.viewport){DimpBase.viewport.ajaxResponse(A.response.viewport)}};DimpCore.addMouseEvents=DimpCore.addMouseEvents.wrap(DimpBase._addMouseEvents.bind(DimpBase));DimpCore.removeMouseEvents=DimpCore.removeMouseEvents.wrap(DimpBase._removeMouseEvents.bind(DimpBase));
\ No newline at end of file
diff --git a/imp/js/DimpCore.js b/imp/js/DimpCore.js
new file mode 100644 (file)
index 0000000..4bf381d
--- /dev/null
@@ -0,0 +1 @@
+var frames={horde_main:true},DimpCore={acount:0,remove_gc:[],server_error:0,view_id:1,buttons:["button_reply","button_forward","button_spam","button_ham","button_deleted"],debug:function(A,B){if(!this.is_logout&&DIMP.conf.debug){alert(A+": "+(B instanceof Error?B.name+"-"+B.message:Object.inspect(B)))}},toRangeString:function(A){var B="";$H(A).each(function(F){if(!F.value.size()){return}var D=F.value.numericSort(),E=last=D.shift(),C=[];D.each(function(G){if(last+1==G){last=G}else{C.push(E+(last==E?"":(":"+last)));E=last=G}});C.push(E+(last==E?"":(":"+last)));B+="{"+F.key.length+"}"+F.key+C.join(",")});return B},parseRangeString:function(G){var E,B,C,F,A={},D=[];G=G.strip();while(!G.blank()){if(!G.startsWith("{")){break}C=G.indexOf("}");E=parseInt(G.substr(1,C-1));F=G.substr(C+1,E);C+=E+1;B=G.indexOf("{",C);if(B==-1){uidstr=G.substr(C);G=""}else{uidstr=G.substr(C,B-C);G=G.substr(B)}uidstr.split(",").each(function(I){var H=I.split(":");if(H.size()==1){D.push(parseInt(I,10))}else{D=D.concat($A($R(parseInt(H[0],10),parseInt(H[1],10))))}});A[F]=D}return A},doAction:function(D,E,C,F,B){var A={};if(!this.doActionOpts){this.doActionOpts={onException:function(G,H){this.debug("onException",H)}.bind(this),onFailure:function(G,H){this.debug("onFailure",G)}.bind(this)}}B=Object.extend(this.doActionOpts,B||{});E=$H(E);D=D.startsWith("*")?D.substring(1):DIMP.conf.URI_IMP+"/"+D;if(C){if(C.viewport_selection){C.get("dataob").each(function(G){if(!A[G.view]){A[G.view]=[]}A[G.view].push(G.imapuid)});C=A}E.set("uid",DimpCore.toRangeString(C))}if(DIMP.conf.SESSION_ID){E.update(DIMP.conf.SESSION_ID.toQueryParams())}B.parameters=E.toQueryString();B.onComplete=function(G,H){this.doActionComplete(G,F)}.bind(this);new Ajax.Request(D,B)},doActionComplete:function(C,E){this.inAjaxCallback=true;var A=false,B={};if(!C.responseText||!C.responseText.length){A=true}else{try{B=C.responseText.evalJSON(true)}catch(D){this.debug("doActionComplete",D);A=true}}if(!B.msgs){B.msgs=[]}if(A){if(++this.server_error==3){this.showNotifications([{type:"horde.error",message:DIMP.text.ajax_timeout}])}this.inAjaxCallback=false;return}if(B.response&&Object.isFunction(E)){if(DIMP.conf.debug){E(B)}else{try{E(B)}catch(D){}}}if(this.server_error>=3){B.msgs.push({type:"horde.success",message:DIMP.text.ajax_recover})}this.server_error=0;if(!B.msgs_noauto){this.showNotifications(B.msgs)}if(this.onDoActionComplete){this.onDoActionComplete(B)}this.inAjaxCallback=false},setTitle:function(A){document.title=DIMP.conf.name+" :: "+A},showNotifications:function(A){if(!A.size()||this.is_logout){return}A.find(function(D){switch(D.type){case"dimp.timeout":this.is_logout=true;this.redirect(DIMP.conf.timeout_url);return true;case"horde.error":case"horde.message":case"horde.success":case"horde.warning":case"imp.reply":case"imp.forward":case"imp.redirect":case"dimp.request":case"dimp.sticky":var H,I,K,E,J,F,G=$("alerts"),B=new Element("DIV",{className:D.type.replace(".","-")}),C=D.message;if(!G){G=new Element("DIV",{id:"alerts"});$(document.body).insert(G)}if($w("dimp.request dimp.sticky").indexOf(D.type)==-1){C=C.unescapeHTML().unescapeHTML()}G.insert(B.update(C));if(DIMP.conf.is_ie6){K=new Element("DIV",{className:"ie6alertsfix"}).clonePosition(B,{setLeft:false,setTop:false});H=K;K.insert(B.remove());G.insert(K)}else{H=B}I=Effect.Fade.bind(this,B,{duration:1.5,afterFinish:this.removeAlert.bind(this)});H.observe("click",I);if($w("horde.error dimp.request dimp.sticky").indexOf(D.type)==-1){I.delay(D.type=="horde.warning"?10:3)}if(D.type=="dimp.request"){J=function(){I();document.stopObserving("click",J)};document.observe("click",J)}if(F=$("alertslog")){switch(D.type){case"horde.error":E=DIMP.text.alog_error;break;case"horde.message":E=DIMP.text.alog_message;break;case"horde.success":E=DIMP.text.alog_success;break;case"horde.warning":E=DIMP.text.alog_warning;break}if(E){F=F.down("DIV UL");if(F.down().hasClassName("noalerts")){F.down().remove()}F.insert(new Element("LI").insert(new Element("P",{className:"label"}).insert(E)).insert(new Element("P",{className:"indent"}).insert(C).insert(new Element("SPAN",{className:"alertdate"}).insert("["+(new Date).toLocaleString()+"]"))))}}}},this)},toggleAlertsLog:function(){var A=$("alertsloglink").down("A"),C=$("alertslog").down("DIV"),B={duration:0.5};if(C.visible()){Effect.BlindUp(C,B);A.update(DIMP.text.showalog)}else{Effect.BlindDown(C,B);A.update(DIMP.text.hidealog)}},removeAlert:function(C){try{var A=$(C.element),B=A.up();if(B&&B.parentNode){this.addGC(A.remove());if(!B.childElements().size()&&B.hasClassName("ie6alertsfix")){this.addGC(B.remove())}}}catch(D){this.debug("removeAlert",D)}},compose:function(C,B){var A=DIMP.conf.compose_url;B=B||{};if(C){B.type=C}this.popupWindow(this.addURLParam(A,B),"compose"+new Date().getTime())},popupWindow:function(B,A){if(!(window.open(B,A.replace(/\W/g,"_"),"width="+DIMP.conf.popup_width+",height="+DIMP.conf.popup_height+",status=1,scrollbars=yes,resizable=yes"))){this.showNotifications([{type:"horde.warning",message:DIMP.text.popup_block}])}},closePopup:function(){if(this.inAjaxCallback){this.closePopup.bind(this).defer()}else{window.close()}},logout:function(){this.is_logout=true;this.redirect(DIMP.conf.URI_IMP+"/LogOut")},redirect:function(A){A=this.addSID(A);if(parent.frames.horde_main){parent.location=A}else{window.location=A}},addMouseEvents:function(A){this.DMenu.addElement(A.id,"ctx_"+A.type,A)},removeMouseEvents:function(A){this.DMenu.removeElement($(A).readAttribute("id"));this.addGC(A)},addPopdown:function(B,A){var C=$(B);C.insert({after:$($("popdown_img").cloneNode(false)).writeAttribute("id",B+"_img").show()});this.addMouseEvents({id:B+"_img",type:A,offset:C.up(),left:true})},buildAddressLinks:function(D,A){var E,C,B=D.size();if(B>15){C=$("largeaddrspan").cloneNode(true);A.insert(C);E=C.down(".dispaddrlist");C=C.down();this.clickObserveHandler({d:C,f:function(F){[F.down(),F.down(1),F.next()].invoke("toggle")}.curry(C)});C=C.down();C.setText(C.getText().replace("%d",B))}else{E=A}D.each(function(H,G){var F;if(H.raw){F=H.raw}else{F=new Element("A",{className:"address",id:"addr"+this.acount++,personal:H.personal,email:H.inner,address:H.address}).insert(H.display?H.display:H.address);F.observe("mouseover",function(){F.stopObserving("mouseover");this.addMouseEvents({id:F.id,type:"contacts",offset:F,left:true})}.bind(this))}E.insert(F);if(G+1!=B){E.insert(", ")}},this);return A},removeAddressLinks:function(A){[A.select(".address"),A.select(".largeaddrtoggle")].flatten().compact().each(this.removeMouseEvents.bind(this))},messageOnLoad:function(){var B=this.clickObserveHandler,A;if($("partlist")){B({d:$("partlist_col").up(),f:function(){$("partlist","partlist_col","partlist_exp").invoke("toggle")}})}if(A=$("msg_print")){B({d:A,f:function(){window.print()}})}if(A=$("msg_view_source")){B({d:A,f:function(){view(DimpCore.addSID(DIMP.conf.URI_VIEW)+"&index="+DIMP.conf.msg_index+"&mailbox="+DIMP.conf.msg_folder,DIMP.conf.msg_index+"|"+DIMP.conf.msg_folder)}})}B({d:$("ctx_contacts_new"),f:function(){this.compose("new",{to:this.DMenu.element().readAttribute("address")})}.bind(this),ns:true});B({d:$("ctx_contacts_add"),f:function(){this.doAction("AddContact",{name:this.DMenu.element().readAttribute("personal"),email:this.DMenu.element().readAttribute("email")},null,true)}.bind(this),ns:true});if($("alertslog")){B({d:$("alertsloglink"),f:this.toggleAlertsLog.bind(this)})}},addGC:function(A){this.remove_gc=this.remove_gc.concat(A)},clickObserveHandler:function(A){return A.d.observe("click",DimpCore._clickFunc.curry(A))},_clickFunc:function(B,A){B.p?B.f(A):B.f();if(!B.ns){A.stop()}},addSID:function(A){if(!DIMP.conf.SESSION_ID){return A}return this.addURLParam(A,DIMP.conf.SESSION_ID.toQueryParams())},addURLParam:function(A,C){var B=A.indexOf("?");if(B!=-1){C=$H(A.toQueryParams()).merge(C).toObject();A=A.substring(0,B)}return A+"?"+Object.toQueryString(C)}};if(typeof ContextSensitive!="undefined"){DimpCore.DMenu=new ContextSensitive()}document.observe("dom:loaded",function(){try{if(parent.opener&&parent.opener.location.host==window.location.host&&parent.opener.DimpCore){DIMP.baseWindow=parent.opener.DIMP.baseWindow||parent.opener}}catch(A){}if(!DIMP.conf.spam_reporting){DimpCore.buttons=DimpCore.buttons.without("button_spam")}if(!DIMP.conf.ham_reporting){DimpCore.buttons=DimpCore.buttons.without("button_ham")}new PeriodicalExecuter(function(){if(DimpCore.remove_gc.size()){try{$A(DimpCore.remove_gc.splice(0,75)).compact().invoke("stopObserving")}catch(B){DimpCore.debug("remove_gc[].stopObserving",B)}}},10)});Event.observe(window,"load",function(){DimpCore.window_load=true});Element.addMethods({setText:function(B,C){var A=0;$A(B.childNodes).each(function(D){if(D.nodeType==3){if(A++){Element.remove(D)}else{D.nodeValue=C}}});if(!A){$(B).insert(C)}},getText:function(B,A){var C="";$A(B.childNodes).each(function(D){if(D.nodeType==3){C+=D.nodeValue}else{if(A&&D.hasChildNodes()){C+=$(D).getText(true)}}});return C}});Object.extend(Array.prototype,{numericSort:function(){return this.sort(function(B,A){if(B>A){return 1}else{if(B<A){return-1}}return 0})}});Object.extend(String.prototype,{evalScripts:function(){var re=/function\s+([^\s(]+)/g;this.extractScripts().each(function(s){var func;eval(s);while(func=re.exec(s)){window[func[1]]=eval(func[1])}})}});function popup_imp(C,A,D,B){DimpCore.compose("new",B.toQueryParams().toObject())}function view(A,B){window.open(A,++DimpCore.view_id+B.replace(/\W/g,"_"),"menubar=yes,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes")};
\ No newline at end of file
diff --git a/imp/js/DimpSlider.js b/imp/js/DimpSlider.js
new file mode 100644 (file)
index 0000000..d5d9687
--- /dev/null
@@ -0,0 +1 @@
+var DimpSlider=Class.create({value:0,initialize:function(A,B){this.track=$(A);this.options=Object.extend({buttonclass:null,cursorclass:null,pagesize:0,totalsize:0},B||{});this.handle=new Element("DIV",{className:this.options.cursorclass}).makePositioned();this.track.insert(this.handle);if(this.options.buttonclass){this.sbup=new Element("DIV",{className:this.options.buttonclass.up});this.sbdown=new Element("DIV",{className:this.options.buttonclass.down}).makePositioned();this.handle.insert({before:this.sbup,after:this.sbdown});[this.sbup,this.sbdown].invoke("observe","mousedown",this._arrowClick.bindAsEventListener(this))}this.sbdownsize=this.sbupsize=this.value=0;this.active=this.dragging=false;if(this._showScroll()){this._initScroll()}this.eventMU=this._endDrag.bindAsEventListener(this);this.eventMM=this._update.bindAsEventListener(this);[this.handle,this.track].invoke("observe","mousedown",this._startDrag.bindAsEventListener(this))},_initScroll:function(){if(this.init){return false}this.init=true;this.track.show();if(this.sbup){this.sbupsize=this.sbup.offsetHeight;this.sbdownsize=this.sbdown.offsetHeight}this._updateHandleLength();return true},_startDrag:function(B){if(!B.isLeftClick()){return}var A,C=this.handle.cumulativeOffset();if(B.element()==this.track){A=(B.pointerY()<C[1])?-1:1;this.setScrollPosition(this.getValue()-A+(this.options.pagesize*A))}else{this.curroffsets=this.track.cumulativeOffset();this.offsetY=B.pointerY()-C[1]+this.sbupsize;this.active=true;document.observe("mouseup",this.eventMU);document.observe("mousemove",this.eventMM)}B.stop()},_update:function(A){if(this.active){this.dragging=true;this._setScrollPosition("px",Math.min(Math.max(0,A.pointerY()-this.offsetY-this.curroffsets[1]),this.handletop));if(this.options.onSlide){this.options.onSlide()}if(Prototype.Browser.WebKit){window.scrollBy(0,0)}A.stop()}},_endDrag:function(A){if(this.active&&this.dragging){this._updateFinished();A.stop();document.stopObserving("mouseup",this.eventMU);document.stopObserving("mousemove",this.eventMM)}this.active=this.dragging=false},_arrowClick:function(A){this.setScrollPosition(this.getValue()+((A.element()==this.sbup)?-1:1))},_updateFinished:function(){if(this.options.onChange){this.options.onChange()}},updateHandleLength:function(B,A){this.options.pagesize=B;this.options.totalsize=A;if(!this._showScroll()){this.value=0;this.track.hide();return}if(!this._initScroll()){this.track.show();this._updateHandleLength()}},_updateHandleLength:function(){var A=this.track.offsetHeight-this.sbupsize-this.sbdownsize;this.handle.setStyle({height:Math.max(10,Math.round((this.options.pagesize/this.options.totalsize)*A))+"px"});this.handletop=A-this.handle.offsetHeight;if(this.sbdown){this.sbdown.setStyle({top:this.handletop+"px"})}this._setScrollPosition("val",this.getValue())},getValue:function(){return this.value},setScrollPosition:function(B){if(this._showScroll()){var A=this.getValue();this._setScrollPosition("val",B);if(A!=this.getValue()){this._updateFinished()}}},_setScrollPosition:function(A,B){this.value=(A=="val")?Math.min(Math.max(0,B),this.options.totalsize-this.options.pagesize):Math.max(0,Math.round(Math.min(B,this.handletop)/this.handletop*(this.options.totalsize-this.options.pagesize)));this.handlevalue=(A=="px")?B:Math.round(this.getValue()/(this.options.totalsize-this.options.pagesize)*this.handletop);this.handle.setStyle({top:this.handlevalue+"px"})},_showScroll:function(){return(this.options.pagesize<this.options.totalsize)}});
\ No newline at end of file
diff --git a/imp/js/ViewPort.js b/imp/js/ViewPort.js
new file mode 100644 (file)
index 0000000..b5f449f
--- /dev/null
@@ -0,0 +1 @@
+var ViewPort=Class.create({initialize:function(A){A.content=$(A.content_container);A.empty=A.empty_container?$(A.empty_container):null;A.error=A.error_container?$(A.error_container):null;this.opts=A;this.scroller=new ViewPort_Scroller(this);this.template=new Template(A.template);this.current_req_lookup=$H();this.current_req=$H();this.fetch_hash=$H();this.slice_hash=$H();this.views=$H();this.showSplitPane(A.show_split_pane);this.isbusy=this.line_height=this.page_size=this.splitbar=this.splitbar_loc=this.uc_run=this.view=this.viewport_init=null;this.request_num=1},loadView:function(F,D,I,C){var E,H,G,B={},A;this._clearWait();if(this.page_size===null){A=this.getPageSize(this.show_split_pane?"default":"max");if(isNaN(A)){this.loadView.bind(this,F,D,I,C).defer();return}this.page_size=A}E=this._getBuffer();if(E){if(!C&&this.view){this.views.set(this.view,{buffer:E,offset:this.currentOffset()})}H=this.views.get(F)}else{G=true}if(C){B={background:true,view:F}}else{this.view=F;if(!this.viewport_init){this.viewport_init=1;this._renderViewport()}}if(H){this.setMetaData("additional_params",$H(D),F);this._updateContent(H.offset,B);if(!C){if(this.opts.onComplete){this.opts.onComplete()}this.opts.ajaxRequest(this.opts.fetch_action,this.addRequestParams({checkcache:1,rownum:this.currentOffset()+1}))}return true}else{if(!G){if(this.opts.onClearRows){this.opts.onClearRows(this.opts.content.childElements())}this.opts.content.update();this.scroller.clear()}}E=this._getBuffer(null,true);this.views.set(F,{buffer:E,offset:0});this.setMetaData({additional_params:$H(D)},F);if(I){B.search=I}else{B.offset=0}this._fetchBuffer(B);return false},deleteView:function(A){this.views.unset(A)},scrollTo:function(A,C){var B=this.scroller;B.noupdate=C;switch(this.isVisible(A)){case-1:B.moveScroll(A-1);break;case 1:B.moveScroll(Math.min(A,this.getMetaData("total_rows")-this.getPageSize()+1));break}B.noupdate=false},isVisible:function(A){var B=this.currentOffset();return(A<B+1)?-1:((A>(B+this.getPageSize("current")))?1:0)},reload:function(A){if(this.isFiltering()){this.filter.filter(null,A)}else{this._fetchBuffer({offset:this.currentOffset(),purge:true,params:A})}},remove:function(F,C){if(this.isbusy){this.remove.bind(this,F,cacheid,view).defer();return}if(!F.size()){return}C=C||{};this.isbusy=true;var A,B=0,E=F.get("div"),D=E.size();this.deselect(F);if(C.cacheid){this.setMetaData({cacheid:C.cacheid},C.view)}if(D){A={duration:0.3,to:0.01};E.each(function(G){if(++B==D){A.afterFinish=this._removeids.bind(this,F,C)}Effect.Fade(G,A)},this)}else{this._removeids(F,C)}},_removeids:function(B,A){this.setMetaData({total_rows:this.getMetaData("total_rows",A.view)-B.size()},A.view);if(this.opts.onRemoveRows){this.opts.onRemoveRows(B)}this._getBuffer().remove(B.get("rownum"));if(!A.noupdate){this.requestContentRefresh(this.currentOffset())}this.isbusy=false},addFilter:function(A,B){this.filter=new ViewPort_Filter(this,A,B)},runFilter:function(B,A){if(this.filter){this.filter.filter(Object.isUndefined(B)?null:B,A)}},isFiltering:function(){return this.filter?this.filter.isFiltering():false},stopFilter:function(A){if(this.filter){this.filter.clear(A)}},onResize:function(B,A){if(!this.uc_run||!this.opts.content.visible()){return}if(this.resizefunc){clearTimeout(this.resizefunc)}if(A){this._onResize(B)}else{this.resizefunc=this._onResize.bind(this,B).delay(0.1)}},_onResize:function(A){if(this.opts.onBeforeResize){this.opts.onBeforeResize()}this._renderViewport(A);if(this.opts.onAfterResize){this.opts.onAfterResize()}},requestContentRefresh:function(B){if(this._updateContent(B)){var A=this._getBuffer().isNearingLimit(B);if(A){this._fetchBuffer({offset:B,background:true,nearing:A})}return true}return false},_fetchBuffer:function(A){if(this.isbusy){this._fetchBuffer.bind(this,A).defer();return}this.isbusy=true;if(this.opts.onFetch&&!A.background){this.opts.onFetch()}var L=(A.view||this.view),G=this.opts.fetch_action,E,M=this._getBuffer(L),I,C,F=$H(A.params),D,P,Q,O,K,B,J,N,H;if(A.purge){F.set("purge",true)}if(A.search){J="search";N=A.search;C=this._lookbehind(L);F.update({search_before:C,search_after:M.bufferSize()-C})}else{J="rownum";N=A.offset+1;E=M.getAllRows();B=this._getSliceBounds(N,A.nearing,L);K=$A($R(B.start,B.end)).diff(E);if(!A.purge&&!K.size()){this.isbusy=false;return}F.update({slice_start:B.start,slice_end:B.end})}F.set(J,Object.toJSON(N));O=[L,J,N];if(this.isFiltering()){G=this.filter.getAction();F=this.filter.addFilterParams(F);O.push(F)}P=O.toJSON();D=this.fetch_hash.get(P);I=this.current_req.get(L);if(I){if(D&&I.get(D)){if(++I.get(D).count==4){this._displayFetchError();this._removeRequest(L,D);this.isbusy=false;return}}else{if(J=="rownum"){H=$A($R(N,N+this.getPageSize())).diff(E);if(!H.size()){this.isbusy=false;return}Q=I.keys().numericSort().find(function(R){var S=I.get(R).rlist;H=H.diff(S);if(!H.size()){return true}K=K.diff(S)});if(Q){if(!A.background){this._addRequest(L,Q,{background:false,offset:N-1})}this.isbusy=false;return}else{if(!A.background){I.keys().each(function(R){this._addRequest(L,R,{background:true})},this)}}}}}if(!this.opts.norows&&K){F.set("rows",K.toJSON())}if(!D){D=this.fetch_hash.set(P,this.request_num++)}F.set("request_id",D);this._addRequest(L,D,{background:A.background,offset:N-1,rlist:K});this.opts.ajaxRequest(G,this.addRequestParams(F,{noslice:true,view:L}));this._handleWait();this.isbusy=false},_getSliceBounds:function(C,D,A){var E=this._getBuffer(A).bufferSize(),B={};switch(D){case"bottom":B.start=C+this.getPageSize();B.end=B.start+E;break;case"top":B.start=Math.max(C-E,1);B.end=C;break;default:B.start=Math.max(C-this._lookbehind(A),1);B.end=B.start+E;break}return B},_lookbehind:function(A){return parseInt(0.4*this._getBuffer(A).bufferSize(),10)},addRequestParams:function(A,B){B=B||{};var E=this.getMetaData("cacheid",B.view),C=this.getMetaData("additional_params",B.view),D;if(E){C.update({cacheid:E})}if(!B.noslice){D=this._getSliceBounds(this.currentOffset(),null,B.view);C.update({slice_start:D.start,slice_end:D.end})}return C.merge(A)},ajaxResponse:function(E){if(this.isbusy){this.ajaxResponse.bind(this,E).defer();return}this.isbusy=true;this._clearWait();var A,D,B,F,C,H,G={};if(E.type=="slice"){F=E.data;C=Object.keys(F);C.each(function(I){F[I].view=E.id;G[I]=F[I].rownum});A=this._getBuffer(E.id);A.update(F,G,{slice:true});if(this.opts.onEndFetch){this.opts.onEndFetch()}D=this.slice_hash.get(E.request_id);if(D){D(new ViewPort_Selection(A,"uid",C));this.slice_hash.unset(E.request_id)}this.isbusy=false;return}H=(E.request_id)?this.current_req_lookup.get(E.request_id):E.id;D=this.current_req.get(H);if(D&&E.request_id){B=D.get(E.request_id)}if(this.viewport_init){this.viewport_init=2}A=this._getBuffer(H);A.update(E.data,E.rowlist,{update:E.update});A.setMetaData($H(E.other).merge({cacheid:E.cacheid,label:E.label,total_rows:E.totalrows}));if(E.request_id){this._removeRequest(H,E.request_id)}this.isbusy=false;if(!(this.view==H||E.search)||(B&&B.background)||!this._updateContent((B&&B.offset)?B.offset:(E.rownum?parseInt(E.rownum)-1:this.currentOffset()))){return}if(this.opts.onComplete){this.opts.onComplete()}if(this.opts.onEndFetch){this.opts.onEndFetch()}},_addRequest:function(A,B,E){var D=this.current_req.get(A),C;if(!D){D=this.current_req.set(A,$H())}C=D.get(B);if(!C){C=D.set(B,{count:1})}["background","offset","rlist"].each(function(F){if(!Object.isUndefined(E[F])){C[F]=E[F]}});this.current_req_lookup.set(B,A)},_removeRequest:function(A,B){var C=this.current_req.get(A);if(C){C.unset(B);if(!C.size()){this.current_req.unset(A)}}this.current_req_lookup.unset(B)},_updateContent:function(F,C){C=C||{};if(!this._getBuffer(C.view).sliceLoaded(F)){this._fetchBuffer($H(C).merge({offset:F}).toObject());return false}if(!this.uc_run){this.uc_run=true;if(this.opts.onFirstContent){this.opts.onFirstContent()}}var G=this.opts.content,A=[],B=this.getPageSize(),E,D=this.getSelected();if(this.opts.onClearRows){this.opts.onClearRows(G.childElements())}this.scroller.updateSize();this.scrollTo(F+1,true);F=this.currentOffset();E=this.createSelection("rownum",$A($R(F+1,F+B)));if(E.size()){E.get("dataob").each(function(I){var H=Object.clone(I);if(H.bg){H.bg=I.bg.clone();if(D.contains("uid",H.vp_id)){H.bg.push(this.opts.selected_class)}H.bg_string=H.bg.join(" ")}A.push(this.template.evaluate(H))},this);G.update(A.join(""))}else{G.update((this.opts.empty&&this.viewport_init!=1)?this.opts.empty.innerHTML:"")}if(this.opts.onContent){this.opts.onContent(E)}return true},_displayFetchError:function(){if(this.opts.onFail){this.opts.onFail()}if(this.opts.error){this.opts.content.update(this.opts.error.innerHTML)}},_getSlice:function(B,E){var D={rangeslice:1,start:B.min(),length:B.size()},A,C;C=this.createSelection("rownum",B);if(B.size()==C.size()){return C}if(this.opts.onFetch){this.opts.onFetch()}if(E){A=this.request_num++;D.request_id=A;this.slice_hash.set(A,E)}this.opts.ajaxRequest(this.opts.fetch_action,this.addRequestParams(D,{noslice:true}));return false},_handleWait:function(A){this._clearWait();if(A&&this.opts.onWait){this.opts.onWait()}if(this.opts.viewport_wait){this.waitHandler=this._handleWait.bind(this,true).delay(this.opts.viewport_wait)}},_clearWait:function(){if(this.waitHandler){clearTimeout(this.waitHandler);this.waitHandler=null}},visibleRows:function(){return this.opts.content.childElements()},getMetaData:function(B,A){return this._getBuffer(A).getMetaData(B)},setMetaData:function(B,A){this._getBuffer(A).setMetaData(B)},_getBuffer:function(B,C){if(!C){var A=this.views.get(B||this.view);if(A){return A.buffer}}return new ViewPort_Buffer(this,this.opts.buffer_pages,this.opts.limit_factor)},currentOffset:function(){return this.scroller.currentOffset()},updateFlag:function(C,A,B){this._updateFlag(C,A,B,this.isFiltering());this._updateClass(C,A,B)},_updateFlag:function(D,A,C,B){D.get("dataob").each(function(E){if(C){E.bg.push(A)}else{E.bg.splice(E.bg.indexOf(A),1)}if(B){this._updateFlag(this.createSelection("uid",E.vp_id,E.view),A,C)}},this)},_updateClass:function(C,A,B){C.get("div").each(function(D){if(B){D.addClassName(A)}else{D.removeClassName(A)}})},_getLineHeight:function(){if(this.line_height){return this.line_height}var A=new Element("DIV",{className:this.opts.content_class}).insert(new Element("DIV",{className:this.opts.row_class})).hide();$(document.body).insert(A);this.line_height=A.getHeight();A.remove();return this.line_height},getPageSize:function(A){switch(A){case"current":return Math.min(this.page_size,this.getMetaData("total_rows"));case"default":return Math.max(parseInt(this.getPageSize("max")*0.45),5);case"max":return parseInt(this._getMaxHeight()/this._getLineHeight());default:return this.page_size}},_getMaxHeight:function(){return document.viewport.getHeight()-this.opts.content.viewportOffset()[1]},showSplitPane:function(A){this.show_split_pane=A;this.onResize(false,true)},_renderViewport:function(D){if(!this.viewport_init){return}if(!this.opts.content.offsetHeight){return this._renderViewport.bind(this,D).defer()}var E,B,H,C,G=$(this.opts.content),F=document.documentElement,A=this._getLineHeight();if(this.opts.split_pane){H=$(this.opts.split_pane);if(this.show_split_pane){if(!H.visible()){this._initSplitBar();this.page_size=(this.splitbar_loc)?this.splitbar_loc:this.getPageSize("default")}C=true}else{if(H.visible()){this.splitbar_loc=this.page_size;$(H,this.splitbar).invoke("hide")}}}if(!C){this.page_size=this.getPageSize("max")}B=A*this.page_size;G.setStyle({height:B+"px"});if(C){H.setStyle({height:(this._getMaxHeight()-B-A)+"px"}).show();this.splitbar.show()}else{if(E=F.scrollHeight-F.clientHeight){G.setStyle({height:(A*(this.page_size-1))+"px"})}}if(!D){this.scroller.onResize()}},_initSplitBar:function(){if(this.splitbar){return}this.splitbar=$(this.opts.splitbar);new Drag(this.splitbar,{constraint:"vertical",ghosting:true,onStart:function(){var A=this._getLineHeight();this.sp={lh:A,pos:$(this.opts.content).positionedOffset()[1],max:parseInt((this._getMaxHeight()-100)/A),lines:this.page_size}}.bind(this),snap:function(A,D,C){var B=parseInt((D-this.sp.pos)/this.sp.lh);if(B<1){B=1}else{if(B>this.sp.max){B=this.sp.max}}this.sp.lines=B;return[A,this.sp.pos+(B*this.sp.lh)]}.bind(this),onEnd:function(){this.page_size=this.sp.lines;this._renderViewport()}.bind(this)});this.splitbar.observe("dblclick",function(){this.page_size=this.getPageSize("default");this._renderViewport()}.bind(this))},createSelection:function(D,C,B){var A=this._getBuffer(B);return A?new ViewPort_Selection(A,D,C):new ViewPort_Selection(this._getBuffer(this.view))},getViewportSelection:function(B){var A=this._getBuffer(B);return this.createSelection("uid",A?A.getAllUIDs():[],B)},select:function(D,B){B=B||{};if(B.range){D=this._getSlice(D,this.select.bind(this));if(D===false){return}}var A=this._getBuffer(),C;if(!B.add){C=this.getSelected();A.deselect(C,true);this._updateClass(C,this.opts.selected_class,false)}A.select(D);this._updateClass(D,this.opts.selected_class,true);if(this.opts.selectCallback){this.opts.selectCallback(D,B)}},deselect:function(B,A){A=A||{};if(!B.size()){return}if(this._getBuffer().deselect(B,A&&A.clearall)){this._updateClass(B,this.opts.selected_class,false);if(this.opts.deselectCallback){this.opts.deselectCallback(B,A)}}},getSelected:function(){return Object.clone(this._getBuffer().getSelected())}}),ViewPort_Scroller=Class.create({initialize:function(A){this.vp=A},_createScrollBar:function(){if(this.scrollDiv){return false}var A=this.vp.opts.content;this.scrollDiv=new Element("DIV",{className:"sbdiv",style:"height:"+A.getHeight()+"px;"}).hide();A.insert({after:this.scrollDiv}).setStyle({marginRight:"-"+this.scrollDiv.getWidth()+"px"});this.scrollbar=new DimpSlider(this.scrollDiv,{buttonclass:{up:"sbup",down:"sbdown"},cursorclass:"sbcursor",onChange:this._onScroll.bind(this),onSlide:this.vp.opts.onSlide?this.vp.opts.onSlide:null,pagesize:this.vp.getPageSize(),totalsize:this.vp.getMetaData("total_rows")});A.observe(Prototype.Browser.Gecko?"DOMMouseScroll":"mousewheel",function(C){if(Prototype.Browser.Gecko&&C.eventPhase==2){return}var B=this.vp.getPageSize();B=(B>3)?3:B;this.moveScroll(this.currentOffset()+((C.wheelDelta>=0||C.detail<0)?(-1*B):B))}.bindAsEventListener(this));return true},onResize:function(){if(!this.scrollDiv){return}this.scrollsize=this.vp.opts.content.getHeight();this.scrollDiv.setStyle({height:this.scrollsize+"px"});this.updateSize();this.vp.requestContentRefresh(this.currentOffset())},updateSize:function(){if(!this._createScrollBar()){this.scrollbar.updateHandleLength(this.vp.getPageSize(),this.vp.getMetaData("total_rows"))}},clear:function(){if(this.scrollDiv){this.scrollbar.updateHandleLength(0,0)}},moveScroll:function(A){this._createScrollBar();this.scrollbar.setScrollPosition(A)},_onScroll:function(){if(!this.noupdate){if(this.vp.opts.onScroll){this.vp.opts.onScroll()}this.vp.requestContentRefresh(this.currentOffset());if(this.vp.opts.onScrollIdle){this.vp.opts.onScrollIdle()}}},currentOffset:function(){return this.scrollbar?this.scrollbar.getValue():0}}),ViewPort_Buffer=Class.create({initialize:function(B,C,A){this.bufferPages=C;this.limitFactor=A;this.vp=B;this.clear()},_limitTolerance:function(){return Math.round(this.bufferSize()*(this.limitFactor/100))},bufferSize:function(){return Math.round(Math.max(this.vp.getPageSize("max")+1,this.bufferPages*this.vp.getPageSize()))},update:function(C,A,B){C=$H(C);A=$H(A);B=B||{};if(B.slice){C.each(function(D){if(!this.data.get(D.key)){this.data.set(D.key,D.value);this.inc.set(D.key,true)}},this)}else{if(this.data.size()){this.data.update(C);if(this.inc.size()){C.keys().each(function(D){this.inc.unset(D)},this)}}else{this.data=C}}this.uidlist=(B.update)?A:(this.uidlist.size()?this.uidlist.merge(A):A);if(B.update){this.rowlist=$H()}A.each(function(D){this.rowlist.set(D.value,D.key)},this)},sliceLoaded:function(A){return!this._rangeCheck($A($R(A+1,Math.min(A+this.vp.getPageSize()-1,this.getMetaData("total_rows")))))},isNearingLimit:function(A){if(this.uidlist.size()!=this.getMetaData("total_rows")){if(A!=0&&this._rangeCheck($A($R(Math.max(A+1-this._limitTolerance(),1),A)))){return"top"}else{if(this._rangeCheck($A($R(A+1,Math.min(A+this._limitTolerance()+this.vp.getPageSize()-1,this.getMetaData("total_rows")))).reverse())){return"bottom"}}}return false},_rangeCheck:function(A){var B=this.inc.size();return A.any(function(D){var C=this.rowlist.get(D);return(Object.isUndefined(C)||(B&&this.inc.get(C)))},this)},getData:function(A){return A.collect(function(B){var C=this.data.get(B);if(!Object.isUndefined(C)){C.domid="vp_row"+B;C.rownum=this.uidlist.get(B);C.vp_id=B;return C}},this).compact()},getAllUIDs:function(){return this.uidlist.keys()},getAllRows:function(){return this.rowlist.keys()},rowsToUIDs:function(A){return A.collect(function(B){return this.rowlist.get(B)},this).compact()},select:function(A){this.selected.add("uid",A.get("uid"))},deselect:function(C,B){var A=this.selected.size();if(B){this.selected.clear()}else{this.selected.remove("uid",C.get("uid"))}return A!=this.selected.size()},getSelected:function(){return this.selected},remove:function(B){var C,D=this.rowlist.size(),A=0;C=D-B.size();return $A($R(B.min(),D)).each(function(G){var F=this.rowlist.get(G),E;if(B.include(G)){this.data.unset(F);this.uidlist.unset(F);A++}else{E=G-A;this.rowlist.set(E,F);this.uidlist.set(F,E)}if(G>C){this.rowlist.unset(G)}},this)},clear:function(){this.data=$H();this.inc=$H();this.mdata=$H({total_rows:0});this.rowlist=$H();this.selected=new ViewPort_Selection(this);this.uidlist=$H()},getMetaData:function(A){return this.mdata.get(A)},setMetaData:function(A){this.mdata.update(A)}}),ViewPort_Filter=Class.create({initialize:function(A,B,C){this.vp=A;this.action=B;this.callback=C;this.filterid=0;this.filtering=this.last_filter=this.last_folder=this.last_folder_params=null},filter:function(C,B){B=B||{};if(C===null){C=this.last_filter}else{C=C.toLowerCase();if(C==this.last_filter){return}}if(!C){this.clear();return}this.last_filter=C;if(this.filtering){this.vp._fetchBuffer({offset:0,params:B});return}this.filtering=++this.filterid+"%search%";this.last_folder=this.vp.view;this.last_folder_params=this.vp.getMetaData("additional_params").merge(B);var D=this.vp.opts.content,A;A=D.childElements().findAll(function(E){return E.collectTextNodes().toLowerCase().indexOf(C)==-1});if(this.vp.opts.onClearRows){this.vp.opts.onClearRows(A)}A.invoke("remove");this.vp.scroller.clear();if(this.vp.opts.empty&&!D.childElements().size()){D.update(this.vp.opts.empty.innerHTML)}this.vp.loadView(this.filtering,this.last_folder_params)},isFiltering:function(){return this.filtering},getAction:function(){return this.action},addFilterParams:function(A){if(!this.filtering){return A}A.update({filter:this.last_filter});if(this.callback){A.update(this.callback())}return A},clear:function(A){if(this.filtering){this.filtering=null;if(!A){this.vp.loadView(this.last_folder,this.last_folder_params)}this.vp.deleteView(this.filtering);this.last_filter=this.last_folder=null}}}),ViewPort_Selection=Class.create({initialize:function(A,C,B){this.buffer=A;this.clear();if(!Object.isUndefined(C)){this.add(C,B)}this.viewport_selection=true},add:function(A,B){var C=this._convert(A,B);this.data=(this.data.size())?this.data.concat(C).uniq():C},remove:function(A,B){this.data=this.data.diff(this._convert(A,B))},_convert:function(A,B){B=Object.isArray(B)?B:[B];switch(A){case"dataob":return B.pluck("vp_id");case"div":return B.pluck("id").invoke("substring",6);case"domid":return B.invoke("substring",6);case"rownum":return this.buffer.rowsToUIDs(B);case"uid":return B}},clear:function(){this.data=[]},get:function(A){A=Object.isUndefined(A)?"uid":A;if(A=="uid"){return this.data}var B=this.buffer.getData(this.data);switch(A){case"dataob":return B;case"div":return B.pluck("domid").collect(function(C){return $(C)}).compact();case"domid":return B.pluck("domid");case"rownum":return B.pluck("rownum")}},contains:function(A,B){return this.data.include(this._convert(A,B).first())},search:function(A){return new ViewPort_Selection(this.buffer,"uid",this.get("dataob").findAll(function(B){return $H(A).all(function(C){return $H(C.value).all(function(D){switch(D.key){case"equal":case"not":var E=B[C.key]&&D.value.include(B[C.key]);return(D.key=="equal")?E:!E;case"regex":return B[C.key].match(D.value)}})})}).pluck("vp_id"))},size:function(){return this.data.size()},set:function(A){this.get("dataob").each(function(B){$H(A).each(function(C){B[C.key]=C.value})})}});Object.extend(Array.prototype,{diff:function(A){return this.select(function(B){return!A.include(B)})},numericSort:function(){return this.sort(function(B,A){return(B>A)?1:((B<A)?-1:0)})}});
\ No newline at end of file
diff --git a/imp/js/compose-dimp.js b/imp/js/compose-dimp.js
new file mode 100644 (file)
index 0000000..37984b0
--- /dev/null
@@ -0,0 +1 @@
+var DimpCompose={last_msg:"",textarea_ready:true,confirmCancel:function(){if(window.confirm(DIMP.text_compose.cancel)){if(DIMP.conf_compose.auto_save_interval_val){DimpCore.doAction("DeleteDraft",{index:$F("index")})}return this._closeCompose()}},_closeCompose:function(){if(DIMP.conf_compose.qreply){this.closeQReply()}else{if(DIMP.baseWindow||DIMP.conf_compose.popup){DimpCore.closePopup()}else{DimpCore.redirect(DIMP.conf.URI_DIMP_INBOX)}}},closeQReply:function(){var A=$("attach_list").childElements();this.last_msg="";if(A.size()){this.removeAttach(A)}$("draft_index","composeCache").invoke("setValue","");$("qreply","sendcc","sendbcc").invoke("hide");[$("msgData"),$("togglecc").up(),$("togglebcc").up()].invoke("show");if(this.editor_on){this.toggleHtmlEditor()}$("compose").reset();if(this.auto_save_interval){this.auto_save_interval.stop()}},change_identity:function(){var E,B,G,F,A=$F("identity"),I=this.get_identity($F("last_identity")),H=$("message"),D=this.get_identity(A),C=$("save_sent_mail");$("sent_mail_folder_label").setText(D.id[5]);$("bcc").setValue(D.id[6]);if(C){C.writeAttribute("checked",D.id[4])}if(this.editor_on){B=FCKeditorAPI.GetInstance("message").GetHTML().replace(/\r\n/g,"\n");E="<p><!--begin_signature--><!--end_signature--></p>";G="<p><!--begin_signature-->"+D.sig.replace(/^ ?<br \/>\n/,"").replace(/ +/g," ")+"<!--end_signature--></p>";B=B.replace(/<p>\s*<!--begin_signature-->[\s\S]*?<!--end_signature-->\s*<\/p>/,E)}else{B=$F(H).replace(/\r\n/g,"\n");E=I.sig;G=D.sig}F=(I.id[2])?B.indexOf(E):B.lastIndexOf(E);if(F!=-1){if(D.id[2]==I.id[2]){B=B.substring(0,F)+G+B.substring(F+E.length,B.length)}else{if(D.id[2]){B=G+B.substring(0,F)+B.substring(F+E.length,B.length)}else{B=B.substring(0,F)+B.substring(F+E.length,B.length)+G}}B=B.replace(/\r\n/g,"\n").replace(/\n/g,"\r\n");if(this.editor_on){FCKeditorAPI.GetInstance("message").SetHTML(B)}else{H.setValue(B)}$("last_identity").setValue(A)}},get_identity:function(B,A){A=Object.isUndefined(A)?this.editor_on:A;return{id:DIMP.conf_compose.identities[B],sig:DIMP.conf_compose.identities[B][(A?1:0)].replace(/^\n/,"")}},uniqueSubmit:function(B){var A,C,E,D=$("compose");if(DIMP.SpellCheckerObject){DIMP.SpellCheckerObject.resume();if(!this.textarea_ready){this.uniqueSubmit.bind(this,B).defer();return}}D.setStyle({cursor:"wait"});if(B=="send_message"||B=="save_draft"){this.button_pressed=true;switch(B){case"send_message":if(!this.sbtext){E=$("send_button");this.sbtext=E.getText();E.setText(DIMP.text_compose.sending)}break;case"save_draft":if(!this.dbtext){A=$("draft_button");this.dbtext=A.getText();A.setText(DIMP.text_compose.saving)}break}if(this.uploading){(function(){if(this.button_pressed){this.uniqueSubmit(B)}}).bind(this).delay(0.25);return}}$("action").setValue(B);if(B=="add_attachment"){this.uploading=true;D.submit()}else{if(this.editor_on){FCKeditorAPI.GetInstance("message").UpdateLinkedField()}C=D.serialize(true);if(!DIMP.baseWindow){C.nonotify=true}DimpCore.doAction("*"+DIMP.conf.compose_url,C,null,this.uniqueSubmitCallback.bind(this))}},uniqueSubmitCallback:function(B){var A,C=B.response;if(!C){return}if(C.imp_compose){$("composeCache").setValue(C.imp_compose)}if(C.success||C.action=="add_attachment"){switch(C.action){case"auto_save_draft":this.button_pressed=false;$("draft_index").setValue(C.draft_index);break;case"save_draft":this.button_pressed=false;if(DIMP.baseWindow){DIMP.baseWindow.DimpBase.pollFolders();DIMP.baseWindow.DimpCore.showNotifications(B.msgs)}if(DIMP.conf_compose.close_draft){return this._closeCompose()}break;case"send_message":this.button_pressed=false;if(DIMP.baseWindow){if(C.reply_type=="reply"){DIMP.baseWindow.DimpBase.flag("answered",C.index,C.reply_folder)}if(C.folder){DIMP.baseWindow.DimpBase.createFolder(C.folder)}if(C.draft_delete){DIMP.baseWindow.DimpBase.pollFolders()}DIMP.baseWindow.DimpCore.showNotifications(B.msgs)}return this._closeCompose();case"add_attachment":this.uploading=false;if(C.success){this.addAttach(C.info.number,C.info.name,C.info.type,C.info.size)}else{this.button_pressed=false}if(DIMP.conf_compose.attach_limit!=-1&&$("attach_list").childElements().size()>DIMP.conf_compose.attach_limit){$("upload").writeAttribute("disabled",false);A=new Element("DIV",[DIMP.text_compose.attachment_limit])}else{A=new Element("INPUT",{type:"file",name:"file_1"});A.observe("change",this.uploadAttachment.bind(this))}$("upload_wait").replace(A.writeAttribute("id","upload"));this.resizeMsgArea();break}}else{this.button_pressed=false}$("compose").setStyle({cursor:null});if(!this.button_pressed){if(this.sbtext){$("send_button").setText(this.sbtext)}if(this.dbtext){$("draft_button").setText(this.dbtext)}this.dbtext=this.sbtext=null}DimpCore.showNotifications(B.msgs)},toggleHtmlEditor:function(A){if(!DIMP.conf_compose.rte_avail){return}A=A||false;if(DIMP.SpellCheckerObject){DIMP.SpellCheckerObject.resume()}var C;if(this.editor_on){this.editor_on=false;C=FCKeditorAPI.GetInstance("message").GetHTML();$("messageParent").childElements().invoke("hide");$("message").show();DimpCore.doAction("Html2Text",{text:C},null,this.setMessageText.bind(this),{asynchronous:false})}else{this.editor_on=true;if(!A){DimpCore.doAction("Text2Html",{text:$F("message")},null,this.setMessageText.bind(this),{asynchronous:false})}oFCKeditor.Height=this.getMsgAreaHeight();try{FCKeditorAPI.GetInstance("message").SetHTML($F("message"));$("messageParent").childElements().invoke("show");$("message").hide()}catch(B){this._RTELoading("show");FCKeditor_OnComplete=this._RTELoading.curry("hide");oFCKeditor.ReplaceTextarea()}}$("htmlcheckbox").checked=this.editor_on;$("html").setValue(this.editor_on?1:0)},_RTELoading:function(B){var C,A;if(!$("rteloading")){A=new Element("DIV",{id:"rteloading"}).clonePosition($("messageParent"));$(document.body).insert(A);C=A.viewportOffset();$(document.body).insert(new Element("SPAN",{id:"rteloadingtxt"}).setStyle({top:(C.top+15)+"px",left:(C.left+15)+"px"}).insert(DIMP.text.loading))}$("rteloading","rteloadingtxt").invoke(B)},toggleHtmlCheckbox:function(){if(!this.editor_on||window.confirm(DIMP.text_compose.toggle_html)){this.toggleHtmlEditor()}},getMsgAreaHeight:function(){return document.viewport.getHeight()-$("messageParent").cumulativeOffset()[1]-this.mp_padding},initializeSpellChecker:function(){if(!DIMP.conf_compose.rte_avail){return}if(typeof DIMP.SpellCheckerObject!="object"){this.initializeSpellChecker.bind(this).defer();return}DIMP.SpellCheckerObject.onBeforeSpellCheck=function(){if(!this.editor_on){return}DIMP.SpellCheckerObject.htmlAreaParent="messageParent";DIMP.SpellCheckerObject.htmlArea=$("message").adjacent("iframe[id*=message]").first();$("message").setValue(FCKeditorAPI.GetInstance("message").GetHTML());this.textarea_ready=false}.bind(this);DIMP.SpellCheckerObject.onAfterSpellCheck=function(){if(!this.editor_on){return}DIMP.SpellCheckerObject.htmlArea=DIMP.SpellCheckerObject.htmlAreaParent=null;var A=FCKeditorAPI.GetInstance("message");A.SetHTML($F("message"));A.Events.AttachEvent("OnAfterSetHTML",function(){this.textarea_ready=true}.bind(this))}.bind(this)},setMessageText:function(B){var A=$("message");if(!A){$("messageParent").insert(new Element("TEXTAREA",{id:"message",name:"message",style:"width:100%;"}).insert(B.response.text))}else{A.setValue(B.response.text)}if(!this.editor_on){this.resizeMsgArea()}},fillForm:function(G,H,B,E){if(!this.resizeto){this.fillForm.bind(this,G,H,B,E).defer();return}var A,D,C=this.get_identity($F("last_identity")),F=$("message");if(!this.last_msg.empty()&&this.last_msg!=$F(F).replace(/\r/g,"")&&!window.confirm(DIMP.text_compose.fillform)){return}if(DIMP.conf_compose.auto_save_interval_val&&!this.auto_save_interval){this.auto_save_interval=new PeriodicalExecuter(function(){var I;if(this.editor_on){I=FCKeditorAPI.GetInstance("message").GetHTML()}else{I=$F(F)}I=I.replace(/\r/g,"");if(!I.empty()&&this.last_msg!=I){this.uniqueSubmit("auto_save_draft");this.last_msg=I}}.bind(this),DIMP.conf_compose.auto_save_interval_val*60)}if(this.editor_on){D=FCKeditorAPI.GetInstance("message");D.SetHTML(G);this.last_msg=D.GetHTML().replace(/\r/g,"")}else{F.setValue(G);this.setCursorPosition(F);this.last_msg=$F(F).replace(/\r/g,"")}$("to").setValue(H.to);this.resizeto.resizeNeeded();if(H.cc){$("cc").setValue(H.cc);this.resizecc.resizeNeeded()}if(DIMP.conf_compose.cc){this.toggleCC("cc")}if(H.bcc){$("bcc").setValue(H.bcc);this.resizebcc.resizeNeeded()}if(C.id[6]){A=$F("bcc");if(A){A+=", "}$("bcc").setValue(A+C.id[6])}if(DIMP.conf_compose.bcc){this.toggleCC("bcc")}$("subject").setValue(H.subject);$("in_reply_to").setValue(H.in_reply_to);$("references").setValue(H.references);$("reply_type").setValue(H.replytype);Field.focus(B||"to");this.resizeMsgArea();if(DIMP.conf_compose.show_editor){if(!this.editor_on){this.toggleHtmlEditor(E||false)}if(B=="message"){this.focusEditor()}}},focusEditor:function(){try{FCKeditorAPI.GetInstance("message").Focus()}catch(A){this.focusEditor.bind(this).defer()}},addAttach:function(E,B,D,C){var F=new Element("DIV").insert(B+" ["+D+"] ("+C+" KB) "),A=new Element("INPUT",{type:"button",atc_id:E,value:DIMP.text_compose.remove});F.insert(A);$("attach_list").insert(F);A.observe("click",this.removeAttach.bind(this,[A.up()]));this.resizeMsgArea()},removeAttach:function(B){var A=[];B.each(function(C){C=$(C);A.push(C.firstDescendant().readAttribute("atc_id"));C.remove()});DimpCore.doAction("DeleteAttach",{atc_indices:A,imp_compose:$F("composeCache")});this.resizeMsgArea()},resizeMsgArea:function(){var A,B,D=document.documentElement,C=$("message");if(!DimpCore.window_load){this.resizeMsgArea.bind(this).defer();return}if(this.editor_on){A=$("messageParent").select("iframe").last();if(A){A.setStyle({height:this.getMsgAreaHeight()+"px"})}else{this.resizeMsgArea.bind(this).defer()}return}this.mp_padding=$("messageParent").getHeight()-C.getHeight();if(!this.row_height){A=$(C.cloneNode(false)).writeAttribute({id:null,name:null}).setStyle({visibility:"hidden"});$(document.body).insert(A);A.writeAttribute("rows",1);this.row_height=A.getHeight();A.writeAttribute("rows",2);this.row_height=A.getHeight()-this.row_height;A.remove()}B=parseInt(this.getMsgAreaHeight()/this.row_height);C.writeAttribute({rows:B,disabled:false});if(D.scrollHeight-D.clientHeight){C.writeAttribute({rows:B-1})}$("composeloading").hide()},uploadAttachment:function(){var A=$("upload");$("submit_frame").observe("load",this.attachmentComplete.bind(this));this.uniqueSubmit("add_attachment");A.stopObserving("change").replace(new Element("DIV",{id:"upload_wait"}).insert(DIMP.text_compose.uploading+" "+$F(A)))},attachmentComplete:function(){var A=$("submit_frame"),B=A.contentDocument||A.contentWindow.document;A.stopObserving("load");DimpCore.doActionComplete({responseText:B.body.innerHTML},this.uniqueSubmitCallback.bind(this))},toggleCC:function(A){$("send"+A).show();$("toggle"+A).up().hide()},setCursorPosition:function(B){var C,A;switch(DIMP.conf_compose.compose_cursor){case"top":C=0;$("message").setValue("\n"+$F("message"));break;case"bottom":C=$F("message").length;break;case"sig":C=$F("message").replace(/\r\n/g,"\n").lastIndexOf(this.get_identity($F("last_identity")).sig)-1;break;default:return}if(B.setSelectionRange){Field.focus(B);B.setSelectionRange(C,C)}else{if(B.createTextRange){A=B.createTextRange();A.collapse(true);A.moveStart("character",C);A.moveEnd("character",0);Field.select(A);A.scrollIntoView(true)}}},openAddressbook:function(){window.open(DIMP.conf_compose.abook_url,"contacts","toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes,width=550,height=300,left=100,top=100")}},ResizeTextArea=Class.create({maxRows:5,initialize:function(C,B){this.field=$(C);this.defaultRows=Math.max(this.field.readAttribute("rows"),1);this.onResize=B;var A=this.resizeNeeded.bindAsEventListener(this);this.field.observe("mousedown",A).observe("keyup",A);this.resizeNeeded()},resizeNeeded:function(){var A=$F(this.field).split("\n"),C=this.field.readAttribute("cols"),B=A.size(),D=this.field.readAttribute("rows");A.each(function(E){if(E.length>=C){B+=Math.floor(E.length/C)}});if(B!=D){this.field.writeAttribute("rows",(B>D)?Math.min(B,this.maxRows):Math.max(this.defaultRows,B));if(this.onResize){this.onResize()}}}});document.observe("dom:loaded",function(){var B,A=DimpCompose,D=A.resizeMsgArea.bind(A),E=DimpCore.clickObserveHandler;A.resizeMsgArea();A.initializeSpellChecker();$("upload").observe("change",A.uploadAttachment.bind(A));A.resizeto=new ResizeTextArea("to",D);A.resizecc=new ResizeTextArea("cc",D);A.resizebcc=new ResizeTextArea("bcc",D);if(Prototype.Browser.WebKit){$("submit_frame").writeAttribute({position:"absolute",width:"1px",height:"1px"}).setStyle({left:"-999px"}).show()}if(B=$("compose_close")){E({d:B,f:A.confirmCancel.bind(A)})}E({d:$("send_button"),f:A.uniqueSubmit.bind(A,"send_message")});E({d:$("draft_button"),f:A.uniqueSubmit.bind(A,"save_draft")});["cc","bcc"].each(function(C){E({d:$("toggle"+C),f:A.toggleCC.bind(A,C)})});if(B=$("htmlcheckbox")){E({d:B,f:A.toggleHtmlCheckbox.bind(A),ns:true})}if(B=$("compose_specialchars")){E({d:B,f:function(){window.open(DIMP.conf_compose.specialchars_url,"chars","height=220,width=400")}})}$("writemsg").select(".composeAddrbook").each(function(C){E({d:C,f:A.openAddressbook.bind(A)})});$("compose").observe("submit",Event.stop);$("identity").observe("change",A.change_identity.bind(A));$("togglecc").observe("click",D);$("togglebcc").observe("click",D);Event.observe(window,"resize",D)});
\ No newline at end of file
diff --git a/imp/js/dragdrop.js b/imp/js/dragdrop.js
new file mode 100644 (file)
index 0000000..01c7648
--- /dev/null
@@ -0,0 +1 @@
+var DragDrop={Drags:{drags:$H(),register:function(A){if(!this.drags.size()){if(!this.div){this.div=new Element("DIV",{className:A.options.classname}).hide()}$(document.body).insert(this.div)}this.drags.set(A.element.readAttribute("id"),A)},unregister:function(A){if(this.drag==A.element){this.drag.deactivate()}this.drags.unset(A.element.readAttribute("id"));if(!this.drags.size()&&this.div){this.div.remove()}},get_drag:function(A){return this.drags.get(Object.isElement(A)?$(A).readAttribute("id"):A)},activate:function(A){if(this.drag){this.deactivate()}this.drag=A;this.mousemoveE=A._mouseMove.bindAsEventListener(A);this.mouseupE=A._mouseUp.bindAsEventListener(A);document.observe("mousemove",this.mousemoveE);document.observe("mouseup",this.mouseupE)},deactivate:function(){if(this.drag){this.drag=null;document.stopObserving("mousemove",this.mousemoveE);document.stopObserving("mouseup",this.mouseupE)}}},Drops:{drops:$H(),register:function(A){this.drops.set(A.element.readAttribute("id"),A)},unregister:function(A){if(this.drop==A.element){this.drop=null}this.drops.unset(A.element.readAttribute("id"))},get_drop:function(A){return this.drops.get(Object.isElement(A)?$(A).readAttribute("id"):A)}},validDrop:function(A){var B=DragDrop.Drops.drop;return(B&&A&&A!=B.element&&(!B.options.accept.size()||B.options.accept.include(A.tagName)))}},Drag=Class.create({initialize:function(A){this.element=$(A);this.options=Object.extend({caption:"",classname:"drag",constraint:null,ghosting:false,scroll:null,snap:null,threshold:0,onDrag:null,onEnd:null,onStart:null},arguments[1]||{});this.mousedownE=this._mouseDown.bindAsEventListener(this);this.element.observe("mousedown",this.mousedownE);if(this.options.scroll){this.options.scroll=$(this.options.scroll)}DragDrop.Drags.register(this);if(Prototype.Browser.IE){this.element.observe("selectstart",Event.stop)}else{if(Prototype.Browser.Gecko){this.element.setStyle({MozUserSelect:"none"})}}},destroy:function(){this.element.stopObserving("mousedown",this.mousedownE);DragDrop.Drags.unregister(this)},_mouseDown:function(A){$(document.body).setStyle({cursor:"default"});DragDrop.Drags.activate(this);this.move=0;this.wasDragged=false;this.lastcaption=null;if(Object.isFunction(this.options.onStart)){this.options.onStart(this,A)}if(!Prototype.Browser.IE&&!Prototype.Browser.Gecko){A.stop()}},_mouseMove:function(E){var B,C,A,D;if(++this.move<=this.options.threshold){return}this.lastCoord=D=[E.pointerX(),E.pointerY()];if(this.options.ghosting){if(!this.ghost){B=this.element.offsetLeft;C=this.element.offsetTop;this.ghost=$(this.element.cloneNode(true)).writeAttribute("id",null).setOpacity(0.7).clonePosition(this.element,{setLeft:false,setTop:false}).setStyle({left:B+"px",position:"absolute",top:C+"px",zIndex:parseInt(this.element.getStyle("zIndex"))+1});this.element.insert({before:this.ghost});A=this.ghost.viewportOffset();this.ghostOffset=[A[0]-B,A[1]-C]}D[0]-=this.ghostOffset[0];D[1]-=this.ghostOffset[1];switch(this.options.constraint){case"horizontal":D[1]=this.ghost.offsetTop;break;case"vertical":D[0]=this.ghost.offsetLeft;break}if(this.options.snap){D=this.options.snap(D[0],D[1],this.element)}if(this.options.offset){D[0]+=this.options.offset.x;D[1]+=this.options.offset.y}this._setContents(this.ghost,D[0],D[1])}this._onMoveDrag(D,E);if(Object.isFunction(this.options.onDrag)){this.options.onDrag(this,E)}this.wasDragged=true;if(this.options.scroll){this._onMoveScroll()}},_mouseUp:function(A){var B=DragDrop.Drops.drop;this._stopScrolling();if(this.ghost){this.ghost.remove();this.ghost=null}DragDrop.Drags.div.hide();if(DragDrop.validDrop(this.element)&&Object.isFunction(B.options.onDrop)){B.options.onDrop(B.element,this.element,A)}DragDrop.Drags.deactivate();if(Object.isFunction(this.options.onEnd)){this.options.onEnd(this,A)}},_onMoveDrag:function(I,E){var G,H,D,C,F=DragDrop.Drops.drop,A=DragDrop.Drags.div,B=true;if(F&&DragDrop.validDrop(this.element)){C=F.options.caption;if(C){H=Object.isFunction(C)?C(F.element,this.element,E):C;if(H&&F.options.hoverclass){D=F.options.hoverclass}}else{B=false}}if(B){if(!H){G=this.options.caption;H=Object.isFunction(G)?G(this.element):G}if(H!=this.lastcaption){this.lastcaption=H;A.update(H).writeAttribute({className:D||this.options.classname});if(H.empty()){A.hide()}}}if(!this.lastcaption.empty()){this._setContents(A,I[0]+15,I[1]+(this.ghost?(this.ghost.getHeight()+5):5))}},_onMoveScroll:function(){this._stopScrolling();var E,D,B,A=this.options.scroll,C=A.getDimensions();if(A.scrollHeight==C.height){return}E=document.viewport.getScrollOffsets();D=A.viewportOffset(),B=[0,0];D[0]+=A.scrollLeft+E.left;D[2]=D[0]+C.width;if(this.lastCoord[0]>D[2]||this.lastCoord[0]<D[0]){return}D[1]+=A.scrollTop+E.top;D[3]=D[1]+C.height;if(this.lastCoord[1]<D[1]){B[1]=this.lastCoord[1]-D[1]}if(this.lastCoord[1]>D[3]){B[1]=this.lastCoord[1]-D[3]}if(B[0]||B[1]){this.lastScrolled=new Date();this.scrollInterval=setInterval(this._scroll.bind(this,B[0]*15,B[1]*15),10)}},_stopScrolling:function(){if(this.scrollInterval){clearInterval(this.scrollInterval);this.scrollInterval=null}},_scroll:function(A,E){var C=new Date(),D=C-this.lastScrolled,B=this.options.scroll;this.lastScrolled=C;B.scrollTop+=E*D/1000},_setContents:function(C,A,E){var D=document.viewport.getDimensions(),B=C.getDimensions();if((A+B.width>D.width)||(E+B.height>D.height)){C.hide()}else{C.setStyle({left:A+"px",top:E+"px"}).show()}}}),Drop=Class.create({initialize:function(A){this.element=$(A);this.options=Object.extend({accept:[],caption:"",hoverclass:"",onDrop:null,onOut:null,onOver:null},arguments[1]||{});this.mouseoverE=this._mouseOver.bindAsEventListener(this);this.mouseoutE=this._mouseOut.bindAsEventListener(this);this.element.observe("mouseover",this.mouseoverE);this.element.observe("mouseout",this.mouseoutE);DragDrop.Drops.register(this)},destroy:function(){this.element.stopObserving("mouseover",this.mouseoverE);this.element.stopObserving("mouseout",this.mouseoutE);DragDrop.Drops.unregister(this)},_mouseOver:function(A){if(DragDrop.Drags.drag){DragDrop.Drops.drop=this;if(Object.isFunction(this.options.onOver)){this.options.onOver(this.element,DragDrop.Drags.drag)}}},_mouseOut:function(A){if(Object.isFunction(this.options.onOut)){this.options.onOut(this.element,DragDrop.Drags.drag)}DragDrop.Drops.drop=null}});
\ No newline at end of file
diff --git a/imp/js/fullmessage-dimp.js b/imp/js/fullmessage-dimp.js
new file mode 100644 (file)
index 0000000..2b03e8a
--- /dev/null
@@ -0,0 +1 @@
+var DimpFullmessage={quickreply:function(B){var C,A={};A[$F("folder")]=[$F("index")];$("msgData").hide();$("qreply").show();switch(B){case"reply":case"reply_all":case"reply_list":C="GetReplyData";break;case"forward_all":case"forward_body":case"forward_attachments":C="GetForwardData";break}DimpCore.doAction(C,{imp_compose:$F("composeCache"),type:B},A,this.msgTextCallback.bind(this))},msgTextCallback:function(B){if(!B.response){return}var D=B.response,A=((D.format=="html")&&!DimpCompose.editor_on),E=(D.identity===null)?$F("identity"):D.identity,C=DimpCompose.get_identity(E,A);$("identity","last_identity").invoke("setValue",E);DimpCompose.fillForm((C.id[2])?("\n"+C.sig+D.body):(D.body+"\n"+C.sig),D.header);if(D.fwd_list&&D.fwd_list.length){D.fwd_list.each(function(F){DimpCompose.addAttach(F.number,F.name,F.type,F.size)})}if(A){DimpCompose.toggleHtmlEditor(true)}if(D.imp_compose){$("composeCache").setValue(D.imp_compose)}}};document.observe("dom:loaded",function(){window.focus();DimpCore.messageOnLoad();DimpCore.addPopdown("reply_link","replypopdown");DimpCore.addPopdown("forward_link","fwdpopdown");["from","to","cc","bcc","replyTo"].each(function(B){if(DimpFullmessage[B]){var C=$("msgHeader"+B.charAt(0).toUpperCase()+B.substring(1)).down("TD",1);C.replace(DimpCore.buildAddressLinks(DimpFullmessage[B],C.cloneNode(false)))}});var A=DimpCore.clickObserveHandler;A({d:$("windowclose"),f:function(){window.close()}});A({d:$("reply_link"),f:DimpFullmessage.quickreply.bind(DimpFullmessage,"reply")});A({d:$("forward_link"),f:DimpFullmessage.quickreply.bind(DimpFullmessage,DIMP.conf.forward_default)});["spam","ham","deleted"].each(function(B){var C=$("button_"+B);if(C){A({d:C,f:function(D){DIMP.baseWindow.DimpBase.flag(D,DIMP.conf.msg_index,DIMP.conf.msg_folder);window.close()}.curry(B)})}});A({d:$("qreply").select("div.headercloseimg img").first(),f:DimpCompose.confirmCancel.bind(DimpCompose)});["reply","reply_all","reply_list"].each(function(B){var C=$("ctx_replypopdown_"+B);if(C){A({d:C,f:DimpFullmessage.quickreply.bind(DimpFullmessage,B),ns:true})}});["forward_all","forward_body","forward_attachments"].each(function(B){A({d:$("ctx_fwdpopdown_"+B),f:DimpFullmessage.quickreply.bind(DimpFullmessage,B),ns:true})})});
\ No newline at end of file
diff --git a/imp/js/src/ContextSensitive.js b/imp/js/src/ContextSensitive.js
new file mode 100644 (file)
index 0000000..5c7c3f1
--- /dev/null
@@ -0,0 +1,226 @@
+/**
+ * ContextSensitive: a library for generating context-sensitive content on
+ * HTML elements. It will take over the click/oncontextmenu functions for the
+ * document, and works only where these are possible to override.  It allows
+ * contextmenus to be created via both a left and right mouse click.
+ *
+ * Requires prototypejs 1.6+ and scriptaculous 1.8+ (effects.js only).
+ *
+ * Original code by Havard Eide (http://eide.org/) released under the MIT
+ * license.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * $Horde: dimp/js/src/ContextSensitive.js,v 1.66 2008/08/25 19:56:23 slusarz Exp $
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ */
+
+var ContextSensitive = Class.create({
+
+    initialize: function()
+    {
+        this.current = this.target = null;
+        this.elements = $H();
+
+        document.observe('contextmenu', this.rightClickHandler.bindAsEventListener(this));
+        document.observe('click', this.leftClickHandler.bindAsEventListener(this));
+        document.observe(Prototype.Browser.Gecko ? 'DOMMouseScroll' : 'mousescroll', this.close.bind(this));
+    },
+
+    /**
+     * Elements are of type ContextSensitive.Element.
+     */
+    addElement: function(id, target, opts)
+    {
+        var left = Boolean(opts.left);
+        if (id && !this.validElement(id, left)) {
+            this.elements.set(id + Number(left), new ContextSensitive.Element(id, target, opts));
+        }
+    },
+
+    /**
+     * Remove a registered element.
+     */
+    removeElement: function(id)
+    {
+        this.elements.unset(id + '0');
+        this.elements.unset(id + '1');
+    },
+
+    /**
+     * Hide the current element.
+     */
+    close: function(immediate)
+    {
+        if (this.current) {
+            if (immediate) {
+                this.current.hide();
+            } else {
+                Effect.Fade(this.current, { duration: 0.2 });
+            }
+            this.current = this.target = null;
+        }
+    },
+
+    /**
+     * Get the element that triggered the current context menu (if any).
+     */
+    element: function()
+    {
+        return this.target;
+    },
+
+    /**
+     * Returns the current displayed menu element, if any.
+     */
+    currentmenu: function()
+    {
+        if (this.current && this.current.visible()) {
+            return this.current;
+        }
+    },
+
+    /**
+     * Get a valid element (the ones that can be right-clicked) based
+     * on a element ID.
+     */
+    validElement: function(id, left)
+    {
+        return this.elements.get(id + Number(Boolean(left)));
+    },
+
+    /**
+     * Set the disabled flag of an event.
+     */
+    disable: function(id, left, disable)
+    {
+        var e = this.validElement(id, left);
+        if (e) {
+            e.disable = disable;
+        }
+    },
+
+    /**
+     * Called when a left click event occurs. Will return before the
+     * element is closed if we click on an element inside of it.
+     */
+    leftClickHandler: function(e)
+    {
+        // Check for a right click. FF on Linux triggers an onclick event even
+        // w/a right click, so disregard.
+        if (e.isRightClick()) {
+            return;
+        }
+
+        // Check if the mouseclick is registered to an element now.
+        this.rightClickHandler(e, true);
+    },
+
+    /**
+     * Called when a right click event occurs.
+     */
+    rightClickHandler: function(e, left)
+    {
+        if (this.trigger(e.element(), left, e.pointerX(), e.pointerY())) {
+            e.stop();
+        };
+    },
+
+    /**
+     * Display context menu if valid element has been activated.
+     */
+    trigger: function(target, leftclick, x, y)
+    {
+        var ctx, el, offset, offsets, size, v, voffsets;
+
+        [ target ].concat(target.ancestors()).find(function(n) {
+            ctx = this.validElement(n.id, leftclick);
+            return ctx;
+        }, this);
+
+        // Return if event not found or event is disabled.
+        if (!ctx || ctx.disable) {
+            this.close();
+            return false;
+        }
+
+        // Try to retrieve the context-sensitive element we want to
+        // display. If we can't find it we just return.
+        el = $(ctx.ctx);
+        if (!el) {
+            this.close();
+            return false;
+        } else if (leftclick && el == this.current) {
+            return false;
+        }
+
+        // Register the current element that will be shown and the
+        // element that was clicked on.
+        this.current = el;
+        this.target = $(ctx.id);
+
+        // Get the base element positions.
+        offset = ctx.opts.offset;
+        if (!offset && (Object.isUndefined(x) || Object.isUndefined(y))) {
+            offset = target.id;
+        }
+        offset = $(offset);
+
+        if (offset) {
+            offsets = offset.viewportOffset();
+            voffsets = document.viewport.getScrollOffsets();
+            x = offsets[0] + voffsets.left;
+            y = offsets[1] + offset.getHeight() + voffsets.top;
+        }
+
+        // Get window/element dimensions
+        v = document.viewport.getDimensions();
+        size = el.getDimensions();
+
+        // Make sure context window is entirely on screen
+        if ((y + size.height) > v.height) {
+            y = v.height - size.height - 10;
+        }
+        if ((x + size.width) > v.width) {
+            x = v.width - size.width - 10;
+        }
+
+        if (ctx.opts.onShow) {
+            ctx.opts.onShow(ctx);
+        }
+
+        Effect.Appear(el.setStyle({ left: x + 'px', top: y + 'px' }), { duration: 0.2 });
+
+        return true;
+    }
+});
+
+ContextSensitive.Element = Class.create({
+
+    // opts: 'left' -> monitor left click; 'offset' -> id of element used to
+    //       determine offset placement
+    initialize: function(id, target, opts)
+    {
+        this.id = id;
+        this.ctx = target;
+        this.opts = opts;
+        this.opts.left = Boolean(opts.left);
+        this.disable = false;
+    }
+
+});
diff --git a/imp/js/src/DimpBase.js b/imp/js/src/DimpBase.js
new file mode 100644 (file)
index 0000000..f3da4d4
--- /dev/null
@@ -0,0 +1,2165 @@
+/**
+ * DimpBase.js - Javascript used in the base DIMP page.
+ *
+ * $Horde: dimp/js/src/DimpBase.js,v 1.174 2008/11/24 21:53:41 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+var DimpBase = {
+    // Vars used and defaulting to null/false:
+    //   filtertoggle, fl_visible, folder, folderswitch, fspecial, isvisible,
+    //   message_list_template, offset, pollPE, pp, searchobserve, uid,
+    //   viewport
+    bcache: $H(),
+    lastrow: -1,
+    mo_sidebar: {},
+    pivotrow: -1,
+    ppcache: {},
+    ppfifo: [],
+    showPreview: DIMP.conf.preview_pref,
+
+    sfiltersfolder: $H({ sf_all: 'all', sf_current: 'current' }),
+    sfilters: $H({ sf_msgall: 'msgall', sf_from: 'from', sf_to: 'to', sf_subject: 'subject' }),
+
+    // Message selection functions
+
+    // vs = (ViewPort_Selection) A ViewPort_Selection object.
+    // opts = (object) Boolean options [delay, right]
+    _select: function(vs, opts)
+    {
+        var d = vs.get('rownum');
+        if (d.size() == 1) {
+            this.lastrow = this.pivotrow = d.first();
+        }
+
+        this.toggleButtons();
+
+        if ($('previewPane').visible()) {
+            if (opts.right) {
+                this.clearPreviewPane();
+            } else {
+                if (opts.delay) {
+                    (this.bcache.get('initPP') || this.bcache.set('initPP', this.initPreviewPane.bind(this))).delay(opts.delay);
+                } else {
+                    this.initPreviewPane();
+                }
+            }
+        }
+    },
+
+    // vs = (ViewPort_Selection) A ViewPort_Selection object.
+    // opts = (object) Boolean options [right]
+    _deselect: function(vs, opts)
+    {
+        var sel = this.viewport.getSelected(),
+            count = sel.size();
+        if (!count) {
+            this.lastrow = this.pivotrow = -1;
+        }
+
+        this.toggleButtons();
+        if (opts.right || !count) {
+            this.clearPreviewPane();
+        } else if ((count == 1) && $('previewPane').visible()) {
+            this._loadPreview(sel.get('dataob').first());
+        }
+    },
+
+    // id = (string) DOM ID
+    // opts = (Object) Boolean options [ctrl, right, shift]
+    msgSelect: function(id, opts)
+    {
+        var bounds,
+            row = this.viewport.createSelection('domid', id),
+            rownum = row.get('rownum').first(),
+            sel = this.isSelected('domid', id),
+            selcount = this.selectedCount();
+
+        this.lastrow = rownum;
+
+        // Some browsers need to stop the mousedown event before it propogates
+        // down to the browser level in order to prevent text selection on
+        // drag/drop actions.  Clicking on a message should always lose focus
+        // from the search input, because the user may immediately start
+        // keyboard navigation after that. Thus, we need to ensure that a
+        // message click loses focus on the search input.
+        $('msgList_filter').blur();
+
+        if (opts.shift) {
+            if (selcount) {
+                if (!sel || selcount != 1) {
+                    bounds = [ rownum, this.pivotrow ];
+                    this.viewport.select($A($R(bounds.min(), bounds.max())), { range: true });
+                }
+                return;
+            }
+        } else if (opts.ctrl) {
+            this.pivotrow = rownum;
+            if (sel) {
+                this.viewport.deselect(row, { right: opts.right });
+                return;
+            } else if (opts.right || selcount) {
+                this.viewport.select(row, { add: true, right: opts.right });
+                return;
+            }
+        }
+
+        this.viewport.select(row, { right: opts.right });
+    },
+
+    selectAll: function()
+    {
+        this.viewport.select($A($R(1, this.viewport.getMetaData('total_rows'))), { range: true });
+    },
+
+    isSelected: function(format, data)
+    {
+        return this.viewport.getSelected().contains(format, data);
+    },
+
+    selectedCount: function()
+    {
+        return (this.viewport) ? this.viewport.getSelected().size() : 0;
+    },
+
+    resetSelected: function()
+    {
+        if (this.viewport) {
+            this.viewport.deselect(this.viewport.getSelected(), { clearall: true });
+        }
+        this.toggleButtons();
+        this.clearPreviewPane();
+    },
+
+    // num = (integer) See absolute.
+    // absolute = Is num an absolute row number - from 1 -> page_size (true) -
+    //            or a relative change from the current selected value (false)
+    //            If no current selected value, the first message in the
+    //            current viewport is selected.
+    moveSelected: function(num, absolute)
+    {
+        var curr, curr_row, row, row_data, sel;
+
+        if (absolute) {
+            if (!this.viewport.getMetaData('total_rows')) {
+                return;
+            }
+            curr = num;
+        } else {
+            if (num == 0) {
+                return;
+            }
+
+            sel = this.viewport.getSelected();
+            switch (sel.size()) {
+            case 0:
+                curr = this.viewport.currentOffset() + 1;
+                break;
+
+            case 1:
+                curr_row = sel.get('dataob').first();
+                curr = curr_row.rownum + num;
+                break;
+
+            default:
+                sel = sel.get('rownum');
+                curr = (num > 0 ? sel.max() : sel.min()) + num;
+                break;
+            }
+            curr = (num > 0) ? Math.min(curr, this.viewport.getMetaData('total_rows')) : Math.max(curr, 1);
+        }
+
+        row = this.viewport.createSelection('rownum', curr);
+        if (row.size()) {
+            row_data = row.get('dataob').first();
+            if (!curr_row || row_data.imapuid != curr_row.imapuid) {
+                this.viewport.scrollTo(row_data.rownum);
+                this.viewport.select(row, { delay: 0.3 });
+            }
+        } else {
+            this.offset = curr;
+            this.viewport.requestContentRefresh(curr - 1);
+        }
+    },
+    // End message selection functions
+
+    go: function(loc, data)
+    {
+        var app, f, separator;
+
+        if (loc.startsWith('compose:')) {
+            return;
+        }
+
+        if (loc.startsWith('msg:')) {
+            separator = loc.indexOf(':', 4);
+            f = loc.substring(4, separator);
+            this.uid = loc.substring(separator + 1);
+            loc = 'folder:' + f;
+            // Now fall through to the 'folder:' check below.
+        }
+
+        if (loc.startsWith('folder:')) {
+            f = loc.substring(7);
+            if (this.folder != f || !$('dimpmain_folder').visible()) {
+                this.highlightSidebar(this.getFolderId(f));
+                if (!$('dimpmain_folder').visible()) {
+                    $('dimpmain_portal').hide();
+                    $('dimpmain_folder').show();
+                }
+                // This catches the refresh case - no need to re-add to history
+                if (!Object.isUndefined(this.folder)) {
+                    this._addHistory(loc);
+                }
+            }
+            this.loadFolder(f);
+            return;
+        }
+
+        this.folder = null;
+        $('dimpmain_folder').hide();
+        $('dimpmain_portal').update(DIMP.text.loading).show();
+
+        if (loc.startsWith('app:')) {
+            app = loc.substr(4);
+            if (app == 'imp' || app == 'dimp') {
+                this.go('folder:INBOX');
+                return;
+            }
+            this.highlightSidebar('app' + app);
+            this._addHistory(loc, data);
+            if (data) {
+                this.iframeContent(loc, data);
+            } else if (DIMP.conf.app_urls[app]) {
+                this.iframeContent(loc, DIMP.conf.app_urls[app]);
+            }
+            return;
+        }
+
+        switch (loc) {
+        case 'portal':
+            this.highlightSidebar('appportal');
+            this._addHistory(loc);
+            DimpCore.setTitle(DIMP.text.portal);
+            DimpCore.doAction('ShowPortal', {}, null, this.bcache.get('portalC') || this.bcache.set('portalC', this._portalCallback.bind(this)));
+            break;
+
+        case 'options':
+            this.highlightSidebar('appoptions');
+            this._addHistory(loc);
+            DimpCore.setTitle(DIMP.text.prefs);
+            this.iframeContent(loc, DIMP.conf.prefs_url);
+            break;
+        }
+    },
+
+    _addHistory: function(loc, data)
+    {
+        if (Horde.dhtmlHistory.getCurrentLocation() != loc) {
+            Horde.dhtmlHistory.add(loc, data);
+        }
+    },
+
+    highlightSidebar: function(id)
+    {
+        // Folder bar may not be fully loaded yet.
+        if ($('foldersLoading').visible()) {
+            this.highlightSidebar.bind(this, id).defer();
+            return;
+        }
+
+        $('sidebarPanel').select('.on').invoke('removeClassName', 'on');
+
+        var elt = $(id);
+        if (!elt) {
+            return;
+        }
+        if (!elt.match('LI')) {
+            elt = elt.up();
+            if (!elt) {
+                return;
+            }
+        }
+        elt.addClassName('on');
+
+        // Make sure all subfolders are expanded
+        elt.ancestors().find(function(n) {
+            if (n.hasClassName('subfolders')) {
+                this._toggleSubFolder(n.id.substring(3), 'exp');
+            } else {
+                return (n.id == 'foldersSidebar');
+            }
+        }, this);
+    },
+
+    iframeContent: function(name, loc)
+    {
+        if (name === null) {
+            name = loc;
+        }
+
+        var container = $('dimpmain_portal'), iframe;
+        if (!container) {
+            DimpCore.showNotifications([ { type: 'horde.error', message: 'Bad portal!' } ]);
+            return;
+        }
+
+        iframe = new Element('IFRAME', { id: 'iframe' + name, className: 'iframe', frameBorder: 0, src: loc });
+        this._resizeIE6Iframe(iframe);
+
+        // Hide menu in prefs pages.
+        if (name == 'options') {
+            iframe.observe('load', function() { $('iframeoptions').contentWindow.document.getElementById('menu').style.display = 'none'; });
+        }
+
+        container.insert(iframe);
+    },
+
+    // r = ViewPort row data
+    msgWindow: function(r)
+    {
+        this.updateUnseenUID(r, 0);
+        var url = DIMP.conf.message_url;
+        url += (url.include('?') ? '&' : '?') +
+               $H({ folder: r.view,
+                    uid: r.imapuid }).toQueryString();
+        DimpCore.popupWindow(url, 'msgview' + r.view + r.imapuid);
+    },
+
+    composeMailbox: function(type)
+    {
+        var sel = this.viewport.getSelected();
+        if (!sel.size()) {
+            return;
+        }
+        sel.get('dataob').each(function(s) {
+            DimpCore.compose(type, { folder: s.view, uid: s.imapuid });
+        });
+    },
+
+    loadFolder: function(f, background)
+    {
+        if (!this.viewport) {
+            this._createViewPort();
+        }
+
+        if (!background) {
+            this.resetSelected();
+
+            if (this.folder == f) {
+                this.searchfilterClear(false);
+                return;
+            }
+
+            this.searchfilterClear(true);
+            $('folderName').update(DIMP.text.loading);
+            $('msgHeader').update();
+            this.folderswitch = true;
+            this.folder = f;
+        }
+
+        this.viewport.loadView(f, { folder: f }, this.uid ? { imapuid: this.uid, view: f } : null, background);
+    },
+
+    _createViewPort: function()
+    {
+        var mf = $('msgList_filter'),
+            // No need to cache - this function only called once.
+            settitle = this.setMessageListTitle.bind(this);
+
+        this.viewport = new ViewPort({
+            content_container: 'msgList',
+            empty_container: 'msgList_empty',
+            error_container: 'msgList_error',
+            fetch_action: 'ListMessages',
+            template: this.message_list_template,
+            buffer_pages: DIMP.conf.buffer_pages,
+            limit_factor: DIMP.conf.limit_factor,
+            viewport_wait: DIMP.conf.viewport_wait,
+            show_split_pane: this.showPreview,
+            split_pane: 'previewPane',
+            splitbar: 'splitBar',
+            content_class: 'msglist',
+            row_class: 'msgRow',
+            selected_class: 'selectedRow',
+            ajaxRequest: DimpCore.doAction.bind(DimpCore),
+            norows: true,
+            onScrollIdle: settitle,
+            onSlide: settitle,
+            onViewChange: function() {
+                DimpCore.addGC(this.viewport.visibleRows());
+            }.bind(this),
+            onContent: function(rows) {
+                var mf, search,
+                    thread = ((this.viewport.getMetaData('sortby') == DIMP.conf.sortthread) && this.viewport.getMetaData('thread'));
+                if (this.viewport.isFiltering()) {
+                    search = this.sfilters.get(this._getSearchfilterField()).capitalize();
+                    mf = new RegExp("(" + $F('msgList_filter') + ")", "i");
+                }
+                rows.get('dataob').each(function(row) {
+                    var elt, tmp, u,
+                        r = $(row.domid);
+                    // Add thread graphics
+                    if (thread && thread[row.imapuid]) {
+                        elt = r.down('.msgSubject');
+                        tmp = elt.cloneNode(false);
+                        u = thread[row.imapuid];
+                        $R(0, u.length, true).each(function(i) {
+                            tmp.insert($($('thread_img_' + u.charAt(i)).cloneNode(false)).writeAttribute('id', ''));
+                        });
+                        elt.replace(tmp.insert(elt.getText().escapeHTML()));
+                    }
+                    // Add attachment graphics
+                    if (row.atc) {
+                        r.down('.msgSize').insert({ top: $($('atc_img_' + row.atc).cloneNode(false)).writeAttribute('id', '') });
+                    }
+
+                    // Add context menu.
+                    DimpCore.addMouseEvents({ id: row.domid, type: row.menutype });
+
+                    // Highlight search terms
+                    if (search == 'From' || search == 'Subject') {
+                        elt = r.down('.msg' + search);
+                        elt.update(elt.getText().escapeHTML().gsub(mf, '<span class="searchMatch">#{1}</span>'));
+                    }
+                }, this);
+                this.setMessageListTitle();
+            }.bind(this),
+            onComplete: function() {
+                var row, ssc,
+                    l = this.viewport.getMetaData('label');
+
+                if (this.uid) {
+                    row = this.viewport.getViewportSelection().search({ imapuid: { equal: [ this.uid ] }, view: { equal: [ this.folder ] } });
+                    if (row.size()) {
+                        this.viewport.scrollTo(row.get('rownum').first());
+                        this.viewport.select(row);
+                    }
+                } else if (this.offset) {
+                    this.viewport.select(this.viewport.createSelection('rownum', this.offset));
+                }
+                this.offset = this.uid = null;
+
+                // 'label' will not be set if there has been an error
+                // retrieving data from the server.
+                l = this.viewport.getMetaData('label');
+                if (l) {
+                    $('folderName').update(l);
+                }
+
+                if (this.folderswitch) {
+                    this.folderswitch = false;
+                    if (this.folder == DIMP.conf.spam_folder) {
+                        if (!DIMP.conf.spam_spamfolder &&
+                            DimpCore.buttons.indexOf('button_spam') != -1) {
+                            [ $('button_spam').up(), $('ctx_message_spam') ].invoke('hide');
+                        }
+                        if (DimpCore.buttons.indexOf('button_ham') != -1) {
+                            [ $('button_ham').up(), $('ctx_message_ham') ].invoke('show');
+                        }
+                    } else {
+                        if (DimpCore.buttons.indexOf('button_spam') != -1) {
+                            [ $('button_spam').up(), $('ctx_message_spam') ].invoke('show');
+                        }
+                        if (DimpCore.buttons.indexOf('button_ham') != -1) {
+                            if (DIMP.conf.ham_spamfolder) {
+                                [ $('button_ham').up(), $('ctx_message_ham') ].invoke('hide');
+                            } else {
+                                [ $('button_ham').up(), $('ctx_message_ham') ].invoke('show');
+                            }
+                        }
+                    }
+                } else if (this.filtertoggle) {
+                    if (this.filtertoggle == 1 &&
+                        this.viewport.getMetaData('sortby') == DIMP.conf.sortthread) {
+                        ssc = DIMP.conf.sortdate;
+                    }
+                    this.filtertoggle = 0;
+                }
+
+                this.setSortColumns(ssc);
+
+                if (this.viewport.isFiltering()) {
+                    this.resetSelected();
+                } else {
+                    this.setFolderLabel(this.folder, this.viewport.getMetaData('unseen') || 0);
+                }
+                this.updateTitle();
+            }.bind(this),
+            onFetch: this.msgListLoading.bind(this, true),
+            onEndFetch: this.msgListLoading.bind(this, false),
+            onWait: function() {
+                if ($('dimpmain_folder').visible()) {
+                    DimpCore.showNotifications([ { type: 'horde.warning', message: DIMP.text.listmsg_wait } ]);
+                }
+            },
+            onFail: function() {
+                if ($('dimpmain_folder').visible()) {
+                    DimpCore.showNotifications([ { type: 'horde.error', message: DIMP.text.listmsg_timeout } ]);
+                }
+                this.msgListLoading(false);
+            }.bind(this),
+            onFirstContent: function() {
+                this.clearPreviewPane();
+                $('msgList').observe('dblclick', this._handleMsgListDblclick.bindAsEventListener(this));
+            }.bind(this),
+            onClearRows: function(r) {
+                r.each(function(row) {
+                    var c = $(row).down('div.msCheck');
+                    if (c) {
+                        DimpCore.addGC(c);
+                    }
+                    if (row.id) {
+                        DimpCore.removeMouseEvents(row);
+                    }
+                });
+            },
+            onBeforeResize: function() {
+                var sel = this.viewport.getSelected();
+                this.isvisible = (sel.size() == 1) && (this.viewport.isVisible(sel.get('rownum').first()) == 0);
+            }.bind(this),
+            onAfterResize: function() {
+                if (this.isvisible) {
+                    this.viewport.scrollTo(this.viewport.getSelected().get('rownum').first());
+                }
+            }.bind(this),
+            selectCallback: this._select.bind(this),
+            deselectCallback: this._deselect.bind(this)
+        });
+
+        // If starting in no preview mode, need to set the no preview class
+        if (!this.showPreview) {
+            $('msgList').addClassName('msglistNoPreview');
+        }
+
+        // Set up viewport filter events.
+        this.viewport.addFilter('ListMessages', this._addSearchfilterParams.bind(this));
+        mf.observe('keyup', this._searchfilterOnKeyup.bind(this));
+        mf.observe('focus', this._searchfilterOnFocus.bind(this));
+        mf.observe('blur', this._searchfilterOnBlur.bind(this));
+        mf.addClassName('msgFilterDefault');
+    },
+
+    _addMouseEvents: function(parentfunc, p)
+    {
+        var elt;
+
+        switch (p.type) {
+        case 'draft':
+        case 'message':
+            new Drag(p.id, this._msgDragConfig);
+            elt = $(p.id).down('div.msCheck');
+            if (elt.visible()) {
+                elt.observe('mousedown', this.bcache.get('handleMLC') || this.bcache.set('handleMLC', this._handleMsgListCheckbox.bindAsEventListener(this)));
+                elt.observe('contextmenu', Event.stop);
+            }
+            break;
+
+        case 'container':
+        case 'folder':
+            new Drag(p.id, this._folderDragConfig);
+            break;
+
+        case 'special':
+            // For purposes of the contextmenu, treat special folders
+            // like regular folders.
+            p.type = 'folder';
+            break;
+
+        case 'vcontainer':
+        case 'virtual':
+            $(p.id).observe('contextmenu', Event.stop);
+            break;
+        }
+
+        p.onShow = this.bcache.get('onMS') || this.bcache.set('onMS', this._onMenuShow.bind(this));
+        parentfunc(p);
+    },
+
+    _removeMouseEvents: function(parentfunc, elt)
+    {
+        var d, id = $(elt).readAttribute('id');
+        if (id && (d = DragDrop.Drags.get_drag(id))) {
+            d.destroy();
+        }
+        parentfunc(elt);
+    },
+
+    _onMenuShow: function(ctx)
+    {
+        var elts, folder, ob, sel;
+
+        switch (ctx.ctx) {
+        case 'ctx_folder':
+            elts = $('ctx_folder_create', 'ctx_folder_rename', 'ctx_folder_delete');
+            folder = DimpCore.DMenu.element();
+            if (folder.readAttribute('mbox') == 'INBOX') {
+                elts.invoke('hide');
+            } else if (DIMP.conf.fixed_folders.indexOf(folder.readAttribute('mbox')) != -1) {
+                elts.shift();
+                elts.invoke('hide');
+            } else {
+                elts.invoke('show');
+            }
+
+            if (folder.hasAttribute('u')) {
+                $('ctx_folder_poll').hide();
+                $('ctx_folder_nopoll').show();
+            } else {
+                $('ctx_folder_poll').show();
+                $('ctx_folder_nopoll').hide();
+            }
+            break;
+
+        case 'ctx_message':
+            [ $('ctx_message_reply_list') ].invoke(this.viewport.createSelection('domid', ctx.id).get('dataob').first().listmsg ? 'show' : 'hide');
+            break;
+
+        case 'ctx_reply':
+            sel = this.viewport.getSelected();
+            if (sel.size() == 1) {
+                ob = sel.get('dataob').first();
+            }
+            [ $('ctx_reply_reply_list') ].invoke(ob && ob.listmsg ? 'show' : 'hide');
+            break;
+
+        case 'ctx_otheractions':
+            $('oa_seen', 'oa_unseen', 'oa_flagged', 'oa_clear', 'oa_sep1', 'oa_blacklist', 'oa_whitelist', 'oa_sep2').compact().invoke(this.viewport.getSelected().size() ? 'show' : 'hide');
+            break;
+        }
+        return true;
+    },
+
+    _onResize: function(noupdate, nowait)
+    {
+        if (this.viewport) {
+            this.viewport.onResize(noupdate, nowait);
+        }
+        this._resizeIE6();
+    },
+
+    _handleMsgListDblclick: function(e)
+    {
+        var elt = this._getMsgRow(e), row;
+        if (!elt) {
+            return;
+        }
+        row = this.viewport.createSelection('domid', elt.id).get('dataob').first();
+        row.draft ? DimpCore.compose('resume', { folder: row.view, uid: row.imapuid }) : this.msgWindow(row);
+        e.stop();
+    },
+
+    _handleMsgListCheckbox: function(e)
+    {
+        var elt = this._getMsgRow(e);
+        if (!elt) {
+            return;
+        }
+        this.msgSelect(elt.readAttribute('id'), { ctrl: true, right: true });
+        e.stop();
+    },
+
+    _getMsgRow: function(e)
+    {
+        e = e.element();
+        if (e && !e.hasClassName('msgRow')) {
+            e = e.up('.msgRow');
+        }
+        return e;
+    },
+
+    updateTitle: function()
+    {
+        var elt, label, unseen;
+        if (this.viewport.isFiltering()) {
+            label = DIMP.text.search + ' :: ' + this.viewport.getMetaData('total_rows') + ' ' + DIMP.text.resfound;
+        } else {
+            elt = $(this.getFolderId(this.folder));
+            if (elt) {
+                unseen = elt.readAttribute('u');
+                label = elt.readAttribute('l');
+                if (unseen > 0) {
+                    label += ' (' + unseen + ')';
+                }
+            } else {
+                label = this.viewport.getMetaData('label');
+            }
+        }
+        DimpCore.setTitle(label);
+    },
+
+    sort: function(e)
+    {
+        // Don't change sort if we are past the sortlimit
+        if (this.viewport.getMetaData('sortlimit')) {
+            return;
+        }
+
+        var s, sortby,
+            elt = e.element();
+        if (!elt.hasAttribute('sortby')) {
+            elt = elt.up('[sortby]');
+            if (!elt) {
+                return;
+            }
+        }
+        sortby = parseInt(elt.readAttribute('sortby'), 10);
+
+        if (sortby == this.viewport.getMetaData('sortby')) {
+            s = { sortdir: (this.viewport.getMetaData('sortdir') ? 0 : 1) };
+            this.viewport.setMetaData({ sortdir: s.sortdir });
+        } else {
+            s = { sortby: sortby };
+            this.viewport.setMetaData({ sortby: s.sortby });
+        }
+        this.setSortColumns(sortby);
+        this.viewport.reload(s);
+    },
+
+    setSortColumns: function(sortby)
+    {
+        var tmp,
+            m = $('msglistHeader');
+
+        if (Object.isUndefined(sortby)) {
+            sortby = this.viewport.getMetaData('sortby');
+        }
+
+        tmp = m.down('small[sortby=' + sortby + ']');
+        if (tmp && tmp.up().visible()) {
+           tmp.up(1).childElements().invoke('toggle');
+        }
+
+        tmp = m.down('div.msgFrom a');
+        if ((this.viewport.isFiltering() && this.fspecial) ||
+            this.viewport.getMetaData('special')) {
+            tmp.hide().next().show();
+        } else {
+            tmp.show().next().hide();
+        }
+
+        tmp = m.down('div.msgSubject a');
+        if (this.viewport.isFiltering() ||
+            this.viewport.getMetaData('nothread') ||
+            this.viewport.getMetaData('sortlimit')) {
+            tmp.show().next().hide();
+            tmp.down().hide();
+        } else {
+            tmp.down().show();
+        }
+
+        m.childElements().invoke('removeClassName', 'sortup').invoke('removeClassName', 'sortdown');
+        m.down('div a[sortby=' + sortby + ']').up().addClassName(this.viewport.getMetaData('sortdir') ? 'sortup' : 'sortdown');
+    },
+
+    // Preview pane functions
+    togglePreviewPane: function()
+    {
+        this.showPreview = !this.showPreview;
+        $('previewtoggle').setText(this.showPreview ? DIMP.text.hide_preview : DIMP.text.show_preview);
+        [ $('msgList') ].invoke(this.showPreview ? 'removeClassName' : 'addClassName', 'msglistNoPreview');
+        new Ajax.Request(DimpCore.addSID(DIMP.conf.URI_PREFS), { parameters: { app: 'dimp', pref: 'show_preview', value: this.showPreview ? 1 : 0 } });
+        this.viewport.showSplitPane(this.showPreview);
+        if (this.showPreview) {
+            this.initPreviewPane();
+        }
+    },
+
+    _loadPreview: function(data)
+    {
+        var pp = $('previewPane'), pp_offset;
+        if (!pp.visible()) {
+            return;
+        }
+        if (this.pp &&
+            this.pp == data) {
+            return;
+        }
+        this.pp = data;
+
+        if (this.ppfifo.indexOf(data.vp_id) != -1) {
+            return this._loadPreviewCallback(this.ppcache[data.vp_id]);
+        }
+
+        pp_offset = pp.positionedOffset();
+        $('msgLoading').setStyle({ position: 'absolute', top: (pp_offset.top + 10) + 'px', left: (pp_offset.left + 10) + 'px' }).show();
+
+        DimpCore.doAction('ShowPreview', {}, this.viewport.createSelection('dataob', data), this.bcache.get('loadPC') || this.bcache.set('loadPC', this._loadPreviewCallback.bind(this)));
+    },
+
+    _loadPreviewCallback: function(resp)
+    {
+        var row, search, tmp, tmp2,
+            pm = $('previewMsg'),
+            r = resp.response,
+            t = $('msgHeadersContent').down('THEAD');
+
+        if (!r.error) {
+            search = this.viewport.getViewportSelection(r.view).search({ vp_id: { equal: [ r.uid ] } });
+            if (search.size()) {
+                row = search.get('dataob').first();
+                this.updateUnseenUID(row, 0);
+            }
+        }
+
+        if (this.pp &&
+            this.pp.vp_id != r.uid) {
+            return;
+        }
+
+        if (r.error || this.viewport.getSelected().size() != 1) {
+            if (r.error) {
+                DimpCore.showNotifications([ { type: r.errortype, message: r.error } ]);
+            }
+            this.clearPreviewPane();
+            return;
+        }
+
+        // Store in cache.
+        this._expirePPCache([ r.uid ]);
+        this.ppcache[r.uid] = resp;
+        this.ppfifo.push(r.uid);
+
+        DimpCore.removeAddressLinks(pm);
+
+        DIMP.conf.msg_index = r.index;
+        DIMP.conf.msg_folder = r.folder;
+
+        // Add subject/priority
+        tmp = pm.select('.subject');
+        tmp.invoke('update', r.subject);
+        switch (r.priority) {
+        case 'high':
+        case 'low':
+            tmp.invoke('insert', { top: $($(r.priority + '_priority_img').cloneNode(false)).writeAttribute('id', false) });
+            break;
+        }
+
+        // Add date
+        $('msgHeadersColl').select('.date').invoke('update', r.minidate);
+        $('msgHeaderDate').select('.date').invoke('update', r.fulldate);
+
+        // Add from/to/cc headers
+        [ 'from', 'to', 'cc' ].each(function(a) {
+            if (r[a]) {
+                (a == 'from' ? pm.select('.' + a) : [ t.down('.' + a) ]).each(function(elt) {
+                    elt.replace(DimpCore.buildAddressLinks(r[a], elt.cloneNode(false)));
+                });
+            }
+            [ $('msgHeader' + a.capitalize()) ].invoke(r[a] ? 'show' : 'hide');
+        });
+
+        // Add attachment information
+        $('toggleHeaders').select('.attachmentImage').invoke(r.atc_label ? 'show' : 'hide');
+        if (r.atc_label) {
+            tmp = $('msgAtc').show().down('.label');
+            tmp2 = $('partlist');
+            tmp2.hide().previous().update(r.atc_label + ' ' + r.atc_download);
+            if (r.atc_list) {
+                $('partlist_col').show();
+                $('partlist_exp').hide();
+                tmp.down().hide().next().show();
+                tmp2.update(r.atc_list);
+            } else {
+                tmp.down().show().next().hide();
+            }
+        } else {
+            $('msgAtc').hide();
+        }
+
+        $('msgBody').down().update(r.msgtext);
+        $('msgLoading', 'previewInfo').invoke('hide');
+        $('previewPane').scrollTop = 0;
+        pm.show();
+
+        if (r.js) {
+            eval(r.js.join(';'));
+        }
+        this._addHistory('msg:' + row.view + ':' + row.imapuid);
+    },
+
+    initPreviewPane: function()
+    {
+        var sel = this.viewport.getSelected();
+        if (sel.size() != 1) {
+            this.clearPreviewPane();
+        } else {
+            this._loadPreview(sel.get('dataob').first());
+        }
+    },
+
+    clearPreviewPane: function()
+    {
+        $('msgLoading', 'previewMsg').invoke('hide');
+        $('previewInfo').show();
+        this.pp = null;
+    },
+
+    _expirePPCache: function(ids)
+    {
+        this.ppfifo = this.ppfifo.without(ids);
+        ids.each(function(i) {
+            delete this.ppcache[i];
+        }, this);
+        // Preview pane cache size is 20 entries. Given that a reasonable guess
+        // of an average e-mail size is 10 KB (including headers), also make
+        // an estimate that the JSON data size will be approx. 10 KB. 200 KB
+        // should be a fairly safe caching value for any recent browser.
+        if (this.ppfifo.size() > 20) {
+            delete this.ppcache[this.ppfifo.shift()];
+        }
+    },
+
+    // Labeling functions
+    updateUnseenUID: function(r, setflag)
+    {
+        var sel, unseen, unseenset;
+        if (!r.bg) {
+            return false;
+        }
+        unseenset = r.bg.indexOf('unseen') != -1;
+        if ((setflag && unseenset) || (!setflag && !unseenset)) {
+            return false;
+        }
+
+        sel = this.viewport.createSelection('dataob', r);
+        unseen = parseInt($(this.getFolderId(r.view)).readAttribute('u'), 10);
+        if (setflag) {
+            this.viewport.updateFlag(sel, 'unseen', true);
+            ++unseen;
+        } else {
+            this.viewport.updateFlag(sel, 'unseen', false);
+            --unseen;
+        }
+
+        this.updateUnseenStatus(r.view, unseen);
+    },
+
+    updateUnseenStatus: function(mbox, unseen)
+    {
+        if (this.viewport) {
+            this.viewport.setMetaData({ unseen: unseen }, mbox);
+        }
+        this.setFolderLabel(mbox, unseen);
+
+        if (this.folder == mbox) {
+            this.updateTitle();
+        }
+    },
+
+    setMessageListTitle: function()
+    {
+        var offset,
+            rows = this.viewport.getMetaData('total_rows');
+        if (rows > 0) {
+            offset = this.viewport.currentOffset();
+            $('msgHeader').update(DIMP.text.messages + ' ' + (offset + 1) + ' - ' + (Math.min(offset + this.viewport.getPageSize(), rows)) + ' ' + DIMP.text.of + ' ' + rows);
+        } else {
+            $('msgHeader').update(DIMP.text.nomessages);
+        }
+    },
+
+    setFolderLabel: function(f, unseen)
+    {
+        var elt, fid = this.getFolderId(f);
+        elt = $(fid);
+        if (!elt || !elt.hasAttribute('u')) {
+            return;
+        }
+
+        unseen = parseInt(unseen);
+        elt.writeAttribute('u', unseen);
+
+        if (f == 'INBOX' && window.fluid) {
+            window.fluid.setDockBadge(unseen ? unseen : '');
+        }
+
+        $(fid + '_label').update((unseen > 0) ?
+            new Element('STRONG').insert(elt.readAttribute('l')).insert('&nbsp;').insert(new Element('SPAN', { className: 'count', dir: 'ltr' }).insert('(' + unseen + ')')) :
+            elt.readAttribute('l'));
+    },
+
+    getFolderId: function(f)
+    {
+        return 'fld' + decodeURIComponent(f).replace(/_/g,'__').replace(/\W/g, '_');
+    },
+
+    getSubFolderId: function(f)
+    {
+        return 'sub' + f;
+    },
+
+    /* Folder list updates. */
+    pollFolders: function()
+    {
+        // Reset poll folder counter.
+        this.setPollFolders();
+        var args = {};
+        if (this.folder && $('dimpmain_folder').visible()) {
+            args = this.viewport.addRequestParams({});
+        }
+        $('checkmaillink').down('A').update('[' + DIMP.text.check + ']');
+        DimpCore.doAction('PollFolders', args, null, this.bcache.get('pollFC') || this.bcache.set('pollFC', this._pollFoldersCallback.bind(this)));
+    },
+
+    _pollFoldersCallback: function(r)
+    {
+        var q, that;
+        r = r.response;
+        if (r.poll) {
+            that = this;
+            $H(r.poll).each(function(u) {
+                that.updateUnseenStatus(u.key, u.value);
+            });
+        }
+        if (r.quota) {
+            q = $('quota').cleanWhitespace();
+            q.setText(r.quota.m);
+            q.down('SPAN.used IMG').writeAttribute({ width: 99 - r.quota.p });
+        }
+        $('checkmaillink').down('A').update(DIMP.text.getmail);
+    },
+
+    setPollFolders: function()
+    {
+        if (DIMP.conf.refresh_time) {
+            if (this.pollPE) {
+                this.pollPE.stop();
+            }
+            // Don't cache - this code is only run once.
+            this.pollPE = new PeriodicalExecuter(this.pollFolders.bind(this), DIMP.conf.refresh_time);
+        }
+    },
+
+    _portalCallback: function(r)
+    {
+        if (r.response.linkTags) {
+            var head = $$('HEAD').first();
+            r.response.linkTags.each(function(newLink) {
+                var link = new Element('LINK', { type: 'text/css', rel: 'stylesheet', href: newLink.href });
+                if (newLink.media) {
+                    link.media = newLink.media;
+                }
+                head.insert(link);
+            });
+        }
+        $('dimpmain_portal').update(r.response.portal);
+
+        /* Link portal block headers to the application. */
+        $('dimpmain_portal').select('h1.header a').each(this.bcache.get('portalClkLink') || this.bcache.set('portalClkLink', function(d) {
+            d.observe('click', function(e, d) {
+                this.go('app:' + d.readAttribute('app'));
+                e.stop();
+            }.bindAsEventListener(this, d));
+        }.bind(this)));
+    },
+
+    /* Search filter functions. */
+    _searchfilterOnKeyup: function()
+    {
+        if (this.searchobserve) {
+            clearTimeout(this.searchobserve);
+        }
+        this.searchobserve = (this.bcache.get('searchfilterR') || this.bcache.set('searchfilterR', this.searchfilterRun.bind(this))).delay(0.5);
+    },
+
+    searchfilterRun: function()
+    {
+        if (!this.viewport.isFiltering()) {
+            this.filtertoggle = 1;
+            this.fspecial = this.viewport.getMetaData('special');
+        }
+        this.viewport.runFilter($F('msgList_filter'));
+    },
+
+    _searchfilterOnFocus: function()
+    {
+        var q = $('qoptions').up();
+
+        if ($('msgList_filter').hasClassName('msgFilterDefault')) {
+            this._setFilterText(false);
+        }
+
+        if (!q.visible()) {
+            $('sf_current').update(this.viewport.getMetaData('label'));
+            this._setSearchfilterParams(this.viewport.getMetaData('special') ? 'to' : 'from', 'msg');
+            this._setSearchfilterParams('current', 'folder');
+            $(document.documentElement).setStyle({ overflowY: 'hidden' });
+            Effect.SlideDown(q, { duration: 0.5, afterFinish: function() { this._onResize(false, true); $(document.documentElement).setStyle({ overflowY: 'auto' }); }.bind(this) });
+        }
+    },
+
+    _searchfilterOnBlur: function()
+    {
+        if (!$F('msgList_filter')) {
+            this._setFilterText(true);
+        }
+    },
+
+    // reset = (boolean) TODO
+    searchfilterClear: function(reset)
+    {
+        var q = $('qoptions').up();
+        if (!q.visible()) {
+            return;
+        }
+        if (this.searchobserve) {
+            clearTimeout(this.searchobserve);
+            this.searchobserve = null;
+        }
+        this._setFilterText(true);
+        Effect.SlideUp(q, { duration: 0.5, afterFinish: this._onResize.bind(this, reset) });
+        this.filtertoggle = 2;
+        this.resetSelected();
+        this.viewport.stopFilter(reset);
+    },
+
+    // d = (boolean) Deactivate filter input?
+    _setFilterText: function(d)
+    {
+        var mf = $('msgList_filter');
+        if (d) {
+            mf.setValue(DIMP.text.search);
+            mf.addClassName('msgFilterDefault');
+        } else {
+            mf.setValue('');
+            mf.removeClassName('msgFilterDefault');
+        }
+    },
+
+    // type = 'folder' or 'msg'
+    _setSearchfilterParams: function(id, type)
+    {
+        var c = (type == 'folder') ? this.sfiltersfolder : this.sfilters;
+        c.keys().each(function(i) {
+            $(i).writeAttribute('className', (id == c.get(i)) ? 'qselected' : '');
+        });
+    },
+
+    updateSearchfilter: function(id, type)
+    {
+        this._setSearchfilterParams(id, type);
+        if ($F('msgList_filter')) {
+            this.viewport.runFilter();
+        }
+    },
+
+    _addSearchfilterParams: function()
+    {
+        var sf = this.sfiltersfolder.keys().find(function(s) {
+                return $(s).hasClassName('qselected');
+            });
+        return { searchfolder: this.sfiltersfolder.get(sf), searchmsg: this.sfilters.get(this._getSearchfilterField()) };
+    },
+
+    _getSearchfilterField: function()
+    {
+        return this.sfilters.keys().find(function(s) {
+            return $(s).hasClassName('qselected');
+        });
+    },
+
+    /* Enable/Disable DIMP action buttons as needed. */
+    toggleButtons: function()
+    {
+        var disable = (this.selectedCount() == 0);
+        DimpCore.buttons.each(function(b) {
+            var elt = $(b);
+            if (elt) {
+                [ elt.up() ].invoke(disable ? 'addClassName' : 'removeClassName', 'disabled');
+                DimpCore.DMenu.disable(b + '_img', true, disable);
+            }
+        });
+    },
+
+    /* Drag/Drop handler. */
+    _folderDropHandler: function(drop, drag, e)
+    {
+        var dropbase, sel, uids,
+            foldername = drop.readAttribute('mbox'),
+            ftype = drop.readAttribute('ftype');
+
+        if (drag.hasClassName('folder')) {
+            dropbase = (drop == $('dropbase'));
+            if (dropbase ||
+                (ftype != 'special' && !this.isSubfolder(drag, drop))) {
+                DimpCore.doAction('RenameFolder', { old_name: drag.readAttribute('mbox'), new_parent: dropbase ? '' : foldername, new_name: drag.readAttribute('l') }, null, this.bcache.get('folderC') || this.bcache.set('folderC', this._folderCallback.bind(this)));
+            }
+        } else if (ftype != 'container') {
+            sel = this.viewport.getSelected();
+
+            if (sel.size()) {
+                // Dragging multiple selected messages.
+                uids = sel;
+            } else if (drag.readAttribute('mbox') != foldername) {
+                // Dragging a single unselected message.
+                uids = this.viewport.createSelection('domid', drag.id);
+            }
+
+            if (uids.size()) {
+                if (e.ctrlKey) {
+                    DimpCore.doAction('CopyMessage', this.viewport.addRequestParams({ tofld: foldername }), uids, this.bcache.get('pollFC') || this.bcache.set('pollFC', this._pollFoldersCallback.bind(this)));
+                } else if (this.folder != foldername) {
+                    // Don't allow drag/drop to the current folder.
+                    this.viewport.updateFlag(uids, 'deletedmsg', true);
+                    DimpCore.doAction('MoveMessage', this.viewport.addRequestParams({ tofld: foldername }), uids, this.bcache.get('deleteC') || this.bcache.set('deleteC', this._deleteCallback.bind(this)));
+                }
+            }
+        }
+    },
+
+    _dragCaption: function()
+    {
+        var cnt = this.selectedCount();
+        return cnt + ' ' + (cnt == 1 ? DIMP.text.message : DIMP.text.messages);
+    },
+
+    /* Keydown event handler */
+    _keydownHandler: function(e)
+    {
+        // Only catch keyboard shortcuts in message list view. Disable catching
+        // when in form elements or the RedBox overlay is visible.
+        if (!$('dimpmain_folder').visible() ||
+            e.findElement('FORM') ||
+            RedBox.overlayVisible()) {
+            return;
+        }
+
+        var co, ps, r, row, rowoff,
+            kc = e.keyCode || e.charCode,
+            sel = this.viewport.getSelected();
+
+        switch (kc) {
+        case Event.KEY_DELETE:
+        case Event.KEY_BACKSPACE:
+            if (sel.size() == 1) {
+                r = sel.get('dataob').first();
+                if (e.shiftKey) {
+                    this.moveSelected(r.rownum + ((r.rownum == this.viewport.getMetaData('total_rows')) ? -1 : 1), true);
+                }
+                this.flag('deleted', r);
+            } else {
+                this.flag('deleted');
+            }
+            e.stop();
+            break;
+
+        case Event.KEY_UP:
+        case Event.KEY_DOWN:
+            if (e.shiftKey && this.lastrow != -1) {
+                row = this.viewport.createSelection('rownum', this.lastrow + ((kc == Event.KEY_UP) ? -1 : 1));
+                if (row.size()) {
+                    row = row.get('dataob').first();
+                    this.viewport.scrollTo(row.rownum);
+                    this.msgSelect(row.domid, { shift: true });
+                }
+            } else {
+                this.moveSelected(kc == Event.KEY_UP ? -1 : 1);
+            }
+            e.stop();
+            break;
+
+        case Event.KEY_PAGEUP:
+        case Event.KEY_PAGEDOWN:
+            if (!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
+                ps = this.viewport.getPageSize() - 1;
+                move = ps * (kc == Event.KEY_PAGEUP ? -1 : 1);
+                if (sel.size() == 1) {
+                    co = this.viewport.currentOffset();
+                    rowoff = sel.get('rownum').first() - 1;
+                    switch (kc) {
+                    case Event.KEY_PAGEUP:
+                        if (co != rowoff) {
+                            move = co - rowoff;
+                        }
+                        break;
+
+                    case Event.KEY_PAGEDOWN:
+                        if ((co + ps) != rowoff) {
+                            move = co + ps - rowoff;
+                        }
+                        break;
+                    }
+                }
+                this.moveSelected(move);
+                e.stop();
+            }
+            break;
+
+        case Event.KEY_HOME:
+        case Event.KEY_END:
+            this.moveSelected(kc == Event.KEY_HOME ? 1 : this.viewport.getMetaData('total_rows'), true);
+            e.stop();
+            break;
+
+        case Event.KEY_RETURN:
+            if (!e.element().match('input')) {
+                // Popup message window if single message is selected.
+                if (sel.size() == 1) {
+                    this.msgWindow(sel.get('dataob').first());
+                }
+            }
+            e.stop();
+            break;
+
+        case 65: // A
+        case 97: // a
+            if (e.ctrlKey) {
+                this.selectAll();
+                e.stop();
+            }
+            break;
+        }
+    },
+
+    /* Handle rename folder actions. */
+    renameFolder: function(folder)
+    {
+        if (Object.isUndefined(folder)) {
+            return;
+        }
+
+        folder = $(folder);
+        var n = this._createFolderForm(function(e) { this._folderAction(folder, e, 'rename'); return false; }.bindAsEventListener(this), DIMP.text.rename_prompt);
+        n.down('input').setValue(folder.readAttribute('l'));
+    },
+
+    /* Handle insert folder actions. */
+    createBaseFolder: function()
+    {
+        this._createFolderForm(function(e) { this._folderAction('', e, 'create'); return false; }.bindAsEventListener(this), DIMP.text.create_prompt);
+    },
+
+    createSubFolder: function(folder)
+    {
+        if (Object.isUndefined(folder)) {
+            return false;
+        }
+
+        this._createFolderForm(function(e) { this._folderAction($(folder), e, 'createsub'); return false; }.bindAsEventListener(this), DIMP.text.createsub_prompt);
+    },
+
+    _createFolderForm: function(action, text)
+    {
+        var n = new Element('FORM', { action: '#', id: 'RB_folder' }).insert(
+                    new Element('P').insert(text)
+                ).insert(
+                    new Element('INPUT', { type: 'text', size: 15 })
+                ).insert(
+                    new Element('INPUT', { type: 'button', className: 'button', value: DIMP.text.ok }).observe('click', action)
+                ).insert(
+                    new Element('INPUT', { type: 'button', className: 'button', value: DIMP.text.cancel }).observe('click', this.bcache.get('closeRB') || this.bcache.set('closeRB', this._closeRedBox.bind(this)))
+                ).observe('keydown', function(e) { if ((e.keyCode || e.charCode) == Event.KEY_RETURN) { e.stop(); action(e); } });
+
+        RedBox.overlay = true;
+        RedBox.onDisplay = Form.focusFirstElement.curry(n);
+        RedBox.showHtml(n);
+        return n;
+    },
+
+    _closeRedBox: function()
+    {
+        var c = RedBox.getWindowContents();
+        DimpCore.addGC([ c, c.descendants() ].flatten());
+        RedBox.close();
+    },
+
+    _folderAction: function(folder, e, mode)
+    {
+        this._closeRedBox();
+
+        var action, params, val,
+            form = e.findElement('form');
+        val = $F(form.down('input'));
+
+        if (val) {
+            switch (mode) {
+            case 'rename':
+                if (folder.readAttribute('l') != val) {
+                    action = 'RenameFolder';
+                    params = { old_name: folder.readAttribute('mbox'),
+                               new_parent: folder.up().hasClassName('folderlist') ? '' : folder.up(1).previous().readAttribute('mbox'),
+                               new_name: val };
+                }
+                break;
+
+            case 'create':
+            case 'createsub':
+                action = 'CreateFolder';
+                params = { folder: val };
+                if (mode == 'createsub') {
+                    params.parent = folder.readAttribute('mbox');
+                }
+                break;
+            }
+            if (action) {
+                DimpCore.doAction(action, params, null, this.bcache.get('folderC') || this.bcache.set('folderC', this._folderCallback.bind(this)));
+            }
+        }
+    },
+
+    /* Folder action callback functions. */
+    _folderCallback: function(r)
+    {
+        r = r.response;
+        if (r.d) {
+            r.d.each(this.bcache.get('deleteFolder') || this.bcache.set('deleteFolder', this.deleteFolder.bind(this)));
+        }
+        if (r.c) {
+            r.c.each(this.bcache.get('changeFolder') || this.bcache.set('changeFolder', this.changeFolder.bind(this)));
+        }
+        if (r.a) {
+            r.a.each(this.bcache.get('createFolder') || this.bcache.set('createFolder', this.createFolder.bind(this)));
+        }
+    },
+
+    _deleteCallback: function(r)
+    {
+        var search, uids = [];
+
+        this.msgListLoading(false);
+        this._pollFoldersCallback(r);
+
+        r = r.response;
+        if (!r.uids || r.folder != this.folder) {
+            return;
+        }
+
+        // Need to convert uid list to listing of unique viewport IDs since
+        // we may be dealing with multiple mailboxes (i.e. virtual folders)
+        $H(DimpCore.parseRangeString(r.uids)).each(function(pair) {
+            pair.value.each(function(v) {
+                uids.push(v + pair.key);
+            });
+        });
+
+        search = this.viewport.getViewportSelection().search({ vp_id: { equal: uids } });
+        if (search.size()) {
+            if (r.remove) {
+                this.viewport.remove(search, { cacheid: r.cacheid, noupdate: r.viewport });
+                this._expirePPCache(search.get('uid'));
+            } else {
+                // Need this to catch spam deletions.
+                this.viewport.updateFlag(search, 'deletedmsg', true);
+            }
+        }
+    },
+
+    _emptyFolderCallback: function(r)
+    {
+        if (r.response.mbox) {
+            if (this.folder == r.response.mbox) {
+                this.viewport.reload();
+                this.clearPreviewPane();
+            }
+            this.setFolderLabel(r.response.mbox, 0);
+        }
+    },
+
+    _flagAllCallback: function(r)
+    {
+        if (r.response.mbox) {
+            this.setFolderLabel(r.response.mbox, r.response.u);
+        }
+    },
+
+    _folderLoadCallback: function(r)
+    {
+        this._folderCallback(r);
+
+        var elts = $('specialfolders', 'normalfolders').compact(),
+            nf = $('normalfolders'),
+            nfheight = nf.getStyle('max-height');
+
+        elts.invoke('observe', 'click', this._handleFolderMouseEvent.bindAsEventListener(this, 'click'));
+        elts.invoke('observe', 'mouseover', this._handleFolderMouseEvent.bindAsEventListener(this, 'over'));
+        if (DIMP.conf.is_ie6) {
+            elts.invoke('observe', 'mouseout', this._handleFolderMouseEvent.bindAsEventListener(this, 'out'));
+        }
+
+        $('foldersLoading').hide();
+        $('foldersSidebar').show();
+
+        // Fix for IE6 - which doesn't support max-height.  We need to search
+        // for height: 0px instead (comment in IE 6 CSS explains this is
+        // needed for auto sizing).
+        if (nfheight !== null ||
+            (Prototype.Browser.IE &&
+             Object.isUndefined(nfheight) &&
+             (nf.getStyle('height') == '0px'))) {
+            this._sizeFolderlist();
+            Event.observe(window, 'resize', this._sizeFolderlist.bind(this));
+        }
+    },
+
+    _handleFolderMouseEvent: function(e, action)
+    {
+        var type,
+            elt = e.element(),
+            li = elt.up('.folder') || elt.up('.custom');
+        if (!li) {
+            return;
+        }
+        type = li.readAttribute('ftype');
+
+        switch (action) {
+        case 'over':
+            if (DIMP.conf.is_ie6) {
+                li.addClassName('over');
+            }
+            if (type && !this.mo_sidebar[li.id]) {
+                DimpCore.addMouseEvents({ id: li.id, type: type });
+                this.mo_sidebar[li.id] = 1;
+            }
+            break;
+
+        case 'out':
+            li.removeClassName('over');
+            break;
+
+        case 'click':
+            if (elt.hasClassName('exp') || elt.hasClassName('col')) {
+                this._toggleSubFolder(li.id, 'tog');
+            } else {
+                switch (type) {
+                case 'container':
+                case 'vcontainer':
+                    e.stop();
+                    break;
+
+                case 'folder':
+                case 'special':
+                case 'virtual':
+                    e.stop();
+                    return this.go('folder:' + li.readAttribute('mbox'));
+                    break;
+                }
+            }
+            break;
+        }
+    },
+
+    _toggleSubFolder: function(base, mode)
+    {
+        base = $(base);
+        var opts = { duration: 0.2 },
+            s = $(this.getSubFolderId(base.id));
+        if (s &&
+            (mode == 'tog' ||
+             (mode == 'exp' && !s.visible()) ||
+             (mode == 'col' && s.visible()))) {
+            if (base.descendantOf('specialfolders')) {
+                opts.afterFinish = this._sizeFolderlist;
+            }
+            base.firstDescendant().writeAttribute({ className: s.visible() ? 'exp' : 'col' });
+            if (s.visible()) {
+                Effect.BlindUp(s, opts);
+            } else {
+                Effect.BlindDown(s, opts);
+            }
+        }
+    },
+
+    // Folder actions.
+    // For format of the ob object, see DIMP::_createFolderElt().
+    createFolder: function(ob)
+    {
+        var div, f_node, li, ll, parent_e,
+            fid = this.getFolderId(ob.m),
+            mbox = decodeURIComponent(ob.m),
+            submboxid = this.getSubFolderId(fid),
+            submbox = $(submboxid);
+
+        li = new Element('LI', { className: 'folder', id: fid, l: ob.l, mbox: mbox, ftype: (ob.v ? (ob.co ? 'vcontainer' : 'virtual') : (ob.co ? 'container' : (ob.s ? 'special' : 'folder'))) });
+
+        div = new Element('DIV', { className: ob.cl || 'base', id: fid + '_div' });
+        if (ob.i) {
+            div.update(ob.i);
+        }
+        if (ob.ch) {
+            div.writeAttribute({ className: 'exp' }).observe('mouseover', this.bcache.get('mo_folder') || this.bcache.set('mo_folder', function(e) {
+                e = e.element();
+                if (DragDrop.Drags.drag && e.hasClassName('exp')) {
+                    this._toggleSubFolder(e.up(), 'exp');
+                }
+            }.bindAsEventListener(this)));
+        }
+
+        li.insert(div).insert(new Element('A', { id: fid + '_label', title: ob.l }).insert(ob.l));
+
+        // Now walk through the parent <ul> to find the right place to
+        // insert the new folder.
+        if (submbox) {
+            if (submbox.insert({ before: li }).visible()) {
+                // If an expanded parent mailbox was deleted, we need to toggle
+                // the icon accordingly.
+                div.removeClassName('exp').addClassName('col');
+            }
+        } else {
+            if (ob.s) {
+                parent_e = $('specialfolders');
+            } else {
+                parent_e = $(this.getSubFolderId(this.getFolderId(ob.pa)));
+                parent_e = (parent_e) ? parent_e.down('UL') : $('normalfolders');
+            }
+
+            ll = mbox.toLowerCase();
+            f_node = parent_e.childElements().find(function(node) {
+                var nodembox = node.readAttribute('mbox');
+                return nodembox &&
+                       (!ob.s || nodembox != 'INBOX') &&
+                       (ll < nodembox.toLowerCase());
+            });
+
+            if (f_node) {
+                f_node.insert({ before: li });
+            } else {
+                parent_e.insert(li);
+            }
+
+            // Make sure the sub<mbox> ul is created if necessary.
+            if (ob.ch) {
+                li.insert({ after: new Element('LI', { className: 'subfolders', id: submboxid }).insert(new Element('UL')).hide() });
+            }
+        }
+
+        // Make the new folder a drop target.
+        if (!ob.v) {
+            new Drop(li, this._folderDropConfig);
+        }
+
+        // Check for unseen messages
+        if (ob.po) {
+            li.writeAttribute('u', '');
+            this.setFolderLabel(mbox, ob.u);
+        }
+    },
+
+    deleteFolder: function(folder)
+    {
+        var f = decodeURIComponent(folder), fid;
+        if (this.folder == f) {
+            this.go('folder:INBOX');
+        }
+
+        fid = this.getFolderId(folder);
+        this.deleteFolderElt(fid, true);
+
+        Effect.Fade(fid, { afterFinish: function(effect) {
+            try {
+                DimpCore.addGC(effect.element.remove());
+            } catch (e) {
+                DimpCore.debug('deleteFolder', e);
+            }
+        } });
+    },
+
+    changeFolder: function(ob)
+    {
+        var fid = this.getFolderId(ob.m),
+            fdiv = $(fid + '_div'),
+            oldexpand = fdiv && fdiv.hasClassName('col');
+        this.deleteFolderElt(fid, !ob.ch);
+        if (ob.co && this.folder == ob.m) {
+            this.go('folder:INBOX');
+        }
+        $(fid).remove();
+        this.createFolder(ob);
+        if (ob.ch && oldexpand) {
+            fdiv.removeClassName('exp').addClassName('col');
+        }
+    },
+
+    deleteFolderElt: function(fid, sub)
+    {
+        var f = $(fid), submbox;
+        DimpCore.addGC($(f, fid + '_div', fid + '_label'));
+        if (sub) {
+            submbox = $(this.getSubFolderId(fid));
+            if (submbox) {
+                submbox.remove();
+            }
+        }
+        [ DragDrop.Drags.get_drag(fid), DragDrop.Drops.get_drop(fid) ].compact().invoke('destroy');
+        DimpCore.removeMouseEvents(f);
+        delete this.mo_sidebar[fid];
+        if (this.viewport) {
+            this.viewport.deleteView(fid);
+        }
+    },
+
+    _sizeFolderlist: function()
+    {
+        var nf = $('normalfolders');
+        nf.setStyle({ height: (document.viewport.getHeight() - nf.cumulativeOffset()[1] - 10) + 'px' });
+    },
+
+    /* Flag actions for message list. */
+    flag: function(action, index, folder)
+    {
+        var actionCall, args, vs,
+            obs = [],
+            unseenstatus = 1;
+
+        if (index) {
+            if (Object.isUndefined(folder)) {
+                vs = this.viewport.createSelection('dataob', index);
+            } else {
+                vs = this.viewport.getViewportSelection().search({ imapuid: { equal: [ index ] }, view: { equal: [ folder ] } });
+                if (!vs.size() && folder != this.folder) {
+                    vs = this.viewport.getViewportSelection(folder).search({ imapuid: { equal: [ index ] } });
+                }
+            }
+        } else {
+            vs = this.viewport.getSelected();
+        }
+
+        switch (action) {
+        case 'allUnseen':
+        case 'allSeen':
+            DimpCore.doAction((action == 'allUnseen') ? 'MarkFolderUnseen' : 'MarkFolderSeen', { folder: folder }, null, this.bcache.get('flagAC') || this.bcache.set('flagAC', this._flagAllCallback.bind(this)));
+            if (folder == this.folder) {
+                this.viewport.updateFlag(this.createSelection('rownum', $A($R(1, this.viewport.getMetaData('total_rows')))), 'unseen', action == 'allUnseen');
+            }
+            break;
+
+        case 'deleted':
+        case 'undeleted':
+        case 'spam':
+        case 'ham':
+        case 'blacklist':
+        case 'whitelist':
+            if (!vs.size()) {
+                break;
+            }
+
+            // Make sure that any given row is not deleted more than once.
+            // Need to explicitly mark here because message may already be
+            // flagged deleted when we load page (i.e. switching to using
+            // trash folder).
+            if (action == 'deleted') {
+                vs = vs.search({ isdel: { not: [ true ] } });
+                if (!vs.size()) {
+                    break;
+                }
+                vs.set({ isdel: true });
+            }
+
+            args = this.viewport.addRequestParams({});
+            if (action == 'deleted' || action == 'undeleted') {
+                this.viewport.updateFlag(vs, 'deletedmsg', action == 'deleted');            }
+
+            if (action == 'undeleted') {
+                DimpCore.doAction('UndeleteMessage', args, vs);
+            } else {
+                actionCall = { deleted: 'DeleteMessage', spam: 'ReportSpam', ham: 'ReportHam', blacklist: 'Blacklist', whitelist: 'Whitelist' };
+                // This needs to be synchronous Ajax if we are calling from a
+                // popup window because Mozilla will not correctly call the
+                // callback function if the calling window has been closed.
+                DimpCore.doAction(actionCall[action], args, vs, this.bcache.get('deleteC') || this.bcache.set('deleteC', this._deleteCallback.bind(this)), { asynchronous: !(index && folder) });
+
+                // If reporting spam, to indicate to the user that something is
+                // happening (since spam reporting may not be instantaneous).
+                if (action == 'spam' || action == 'ham') {
+                    this.msgListLoading(true);
+                }
+            }
+            break;
+
+        case 'unseen':
+        case 'seen':
+            if (!vs.size()) {
+                break;
+            }
+            args = { folder: this.folder, messageFlag: '-seen' };
+            if (action == 'seen') {
+                unseenstatus = 0;
+                args.messageFlag = 'seen';
+            }
+            obs = vs.get('dataob');
+            if (obs.size()) {
+                obs.each(function(s) {
+                    this.updateUnseenUID(s, unseenstatus);
+                }, this);
+                DimpCore.doAction('MarkMessage', args, this.viewport.createSelection('dataob', obs));
+            }
+            break;
+
+        case 'flagged':
+        case 'clear':
+            if (!vs.size()) {
+                break;
+            }
+            args = {
+                folder: this.folder,
+                messageFlag: ((action == 'flagged') ? 'flagged' : '-flagged')
+            };
+            this.viewport.updateFlag(vs, 'flagged', action == 'flagged');
+            DimpCore.doAction('MarkMessage', args, vs);
+            break;
+
+        case 'answered':
+            this.viewport.updateFlag(vs, 'answered', true);
+            this.viewport.updateFlag(vs, 'flagged', false);
+            break;
+        }
+    },
+
+    /* Miscellaneous folder actions. */
+    purgeDeleted: function()
+    {
+        DimpCore.doAction('PurgeDeleted', this.viewport.addRequestParams({}), null, this.bcache.get('deleteC') || this.bcache.set('deleteC', this._deleteCallback.bind(this)));
+    },
+
+    modifyPollFolder: function(folder, add)
+    {
+        DimpCore.doAction('ModifyPollFolder', { folder: folder, add: (add) ? 1 : 0 }, null, this.bcache.get('modifyPFC') || this.bcache.set('modifyPFC', this._modifyPollFolderCallback.bind(this)));
+    },
+
+    _modifyPollFolderCallback: function(r)
+    {
+        r = r.response;
+        var f = r.folder, fid, p = { response: { poll: {} } };
+        fid = $(this.getFolderId(f));
+
+        if (r.add) {
+            p.response.poll[f] = r.poll.u;
+            fid.writeAttribute('u', 0);
+        } else {
+            p.response.poll[f] = 0;
+        }
+
+        this._pollFoldersCallback(p);
+
+        if (!r.add) {
+            fid.removeAttribute('u');
+        }
+    },
+
+    msgListLoading: function(show)
+    {
+        var ml_offset;
+
+        if (this.fl_visible != show) {
+            this.fl_visible = show;
+            if (show) {
+                ml_offset = $('msgList').positionedOffset();
+                $('folderLoading').setStyle({ position: 'absolute', top: (ml_offset.top + 10) + 'px', left: (ml_offset.left + 10) + 'px' });
+                Effect.Appear('folderLoading', { duration: 0.2 });
+                $(document.body).setStyle({ cursor: 'progress' });
+            } else {
+                Effect.Fade('folderLoading', { duration: 0.2 });
+                $(document.body).setStyle({ cursor: 'default' });
+            }
+        }
+    },
+
+    // p = (element) Parent element
+    // c = (element) Child element
+    isSubfolder: function(p, c)
+    {
+        var sf = $(this.getSubFolderId(p.readAttribute('id')));
+        return sf && c.descendantOf(sf);
+    },
+
+    /* Onload function. */
+    _onLoad: function() {
+        var tmp,
+             C = DimpCore.clickObserveHandler,
+             dmenu = DimpCore.DMenu;
+
+        if (Horde.dhtmlHistory.initialize()) {
+            Horde.dhtmlHistory.addListener(this.go.bind(this));
+        }
+
+        /* Initialize the starting page if necessary. addListener() will have
+         * already fired if there is a current location so only do a go()
+         * call if there is no current location. */
+        if (!Horde.dhtmlHistory.getCurrentLocation()) {
+            if (DIMP.conf.login_view == 'inbox') {
+                this.go('folder:INBOX');
+            } else {
+                this.go('portal');
+                if (DIMP.conf.background_inbox) {
+                    this.loadFolder('INBOX', true);
+                }
+            }
+        }
+
+        this._setFilterText(true);
+
+        /* Add popdown menus. */
+        DimpCore.addPopdown('button_reply', 'reply');
+        dmenu.disable('button_reply_img', true, true);
+        DimpCore.addPopdown('button_forward', 'forward');
+        dmenu.disable('button_forward_img', true, true);
+        DimpCore.addPopdown('button_other', 'otheractions');
+
+        /* Set up click event observers for elements on main page. */
+        tmp = $('logo');
+        if (tmp.visible()) {
+            C({ d: tmp.down('a'), f: this.go.bind(this, 'portal') });
+        }
+
+        C({ d: $('composelink'), f: DimpCore.compose.bind(DimpCore, 'new') });
+        C({ d: $('checkmaillink'), f: this.pollFolders.bind(this) });
+
+        [ 'portal', 'options' ].each(function(a) {
+            var d = $('app' + a);
+            if (d) {
+                C({ d: d, f: this.go.bind(this, a) });
+            }
+        }, this);
+        tmp = $('applogout');
+        if (tmp) {
+            C({ d: tmp, f: DimpCore.logout.bind(DimpCore) });
+        }
+
+        tmp = $('applicationfolders');
+        if (tmp) {
+            tmp.select('li.custom a').each(function(s) {
+                C({ d: s, f: this.go.bind(this, 'app:' + s.readAttribute('app')) });
+            }, this);
+        }
+
+        C({ d: $('newfolder'), f: this.createBaseFolder.bind(this) });
+        new Drop('dropbase', this._folderDropConfig);
+        tmp = $('hometab');
+        if (tmp) {
+            C({ d: tmp, f: this.go.bind(this, 'portal') });
+        }
+        $('tabbar').select('a.applicationtab').each(function(a) {
+            C({ d: a, f: this.go.bind(this, 'app:' + a.readAttribute('app')) });
+        }, this);
+        C({ d: $('button_reply'), f: this.composeMailbox.bind(this, 'reply'), ns: true });
+        C({ d: $('button_forward'), f: this.composeMailbox.bind(this, DIMP.conf.forward_default), ns: true });
+        [ 'spam', 'ham', 'deleted' ].each(function(a) {
+            var d = $('button_' + a);
+            if (d) {
+                C({ d: d, f: this.flag.bind(this, a) });
+            }
+        }, this);
+        C({ d: $('button_compose').down('A'), f: DimpCore.compose.bind(DimpCore, 'new') });
+        C({ d: $('button_other'), f: function(e) { dmenu.trigger(e.findElement('A').next(), true); }, p: true });
+        C({ d: $('qoptions').down('.qclose a'), f: this.searchfilterClear.bind(this, false) });
+        [ 'all', 'current' ].each(function(a) {
+            var d = $('sf_' + a);
+            if (d) {
+                C({ d: d, f: this.updateSearchfilter.bind(this, a, 'folder') });
+            }
+        }, this);
+        [ 'msgall', 'from', 'to', 'subject' ].each(function(a) {
+            C({ d: $('sf_' + a), f: this.updateSearchfilter.bind(this, a, 'msg') });
+        }, this);
+        C({ d: $('msglistHeader'), f: this.sort.bind(this), p: true });
+        C({ d: $('ctx_folder_create'), f: function() { this.createSubFolder(dmenu.element()); }.bind(this), ns: true });
+        C({ d: $('ctx_folder_rename'), f: function() { this.renameFolder(dmenu.element()); }.bind(this), ns: true });
+        C({ d: $('ctx_folder_empty'), f: function() { var mbox = dmenu.element().readAttribute('mbox'); dmenu.close(true); if (window.confirm(DIMP.text.empty_folder)) { DimpCore.doAction('EmptyFolder', { folder: mbox }, null, this._emptyFolderCallback.bind(this)); } }.bind(this), ns: true });
+        C({ d: $('ctx_folder_delete'), f: function() { var mbox = dmenu.element().readAttribute('mbox'); dmenu.close(true); if (window.confirm(DIMP.text.delete_folder)) { DimpCore.doAction('DeleteFolder', { folder: mbox }, null, this.bcache.get('folderC') || this.bcache.set('folderC', this._folderCallback.bind(this))); } }.bind(this), ns: true });
+        [ 'ctx_folder_seen', 'ctx_folder_unseen' ].each(function(a) {
+            C({ d: $(a), f: function(type) { this.flag(type, null, dmenu.element().readAttribute('mbox')); }.bind(this, a == 'ctx_folder_seen' ? 'allSeen' : 'allUnseen'), ns: true });
+        }, this);
+        [ 'ctx_folder_poll', 'ctx_folder_nopoll' ].each(function(a) {
+            C({ d: $(a), f: function(modify) { this.modifyPollFolder(dmenu.element().readAttribute('mbox'), modify); }.bind(this, a == 'ctx_folder_poll'), ns: true });
+        }, this);
+        C({ d: $('ctx_container_create'), f: function() { this.createSubFolder(dmenu.element()); }.bind(this), ns: true });
+        C({ d: $('ctx_container_rename'), f: function() { this.renameFolder(dmenu.element()); }.bind(this), ns: true });
+        [ 'reply', 'reply_all', 'reply_list', 'forward_all', 'forward_body', 'forward_attachments' ].each(function(a) {
+            C({ d: $('ctx_message_' + a), f: this.composeMailbox.bind(this, a), ns: true });
+        }, this);
+        [ 'seen', 'unseen', 'flagged', 'clear', 'spam', 'ham', 'blacklist', 'whitelist', 'deleted', 'undeleted' ].each(function(a) {
+            var d = $('ctx_message_' + a);
+            if (d) {
+                C({ d: d, f: this.flag.bind(this, a), ns: true });
+            }
+        }, this);
+        C({ d: $('ctx_draft_resume'), f: this.composeMailbox.bind(this, 'resume') });
+        [ 'flagged', 'clear', 'deleted', 'undeleted' ].each(function(a) {
+            var d = $('ctx_draft_' + a);
+            if (d) {
+                C({ d: d, f: this.flag.bind(this, a), ns: true });
+            }
+        }, this);
+        [ 'reply', 'reply_all', 'reply_list' ].each(function(a) {
+            C({ d: $('ctx_reply_' + a), f: this.composeMailbox.bind(this, a), ns: true });
+        }, this);
+        [ 'forward_all', 'forward_body', 'forward_attachments' ].each(function(a) {
+            C({ d: $('ctx_forward_' + a), f: this.composeMailbox.bind(this, a), ns: true });
+        }, this);
+        C({ d: $('previewtoggle'), f: this.togglePreviewPane.bind(this), ns: true });
+        [ 'seen', 'unseen', 'flagged', 'clear', 'blacklist', 'whitelist' ].each(function(a) {
+            var d = $('oa_' + a);
+            if (d) {
+                C({ d: d, f: this.flag.bind(this, a), ns: true });
+            }
+        }, this);
+        C({ d: $('oa_selectall'), f: this.selectAll.bind(this), ns: true });
+
+        tmp = $('oa_purge_deleted');
+        if (tmp) {
+            C({ d: tmp, f: this.purgeDeleted.bind(this), ns: true });
+        }
+
+        $('toggleHeaders').select('A').each(function(a) {
+            C({ d: a, f: function() { [ a.up().select('A'), $('msgHeadersColl', 'msgHeaders') ].flatten().invoke('toggle'); }, ns: true });
+        });
+        $('msg_newwin', 'msg_newwin_options').compact().each(function(a) {
+            C({ d: a, f: function() { this.msgWindow(this.viewport.getViewportSelection().search({ imapuid: { equal: [ DIMP.conf.msg_index ] } , view: { equal: [ DIMP.conf.msg_folder ] } }).get('dataob').first()); }.bind(this) });
+        }, this);
+
+        DimpCore.messageOnLoad();
+        this._resizeIE6();
+    },
+
+    // IE 6 width fixes (See Bug #6793)
+    _resizeIE6: function()
+    {
+        if (DIMP.conf.is_ie6) {
+            var tmp = parseInt($('sidebarPanel').getStyle('width'), 10),
+                tmp1 = document.viewport.getWidth() - tmp - 30;
+            $('normalfolders').setStyle({ width: tmp + 'px' });
+            $('dimpmain').setStyle({ width: tmp1 + 'px' });
+            $('msglist').setStyle({ width: (tmp1 - 5) + 'px' });
+            $('msgBody').setStyle({ width: (tmp1 - 25) + 'px' });
+            tmp = $('dimpmain_portal').down('IFRAME');
+            if (tmp) {
+                this._resizeIE6Iframe(tmp);
+            }
+        }
+    },
+
+    _resizeIE6Iframe: function(iframe)
+    {
+        if (DIMP.conf.is_ie6) {
+            iframe.setStyle({ width: $('dimpmain').getStyle('width'), height: (document.viewport.getHeight() - 20) + 'px' });
+        }
+    }
+};
+
+/* Need to add after DimpBase is defined. */
+DimpBase._msgDragConfig = {
+    scroll: 'normalfolders',
+    threshold: 5,
+    caption: DimpBase._dragCaption.bind(DimpBase),
+    onStart: function(d, e) {
+        var args = { right: e.isRightClick() },
+            id = d.element.id;
+
+        d.selectIfNoDrag = false;
+
+        // Handle selection first.
+        if (!args.right && (e.ctrlKey || e.metaKey)) {
+            this.msgSelect(id, $H({ ctrl: true }).merge(args).toObject());
+        } else if (e.shiftKey) {
+            this.msgSelect(id, $H({ shift: true }).merge(args).toObject());
+        } else if (this.isSelected('domid', id)) {
+            if (!args.right && this.selectedCount()) {
+                d.selectIfNoDrag = true;
+            }
+        } else {
+            this.msgSelect(id, args);
+        }
+    }.bind(DimpBase),
+    onEnd: function(d, e) {
+        if (d.selectIfNoDrag && !d.wasDragged) {
+            this.msgSelect(d.element.id, { right: e.isRightClick() });
+        }
+    }.bind(DimpBase)
+};
+
+DimpBase._folderDragConfig = {
+    ghosting: true,
+    offset: { x: 5, y: 5 },
+    scroll: 'normalfolders',
+    threshold: 5,
+    onDrag: function(d, e) {
+        if (!d.wasDragged) {
+            $('newfolder').hide();
+            $('dropbase').show();
+            d.ghost.removeClassName('on');
+        }
+    },
+    onEnd: function(d, e) {
+        if (d.wasDragged) {
+            $('newfolder').show();
+            $('dropbase').hide();
+        }
+    }
+};
+
+DimpBase._folderDropConfig = {
+    hoverclass: 'dragdrop',
+    caption: function(drop, drag, e) {
+        var m,
+            d = drag.readAttribute('l'),
+            ftype = drop.readAttribute('ftype'),
+            l = drop.readAttribute('l');
+
+        if (drop == $('dropbase')) {
+            return DIMP.text.moveto.replace(/%s/, d).replace(/%s/, DIMP.text.baselevel);
+        } else {
+            m = (e.ctrlKey) ? DIMP.text.copyto : DIMP.text.moveto;
+            if (drag.hasClassName('folder')) {
+                return (ftype != 'special' && !this.isSubfolder(drag, drop)) ? m.replace(/%s/, d).replace(/%s/, l) : '';
+            } else {
+                return ftype != 'container' ? m.replace(/%s/, this._dragCaption()).replace(/%s/, l) : '';
+            }
+        }
+    }.bind(DimpBase),
+    onDrop: DimpBase._folderDropHandler.bind(DimpBase)
+};
+
+/* Stuff to do immediately when page is ready. */
+document.observe('dom:loaded', function() {
+    $('dimpLoading').hide();
+    $('dimpPage').show();
+
+    /* Create the folder list. Any pending notifications will be caught via
+     * the return from this call. */
+    DimpCore.doAction('ListFolders', {}, null, DimpBase._folderLoadCallback.bind(DimpBase));
+
+    /* Start message list loading as soon as possible. */
+    DimpBase._onLoad();
+
+    /* Remove unneeded search folders. */
+    if (!DIMP.conf.search_all) {
+        DimpBase.sfiltersfolder.unset('sf_all');
+    }
+
+    /* Check for new mail. */
+    DimpBase.setPollFolders();
+
+    /* Bind key shortcuts. */
+    document.observe('keydown', DimpBase._keydownHandler.bind(DimpBase));
+
+    /* Resize elements on window size change. */
+    Event.observe(window, 'resize', DimpBase._onResize.bind(DimpBase));
+
+    if (DIMP.conf.is_ie6) {
+        /* Disable text selection in preview pane for IE 6. */
+        document.observe('selectstart', Event.stop);
+
+        /* Since IE 6 doesn't support hover over non-links, use javascript
+         * events to replicate mouseover CSS behavior. */
+        $('dimpbarActions', 'serviceActions', 'applicationfolders').compact().invoke('select', 'LI').flatten().compact().each(function(e) {
+            e.observe('mouseover', e.addClassName.curry('over')).observe('mouseout', e.removeClassName.curry('over'));
+        });
+    }
+});
+
+/* Need to register a callback function for doAction to catch viewport
+ * information returned from the server. */
+DimpCore.onDoActionComplete = function(r) {
+    if (DimpBase.viewport && r.response.viewport) {
+        DimpBase.viewport.ajaxResponse(r.response.viewport);
+    }
+};
+
+/* Extend these functions from DimpCore since additional processing needs to
+ * be done re: drag/drop and menu manipulation. */
+DimpCore.addMouseEvents = DimpCore.addMouseEvents.wrap(DimpBase._addMouseEvents.bind(DimpBase));
+DimpCore.removeMouseEvents = DimpCore.removeMouseEvents.wrap(DimpBase._removeMouseEvents.bind(DimpBase));
diff --git a/imp/js/src/DimpCore.js b/imp/js/src/DimpCore.js
new file mode 100644 (file)
index 0000000..f5121fe
--- /dev/null
@@ -0,0 +1,650 @@
+/**
+ * DimpCore.js - Dimp UI application logic.
+ * NOTE: ContextSensitive.js must be loaded before this file.
+ *
+ * $Horde: dimp/js/src/DimpCore.js,v 1.470 2008/09/30 20:14:57 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+/* Trick some Horde js into thinking this is the parent Horde window. */
+var frames = { horde_main: true },
+
+/* DimpCore object. */
+DimpCore = {
+    // Vars used and defaulting to null/false:
+    //   DMenu, inAjaxCallback, is_logout, onDoActionComplete, window_load
+    acount: 0,
+    remove_gc: [],
+    server_error: 0,
+    view_id: 1,
+
+    buttons: [ 'button_reply', 'button_forward', 'button_spam', 'button_ham', 'button_deleted' ],
+
+    debug: function(label, e)
+    {
+        if (!this.is_logout && DIMP.conf.debug) {
+            alert(label + ': ' + (e instanceof Error ? e.name + '-' + e.message : Object.inspect(e)));
+        }
+    },
+
+    // Convert object to an IMP UID Range string. See IMP::toRangeString()
+    // ob = (object) mailbox name as keys, values are array of uids.
+    toRangeString: function(ob)
+    {
+        var str = '';
+
+        $H(ob).each(function(o) {
+            if (!o.value.size()) {
+                return;
+            }
+
+            var u = o.value.numericSort(),
+                first = last = u.shift(),
+                out = [];
+
+            u.each(function(k) {
+                if (last + 1 == k) {
+                    last = k;
+                } else {
+                    out.push(first + (last == first ? '' : (':' + last)));
+                    first = last = k;
+                }
+            });
+            out.push(first + (last == first ? '' : (':' + last)));
+            str += '{' + o.key.length + '}' + o.key + out.join(',');
+        });
+
+        return str;
+    },
+
+    // Parses an IMP UID Range string. See IMP::parseRangeString()
+    // str = (string) An IMP UID range string.
+    parseRangeString: function(str)
+    {
+        var count, end, i, mbox,
+            mlist = {},
+            uids = [];
+        str = str.strip();
+
+        while (!str.blank()) {
+            if (!str.startsWith('{')) {
+                break;
+            }
+            i = str.indexOf('}');
+            count = parseInt(str.substr(1, i - 1));
+            mbox = str.substr(i + 1, count);
+            i += count + 1;
+            end = str.indexOf('{', i);
+            if (end == -1) {
+                uidstr = str.substr(i);
+                str = '';
+            } else {
+                uidstr = str.substr(i, end - i);
+                str = str.substr(end);
+            }
+
+            uidstr.split(',').each(function(e) {
+                var r = e.split(':');
+                if (r.size() == 1) {
+                    uids.push(parseInt(e, 10));
+                } else {
+                    uids = uids.concat($A($R(parseInt(r[0], 10), parseInt(r[1], 10))));
+                }
+            });
+
+            mlist[mbox] = uids;
+        }
+
+        return mlist;
+    },
+
+    /* 'action' -> if action begins with a '*', the exact string will be used
+     *  instead of sending the action to the IMP handler. */
+    doAction: function(action, params, uids, callback, opts)
+    {
+        var tmp = {};
+
+        if (!this.doActionOpts) {
+            this.doActionOpts = {
+                onException: function(r, e) {
+                    this.debug('onException', e);
+                }.bind(this),
+                onFailure: function(t, o) {
+                    this.debug('onFailure', t);
+                }.bind(this)
+            };
+        };
+
+        opts = Object.extend(this.doActionOpts, opts || {});
+        params = $H(params);
+        action = action.startsWith('*')
+            ? action.substring(1)
+            : DIMP.conf.URI_IMP + '/' + action;
+        if (uids) {
+            if (uids.viewport_selection) {
+                uids.get('dataob').each(function(r) {
+                    if (!tmp[r.view]) {
+                        tmp[r.view] = [];
+                    }
+                    tmp[r.view].push(r.imapuid);
+                });
+                uids = tmp;
+            }
+            params.set('uid', DimpCore.toRangeString(uids));
+        }
+        if (DIMP.conf.SESSION_ID) {
+            params.update(DIMP.conf.SESSION_ID.toQueryParams());
+        }
+        opts.parameters = params.toQueryString();
+        opts.onComplete = function(t, o) { this.doActionComplete(t, callback); }.bind(this);
+        new Ajax.Request(action, opts);
+    },
+
+    doActionComplete: function(request, callback)
+    {
+        this.inAjaxCallback = true;
+        var error = false, r = {};
+
+        if (!request.responseText || !request.responseText.length) {
+            error = true;
+        } else {
+            try {
+                r = request.responseText.evalJSON(true);
+            } catch (e) {
+                this.debug('doActionComplete', e);
+                error = true;
+            }
+        }
+
+        if (!r.msgs) {
+            r.msgs = [];
+        }
+
+        if (error) {
+            if (++this.server_error == 3) {
+                this.showNotifications([ { type: 'horde.error', message: DIMP.text.ajax_timeout } ]);
+            }
+            this.inAjaxCallback = false;
+            return;
+        }
+
+        if (r.response && Object.isFunction(callback)) {
+            if (DIMP.conf.debug) {
+                callback(r);
+            } else {
+                try {
+                    callback(r);
+                } catch (e) {}
+            }
+        }
+
+        if (this.server_error >= 3) {
+            r.msgs.push({ type: 'horde.success', message: DIMP.text.ajax_recover });
+        }
+        this.server_error = 0;
+
+        if (!r.msgs_noauto) {
+            this.showNotifications(r.msgs);
+        }
+
+        if (this.onDoActionComplete) {
+            this.onDoActionComplete(r);
+        }
+
+        this.inAjaxCallback = false;
+    },
+
+    setTitle: function(title)
+    {
+        document.title = DIMP.conf.name + ' :: ' + title;
+    },
+
+    showNotifications: function(msgs)
+    {
+        if (!msgs.size() || this.is_logout) {
+            return;
+        }
+
+        msgs.find(function(m) {
+            switch (m.type) {
+            case 'dimp.timeout':
+                this.is_logout = true;
+                this.redirect(DIMP.conf.timeout_url);
+                return true;
+
+            case 'horde.error':
+            case 'horde.message':
+            case 'horde.success':
+            case 'horde.warning':
+            case 'imp.reply':
+            case 'imp.forward':
+            case 'imp.redirect':
+            case 'dimp.request':
+            case 'dimp.sticky':
+                var clickdiv, fadeeffect, iefix, log, requestfunc, tmp,
+                    alerts = $('alerts'),
+                    div = new Element('DIV', { className: m.type.replace('.', '-') }),
+                    msg = m.message;;
+
+                if (!alerts) {
+                    alerts = new Element('DIV', { id: 'alerts' });
+                    $(document.body).insert(alerts);
+                }
+
+                if ($w('dimp.request dimp.sticky').indexOf(m.type) == -1) {
+                    msg = msg.unescapeHTML().unescapeHTML();
+                }
+                alerts.insert(div.update(msg));
+
+                // IE6 has a bug that does not allow the body of a div to be
+                // clicked to trigger an onclick event for that div (it only
+                // seems to be an issue if the div is overlaying an element
+                // that itself contains an image).  However, the alert box
+                // normally displays over the message list, and we use several
+                // graphics in the default message list layout, so we see this
+                // buggy behavior 99% of the time.  The workaround is to
+                // overlay the div with a like sized div containing a clear
+                // gif, which tricks IE into the correct behavior.
+                if (DIMP.conf.is_ie6) {
+                    iefix = new Element('DIV', { className: 'ie6alertsfix' }).clonePosition(div, { setLeft: false, setTop: false });
+                    clickdiv = iefix;
+                    iefix.insert(div.remove());
+                    alerts.insert(iefix);
+                } else {
+                    clickdiv = div;
+                }
+
+                fadeeffect = Effect.Fade.bind(this, div, { duration: 1.5, afterFinish: this.removeAlert.bind(this) });
+
+                clickdiv.observe('click', fadeeffect);
+
+                if ($w('horde.error dimp.request dimp.sticky').indexOf(m.type) == -1) {
+                    fadeeffect.delay(m.type == 'horde.warning' ? 10 : 3);
+                }
+
+                if (m.type == 'dimp.request') {
+                    requestfunc = function() {
+                        fadeeffect();
+                        document.stopObserving('click', requestfunc)
+                    };
+                    document.observe('click', requestfunc);
+                }
+
+                if (tmp = $('alertslog')) {
+                    switch (m.type) {
+                    case 'horde.error':
+                        log = DIMP.text.alog_error;
+                        break;
+
+                    case 'horde.message':
+                        log = DIMP.text.alog_message;
+                        break;
+
+                    case 'horde.success':
+                        log = DIMP.text.alog_success;
+                        break;
+
+                    case 'horde.warning':
+                        log = DIMP.text.alog_warning;
+                        break;
+                    }
+
+                    if (log) {
+                        tmp = tmp.down('DIV UL');
+                        if (tmp.down().hasClassName('noalerts')) {
+                            tmp.down().remove();
+                        }
+                        tmp.insert(new Element('LI').insert(new Element('P', { className: 'label' }).insert(log)).insert(new Element('P', { className: 'indent' }).insert(msg).insert(new Element('SPAN', { className: 'alertdate'}).insert('[' + (new Date).toLocaleString() + ']'))));
+                    }
+                }
+            }
+        }, this);
+    },
+
+    toggleAlertsLog: function()
+    {
+        var alink = $('alertsloglink').down('A'),
+            div = $('alertslog').down('DIV'),
+            opts = { duration: 0.5 };
+        if (div.visible()) {
+            Effect.BlindUp(div, opts);
+            alink.update(DIMP.text.showalog);
+        } else {
+            Effect.BlindDown(div, opts);
+            alink.update(DIMP.text.hidealog);
+        }
+    },
+
+    removeAlert: function(effect)
+    {
+        try {
+            var elt = $(effect.element),
+                parent = elt.up();
+            // We may have already removed this element from the DOM tree
+            // (if the user clicked on the notification), so check parentNode
+            // here - will return null if node is not part of DOM tree.
+            if (parent && parent.parentNode) {
+                this.addGC(elt.remove());
+                if (!parent.childElements().size() &&
+                    parent.hasClassName('ie6alertsfix')) {
+                    this.addGC(parent.remove());
+                }
+            }
+        } catch (e) {
+            this.debug('removeAlert', e);
+        }
+    },
+
+    compose: function(type, args)
+    {
+        var url = DIMP.conf.compose_url;
+        args = args || {};
+        if (type) {
+            args.type = type;
+        }
+        this.popupWindow(this.addURLParam(url, args), 'compose' + new Date().getTime());
+    },
+
+    popupWindow: function(url, name)
+    {
+        if (!(window.open(url, name.replace(/\W/g, '_'), 'width=' + DIMP.conf.popup_width + ',height=' + DIMP.conf.popup_height + ',status=1,scrollbars=yes,resizable=yes'))) {
+            this.showNotifications([ { type: 'horde.warning', message: DIMP.text.popup_block } ]);
+        }
+    },
+
+    closePopup: function()
+    {
+        // Mozilla bug/feature: it will not close a browser window
+        // automatically if there is code remaining to be performed (or, at
+        // least, not here) unless the mouse is moved or a keyboard event
+        // is triggered after the callback is complete. (As of FF 2.0.0.3 and
+        // 1.5.0.11).  So wait for the callback to complete before attempting
+        // to close the window.
+        if (this.inAjaxCallback) {
+            this.closePopup.bind(this).defer();
+        } else {
+            window.close();
+        }
+    },
+
+    logout: function()
+    {
+        this.is_logout = true;
+        this.redirect(DIMP.conf.URI_IMP + '/LogOut');
+    },
+
+    redirect: function(url)
+    {
+        url = this.addSID(url);
+        if (parent.frames.horde_main) {
+            parent.location = url;
+        } else {
+            window.location = url;
+        }
+    },
+
+    /* Add/remove mouse events on the fly.
+     * Parameter: object with the following names - id, type, offset
+     *   (optional), left (optional), onShow (optional)
+     * Valid types:
+     *   'message', 'draft'  --  Message list rows
+     *   'container', 'special', 'folder'  --  Folders
+     *   'reply', 'forward', 'otheractions'  --  Message list buttons
+     *   'contacts'  --  Linked e-mail addresses */
+    addMouseEvents: function(p)
+    {
+        this.DMenu.addElement(p.id, 'ctx_' + p.type, p);
+    },
+
+    /* elt = DOM element */
+    removeMouseEvents: function(elt)
+    {
+        this.DMenu.removeElement($(elt).readAttribute('id'));
+        this.addGC(elt);
+    },
+
+    /* Add a popdown menu to a dimpactions button. */
+    addPopdown: function(bid, ctx)
+    {
+        var bidelt = $(bid);
+        bidelt.insert({ after: $($('popdown_img').cloneNode(false)).writeAttribute('id', bid + '_img').show() });
+        this.addMouseEvents({ id: bid + '_img', type: ctx, offset: bidelt.up(), left: true });
+    },
+
+    /* Add dropdown menus to addresses. */
+    buildAddressLinks: function(alist, elt)
+    {
+        var base, tmp,
+            cnt = alist.size();
+
+        if (cnt > 15) {
+            tmp = $('largeaddrspan').cloneNode(true);
+            elt.insert(tmp);
+            base = tmp.down('.dispaddrlist');
+            tmp = tmp.down();
+            this.clickObserveHandler({ d: tmp, f: function(d) { [ d.down(), d.down(1), d.next() ].invoke('toggle'); }.curry(tmp) });
+            tmp = tmp.down();
+            tmp.setText(tmp.getText().replace('%d', cnt));
+        } else {
+            base = elt;
+        }
+
+        alist.each(function(o, i) {
+            var a;
+            if (o.raw) {
+                a = o.raw;
+            } else {
+                a = new Element('A', { className: 'address', id: 'addr' + this.acount++, personal: o.personal, email: o.inner, address: o.address }).insert(o.display ? o.display : o.address);
+                a.observe('mouseover', function() { a.stopObserving('mouseover'); this.addMouseEvents({ id: a.id, type: 'contacts', offset: a, left: true }); }.bind(this));
+            }
+            base.insert(a);
+            if (i + 1 != cnt) {
+                base.insert(', ');
+            }
+        }, this);
+
+        return elt;
+    },
+
+    /* Removes event handlers from address links. */
+    removeAddressLinks: function(id)
+    {
+        [ id.select('.address'), id.select('.largeaddrtoggle') ].flatten().compact().each(this.removeMouseEvents.bind(this));
+    },
+
+    /* Add event observers to message output.  Adds observers used in both
+     * the base page and the popup message window. */
+    messageOnLoad: function()
+    {
+        var C = this.clickObserveHandler, tmp;
+
+        if ($('partlist')) {
+            C({ d: $('partlist_col').up(), f: function() { $('partlist', 'partlist_col', 'partlist_exp').invoke('toggle'); } });
+        }
+        if (tmp = $('msg_print')) {
+            C({ d: tmp, f: function() { window.print(); } });
+        }
+        if (tmp = $('msg_view_source')) {
+            C({ d: tmp, f: function() { view(DimpCore.addSID(DIMP.conf.URI_VIEW) + '&index=' + DIMP.conf.msg_index + '&mailbox=' + DIMP.conf.msg_folder, DIMP.conf.msg_index + '|' + DIMP.conf.msg_folder) } });
+        }
+        C({ d: $('ctx_contacts_new'), f: function() { this.compose('new', { to: this.DMenu.element().readAttribute('address') }); }.bind(this), ns: true });
+        C({ d: $('ctx_contacts_add'), f: function() { this.doAction('AddContact', { name: this.DMenu.element().readAttribute('personal'), email: this.DMenu.element().readAttribute('email') }, null, true); }.bind(this), ns: true });
+        if ($('alertslog')) {
+            C({ d: $('alertsloglink'), f: this.toggleAlertsLog.bind(this) });
+        }
+    },
+
+    /* Utility functions. */
+    addGC: function(elt)
+    {
+        this.remove_gc = this.remove_gc.concat(elt);
+    },
+
+    // o: (object) Contains the following items:
+    //    'd'  - (required) The DOM element
+    //    'f'  - (required) The function to bind to the click event
+    //    'ns' - (optional) If set, don't stop the event's propogation
+    //    'p'  - (optional) If set, passes in the event object to the called
+    //                      function
+    clickObserveHandler: function(o)
+    {
+        return o.d.observe('click', DimpCore._clickFunc.curry(o));
+    },
+
+    _clickFunc: function(o, e)
+    {
+        o.p ? o.f(e) : o.f();
+        if (!o.ns) {
+            e.stop();
+        }
+    },
+
+    addSID: function(url)
+    {
+        if (!DIMP.conf.SESSION_ID) {
+            return url;
+        }
+        return this.addURLParam(url, DIMP.conf.SESSION_ID.toQueryParams());
+    },
+
+    addURLParam: function(url, params)
+    {
+        var q = url.indexOf('?');
+
+        if (q != -1) {
+            params = $H(url.toQueryParams()).merge(params).toObject();
+            url = url.substring(0, q);
+        }
+        return url + '?' + Object.toQueryString(params);
+    }
+};
+
+// Initialize DMenu now.  Need to init here because IE doesn't load dom:loaded
+// in a predictable order.
+if (typeof ContextSensitive != 'undefined') {
+    DimpCore.DMenu = new ContextSensitive();
+}
+
+document.observe('dom:loaded', function() {
+    /* Don't do additional onload stuff if we are in a popup. We need a
+     * try/catch block here since, if the page was loaded by an opener
+     * out of this current domain, this will throw an exception. */
+    try {
+        if (parent.opener &&
+            parent.opener.location.host == window.location.host &&
+            parent.opener.DimpCore) {
+            DIMP.baseWindow = parent.opener.DIMP.baseWindow || parent.opener;
+        }
+    } catch (e) {}
+
+    /* Remove unneeded buttons. */
+    if (!DIMP.conf.spam_reporting) {
+        DimpCore.buttons = DimpCore.buttons.without('button_spam');
+    }
+    if (!DIMP.conf.ham_reporting) {
+        DimpCore.buttons = DimpCore.buttons.without('button_ham');
+    }
+
+    /* Init garbage collection function - runs every 10 seconds. */
+    new PeriodicalExecuter(function() {
+        if (DimpCore.remove_gc.size()) {
+            try {
+                $A(DimpCore.remove_gc.splice(0, 75)).compact().invoke('stopObserving');
+            } catch (e) {
+                DimpCore.debug('remove_gc[].stopObserving', e);
+            }
+        }
+    }, 10);
+});
+
+Event.observe(window, 'load', function() {
+    DimpCore.window_load = true;
+});
+
+/* Helper methods for setting/getting element text without mucking
+ * around with multiple TextNodes. */
+Element.addMethods({
+    setText: function(element, text)
+    {
+        var t = 0;
+        $A(element.childNodes).each(function(node) {
+            if (node.nodeType == 3) {
+                if (t++) {
+                    Element.remove(node);
+                } else {
+                    node.nodeValue = text;
+                }
+            }
+        });
+
+        if (!t) {
+            $(element).insert(text);
+        }
+    },
+
+    getText: function(element, recursive)
+    {
+        var text = '';
+        $A(element.childNodes).each(function(node) {
+            if (node.nodeType == 3) {
+                text += node.nodeValue;
+            } else if (recursive && node.hasChildNodes()) {
+                text += $(node).getText(true);
+            }
+        });
+        return text;
+    }
+});
+
+/* Create some utility functions. */
+Object.extend(Array.prototype, {
+    numericSort: function()
+    {
+        return this.sort(function(a, b) {
+            if (a > b) {
+                return 1;
+            } else if (a < b) {
+                return -1;
+            }
+            return 0;
+        });
+    }
+});
+
+Object.extend(String.prototype, {
+    // We define our own version of evalScripts() to make sure that all
+    // scripts are running in the same scope and that all functions are
+    // defined in the global scope. This is not the case when using
+    // prototype's evalScripts().
+    evalScripts: function()
+    {
+        var re = /function\s+([^\s(]+)/g;
+        this.extractScripts().each(function(s) {
+            var func;
+            eval(s);
+            while (func = re.exec(s)) {
+                window[func[1]] = eval(func[1]);
+            }
+        });
+    }
+});
+
+/** Functions overriding IMP/prototypejs JS functions. **/
+
+/* We need to replace the IMP javascript for this function with code that
+ * calls the correct DIMP functions. */
+function popup_imp(url, w, h, args)
+{
+    DimpCore.compose('new', args.toQueryParams().toObject());
+}
+
+/* For attachment viewing to work, replaces the function from
+ * horde/templates/contents/open_view_win.js. */
+function view(url, uniqid)
+{
+    window.open(url, ++DimpCore.view_id + uniqid.replace(/\W/g, '_'), 'menubar=yes,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes');
+}
diff --git a/imp/js/src/DimpSlider.js b/imp/js/src/DimpSlider.js
new file mode 100644 (file)
index 0000000..07b9580
--- /dev/null
@@ -0,0 +1,208 @@
+/**
+ * DimpSlider.js - A minimalist library to create a slider that acts like a
+ * browser's native scrollbar.
+ * Requires prototype.js 1.6.0.2+
+ *
+ * Adapted from script.aculo.us slider.js v1.8.0
+ *   (c) 2005-2007 Marty Haught, Thomas Fuchs
+ *   http://script.aculo.us/
+ *
+ * The original script was freely distributable under the terms of an
+ * MIT-style license.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * $Horde: dimp/js/src/DimpSlider.js,v 1.21 2008/08/20 20:18:56 slusarz Exp $
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Michael Slusarz <slusarz@curecanti.org>
+ */
+
+var DimpSlider = Class.create({
+    value: 0,
+
+    initialize: function(track, options)
+    {
+        this.track = $(track);
+        this.options = Object.extend({
+            buttonclass: null,
+            cursorclass: null,
+            pagesize: 0,
+            totalsize: 0
+        }, options || {});
+
+        this.handle = new Element('DIV', { className: this.options.cursorclass }).makePositioned();
+        this.track.insert(this.handle);
+
+        if (this.options.buttonclass) {
+            this.sbup = new Element('DIV', { className: this.options.buttonclass.up });
+            this.sbdown = new Element('DIV', { className: this.options.buttonclass.down }).makePositioned();
+            this.handle.insert({ before: this.sbup, after: this.sbdown });
+            [ this.sbup, this.sbdown ].invoke('observe', 'mousedown', this._arrowClick.bindAsEventListener(this));
+        }
+
+        this.sbdownsize = this.sbupsize = this.value = 0;
+        this.active = this.dragging = false;
+
+        if (this._showScroll()) {
+            this._initScroll();
+        }
+
+        this.eventMU = this._endDrag.bindAsEventListener(this);
+        this.eventMM = this._update.bindAsEventListener(this);
+
+        [ this.handle, this.track ].invoke('observe', 'mousedown', this._startDrag.bindAsEventListener(this));
+    },
+
+    _initScroll: function()
+    {
+        if (this.init) {
+            return false;
+        }
+        this.init = true;
+        this.track.show();
+        if (this.sbup) {
+            this.sbupsize = this.sbup.offsetHeight;
+            this.sbdownsize = this.sbdown.offsetHeight;
+        }
+        this._updateHandleLength();
+        return true;
+    },
+
+    _startDrag: function(e)
+    {
+        if (!e.isLeftClick()) {
+            return;
+        }
+
+        var dir,
+            hoffsets = this.handle.cumulativeOffset();
+
+        if (e.element() == this.track) {
+            dir = (e.pointerY() < hoffsets[1]) ? -1 : 1;
+            this.setScrollPosition(this.getValue() - dir + (this.options.pagesize * dir));
+        } else {
+            this.curroffsets = this.track.cumulativeOffset();
+            this.offsetY = e.pointerY() - hoffsets[1] + this.sbupsize;
+            this.active = true;
+
+            document.observe('mouseup', this.eventMU);
+            document.observe('mousemove', this.eventMM);
+        }
+
+        e.stop();
+    },
+
+    _update: function(e)
+    {
+        if (this.active) {
+            this.dragging = true;
+            this._setScrollPosition('px', Math.min(Math.max(0, e.pointerY() - this.offsetY - this.curroffsets[1]), this.handletop));
+            if (this.options.onSlide) {
+                this.options.onSlide();
+            }
+            if (Prototype.Browser.WebKit) {
+                window.scrollBy(0,0);
+            }
+            e.stop();
+        }
+    },
+
+    _endDrag: function(e)
+    {
+        if (this.active && this.dragging) {
+            this._updateFinished();
+            e.stop();
+
+            document.stopObserving('mouseup', this.eventMU);
+            document.stopObserving('mousemove', this.eventMM);
+        }
+        this.active = this.dragging = false;
+    },
+
+    _arrowClick: function(e)
+    {
+        this.setScrollPosition(this.getValue() + ((e.element() == this.sbup) ? -1 : 1));
+    },
+
+    _updateFinished: function()
+    {
+        if (this.options.onChange) {
+            this.options.onChange();
+        }
+    },
+
+    updateHandleLength: function(pagesize, totalsize)
+    {
+        this.options.pagesize = pagesize;
+        this.options.totalsize = totalsize;
+        if (!this._showScroll()) {
+            this.value = 0;
+            this.track.hide();
+            return;
+        }
+        if (!this._initScroll()) {
+            this.track.show();
+            this._updateHandleLength();
+        }
+    },
+
+    _updateHandleLength: function()
+    {
+        var t = this.track.offsetHeight - this.sbupsize - this.sbdownsize;
+
+        // Minimum handle size = 10px
+        this.handle.setStyle({ height: Math.max(10, Math.round((this.options.pagesize / this.options.totalsize) * t)) + 'px' });
+        this.handletop = t - this.handle.offsetHeight;
+        if (this.sbdown) {
+            this.sbdown.setStyle({ top: this.handletop + 'px' });
+        }
+        this._setScrollPosition('val', this.getValue());
+    },
+
+    getValue: function()
+    {
+        return this.value;
+    },
+
+    setScrollPosition: function(val)
+    {
+        if (this._showScroll()) {
+            var oldval = this.getValue();
+            this._setScrollPosition('val', val);
+            if (oldval != this.getValue()) {
+                this._updateFinished();
+            }
+        }
+    },
+
+    _setScrollPosition: function(type, data)
+    {
+        this.value = (type == 'val')
+            ? Math.min(Math.max(0, data), this.options.totalsize - this.options.pagesize)
+            : Math.max(0, Math.round(Math.min(data, this.handletop) / this.handletop * (this.options.totalsize - this.options.pagesize)));
+        this.handlevalue = (type == 'px')
+            ? data
+            : Math.round(this.getValue() / (this.options.totalsize - this.options.pagesize) * this.handletop);
+        this.handle.setStyle({ top: this.handlevalue + 'px' });
+    },
+
+    _showScroll: function()
+    {
+        return (this.options.pagesize < this.options.totalsize);
+    }
+});
diff --git a/imp/js/src/ViewPort.js b/imp/js/src/ViewPort.js
new file mode 100644 (file)
index 0000000..5a7200e
--- /dev/null
@@ -0,0 +1,1607 @@
+/**
+ * ViewPort.js - Code to create a viewport window in a web browser.
+ *
+ * Requires prototypejs 1.6+, DimpSlider.js, scriptaculous 1.8+ (effects.js
+ * only), and DIMP's dragdrop.js.
+ *
+ * $Horde: dimp/js/src/ViewPort.js,v 1.274 2008/10/20 00:57:59 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+/**
+ * ViewPort
+ */
+var ViewPort = Class.create({
+
+    // Required: content_container, lines, fetch_action, template,
+    //           cachecheck_action, ajaxRequest, buffer_pages,
+    //           limit_factor, content_class, row_class, selected_class
+    // Optional: norows, show_split_pane
+    initialize: function(opts)
+    {
+        opts.content = $(opts.content_container);
+        opts.empty = opts.empty_container ? $(opts.empty_container) : null;
+        opts.error = opts.error_container ? $(opts.error_container) : null;
+        this.opts = opts;
+
+        this.scroller = new ViewPort_Scroller(this);
+        this.template = new Template(opts.template);
+
+        this.current_req_lookup = $H();
+        this.current_req = $H();
+        this.fetch_hash = $H();
+        this.slice_hash = $H();
+        this.views = $H();
+
+        this.showSplitPane(opts.show_split_pane);
+
+        // Initialize all other variables
+        this.isbusy = this.line_height = this.page_size = this.splitbar = this.splitbar_loc = this.uc_run = this.view = this.viewport_init = null;
+        this.request_num = 1;
+    },
+
+    // view = ID of view. Can not contain a '%' character.
+    // params = TODO
+    // search = (object) Search parameters
+    // background = Load view in background?
+    loadView: function(view, params, search, background)
+    {
+        var buffer, curr, init, opts = {}, ps;
+
+        this._clearWait();
+
+        // Need a page size before we can continue - this is what determines
+        // the slice size to request from the server.
+        if (this.page_size === null) {
+            ps = this.getPageSize(this.show_split_pane ? 'default' : 'max');
+            if (isNaN(ps)) {
+                this.loadView.bind(this, view, params, search, background).defer();
+                return;
+            }
+            this.page_size = ps;
+        }
+
+        buffer = this._getBuffer();
+        if (buffer) {
+            if (!background && this.view) {
+                // Need to store current buffer to save current offset
+                this.views.set(this.view, { buffer: buffer, offset: this.currentOffset() });
+            }
+            curr = this.views.get(view);
+        } else {
+            init = true;
+        }
+
+        if (background) {
+            opts = { background: true, view: view };
+        } else {
+            this.view = view;
+            if (!this.viewport_init) {
+                this.viewport_init = 1;
+                this._renderViewport();
+            }
+        }
+
+        if (curr) {
+            this.setMetaData('additional_params', $H(params), view);
+            this._updateContent(curr.offset, opts);
+            if (!background) {
+                if (this.opts.onComplete) {
+                    this.opts.onComplete();
+                }
+                this.opts.ajaxRequest(this.opts.fetch_action, this.addRequestParams({ checkcache: 1, rownum: this.currentOffset() + 1 }));
+            }
+            return true;
+        } else if (!init) {
+            if (this.opts.onClearRows) {
+                this.opts.onClearRows(this.opts.content.childElements());
+            }
+            this.opts.content.update();
+            this.scroller.clear();
+        }
+
+        buffer = this._getBuffer(null, true);
+        this.views.set(view, { buffer: buffer, offset: 0 });
+        this.setMetaData({ additional_params: $H(params) }, view);
+        if (search) {
+            opts.search = search;
+        } else {
+            opts.offset = 0;
+        }
+        this._fetchBuffer(opts);
+
+        return false;
+    },
+
+    // view = ID of view
+    deleteView: function(view)
+    {
+        this.views.unset(view);
+    },
+
+    // rownum = Row number
+    // noupdate = boolean
+    scrollTo: function(rownum, noupdate)
+    {
+        var s = this.scroller;
+
+        s.noupdate = noupdate;
+
+        switch (this.isVisible(rownum)) {
+        case -1:
+            s.moveScroll(rownum - 1);
+            break;
+
+        // case 0:
+        // noop
+
+        case 1:
+            s.moveScroll(Math.min(rownum, this.getMetaData('total_rows') - this.getPageSize() + 1));
+            break;
+        }
+
+        s.noupdate = false;
+    },
+
+    // rownum = Row number
+    isVisible: function(rownum)
+    {
+        var offset = this.currentOffset();
+        return (rownum < offset + 1) ? -1 :
+               ((rownum > (offset + this.getPageSize('current'))) ? 1 : 0);
+    },
+
+    // params = TODO
+    reload: function(params)
+    {
+        if (this.isFiltering()) {
+            this.filter.filter(null, params);
+        } else {
+            this._fetchBuffer({ offset: this.currentOffset(), purge: true, params: params });
+        }
+    },
+
+    // vs = (Viewport_Selection) A Viewport_Selection object.
+    // opts = (object) TODO [cacheid, noupdate, view]
+    remove: function(vs, opts)
+    {
+        if (this.isbusy) {
+            this.remove.bind(this, vs, cacheid, view).defer();
+            return;
+        }
+
+        if (!vs.size()) {
+            return;
+        }
+
+        opts = opts || {};
+        this.isbusy = true;
+
+        var args,
+            i = 0,
+            visible = vs.get('div'),
+            vsize = visible.size();
+
+        this.deselect(vs);
+
+        if (opts.cacheid) {
+            this.setMetaData({ cacheid: opts.cacheid }, opts.view);
+        }
+
+        // If we have visible elements to remove, only call refresh after
+        // the last effect has finished.
+        if (vsize) {
+            // Set 'to' to a value slightly above 0 to prevent Effect.Fade
+            // from auto hiding.  Hiding is unnecessary, since we will be
+            // removing from the document shortly.
+            args = { duration: 0.3, to: 0.01 };
+            visible.each(function(v) {
+                if (++i == vsize) {
+                    args.afterFinish = this._removeids.bind(this, vs, opts);
+                }
+                Effect.Fade(v, args);
+            }, this);
+        } else {
+            this._removeids(vs, opts);
+        }
+    },
+
+    // vs = (Viewport_Selection) A Viewport_Selection object.
+    // opts = (object) TODO [noupdate, view]
+    _removeids: function(vs, opts)
+    {
+        this.setMetaData({ total_rows: this.getMetaData('total_rows', opts.view) - vs.size() }, opts.view);
+
+        if (this.opts.onRemoveRows) {
+            this.opts.onRemoveRows(vs);
+        }
+
+        this._getBuffer().remove(vs.get('rownum'));
+        if (!opts.noupdate) {
+            this.requestContentRefresh(this.currentOffset());
+        }
+        this.isbusy = false;
+    },
+
+    // action = TODO
+    // callback = TODO
+    addFilter: function(action, callback)
+    {
+        this.filter = new ViewPort_Filter(this, action, callback);
+    },
+
+    // val = TODO
+    // params = TODO
+    runFilter: function(val, params)
+    {
+        if (this.filter) {
+            this.filter.filter(Object.isUndefined(val) ? null : val, params);
+        }
+    },
+
+    // Return: (boolean) Is filtering currently active?
+    isFiltering: function()
+    {
+        return this.filter ? this.filter.isFiltering() : false;
+    },
+
+    // reset = (boolean) If true, don't update the viewport
+    stopFilter: function(reset)
+    {
+        if (this.filter) {
+            this.filter.clear(reset);
+        }
+    },
+
+    // noupdate = (boolean) TODO
+    // nowait = (boolean) TODO
+    onResize: function(noupdate, nowait)
+    {
+        if (!this.uc_run || !this.opts.content.visible()) {
+            return;
+        }
+
+        if (this.resizefunc) {
+            clearTimeout(this.resizefunc);
+        }
+
+        if (nowait) {
+            this._onResize(noupdate);
+        } else {
+            this.resizefunc = this._onResize.bind(this, noupdate).delay(0.1);
+        }
+    },
+
+    _onResize: function(noupdate)
+    {
+        if (this.opts.onBeforeResize) {
+            this.opts.onBeforeResize();
+        }
+
+        this._renderViewport(noupdate);
+
+        if (this.opts.onAfterResize) {
+            this.opts.onAfterResize();
+        }
+    },
+
+    // offset = (integer) TODO
+    requestContentRefresh: function(offset)
+    {
+        if (this._updateContent(offset)) {
+            var limit = this._getBuffer().isNearingLimit(offset);
+            if (limit) {
+                this._fetchBuffer({ offset: offset, background: true, nearing: limit });
+            }
+            return true;
+        }
+
+        return false;
+    },
+
+    // opts = (object) The following parameters are used
+    // One of the following:
+    //   offset: (integer) Value of offset
+    //   search: (object) List of search keys/values
+    // Optional:
+    //   background: (boolean) Do fetch in background
+    //   purge: (boolean) TODO
+    //   nearing: (string) TODO [only used w/offset]
+    //   params: (object) Parameters to add to outgoing URL
+    //   view: (string) The view to retrieve. Defaults to current view.
+    //
+    // Outgoing request has the following params:
+    //   rownum: (integer) TODO
+    //   request_id: (string) TODO
+    //   rows: (JSON array) TODO [optional]
+    //
+    //   search: (JSON object)
+    //   search_after: (integer)
+    //   search_before: (integer)
+    _fetchBuffer: function(opts)
+    {
+        if (this.isbusy) {
+            this._fetchBuffer.bind(this, opts).defer();
+            return;
+        }
+
+        this.isbusy = true;
+
+        // Only call onFetch() if we are loading in foreground.
+        if (this.opts.onFetch && !opts.background) {
+            this.opts.onFetch();
+        }
+
+        var view = (opts.view || this.view),
+            action = this.opts.fetch_action,
+            allrows,
+            b = this._getBuffer(view),
+            cr,
+            lb,
+            params = $H(opts.params),
+            request_id,
+            request_string,
+            request_old,
+            request_vals,
+            rlist,
+            rowlist,
+            type,
+            value,
+            viewable;
+
+        // If asking for an explicit purge, add to the request.
+        if (opts.purge) {
+            params.set('purge', true);
+        }
+
+        // Determine if we are querying via offset or a search query
+        if (opts.search) {
+            type = 'search';
+            value = opts.search;
+            lb = this._lookbehind(view);
+
+            params.update({ search_before: lb, search_after: b.bufferSize() - lb });
+        } else {
+            type = 'rownum';
+            value = opts.offset + 1;
+
+            // This gets the list of rows needed which do not already appear
+            // in the buffer.
+            allrows = b.getAllRows();
+            rowlist = this._getSliceBounds(value, opts.nearing, view);
+            rlist = $A($R(rowlist.start, rowlist.end)).diff(allrows);
+            if (!opts.purge && !rlist.size()) {
+                this.isbusy = false;
+                return;
+            }
+
+            params.update({ slice_start: rowlist.start, slice_end: rowlist.end });
+        }
+        params.set(type, Object.toJSON(value));
+        request_vals = [ view, type, value ];
+
+        // Are we currently filtering results?
+        if (this.isFiltering()) {
+            action = this.filter.getAction();
+            params = this.filter.addFilterParams(params);
+            // Need to capture filter params changes in the request ID
+            request_vals.push(params);
+        }
+
+        // Generate a unique request ID value based on the search params.
+        // Since javascript does not have a native hashing function, use a
+        // local lookup table instead.
+        request_string = request_vals.toJSON();
+        request_id = this.fetch_hash.get(request_string);
+
+        // If we have a current request pending in the current view, figure
+        // out if we need to send a new request
+        cr = this.current_req.get(view);
+        if (cr) {
+            if (request_id && cr.get(request_id)) {
+                // Check for repeat request.  We technically should never
+                // reach here but if we do, make sure we don't go into an
+                // infinite loop.
+                if (++cr.get(request_id).count == 4) {
+                    this._displayFetchError();
+                    this._removeRequest(view, request_id);
+                    this.isbusy = false;
+                    return;
+                }
+            } else if (type == 'rownum') {
+                // Check for message list requests that are requesting
+                // (essentially) the same message slice - such as two
+                // scroll down requests sent in quick succession.  If the
+                // original request contains the viewable slice needed by the
+                // second request, ignore the later request and just
+                // reposition the viewport on display.
+                viewable = $A($R(value, value + this.getPageSize())).diff(allrows);
+                if (!viewable.size()) {
+                    this.isbusy = false;
+                    return;
+                }
+                request_old = cr.keys().numericSort().find(function(k) {
+                    var r = cr.get(k).rlist;
+                    viewable = viewable.diff(r);
+                    if (!viewable.size()) {
+                        return true;
+                    }
+                    rlist = rlist.diff(r);
+                });
+                if (request_old) {
+                    if (!opts.background) {
+                        this._addRequest(view, request_old, { background: false, offset: value - 1 });
+                    }
+                    this.isbusy = false;
+                    return;
+                } else if (!opts.background) {
+                    // Set all other pending requests to background, since the
+                    // current request is now the active request.
+                    cr.keys().each(function(k) {
+                        this._addRequest(view, k, { background: true });
+                    }, this);
+                }
+            }
+            // If we are in search mode, we must bite the bullet and simply
+            // accept the entire slice back from the server.
+        }
+
+        // We don't need to send row information to the server if the server
+        // is keeping track of what has been sent to the browser.
+        if (!this.opts.norows && rlist) {
+            params.set('rows', rlist.toJSON());
+        }
+
+        if (!request_id) {
+            request_id = this.fetch_hash.set(request_string, this.request_num++);
+        }
+        params.set('request_id', request_id);
+        this._addRequest(view, request_id, { background: opts.background, offset: value - 1, rlist: rlist });
+
+        this.opts.ajaxRequest(action, this.addRequestParams(params, { noslice: true, view: view }));
+        this._handleWait();
+        this.isbusy = false;
+    },
+
+    // rownum = (integer) Row number
+    // nearing = (string) 'bottom', 'top', null
+    // view = (string) The view to retrieve. Defaults to current view.
+    _getSliceBounds: function(rownum, nearing, view)
+    {
+        var b_size = this._getBuffer(view).bufferSize(),
+            ob = {};
+
+        switch (nearing) {
+        case 'bottom':
+            ob.start = rownum + this.getPageSize();
+            ob.end = ob.start + b_size;
+            break;
+
+        case 'top':
+            ob.start = Math.max(rownum - b_size, 1);
+            ob.end = rownum;
+            break;
+
+        default:
+            ob.start = Math.max(rownum - this._lookbehind(view), 1);
+            ob.end = ob.start + b_size;
+            break;
+        }
+
+        return ob;
+    },
+
+    // view = (string) The view to retrieve. Defaults to current view.
+    _lookbehind: function(view)
+    {
+        return parseInt(0.4 * this._getBuffer(view).bufferSize(), 10);
+    },
+
+    // args = (object) The list of parameters.
+    // opts = (object) [noslice, view]
+    // Returns a Hash object
+    addRequestParams: function(args, opts)
+    {
+        opts = opts || {};
+        var cid = this.getMetaData('cacheid', opts.view),
+            params = this.getMetaData('additional_params', opts.view),
+            rowlist;
+        if (cid) {
+            params.update({ cacheid: cid });
+        }
+        if (!opts.noslice) {
+            rowlist = this._getSliceBounds(this.currentOffset(), null, opts.view);
+            params.update({ slice_start: rowlist.start, slice_end: rowlist.end });
+        }
+        return params.merge(args);
+    },
+
+    // r = (Object) viewport response object.
+    //     Common properties:
+    //         id
+    //         request_id
+    //         type: 'list', 'slice' (DEFAULT: list)
+    //
+    //     Properties needed for type 'list':
+    //         cacheid
+    //         data
+    //         label
+    //         total_rows
+    //         other
+    //         rowlist
+    //         rownum (optional)
+    //         totalrows
+    //         update (optional)
+    //
+    //     Properties needed for type 'slice':
+    //         data (object) - rownum is the only required property
+    ajaxResponse: function(r)
+    {
+        if (this.isbusy) {
+            this.ajaxResponse.bind(this, r).defer();
+            return;
+        }
+
+        this.isbusy = true;
+        this._clearWait();
+
+        var buffer, cr, cr_id, data, datakeys, id, rowlist = {};
+
+        if (r.type == 'slice') {
+            data = r.data;
+            datakeys = Object.keys(data);
+            datakeys.each(function(k) {
+                data[k].view = r.id;
+                rowlist[k] = data[k].rownum;
+            });
+            buffer = this._getBuffer(r.id);
+            buffer.update(data, rowlist, { slice: true });
+            if (this.opts.onEndFetch) {
+                this.opts.onEndFetch();
+            }
+            cr = this.slice_hash.get(r.request_id);
+            if (cr) {
+                cr(new ViewPort_Selection(buffer, 'uid', datakeys));
+                this.slice_hash.unset(r.request_id);
+            }
+            this.isbusy = false;
+            return;
+        }
+
+        id = (r.request_id) ? this.current_req_lookup.get(r.request_id) : r.id;
+        cr = this.current_req.get(id);
+        if (cr && r.request_id) {
+            cr_id = cr.get(r.request_id);
+        }
+
+        if (this.viewport_init) {
+            this.viewport_init = 2;
+        }
+
+        buffer = this._getBuffer(id);
+        buffer.update(r.data, r.rowlist, { update: r.update });
+        buffer.setMetaData($H(r.other).merge({
+            cacheid: r.cacheid,
+            label: r.label,
+            total_rows: r.totalrows
+        }));
+
+        if (r.request_id) {
+            this._removeRequest(id, r.request_id);
+        }
+
+        this.isbusy = false;
+
+        // Don't update the viewport if we are now in a different view, or if
+        // we are loading in the background.
+        if (!(this.view == id || r.search) ||
+            (cr_id && cr_id.background) ||
+            !this._updateContent((cr_id && cr_id.offset) ? cr_id.offset : (r.rownum ? parseInt(r.rownum) - 1 : this.currentOffset()))) {
+            return;
+        }
+
+        if (this.opts.onComplete) {
+            this.opts.onComplete();
+        }
+
+        if (this.opts.onEndFetch) {
+            this.opts.onEndFetch();
+        }
+    },
+
+    // Adds a request to the current request queue.
+    // Requests are stored by view ID. Under each ID is the following:
+    //   count: (integer) Number of times slice has attempted to be loaded
+    //   background: (boolean) Do not update current view
+    //   offset: (integer) The offset to use
+    //   rlist: (array) The row list
+    // params = (object) [background, offset, rlist]
+    _addRequest: function(view, r_id, params)
+    {
+        var req_view = this.current_req.get(view), req;
+        if (!req_view) {
+            req_view = this.current_req.set(view, $H());
+        }
+
+        req = req_view.get(r_id);
+        if (!req) {
+            req = req_view.set(r_id, { count: 1 });
+        }
+        ['background', 'offset', 'rlist'].each(function(p) {
+            if (!Object.isUndefined(params[p])) {
+                req[p] = params[p];
+            }
+        });
+
+        this.current_req_lookup.set(r_id, view);
+    },
+
+    // Removes a request to the current request queue.
+    // view = (string) The view to remove a request for
+    // r_id = (string) The request ID to remove
+    _removeRequest: function(view, r_id)
+    {
+        var cr = this.current_req.get(view);
+        if (cr) {
+            cr.unset(r_id);
+            if (!cr.size()) {
+                this.current_req.unset(view);
+            }
+        }
+        this.current_req_lookup.unset(r_id);
+    },
+
+    // offset = (integer) TODO
+    // opts = (object) TODO [view]
+    _updateContent: function(offset, opts)
+    {
+        opts = opts || {};
+
+        if (!this._getBuffer(opts.view).sliceLoaded(offset)) {
+            this._fetchBuffer($H(opts).merge({ offset: offset }).toObject());
+            return false;
+        }
+
+        if (!this.uc_run) {
+            // Code for viewport that only needs to be initialized once.
+            this.uc_run = true;
+            if (this.opts.onFirstContent) {
+                this.opts.onFirstContent();
+            }
+        }
+
+        var c = this.opts.content,
+            c_nodes = [],
+            page_size = this.getPageSize(),
+            rows,
+            sel = this.getSelected();
+
+        if (this.opts.onClearRows) {
+            this.opts.onClearRows(c.childElements());
+        }
+
+        this.scroller.updateSize();
+        this.scrollTo(offset + 1, true);
+
+        offset = this.currentOffset();
+        rows = this.createSelection('rownum', $A($R(offset + 1, offset + page_size)));
+
+        if (rows.size()) {
+            rows.get('dataob').each(function(row) {
+                var r = Object.clone(row);
+                if (r.bg) {
+                    r.bg = row.bg.clone();
+                    if (sel.contains('uid', r.vp_id)) {
+                        r.bg.push(this.opts.selected_class);
+                    }
+                    r.bg_string = r.bg.join(' ');
+                }
+                c_nodes.push(this.template.evaluate(r));
+            }, this);
+            c.update(c_nodes.join(''));
+        } else {
+            // If loading a viewport for the first time, show a blank
+            // viewport rather than the empty viewport status message.
+            c.update((this.opts.empty && this.viewport_init != 1) ? this.opts.empty.innerHTML : '');
+        }
+
+        if (this.opts.onContent) {
+            this.opts.onContent(rows);
+        }
+
+        return true;
+    },
+
+    _displayFetchError: function()
+    {
+        if (this.opts.onFail) {
+            this.opts.onFail();
+        }
+        if (this.opts.error) {
+            this.opts.content.update(this.opts.error.innerHTML);
+        }
+    },
+
+    // rows = (array) An array of row numbers
+    // callback = (function; optional) A callback function to run after we
+    //            retrieve list of rows from server. Callback function
+    //            receives one parameter - a ViewPort_Selection object
+    //            containing the slice.
+    // Return: Either a ViewPort_Selection object or false if the server needs
+    //         to be queried.
+    _getSlice: function(rows, callback)
+    {
+        var params = { rangeslice: 1, start: rows.min(), length: rows.size() },
+            r_id,
+            slice;
+
+        slice = this.createSelection('rownum', rows);
+        if (rows.size() == slice.size()) {
+            return slice;
+        }
+
+        if (this.opts.onFetch) {
+            this.opts.onFetch();
+        }
+        if (callback) {
+            r_id = this.request_num++;
+            params.request_id = r_id;
+            this.slice_hash.set(r_id, callback);
+        }
+        this.opts.ajaxRequest(this.opts.fetch_action, this.addRequestParams(params, { noslice: true }));
+        return false;
+    },
+
+    _handleWait: function(call)
+    {
+        this._clearWait();
+
+        // Server did not respond in defined amount of time.  Alert the
+        // callback function and set the next timeout.
+        if (call && this.opts.onWait) {
+            this.opts.onWait();
+        }
+
+        // Call wait handler every x seconds
+        if (this.opts.viewport_wait) {
+            this.waitHandler = this._handleWait.bind(this, true).delay(this.opts.viewport_wait);
+        }
+    },
+
+    _clearWait: function()
+    {
+        if (this.waitHandler) {
+            clearTimeout(this.waitHandler);
+            this.waitHandler = null;
+        }
+    },
+
+    visibleRows: function()
+    {
+        return this.opts.content.childElements();
+    },
+
+    getMetaData: function(id, view)
+    {
+        return this._getBuffer(view).getMetaData(id);
+    },
+
+    setMetaData: function(vals, view)
+    {
+        this._getBuffer(view).setMetaData(vals);
+    },
+
+    _getBuffer: function(view, create)
+    {
+        if (!create) {
+            var b = this.views.get(view || this.view);
+            if (b) {
+                return b.buffer;
+            }
+        }
+        return new ViewPort_Buffer(this, this.opts.buffer_pages, this.opts.limit_factor);
+    },
+
+    currentOffset: function()
+    {
+        return this.scroller.currentOffset();
+    },
+
+    // vs = (Viewport_Selection) A Viewport_Selection object.
+    // flag = (string) Flag name.
+    // add = (boolean) Whether to set/unset flag.
+    updateFlag: function(vs, flag, add)
+    {
+        this._updateFlag(vs, flag, add, this.isFiltering());
+        this._updateClass(vs, flag, add);
+    },
+
+    // vs = (Viewport_Selection) A Viewport_Selection object.
+    // flag = (string) Flag name.
+    // add = (boolean) Whether to set/unset flag.
+    // filter = (boolean) Are we filtering results?
+    _updateFlag: function(vs, flag, add, filter)
+    {
+        vs.get('dataob').each(function(r) {
+            if (add) {
+                r.bg.push(flag);
+            } else {
+                r.bg.splice(r.bg.indexOf(flag), 1);
+            }
+            if (filter) {
+                this._updateFlag(this.createSelection('uid', r.vp_id, r.view), flag, add);
+            }
+        }, this);
+    },
+
+    // vs = (Viewport_Selection) A Viewport_Selection object.
+    // flag = (string) Flag name.
+    // add = (boolean) Whether to set/unset flag.
+    _updateClass: function(vs, flag, add)
+    {
+        vs.get('div').each(function(d) {
+            if (add) {
+                d.addClassName(flag);
+            } else {
+                d.removeClassName(flag);
+            }
+        });
+    },
+
+    _getLineHeight: function()
+    {
+        if (this.line_height) {
+            return this.line_height;
+        }
+
+        // To avoid hardcoding the line height, create a temporary row to
+        // figure out what the CSS says.
+        var d = new Element('DIV', { className: this.opts.content_class }).insert(new Element('DIV', { className: this.opts.row_class })).hide();
+        $(document.body).insert(d);
+        this.line_height = d.getHeight();
+        d.remove();
+
+        return this.line_height;
+    },
+
+    // (type) = (string) [null (DEFAULT), 'current', 'default', 'max']
+    getPageSize: function(type)
+    {
+        switch (type) {
+        case 'current':
+            return Math.min(this.page_size, this.getMetaData('total_rows'));
+
+        case 'default':
+            return Math.max(parseInt(this.getPageSize('max') * 0.45), 5);
+
+        case 'max':
+            return parseInt(this._getMaxHeight() / this._getLineHeight());
+
+        default:
+            return this.page_size;
+        }
+    },
+
+    _getMaxHeight: function()
+    {
+        return document.viewport.getHeight() - this.opts.content.viewportOffset()[1];
+    },
+
+    showSplitPane: function(show)
+    {
+        this.show_split_pane = show;
+        this.onResize(false, true);
+    },
+
+    _renderViewport: function(noupdate)
+    {
+        if (!this.viewport_init) {
+            return;
+        }
+
+        // This is needed for IE 6 - or else horizontal scrolling can occur.
+        if (!this.opts.content.offsetHeight) {
+            return this._renderViewport.bind(this, noupdate).defer();
+        }
+
+        var diff, h, pane, setpane,
+            c = $(this.opts.content),
+            de = document.documentElement,
+            lh = this._getLineHeight();
+
+        // Get split pane dimensions
+        if (this.opts.split_pane) {
+            pane = $(this.opts.split_pane);
+            if (this.show_split_pane) {
+                if (!pane.visible()) {
+                    this._initSplitBar();
+                    this.page_size = (this.splitbar_loc) ? this.splitbar_loc : this.getPageSize('default');
+                }
+                setpane = true;
+            } else if (pane.visible()) {
+                this.splitbar_loc = this.page_size;
+                $(pane, this.splitbar).invoke('hide');
+            }
+        }
+
+        if (!setpane) {
+            this.page_size = this.getPageSize('max');
+        }
+
+        // Do some magic to ensure we never cause a horizontal scroll.
+        h = lh * this.page_size;
+        c.setStyle({ height: h + 'px' });
+        if (setpane) {
+            pane.setStyle({ height: (this._getMaxHeight() - h - lh) + 'px' }).show();
+            this.splitbar.show();
+        } else {
+            if (diff = de.scrollHeight - de.clientHeight) {
+                c.setStyle({ height: (lh * (this.page_size - 1)) + 'px' });
+            }
+        }
+
+        if (!noupdate) {
+            this.scroller.onResize();
+        }
+    },
+
+    _initSplitBar: function()
+    {
+        if (this.splitbar) {
+            return;
+        }
+
+        this.splitbar = $(this.opts.splitbar);
+        new Drag(this.splitbar, {
+            constraint: 'vertical',
+            ghosting: true,
+            onStart: function() {
+                // Cache these values since we will be using them multiple
+                // times in snap().
+                var lh = this._getLineHeight();
+                this.sp = { lh: lh, pos: $(this.opts.content).positionedOffset()[1], max: parseInt((this._getMaxHeight() - 100) / lh), lines: this.page_size };
+            }.bind(this),
+            snap: function(x, y, elt) {
+                var l = parseInt((y - this.sp.pos) / this.sp.lh);
+                if (l < 1) {
+                    l = 1;
+                } else if (l > this.sp.max) {
+                    l = this.sp.max;
+                }
+                this.sp.lines = l;
+                return [ x, this.sp.pos + (l * this.sp.lh) ];
+            }.bind(this),
+            onEnd: function() {
+                this.page_size = this.sp.lines;
+                this._renderViewport();
+            }.bind(this)
+        });
+        this.splitbar.observe('dblclick', function() {
+            this.page_size = this.getPageSize('default');
+            this._renderViewport();
+        }.bind(this));
+    },
+
+    createSelection: function(format, data, view)
+    {
+        var buffer = this._getBuffer(view);
+        return buffer ? new ViewPort_Selection(buffer, format, data) : new ViewPort_Selection(this._getBuffer(this.view));
+    },
+
+    getViewportSelection: function(view)
+    {
+        var buffer = this._getBuffer(view);
+        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]
+    select: function(vs, opts)
+    {
+        opts = opts || {};
+
+        if (opts.range) {
+            vs = this._getSlice(vs, this.select.bind(this));
+            if (vs === false) {
+                return;
+            }
+        }
+
+        var b = this._getBuffer(),
+            sel;
+
+        if (!opts.add) {
+            sel = this.getSelected();
+            b.deselect(sel, true);
+            this._updateClass(sel, this.opts.selected_class, false);
+        }
+        b.select(vs);
+        this._updateClass(vs, this.opts.selected_class, true);
+        if (this.opts.selectCallback) {
+            this.opts.selectCallback(vs, opts);
+        }
+    },
+
+    // vs = (Viewport_Selection) A Viewport_Selection object.
+    // opts = (object) TODO [clearall]
+    deselect: function(vs, opts)
+    {
+        opts = opts || {};
+
+        if (!vs.size()) {
+            return;
+        }
+
+        if (this._getBuffer().deselect(vs, opts && opts.clearall)) {
+            this._updateClass(vs, this.opts.selected_class, false);
+            if (this.opts.deselectCallback) {
+                this.opts.deselectCallback(vs, opts)
+            }
+        }
+    },
+
+    getSelected: function()
+    {
+        return Object.clone(this._getBuffer().getSelected());
+    }
+
+}),
+
+/**
+ * ViewPort_Scroller
+ */
+ViewPort_Scroller = Class.create({
+
+    initialize: function(vp)
+    {
+        this.vp = vp;
+    },
+
+    _createScrollBar: function()
+    {
+        if (this.scrollDiv) {
+            return false;
+        }
+
+        var c = this.vp.opts.content;
+
+        // Create the outer div.
+        this.scrollDiv = new Element('DIV', { className: 'sbdiv', style: 'height:' + c.getHeight() + 'px;' }).hide();
+
+        // Add scrollbar to parent viewport and give our parent a right
+        // margin just big enough to accomodate the scrollbar.
+        c.insert({ after: this.scrollDiv }).setStyle({ marginRight: '-' + this.scrollDiv.getWidth() + 'px' });
+
+        // Create scrollbar object.
+        this.scrollbar = new DimpSlider(this.scrollDiv, { buttonclass: { up: 'sbup', down: 'sbdown' }, cursorclass: 'sbcursor', onChange: this._onScroll.bind(this), onSlide: this.vp.opts.onSlide ? this.vp.opts.onSlide : null, pagesize: this.vp.getPageSize(), totalsize: this.vp.getMetaData('total_rows') });
+
+        // Mouse wheel handler.
+        c.observe(Prototype.Browser.Gecko ? 'DOMMouseScroll' : 'mousewheel', function(e) {
+            // Fix issue on FF 3 (as of 3.0) that triggers two events
+            if (Prototype.Browser.Gecko && e.eventPhase == 2) {
+                return;
+            }
+            var move_num = this.vp.getPageSize();
+            move_num = (move_num > 3) ? 3 : move_num;
+            this.moveScroll(this.currentOffset() + ((e.wheelDelta >= 0 || e.detail < 0) ? (-1 * move_num) : move_num));
+        }.bindAsEventListener(this));
+
+        return true;
+    },
+
+    onResize: function()
+    {
+        if (!this.scrollDiv) {
+            return;
+        }
+
+        // Update the container div.
+        this.scrollsize = this.vp.opts.content.getHeight();
+        this.scrollDiv.setStyle({ height: this.scrollsize + 'px' });
+
+        // Update the scrollbar size
+        this.updateSize();
+
+        // Update displayed content.
+        this.vp.requestContentRefresh(this.currentOffset());
+    },
+
+    updateSize: function()
+    {
+        if (!this._createScrollBar()) {
+            this.scrollbar.updateHandleLength(this.vp.getPageSize(), this.vp.getMetaData('total_rows'));
+        }
+    },
+
+    clear: function()
+    {
+        if (this.scrollDiv) {
+            this.scrollbar.updateHandleLength(0, 0);
+        }
+    },
+
+    // offset = (integer) Offset to move the scrollbar to
+    moveScroll: function(offset)
+    {
+        this._createScrollBar();
+        this.scrollbar.setScrollPosition(offset);
+    },
+
+    _onScroll: function()
+    {
+        if (!this.noupdate) {
+            if (this.vp.opts.onScroll) {
+                this.vp.opts.onScroll();
+            }
+
+            this.vp.requestContentRefresh(this.currentOffset());
+
+            if (this.vp.opts.onScrollIdle) {
+                this.vp.opts.onScrollIdle();
+            }
+        }
+    },
+
+    currentOffset: function()
+    {
+        return this.scrollbar ? this.scrollbar.getValue() : 0;
+    }
+
+}),
+
+/**
+ * ViewPort_Buffer
+ *
+ * Note: recognize the difference between offset (current location in the
+ * viewport - starts at 0) with start parameters (the row numbers - starts
+ * at 1).
+ */
+ViewPort_Buffer = Class.create({
+
+    initialize: function(vp, b_pages, l_factor)
+    {
+        this.bufferPages = b_pages;
+        this.limitFactor = l_factor;
+        this.vp = vp;
+        this.clear();
+    },
+
+    _limitTolerance: function()
+    {
+        return Math.round(this.bufferSize() * (this.limitFactor / 100));
+    },
+
+    bufferSize: function()
+    {
+        // Buffer size must be at least the maximum page size.
+        return Math.round(Math.max(this.vp.getPageSize('max') + 1, this.bufferPages * this.vp.getPageSize()));
+    },
+
+    // d = TODO
+    // l = TODO
+    // opts = (object) TODO [slice, update]
+    update: function(d, l, opts)
+    {
+        d = $H(d);
+        l = $H(l);
+        opts = opts || {};
+
+        if (opts.slice) {
+            d.each(function(o) {
+                if (!this.data.get(o.key)) {
+                    this.data.set(o.key, o.value);
+                    this.inc.set(o.key, true);
+                }
+            }, this);
+        } else {
+            if (this.data.size()) {
+                this.data.update(d);
+                if (this.inc.size()) {
+                    d.keys().each(function(k) {
+                        this.inc.unset(k);
+                    }, this);
+                }
+            } else {
+                this.data = d;
+            }
+        }
+
+        this.uidlist = (opts.update) ? l : (this.uidlist.size() ? this.uidlist.merge(l) : l);
+
+        if (opts.update) {
+            this.rowlist = $H();
+        }
+        l.each(function(o) {
+            this.rowlist.set(o.value, o.key);
+        }, this);
+    },
+
+    // offset = (integer) Offset of the beginning of the slice.
+    sliceLoaded: function(offset)
+    {
+        return !this._rangeCheck($A($R(offset + 1, Math.min(offset + this.vp.getPageSize() - 1, this.getMetaData('total_rows')))));
+    },
+
+    isNearingLimit: function(offset)
+    {
+        if (this.uidlist.size() != this.getMetaData('total_rows')) {
+            if (offset != 0 &&
+                this._rangeCheck($A($R(Math.max(offset + 1 - this._limitTolerance(), 1), offset)))) {
+                return 'top';
+            } else if (this._rangeCheck($A($R(offset + 1, Math.min(offset + this._limitTolerance() + this.vp.getPageSize() - 1, this.getMetaData('total_rows')))).reverse())) {
+                // Search for missing messages in reverse order since in
+                // normal usage (sequential scrolling through the message
+                // list) messages are more likely to be missing at furthest
+                // from the current view.
+                return 'bottom';
+            }
+        }
+        return false;
+    },
+
+    _rangeCheck: function(range)
+    {
+        var i = this.inc.size();
+        return range.any(function(o) {
+            var g = this.rowlist.get(o);
+            return (Object.isUndefined(g) || (i && this.inc.get(g)));
+        }, this);
+    },
+
+    getData: function(uids)
+    {
+        return uids.collect(function(u) {
+            var e = this.data.get(u);
+            if (!Object.isUndefined(e)) {
+                // We can directly write the rownum to the original object
+                // since we will always rewrite when creating rows.
+                e.domid = 'vp_row' + u;
+                e.rownum = this.uidlist.get(u);
+                e.vp_id = u;
+                return e;
+            }
+        }, this).compact();
+    },
+
+    getAllUIDs: function()
+    {
+        return this.uidlist.keys();
+    },
+
+    getAllRows: function()
+    {
+        return this.rowlist.keys();
+    },
+
+    rowsToUIDs: function(rows)
+    {
+        return rows.collect(function(n) {
+            return this.rowlist.get(n);
+        }, this).compact();
+    },
+
+    // vs = (Viewport_Selection) TODO
+    select: function(vs)
+    {
+        this.selected.add('uid', vs.get('uid'));
+    },
+
+    // vs = (Viewport_Selection) TODO
+    // clearall = (boolean) Clear all entries?
+    deselect: function(vs, clearall)
+    {
+        var size = this.selected.size();
+
+        if (clearall) {
+            this.selected.clear();
+        } else {
+            this.selected.remove('uid', vs.get('uid'));
+        }
+        return size != this.selected.size();
+    },
+
+    getSelected: function()
+    {
+        return this.selected;
+    },
+
+    // rownums = (array) Array of row numbers to remove.
+    remove: function(rownums)
+    {
+        var newsize,
+            rowsize = this.rowlist.size(),
+            rowsubtract = 0;
+        newsize = rowsize - rownums.size();
+
+        return $A($R(rownums.min(), rowsize)).each(function(n) {
+            var id = this.rowlist.get(n), r;
+            if (rownums.include(n)) {
+                this.data.unset(id);
+                this.uidlist.unset(id);
+                rowsubtract++;
+            } else {
+                r = n - rowsubtract;
+                this.rowlist.set(r, id);
+                this.uidlist.set(id, r);
+            }
+            if (n > newsize) {
+                this.rowlist.unset(n);
+            }
+        }, this);
+    },
+
+    clear: function()
+    {
+        this.data = $H();
+        this.inc = $H();
+        this.mdata = $H({ total_rows: 0 });
+        this.rowlist = $H();
+        this.selected = new ViewPort_Selection(this);
+        this.uidlist = $H();
+    },
+
+    getMetaData: function(id)
+    {
+        return this.mdata.get(id);
+    },
+
+    setMetaData: function(vals)
+    {
+        this.mdata.update(vals);
+    }
+
+}),
+
+/**
+ * ViewPort_Filter
+ */
+ViewPort_Filter = Class.create({
+
+    initialize: function(vp, action, callback)
+    {
+        this.vp = vp;
+        this.action = action;
+        this.callback = callback;
+
+        // Initialize other variables
+        this.filterid = 0;
+        this.filtering = this.last_filter = this.last_folder = this.last_folder_params = null;
+    },
+
+    // val = (string) The string to filter on. if null, will use the last
+    //                filter string.
+    filter: function(val, params)
+    {
+        params = params || {};
+
+        if (val === null) {
+            val = this.last_filter;
+        } else {
+            val = val.toLowerCase();
+            if (val == this.last_filter) {
+                return;
+            }
+        }
+
+        if (!val) {
+            this.clear();
+            return;
+        }
+
+        this.last_filter = val;
+
+        if (this.filtering) {
+            this.vp._fetchBuffer({ offset: 0, params: params });
+            return;
+        }
+
+        this.filtering = ++this.filterid + '%search%';
+        this.last_folder = this.vp.view;
+        this.last_folder_params = this.vp.getMetaData('additional_params').merge(params);
+
+        // Filter visible rows immediately.
+        var c = this.vp.opts.content, delrows;
+        delrows = c.childElements().findAll(function(n) {
+            return n.collectTextNodes().toLowerCase().indexOf(val) == -1;
+        });
+        if (this.vp.opts.onClearRows) {
+            this.vp.opts.onClearRows(delrows);
+        }
+        delrows.invoke('remove');
+        this.vp.scroller.clear();
+        if (this.vp.opts.empty && !c.childElements().size()) {
+            c.update(this.vp.opts.empty.innerHTML);
+        }
+
+        this.vp.loadView(this.filtering, this.last_folder_params);
+    },
+
+    isFiltering: function()
+    {
+        return this.filtering;
+    },
+
+    getAction: function()
+    {
+        return this.action;
+    },
+
+    // params is a Hash object
+    addFilterParams: function(params)
+    {
+        if (!this.filtering) {
+            return params;
+        }
+
+        params.update({ filter: this.last_filter });
+
+        // Get parameters from a callback function, if defined.
+        if (this.callback) {
+            params.update(this.callback());
+        }
+
+        return params;
+    },
+
+    clear: function(reset)
+    {
+        if (this.filtering) {
+            this.filtering = null;
+            if (!reset) {
+                this.vp.loadView(this.last_folder, this.last_folder_params);
+            }
+            this.vp.deleteView(this.filtering);
+            this.last_filter = this.last_folder = null;
+        }
+    }
+
+}),
+
+/**
+ * ViewPort_Selection
+ */
+ViewPort_Selection = Class.create({
+    // Formats:
+    //     'dataob' = Data objects
+    //     'div' = DOM DIVs
+    //     'domid' = DOM IDs
+    //     'rownum' = Row numbers
+    //     'uid' = Unique IDs
+    initialize: function(buffer, format, data)
+    {
+        this.buffer = buffer;
+        this.clear();
+        if (!Object.isUndefined(format)) {
+            this.add(format, data);
+        }
+
+        // Define property to aid in object detection
+        this.viewport_selection = true;
+    },
+
+    add: function(format, d)
+    {
+        var c = this._convert(format, d);
+        this.data = (this.data.size()) ? this.data.concat(c).uniq() : c;
+    },
+
+    remove: function(format, d)
+    {
+        this.data = this.data.diff(this._convert(format, d));
+    },
+
+    _convert: function(format, d)
+    {
+        d = Object.isArray(d) ? d : [ d ];
+        switch (format) {
+        case 'dataob':
+            return d.pluck('vp_id');
+
+        case 'div':
+            return d.pluck('id').invoke('substring', 6);
+
+        case 'domid':
+            return d.invoke('substring', 6);
+
+        case 'rownum':
+            return this.buffer.rowsToUIDs(d);
+
+        case 'uid':
+            return d;
+        }
+    },
+
+    clear: function()
+    {
+        this.data = [];
+    },
+
+    get: function(format)
+    {
+        format = Object.isUndefined(format) ? 'uid' : format;
+        if (format == 'uid') {
+            return this.data;
+        }
+        var d = this.buffer.getData(this.data);
+
+        switch (format) {
+        case 'dataob':
+            return d;
+
+        case 'div':
+            return d.pluck('domid').collect(function(e) { return $(e); }).compact();
+
+        case 'domid':
+            return d.pluck('domid');
+
+        case 'rownum':
+            return d.pluck('rownum');
+        }
+    },
+
+    contains: function(format, d)
+    {
+        return this.data.include(this._convert(format, d).first());
+    },
+
+    // params = (Object) Key is search key, value is object -> key of object
+    // must be the following:
+    //   equal - Matches any value contained in the query array.
+    //   not - Matches any value not contained in the query array.
+    //   regex - Matches the RegExp contained in the query.
+    search: function(params)
+    {
+        return new ViewPort_Selection(this.buffer, 'uid', this.get('dataob').findAll(function(i) {
+            // i = data object
+            return $H(params).all(function(k) {
+                // k.key = search key; k.value = search criteria
+                return $H(k.value).all(function(s) {
+                    // s.key = search type; s.value = search query
+                    switch (s.key) {
+                    case 'equal':
+                    case 'not':
+                        var r = i[k.key] && s.value.include(i[k.key]);
+                        return (s.key == 'equal') ? r : !r;
+
+                    case 'regex':
+                        return i[k.key].match(s.value);
+                    }
+                });
+            });
+        }).pluck('vp_id'));
+    },
+
+    size: function()
+    {
+        return this.data.size();
+    },
+
+    set: function(vals)
+    {
+        this.get('dataob').each(function(d) {
+            $H(vals).each(function(v) {
+                d[v.key] = v.value;
+            });
+        });
+    }
+
+});
+
+/** Utility Functions **/
+Object.extend(Array.prototype, {
+    // Need our own diff() function because prototypejs's without() function
+    // does not handle array input.
+    diff: function(values) {
+        return this.select(function(value) {
+            return !values.include(value);
+        });
+    },
+    numericSort: function() {
+        return this.sort(function(a,b) { return (a > b) ? 1 : ((a < b) ? -1 : 0); });
+    }
+});
diff --git a/imp/js/src/compose-dimp.js b/imp/js/src/compose-dimp.js
new file mode 100644 (file)
index 0000000..055357d
--- /dev/null
@@ -0,0 +1,718 @@
+/**
+ * compose.js - Javascript code used in the DIMP compose view.
+ *
+ * $Horde: dimp/js/src/compose.js,v 1.123 2008/10/20 03:54:51 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+var DimpCompose = {
+    // Variables defaulting to empty/false:
+    //   auto_save_interval, button_pressed, compose_cursor, dbtext,
+    //   editor_on, mp_padding, resizebcc, resizecc, resizeto, row_height,
+    //   sbtext, uploading
+    last_msg: '',
+    textarea_ready: true,
+
+    confirmCancel: function()
+    {
+        if (window.confirm(DIMP.text_compose.cancel)) {
+            if (DIMP.conf_compose.auto_save_interval_val) {
+                DimpCore.doAction('DeleteDraft', { index: $F('index') });
+            }
+            return this._closeCompose();
+        }
+    },
+
+    _closeCompose: function()
+    {
+        if (DIMP.conf_compose.qreply) {
+            this.closeQReply();
+        } else if (DIMP.baseWindow || DIMP.conf_compose.popup) {
+            DimpCore.closePopup();
+        } else {
+            DimpCore.redirect(DIMP.conf.URI_DIMP_INBOX);
+        }
+    },
+
+    closeQReply: function()
+    {
+        var al = $('attach_list').childElements();
+        this.last_msg = '';
+
+        if (al.size()) {
+            this.removeAttach(al);
+        }
+
+        $('draft_index', 'composeCache').invoke('setValue', '');
+        $('qreply', 'sendcc', 'sendbcc').invoke('hide');
+        [ $('msgData'), $('togglecc').up(), $('togglebcc').up() ].invoke('show');
+        if (this.editor_on) {
+            this.toggleHtmlEditor();
+        }
+        $('compose').reset();
+
+        // Disable auto-save-drafts now.
+        if (this.auto_save_interval) {
+            this.auto_save_interval.stop();
+        }
+    },
+
+    change_identity: function()
+    {
+        var lastSignature, msg, nextSignature, pos,
+            id = $F('identity'),
+            last = this.get_identity($F('last_identity')),
+            msgval = $('message'),
+            next = this.get_identity(id),
+            ssm = $('save_sent_mail');
+
+        $('sent_mail_folder_label').setText(next.id[5]);
+        $('bcc').setValue(next.id[6]);
+        if (ssm) {
+            ssm.writeAttribute('checked', next.id[4]);
+        }
+
+        // Finally try and replace the signature.
+        if (this.editor_on) {
+            msg = FCKeditorAPI.GetInstance('message').GetHTML().replace(/\r\n/g, '\n');
+            lastSignature = '<p><!--begin_signature--><!--end_signature--></p>';
+            nextSignature = '<p><!--begin_signature-->' + next.sig.replace(/^ ?<br \/>\n/, '').replace(/ +/g, ' ') + '<!--end_signature--></p>';
+
+            // Dot-all functionality achieved with [\s\S], see:
+            // http://simonwillison.net/2004/Sep/20/newlines/
+            msg = msg.replace(/<p>\s*<!--begin_signature-->[\s\S]*?<!--end_signature-->\s*<\/p>/, lastSignature);
+        } else {
+            msg = $F(msgval).replace(/\r\n/g, '\n');
+            lastSignature = last.sig;
+            nextSignature = next.sig;
+        }
+
+        pos = (last.id[2])
+            ? msg.indexOf(lastSignature)
+            : msg.lastIndexOf(lastSignature);
+
+        if (pos != -1) {
+            if (next.id[2] == last.id[2]) {
+                msg = msg.substring(0, pos) + nextSignature + msg.substring(pos + lastSignature.length, msg.length);
+            } else if (next.id[2]) {
+                msg = nextSignature + msg.substring(0, pos) + msg.substring(pos + lastSignature.length, msg.length);
+            } else {
+                msg = msg.substring(0, pos) + msg.substring(pos + lastSignature.length, msg.length) + nextSignature;
+            }
+
+            msg = msg.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
+            if (this.editor_on) {
+                FCKeditorAPI.GetInstance('message').SetHTML(msg);
+            } else {
+                msgval.setValue(msg);
+            }
+            $('last_identity').setValue(id);
+        }
+    },
+
+    get_identity: function(id, editor_on)
+    {
+        editor_on = Object.isUndefined(editor_on) ? this.editor_on : editor_on;
+        return {
+            id: DIMP.conf_compose.identities[id],
+            sig: DIMP.conf_compose.identities[id][(editor_on ? 1 : 0)].replace(/^\n/, '')
+        };
+    },
+
+    uniqueSubmit: function(action)
+    {
+        var db, params, sb,
+            c = $('compose');
+
+        if (DIMP.SpellCheckerObject) {
+            DIMP.SpellCheckerObject.resume();
+            if (!this.textarea_ready) {
+                this.uniqueSubmit.bind(this, action).defer();
+                return;
+            }
+        }
+
+        c.setStyle({ cursor: 'wait' });
+
+        if (action == 'send_message' || action == 'save_draft') {
+            this.button_pressed = true;
+
+            switch (action) {
+            case 'send_message':
+                if (!this.sbtext) {
+                    sb = $('send_button');
+                    this.sbtext = sb.getText();
+                    sb.setText(DIMP.text_compose.sending);
+                }
+                break;
+
+            case 'save_draft':
+                if (!this.dbtext) {
+                    db = $('draft_button');
+                    this.dbtext = db.getText();
+                    db.setText(DIMP.text_compose.saving);
+                }
+                break;
+            }
+
+            // Don't send/save until uploading is completed.
+            if (this.uploading) {
+                (function() { if (this.button_pressed) { this.uniqueSubmit(action); } }).bind(this).delay(0.25);
+                return;
+            }
+        }
+        $('action').setValue(action);
+
+        if (action == 'add_attachment') {
+            // We need a submit action here because browser security models
+            // won't let us access files on user's filesystem otherwise.
+            this.uploading = true;
+            c.submit();
+        } else {
+            // Move HTML text to textarea field for submission.
+            if (this.editor_on) {
+                FCKeditorAPI.GetInstance('message').UpdateLinkedField();
+            }
+
+            // Use an AJAX submit here so that we can do javascript-y stuff
+            // before having to close the window on success.
+            params = c.serialize(true);
+            if (!DIMP.baseWindow) {
+                params.nonotify = true;
+            }
+            DimpCore.doAction('*' + DIMP.conf.compose_url, params, null, this.uniqueSubmitCallback.bind(this));
+        }
+    },
+
+    uniqueSubmitCallback: function(r)
+    {
+        var elt,
+            d = r.response;
+
+        if (!d) {
+            return;
+        }
+
+        if (d.imp_compose) {
+            $('composeCache').setValue(d.imp_compose);
+        }
+
+        if (d.success || d.action == 'add_attachment') {
+            switch (d.action) {
+            case 'auto_save_draft':
+                this.button_pressed = false;
+                $('draft_index').setValue(d.draft_index);
+                break;
+
+            case 'save_draft':
+                this.button_pressed = false;
+                if (DIMP.baseWindow) {
+                    DIMP.baseWindow.DimpBase.pollFolders();
+                    DIMP.baseWindow.DimpCore.showNotifications(r.msgs);
+                }
+                if (DIMP.conf_compose.close_draft) {
+                    return this._closeCompose();
+                }
+                break;
+
+            case 'send_message':
+                this.button_pressed = false;
+                if (DIMP.baseWindow) {
+                    if (d.reply_type == 'reply') {
+                        DIMP.baseWindow.DimpBase.flag('answered', d.index, d.reply_folder);
+                    }
+
+                    if (d.folder) {
+                        DIMP.baseWindow.DimpBase.createFolder(d.folder);
+                    }
+
+                    if (d.draft_delete) {
+                        DIMP.baseWindow.DimpBase.pollFolders();
+                    }
+
+                    DIMP.baseWindow.DimpCore.showNotifications(r.msgs);
+                }
+                return this._closeCompose();
+
+            case 'add_attachment':
+                this.uploading = false;
+                if (d.success) {
+                    this.addAttach(d.info.number, d.info.name, d.info.type, d.info.size);
+                } else {
+                    this.button_pressed = false;
+                }
+                if (DIMP.conf_compose.attach_limit != -1 &&
+                    $('attach_list').childElements().size() > DIMP.conf_compose.attach_limit) {
+                    $('upload').writeAttribute('disabled', false);
+                    elt = new Element('DIV', [ DIMP.text_compose.attachment_limit ]);
+                } else {
+                    elt = new Element('INPUT', { type: 'file', name: 'file_1' });
+                    elt.observe('change', this.uploadAttachment.bind(this));
+                }
+                $('upload_wait').replace(elt.writeAttribute('id', 'upload'));
+                this.resizeMsgArea();
+                break;
+            }
+        } else {
+            this.button_pressed = false;
+        }
+
+        $('compose').setStyle({ cursor: null });
+
+        // Re-enable buttons if needed.
+        if (!this.button_pressed) {
+            if (this.sbtext) {
+                $('send_button').setText(this.sbtext);
+            }
+            if (this.dbtext) {
+                $('draft_button').setText(this.dbtext);
+            }
+            this.dbtext = this.sbtext = null;
+        }
+
+        DimpCore.showNotifications(r.msgs);
+    },
+
+    toggleHtmlEditor: function(noupdate)
+    {
+        if (!DIMP.conf_compose.rte_avail) {
+            return;
+        }
+        noupdate = noupdate || false;
+        if (DIMP.SpellCheckerObject) {
+            DIMP.SpellCheckerObject.resume();
+        }
+
+        var text;
+
+        if (this.editor_on) {
+            this.editor_on = false;
+
+            text = FCKeditorAPI.GetInstance('message').GetHTML();
+            $('messageParent').childElements().invoke('hide');
+            $('message').show();
+
+            DimpCore.doAction('Html2Text', { text: text }, null, this.setMessageText.bind(this), { asynchronous: false });
+        } else {
+            this.editor_on = true;
+            if (!noupdate) {
+                DimpCore.doAction('Text2Html', { text: $F('message') }, null, this.setMessageText.bind(this), { asynchronous: false });
+            }
+
+            oFCKeditor.Height = this.getMsgAreaHeight();
+            // Try to reuse the old fckeditor instance.
+            try {
+                FCKeditorAPI.GetInstance('message').SetHTML($F('message'));
+                $('messageParent').childElements().invoke('show');
+                $('message').hide();
+            } catch (e) {
+                this._RTELoading('show');
+                FCKeditor_OnComplete = this._RTELoading.curry('hide');
+                oFCKeditor.ReplaceTextarea();
+            }
+        }
+        $('htmlcheckbox').checked = this.editor_on;
+        $('html').setValue(this.editor_on ? 1 : 0);
+    },
+
+    _RTELoading: function(cmd)
+    {
+        var o, r;
+        if (!$('rteloading')) {
+            r = new Element('DIV', { id: 'rteloading' }).clonePosition($('messageParent'));
+            $(document.body).insert(r);
+            o = r.viewportOffset();
+            $(document.body).insert(new Element('SPAN', { id: 'rteloadingtxt' }).setStyle({ top: (o.top + 15) + 'px', left: (o.left + 15) + 'px' }).insert(DIMP.text.loading));
+        }
+        $('rteloading', 'rteloadingtxt').invoke(cmd);
+    },
+
+    toggleHtmlCheckbox: function()
+    {
+        if (!this.editor_on || window.confirm(DIMP.text_compose.toggle_html)) {
+            this.toggleHtmlEditor();
+        }
+    },
+
+    getMsgAreaHeight: function()
+    {
+        return document.viewport.getHeight() - $('messageParent').cumulativeOffset()[1] - this.mp_padding;
+    },
+
+    initializeSpellChecker: function()
+    {
+        if (!DIMP.conf_compose.rte_avail) {
+            return;
+        }
+
+        if (typeof DIMP.SpellCheckerObject != 'object') {
+            // If we fired before the onload that initializes the spellcheck,
+            // wait.
+            this.initializeSpellChecker.bind(this).defer();
+            return;
+        }
+
+        DIMP.SpellCheckerObject.onBeforeSpellCheck = function() {
+            if (!this.editor_on) {
+                return;
+            }
+            DIMP.SpellCheckerObject.htmlAreaParent = 'messageParent';
+            DIMP.SpellCheckerObject.htmlArea = $('message').adjacent('iframe[id*=message]').first();
+            $('message').setValue(FCKeditorAPI.GetInstance('message').GetHTML());
+            this.textarea_ready = false;
+        }.bind(this);
+        DIMP.SpellCheckerObject.onAfterSpellCheck = function() {
+            if (!this.editor_on) {
+                return;
+            }
+            DIMP.SpellCheckerObject.htmlArea = DIMP.SpellCheckerObject.htmlAreaParent = null;
+            var ed = FCKeditorAPI.GetInstance('message');
+            ed.SetHTML($F('message'));
+            ed.Events.AttachEvent('OnAfterSetHTML', function() { this.textarea_ready = true; }.bind(this));
+        }.bind(this);
+    },
+
+    setMessageText: function(r)
+    {
+        var ta = $('message');
+        if (!ta) {
+            $('messageParent').insert(new Element('TEXTAREA', { id: 'message', name: 'message', style: 'width:100%;' }).insert(r.response.text));
+        } else {
+            ta.setValue(r.response.text);
+        }
+
+        if (!this.editor_on) {
+            this.resizeMsgArea();
+        }
+    },
+
+    fillForm: function(msg, header, focus, noupdate)
+    {
+        // On IE, this can get loaded before DOM;loaded. Check for an init
+        // value and don't load until it is available.
+        if (!this.resizeto) {
+            this.fillForm.bind(this, msg, header, focus, noupdate).defer();
+            return;
+        }
+
+        var bcc_add, fo,
+            identity = this.get_identity($F('last_identity')),
+            msgval = $('message');
+
+        if (!this.last_msg.empty() &&
+            this.last_msg != $F(msgval).replace(/\r/g, '') &&
+            !window.confirm(DIMP.text_compose.fillform)) {
+            return;
+        }
+
+        // Set auto-save-drafts now if not already active.
+        if (DIMP.conf_compose.auto_save_interval_val && !this.auto_save_interval) {
+            this.auto_save_interval = new PeriodicalExecuter(function() {
+                var cur_msg;
+                if (this.editor_on) {
+                    cur_msg = FCKeditorAPI.GetInstance('message').GetHTML();
+                } else {
+                    cur_msg = $F(msgval);
+                }
+                cur_msg = cur_msg.replace(/\r/g, '');
+                if (!cur_msg.empty() && this.last_msg != cur_msg) {
+                    this.uniqueSubmit('auto_save_draft');
+                    this.last_msg = cur_msg;
+                }
+            }.bind(this), DIMP.conf_compose.auto_save_interval_val * 60);
+        }
+
+        if (this.editor_on) {
+            fo = FCKeditorAPI.GetInstance('message');
+            fo.SetHTML(msg);
+            this.last_msg = fo.GetHTML().replace(/\r/g, '');
+        } else {
+            msgval.setValue(msg);
+            this.setCursorPosition(msgval);
+            this.last_msg = $F(msgval).replace(/\r/g, '');
+        }
+
+        $('to').setValue(header.to);
+        this.resizeto.resizeNeeded();
+        if (header.cc) {
+            $('cc').setValue(header.cc);
+            this.resizecc.resizeNeeded();
+        }
+        if (DIMP.conf_compose.cc) {
+            this.toggleCC('cc');
+        }
+        if (header.bcc) {
+            $('bcc').setValue(header.bcc);
+            this.resizebcc.resizeNeeded();
+        }
+        if (identity.id[6]) {
+            bcc_add = $F('bcc');
+            if (bcc_add) {
+                bcc_add += ', ';
+            }
+            $('bcc').setValue(bcc_add + identity.id[6]);
+        }
+        if (DIMP.conf_compose.bcc) {
+            this.toggleCC('bcc');
+        }
+        $('subject').setValue(header.subject);
+        $('in_reply_to').setValue(header.in_reply_to);
+        $('references').setValue(header.references);
+        $('reply_type').setValue(header.replytype);
+
+        Field.focus(focus || 'to');
+        this.resizeMsgArea();
+
+        if (DIMP.conf_compose.show_editor) {
+            if (!this.editor_on) {
+                this.toggleHtmlEditor(noupdate || false);
+            }
+            if (focus == 'message') {
+                this.focusEditor();
+            }
+        }
+    },
+
+    focusEditor: function()
+    {
+        try {
+            FCKeditorAPI.GetInstance('message').Focus();
+        } catch (e) {
+            this.focusEditor.bind(this).defer();
+        }
+    },
+
+    addAttach: function(number, name, type, size)
+    {
+        var div = new Element('DIV').insert(name + ' [' + type + '] (' + size + ' KB) '),
+            input = new Element('INPUT', { type: 'button', atc_id: number, value: DIMP.text_compose.remove });
+        div.insert(input);
+        $('attach_list').insert(div);
+        input.observe('click', this.removeAttach.bind(this, [ input.up() ]));
+        this.resizeMsgArea();
+    },
+
+    removeAttach: function(e)
+    {
+        var ids = [];
+        e.each(function(n) {
+            n = $(n);
+            ids.push(n.firstDescendant().readAttribute('atc_id'));
+            n.remove();
+        });
+        DimpCore.doAction('DeleteAttach', { atc_indices: ids, imp_compose: $F('composeCache') });
+        this.resizeMsgArea();
+    },
+
+    resizeMsgArea: function()
+    {
+        var m, rows,
+            de = document.documentElement,
+            msg = $('message');
+
+        if (!DimpCore.window_load) {
+            this.resizeMsgArea.bind(this).defer();
+            return;
+        }
+
+        if (this.editor_on) {
+            m = $('messageParent').select('iframe').last();
+            if (m) {
+                m.setStyle({ height: this.getMsgAreaHeight() + 'px' });
+            } else {
+                this.resizeMsgArea.bind(this).defer();
+            }
+            return;
+        }
+
+        this.mp_padding = $('messageParent').getHeight() - msg.getHeight();
+
+        if (!this.row_height) {
+            // Change the ID and name to not conflict with msg node.
+            m = $(msg.cloneNode(false)).writeAttribute({ id: null, name: null }).setStyle({ visibility: 'hidden' });
+            $(document.body).insert(m);
+            m.writeAttribute('rows', 1);
+            this.row_height = m.getHeight();
+            m.writeAttribute('rows', 2);
+            this.row_height = m.getHeight() - this.row_height;
+            m.remove();
+        }
+
+        /* Logic: Determine the size of a given textarea row, divide that size
+         * by the available height, round down to the lowest integer row, and
+         * resize the textarea. */
+        rows = parseInt(this.getMsgAreaHeight() / this.row_height);
+        msg.writeAttribute({ rows: rows, disabled: false });
+        if (de.scrollHeight - de.clientHeight) {
+            msg.writeAttribute({ rows: rows - 1 });
+        }
+
+        $('composeloading').hide();
+    },
+
+    uploadAttachment: function()
+    {
+        var u = $('upload');
+        $('submit_frame').observe('load', this.attachmentComplete.bind(this));
+        this.uniqueSubmit('add_attachment');
+        u.stopObserving('change').replace(new Element('DIV', { id: 'upload_wait' }).insert(DIMP.text_compose.uploading + ' ' + $F(u)));
+    },
+
+    attachmentComplete: function()
+    {
+        var sf = $('submit_frame'),
+            doc = sf.contentDocument || sf.contentWindow.document;
+        sf.stopObserving('load');
+        DimpCore.doActionComplete({ responseText: doc.body.innerHTML }, this.uniqueSubmitCallback.bind(this));
+    },
+
+    toggleCC: function(type)
+    {
+        $('send' + type).show();
+        $('toggle' + type).up().hide();
+    },
+
+    /* Sets the cursor to the given position. */
+    setCursorPosition: function(input)
+    {
+        var pos, range;
+
+        switch (DIMP.conf_compose.compose_cursor) {
+        case 'top':
+            pos = 0;
+            $('message').setValue('\n' + $F('message'));
+            break;
+
+        case 'bottom':
+            pos = $F('message').length;
+            break;
+
+        case 'sig':
+            pos = $F('message').replace(/\r\n/g, '\n').lastIndexOf(this.get_identity($F('last_identity')).sig) - 1;
+            break;
+
+        default:
+            return;
+        }
+
+        if (input.setSelectionRange) {
+            /* This works in Mozilla */
+            Field.focus(input);
+            input.setSelectionRange(pos, pos);
+        } else if (input.createTextRange) {
+            /* This works in IE */
+            range = input.createTextRange();
+            range.collapse(true);
+            range.moveStart('character', pos);
+            range.moveEnd('character', 0);
+            Field.select(range);
+            range.scrollIntoView(true);
+        }
+    },
+
+    /* Open the addressbook window. */
+    openAddressbook: function()
+    {
+        window.open(DIMP.conf_compose.abook_url, 'contacts', 'toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes,width=550,height=300,left=100,top=100');
+    }
+},
+
+ResizeTextArea = Class.create({
+    // Variables defaulting to empty:
+    //   defaultRows, field, onResize
+    maxRows: 5,
+
+    initialize: function(field, onResize)
+    {
+        this.field = $(field);
+
+        this.defaultRows = Math.max(this.field.readAttribute('rows'), 1);
+        this.onResize = onResize;
+
+        var func = this.resizeNeeded.bindAsEventListener(this);
+        this.field.observe('mousedown', func).observe('keyup', func);
+
+        this.resizeNeeded();
+    },
+
+    resizeNeeded: function()
+    {
+        var lines = $F(this.field).split('\n'),
+            cols = this.field.readAttribute('cols'),
+            newRows = lines.size(),
+            oldRows = this.field.readAttribute('rows');
+
+        lines.each(function(line) {
+            if (line.length >= cols) {
+                newRows += Math.floor(line.length / cols);
+            }
+        });
+
+        if (newRows != oldRows) {
+            this.field.writeAttribute('rows', (newRows > oldRows) ? Math.min(newRows, this.maxRows) : Math.max(this.defaultRows, newRows));
+
+            if (this.onResize) {
+                this.onResize();
+            }
+        }
+    }
+});
+
+document.observe('dom:loaded', function() {
+    var tmp,
+        DC = DimpCompose,
+        boundResize = DC.resizeMsgArea.bind(DC),
+        C = DimpCore.clickObserveHandler;
+
+    DC.resizeMsgArea();
+    DC.initializeSpellChecker();
+    $('upload').observe('change', DC.uploadAttachment.bind(DC));
+
+    // Automatically resize address fields.
+    DC.resizeto = new ResizeTextArea('to', boundResize);
+    DC.resizecc = new ResizeTextArea('cc', boundResize);
+    DC.resizebcc = new ResizeTextArea('bcc', boundResize);
+
+    // Safari requires a submit target iframe to be at least 1x1 size or else
+    // it will open content in a new window.  See:
+    //   http://blog.caboo.se/articles/2007/4/2/ajax-file-upload
+    if (Prototype.Browser.WebKit) {
+        $('submit_frame').writeAttribute({ position: 'absolute', width: '1px', height: '1px' }).setStyle({ left: '-999px' }).show();
+    }
+
+    /* Attach click handlers. */
+    if (tmp = $('compose_close')) {
+        C({ d: tmp, f: DC.confirmCancel.bind(DC) });
+    }
+    C({ d: $('send_button'), f: DC.uniqueSubmit.bind(DC, 'send_message') });
+    C({ d: $('draft_button'), f: DC.uniqueSubmit.bind(DC, 'save_draft') });
+    [ 'cc', 'bcc' ].each(function(a) {
+        C({ d: $('toggle' + a), f: DC.toggleCC.bind(DC, a) });
+    });
+    if (tmp = $('htmlcheckbox')) {
+        C({ d: tmp, f: DC.toggleHtmlCheckbox.bind(DC), ns: true });
+    }
+    if (tmp = $('compose_specialchars')) {
+        C({ d: tmp, f: function() { window.open(DIMP.conf_compose.specialchars_url, 'chars', 'height=220,width=400'); } });
+    }
+
+    $('writemsg').select('.composeAddrbook').each(function(a) {
+        C({ d: a, f: DC.openAddressbook.bind(DC) });
+    });
+
+    /* Only allow submit through send button. */
+    $('compose').observe('submit', Event.stop);
+
+    /* Attach other handlers. */
+    $('identity').observe('change', DC.change_identity.bind(DC));
+
+    // Various events that may cause the textarea to grow larger than the
+    // window size.
+    $('togglecc').observe('click', boundResize);
+    $('togglebcc').observe('click', boundResize);
+    Event.observe(window, 'resize', boundResize);
+});
diff --git a/imp/js/src/dragdrop.js b/imp/js/src/dragdrop.js
new file mode 100644 (file)
index 0000000..3d4b21e
--- /dev/null
@@ -0,0 +1,469 @@
+/**
+ * dragdrop.js - A minimalist library to handle drag/drop actions.
+ * Requires prototype.js 1.6.0.2+
+ *
+ * Adapted from SkyByte.js/SkyByteDD.js v1.0-beta, May 17 2007
+ *   (c) 2007 Aleksandras Ilarionovas (Alex)
+ *   http://www.skybyte.net/scripts/
+ *
+ * Scrolling and ghosting code adapted from script.aculo.us dragdrop.js v1.8.0
+ *   (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+ *   (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
+ *
+ * The original scripts were freely distributable under the terms of an
+ * MIT-style license.
+ *
+ * Usage:
+ *   new Drag(element, {
+ *       classname: '',           // Class name of the drag element
+ *       caption: '',             // Either string or function to set caption
+ *                                // on mouse move
+ *       ghosting: false,         // Show ghost outline when dragging.
+ *       offset: { x:0, y:0 },    // An offset to apply to ghosted elements.
+ *       scroll: element,         // Scroll this element when above/below.
+ *                                // Only working for vertical elements
+ *       snap: null,              // If ghosting, snap allows to specify
+ *                                // coords at which the ghosted image will
+ *                                // "snap" into place.
+ *       threshold: 0,            // Move threshold
+ *       // For the following functions, d = drop element, e = event object
+ *       onStart: function(d,e),  // A function to run on mousedown
+ *       onDrag: function(d,e),   // A function to run on mousemove
+ *       onEnd: function(d,e)     // A function to run on mouseup
+ *   });
+ *
+ *   new Drop(element, {
+ *       accept: [],           // Accept filter by tag name(s) or leave empty
+ *                             // to accept all tags
+ *       caption: '',          // Either string or function to set caption on
+ *                             // mouse over
+ *       hoverclass: '',       // Change the drag element to this class when
+ *                             // hovering over an element.
+ *       onDrop: function(drop,drag)  // Function fired when mouse button
+ *                                    // released (a/k/a a drop event)
+ *       onOver: function(drop,drag)  // Function fired when mouse over zone
+ *       onOut: function(drop,drag)   // Function fired when mouse leaves the
+ *                                    // zone
+ *  });
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * $Horde: dimp/js/src/dragdrop.js,v 1.31 2008/08/20 20:18:59 slusarz Exp $
+ *
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Michael Slusarz <slusarz@curecanti.org>
+ */
+
+var DragDrop = {
+    Drags: {
+        drags: $H(),
+
+        register: function(obj)
+        {
+            if (!this.drags.size()) {
+                if (!this.div) {
+                    this.div = new Element('DIV', { className: obj.options.classname }).hide();
+                }
+                $(document.body).insert(this.div);
+            }
+
+            this.drags.set(obj.element.readAttribute('id'), obj);
+        },
+
+        unregister: function(obj)
+        {
+            if (this.drag == obj.element) {
+                this.drag.deactivate();
+            }
+
+            this.drags.unset(obj.element.readAttribute('id'));
+
+            if (!this.drags.size() && this.div) {
+                this.div.remove();
+            }
+        },
+
+        get_drag: function(el)
+        {
+            return this.drags.get(Object.isElement(el) ? $(el).readAttribute('id') : el);
+        },
+
+        activate: function(drag)
+        {
+            if (this.drag) {
+                this.deactivate();
+            }
+            this.drag = drag;
+            this.mousemoveE = drag._mouseMove.bindAsEventListener(drag);
+            this.mouseupE = drag._mouseUp.bindAsEventListener(drag);
+            document.observe('mousemove', this.mousemoveE);
+            document.observe('mouseup', this.mouseupE);
+        },
+
+        deactivate: function()
+        {
+            if (this.drag) {
+                this.drag = null;
+                document.stopObserving('mousemove', this.mousemoveE);
+                document.stopObserving('mouseup', this.mouseupE);
+            }
+        }
+    },
+
+    Drops: {
+        drops: $H(),
+
+        register: function(obj)
+        {
+            this.drops.set(obj.element.readAttribute('id'), obj);
+        },
+
+        unregister: function(obj)
+        {
+            if (this.drop == obj.element) {
+                this.drop = null;
+            }
+
+            this.drops.unset(obj.element.readAttribute('id'));
+        },
+
+        get_drop: function(el)
+        {
+            return this.drops.get(Object.isElement(el) ? $(el).readAttribute('id') : el);
+        }
+    },
+
+    validDrop: function(el)
+    {
+        var d = DragDrop.Drops.drop;
+        return (d &&
+                el &&
+                el != d.element &&
+                (!d.options.accept.size() ||
+                 d.options.accept.include(el.tagName)));
+    }
+},
+
+Drag = Class.create({
+
+    initialize: function(el) {
+        this.element = $(el);
+        this.options = Object.extend({
+            caption: '',
+            classname: 'drag',
+            constraint: null,
+            ghosting: false,
+            scroll: null,
+            snap: null,
+            threshold: 0,
+            onDrag: null,
+            onEnd: null,
+            onStart: null
+        }, arguments[1] || {});
+        this.mousedownE = this._mouseDown.bindAsEventListener(this);
+        this.element.observe('mousedown', this.mousedownE);
+        if (this.options.scroll) {
+            this.options.scroll = $(this.options.scroll);
+        }
+        DragDrop.Drags.register(this);
+
+        // Disable text selection.
+        // See: http://ajaxcookbook.org/disable-text-selection/
+        // Stopping the event on mousedown works on all browsers, but avoid
+        // that if possible because it will prevent any event handlers further
+        // up the DOM tree from firing.
+        if (Prototype.Browser.IE) {
+            this.element.observe('selectstart', Event.stop);
+        } else if (Prototype.Browser.Gecko) {
+            this.element.setStyle({ MozUserSelect: 'none' });
+        }
+    },
+
+    destroy: function()
+    {
+        this.element.stopObserving('mousedown', this.mousedownE);
+        DragDrop.Drags.unregister(this);
+    },
+
+    _mouseDown: function(e)
+    {
+        $(document.body).setStyle({ cursor: 'default' });
+        DragDrop.Drags.activate(this);
+        this.move = 0;
+        this.wasDragged = false;
+        this.lastcaption = null;
+
+        if (Object.isFunction(this.options.onStart)) {
+            this.options.onStart(this, e);
+        }
+
+        if (!Prototype.Browser.IE && !Prototype.Browser.Gecko) {
+            e.stop();
+        }
+    },
+
+    _mouseMove: function(e)
+    {
+        var oleft, otop, vo, xy;
+
+        if (++this.move <= this.options.threshold) {
+            return;
+        }
+
+        this.lastCoord = xy = [ e.pointerX(), e.pointerY() ];
+
+        if (this.options.ghosting) {
+            if (!this.ghost) {
+                oleft = this.element.offsetLeft;
+                otop = this.element.offsetTop;
+                this.ghost = $(this.element.cloneNode(true)).writeAttribute('id', null).setOpacity(0.7).clonePosition(this.element, { setLeft: false, setTop: false }).setStyle({ left: oleft + 'px', position: 'absolute', top: otop + 'px', zIndex: parseInt(this.element.getStyle('zIndex')) + 1 });
+                this.element.insert({ before: this.ghost });
+                vo = this.ghost.viewportOffset();
+                this.ghostOffset = [ vo[0] - oleft, vo[1] - otop ];
+            }
+
+            xy[0] -= this.ghostOffset[0];
+            xy[1] -= this.ghostOffset[1];
+
+            switch (this.options.constraint) {
+            case 'horizontal':
+                xy[1] = this.ghost.offsetTop;
+                break;
+
+            case 'vertical':
+                xy[0] = this.ghost.offsetLeft;
+                break;
+            }
+
+            if (this.options.snap) {
+                xy = this.options.snap(xy[0], xy[1], this.element);
+            }
+
+            if (this.options.offset) {
+                xy[0] += this.options.offset.x;
+                xy[1] += this.options.offset.y;
+            }
+
+            this._setContents(this.ghost, xy[0], xy[1]);
+        }
+
+        this._onMoveDrag(xy, e);
+
+        if (Object.isFunction(this.options.onDrag)) {
+            this.options.onDrag(this, e);
+        }
+
+        this.wasDragged = true;
+
+        if (this.options.scroll) {
+            this._onMoveScroll();
+        }
+    },
+
+    _mouseUp: function(e)
+    {
+        var d = DragDrop.Drops.drop;
+
+        this._stopScrolling();
+
+        if (this.ghost) {
+            this.ghost.remove();
+            this.ghost = null;
+        }
+        DragDrop.Drags.div.hide();
+
+        if (DragDrop.validDrop(this.element) &&
+            Object.isFunction(d.options.onDrop)) {
+            d.options.onDrop(d.element, this.element, e);
+        }
+
+        DragDrop.Drags.deactivate();
+
+        if (Object.isFunction(this.options.onEnd)) {
+            this.options.onEnd(this, e);
+        }
+    },
+
+    _onMoveDrag: function(xy, e)
+    {
+        var c_opt, caption, cname, d_cap,
+            d = DragDrop.Drops.drop,
+            div = DragDrop.Drags.div,
+            d_update = true;
+
+        if (d && DragDrop.validDrop(this.element)) {
+            d_cap = d.options.caption;
+            if (d_cap) {
+                caption = Object.isFunction(d_cap) ? d_cap(d.element, this.element, e) : d_cap;
+                if (caption && d.options.hoverclass) {
+                    cname = d.options.hoverclass;
+                }
+            } else {
+                d_update = false;
+            }
+        }
+
+        if (d_update) {
+            if (!caption) {
+                c_opt = this.options.caption;
+                caption = Object.isFunction(c_opt) ? c_opt(this.element) : c_opt;
+            }
+            if (caption != this.lastcaption) {
+                this.lastcaption = caption;
+                div.update(caption).writeAttribute({ className: cname || this.options.classname });
+                if (caption.empty()) {
+                    div.hide();
+                }
+            }
+        }
+
+        if (!this.lastcaption.empty()) {
+            this._setContents(div, xy[0] + 15, xy[1] + (this.ghost ? (this.ghost.getHeight() + 5) : 5));
+        }
+    },
+
+    _onMoveScroll: function()
+    {
+        this._stopScrolling();
+
+        var delta, p, speed,
+            s = this.options.scroll,
+            dim = s.getDimensions();
+
+        // No need to scroll if element is not current scrolling.
+        if (s.scrollHeight == dim.height) {
+            return;
+        }
+
+        delta = document.viewport.getScrollOffsets();
+        p = s.viewportOffset(),
+        speed = [ 0, 0 ];
+
+        p[0] += s.scrollLeft + delta.left;
+        p[2] = p[0] + dim.width;
+
+        // Only scroll if directly above/below element
+        if (this.lastCoord[0] > p[2] ||
+            this.lastCoord[0] < p[0]) {
+            return;
+        }
+
+        p[1] += s.scrollTop + delta.top;
+        p[3] = p[1] + dim.height;
+
+        // Left scroll
+        //if (this.lastCoord[0] < p[0]) {
+        //    speed[0] = this.lastCoord[0] - p[0];
+        //}
+        // Top scroll
+        if (this.lastCoord[1] < p[1]) {
+            speed[1] = this.lastCoord[1] - p[1];
+        }
+        // Scroll right
+        //if (this.lastCoord[0] > p[2]) {
+        //    speed[0] = this.lastCoord[0] - p[2];
+        //}
+        // Scroll left
+        if (this.lastCoord[1] > p[3]) {
+            speed[1] = this.lastCoord[1] - p[3];
+        }
+
+        if (speed[0] || speed[1]) {
+            this.lastScrolled = new Date();
+            this.scrollInterval = setInterval(this._scroll.bind(this, speed[0] * 15, speed[1] * 15), 10);
+        }
+    },
+
+    _stopScrolling: function()
+    {
+        if (this.scrollInterval) {
+            clearInterval(this.scrollInterval);
+            this.scrollInterval = null;
+        }
+    },
+
+    _scroll: function(x, y)
+    {
+        var current = new Date(),
+            delta = current - this.lastScrolled,
+            s = this.options.scroll;
+        this.lastScrolled = current;
+
+        //s.scrollLeft += x * delta / 1000;
+        s.scrollTop += y * delta / 1000;
+    },
+
+    _setContents: function(elt, x, y)
+    {
+        var d_pos = document.viewport.getDimensions(),
+            e_pos = elt.getDimensions();
+
+        if ((x + e_pos.width > d_pos.width) ||
+            (y + e_pos.height > d_pos.height)) {
+            elt.hide();
+        } else {
+            elt.setStyle({ left: x + 'px', top: y + 'px' }).show();
+        }
+    }
+
+}),
+
+Drop = Class.create({
+
+    initialize: function(el)
+    {
+        this.element = $(el);
+        this.options = Object.extend({
+            accept: [],
+            caption: '',
+            hoverclass: '',
+            onDrop: null,
+            onOut: null,
+            onOver: null
+        }, arguments[1] || {});
+        this.mouseoverE = this._mouseOver.bindAsEventListener(this);
+        this.mouseoutE = this._mouseOut.bindAsEventListener(this);
+        this.element.observe('mouseover', this.mouseoverE);
+        this.element.observe('mouseout', this.mouseoutE);
+        DragDrop.Drops.register(this);
+    },
+
+    destroy: function()
+    {
+        this.element.stopObserving('mouseover', this.mouseoverE);
+        this.element.stopObserving('mouseout', this.mouseoutE);
+        DragDrop.Drops.unregister(this);
+    },
+
+    _mouseOver: function(e)
+    {
+        if (DragDrop.Drags.drag) {
+            DragDrop.Drops.drop = this;
+            if (Object.isFunction(this.options.onOver)) {
+                this.options.onOver(this.element, DragDrop.Drags.drag);
+            }
+        }
+    },
+
+    _mouseOut: function(e)
+    {
+        if (Object.isFunction(this.options.onOut)) {
+            this.options.onOut(this.element, DragDrop.Drags.drag);
+        }
+        DragDrop.Drops.drop = null;
+    }
+});
diff --git a/imp/js/src/fullmessage-dimp.js b/imp/js/src/fullmessage-dimp.js
new file mode 100644 (file)
index 0000000..13ac2ce
--- /dev/null
@@ -0,0 +1,108 @@
+/**
+ * $Horde: dimp/js/src/fullmessage.js,v 1.42 2008/09/04 05:00:53 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+var DimpFullmessage = {
+
+    quickreply: function(type)
+    {
+        var func, ob = {};
+        ob[$F('folder')] = [ $F('index') ];
+
+        $('msgData').hide();
+        $('qreply').show();
+
+        switch (type) {
+        case 'reply':
+        case 'reply_all':
+        case 'reply_list':
+            func = 'GetReplyData';
+            break;
+
+        case 'forward_all':
+        case 'forward_body':
+        case 'forward_attachments':
+            func = 'GetForwardData';
+            break;
+        }
+
+        DimpCore.doAction(func,
+                          { imp_compose: $F('composeCache'),
+                            type: type },
+                          ob,
+                          this.msgTextCallback.bind(this));
+    },
+
+    msgTextCallback: function(result)
+    {
+        if (!result.response) {
+            return;
+        }
+
+        var r = result.response,
+            editor_on = ((r.format == 'html') && !DimpCompose.editor_on),
+            id = (r.identity === null) ? $F('identity') : r.identity,
+            i = DimpCompose.get_identity(id, editor_on);
+
+        $('identity', 'last_identity').invoke('setValue', id);
+
+        DimpCompose.fillForm((i.id[2]) ? ("\n" + i.sig + r.body) : (r.body + "\n" + i.sig), r.header);
+
+        if (r.fwd_list && r.fwd_list.length) {
+            r.fwd_list.each(function(ptr) {
+                DimpCompose.addAttach(ptr.number, ptr.name, ptr.type, ptr.size);
+            });
+        }
+
+        if (editor_on) {
+            DimpCompose.toggleHtmlEditor(true);
+        }
+
+        if (r.imp_compose) {
+            $('composeCache').setValue(r.imp_compose);
+        }
+    }
+
+};
+
+document.observe('dom:loaded', function() {
+    window.focus();
+    DimpCore.messageOnLoad();
+    DimpCore.addPopdown('reply_link', 'replypopdown');
+    DimpCore.addPopdown('forward_link', 'fwdpopdown');
+
+    /* Set up address linking. */
+    [ 'from', 'to', 'cc', 'bcc', 'replyTo' ].each(function(a) {
+        if (DimpFullmessage[a]) {
+            var elt = $('msgHeader' + a.charAt(0).toUpperCase() + a.substring(1)).down('TD', 1);
+            elt.replace(DimpCore.buildAddressLinks(DimpFullmessage[a], elt.cloneNode(false)));
+        }
+    });
+
+    /* Set up click handlers. */
+    var C = DimpCore.clickObserveHandler;
+    C({ d: $('windowclose'), f: function() { window.close(); } });
+    C({ d: $('reply_link'), f: DimpFullmessage.quickreply.bind(DimpFullmessage, 'reply') });
+    C({ d: $('forward_link'), f: DimpFullmessage.quickreply.bind(DimpFullmessage, DIMP.conf.forward_default) });
+    [ 'spam', 'ham', 'deleted' ].each(function(a) {
+        var d = $('button_' + a);
+        if (d) {
+            C({ d: d, f: function(a) { DIMP.baseWindow.DimpBase.flag(a, DIMP.conf.msg_index, DIMP.conf.msg_folder); window.close(); }.curry(a) });
+        }
+    });
+    C({ d: $('qreply').select('div.headercloseimg img').first(), f: DimpCompose.confirmCancel.bind(DimpCompose) });
+    [ 'reply', 'reply_all', 'reply_list' ].each(function(a) {
+        var d = $('ctx_replypopdown_' + a);
+        if (d) {
+            C({ d: d, f: DimpFullmessage.quickreply.bind(DimpFullmessage, a), ns: true });
+        }
+    });
+    [ 'forward_all', 'forward_body', 'forward_attachments' ].each(function(a) {
+        C({ d: $('ctx_fwdpopdown_' + a), f: DimpFullmessage.quickreply.bind(DimpFullmessage, a), ns: true });
+    });
+});
diff --git a/imp/lib/Block/foldersummary.php b/imp/lib/Block/foldersummary.php
new file mode 100644 (file)
index 0000000..d66d305
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Horde_Block
+ */
+
+class Horde_Block_dimp_foldersummary extends Horde_Block
+{
+    var $_app = 'imp';
+
+    function _content()
+    {
+        if (!IMP::checkAuthentication(true)) {
+            return '';
+        }
+
+        /* Filter on INBOX display, if requested. */
+        if ($GLOBALS['prefs']->getValue('filter_on_display')) {
+            IMP_Filter::filter('INBOX');
+        }
+
+        /* Get list of mailboxes to poll. */
+        $imptree = &IMP_IMAP_Tree::singleton();
+        $folders = $imptree->getPollList(true, true);
+
+        $anyUnseen = false;
+        $newmsgs = array();
+        $html = '<table cellspacing="0" width="100%">';
+
+        foreach ($folders as $folder) {
+            if (($folder == 'INBOX') ||
+                ($_SESSION['imp']['protocol'] != 'pop')) {
+                $info = $imptree->getElementInfo($folder);
+                if (!empty($info)) {
+                    if (empty($this->_params['show_unread']) ||
+                        !empty($info['unseen'])) {
+                        if (!empty($info['newmsg'])) {
+                            $newmsgs[$folder] = $info['newmsg'];
+                        }
+                        $html .= '<tr style="cursor:pointer" class="text" onclick="DimpBase.go(\'folder:' . htmlspecialchars($folder) . '\');return false;"><td>';
+                        if (!empty($info['unseen'])) {
+                            $html .= '<strong>';
+                            $anyUnseen = true;
+                        }
+                        $html .= '<a>' . IMP::displayFolder($folder) . '</a>';
+                        if (!empty($info['unseen'])) {
+                            $html .= '</strong>';
+                        }
+                        $html .= '</td><td>' .
+                            (!empty($info['unseen']) ? '<strong>' . $info['unseen'] . '</strong>' : '0') .
+                            (!empty($this->_params['show_total']) ? '</td><td>(' . $info['messages'] . ')' : '') .
+                            '</td></tr>';
+                    }
+                }
+            }
+        }
+
+        $html .= '</table>';
+
+        if (count($newmsgs) == 0 && !empty($this->_params['show_unread'])) {
+            if (count($folders) == 0) {
+                $html = _("No folders are being checked for new mail.");
+            } elseif (!$anyUnseen) {
+                $html = '<em>' . _("No folders with unseen messages") . '</em>';
+            } elseif ($GLOBALS['prefs']->getValue('nav_popup')) {
+                $html = '<em>' . _("No folders with new messages") . '</em>';
+            }
+        }
+
+        return $html;
+    }
+
+}
diff --git a/imp/lib/Block/newmail.php b/imp/lib/Block/newmail.php
new file mode 100644 (file)
index 0000000..11c4d54
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package Horde_Block
+ * @author  Michael Slusarz <slusarz@curecanti.org>
+ */
+class Horde_Block_dimp_newmail extends Horde_Block
+{
+    var $_app = 'imp';
+
+    function _content()
+    {
+        $GLOBALS['authentication'] = 'none';
+        require_once $GLOBALS['registry']->get('fileroot', 'imp') . '/lib/base.php';
+
+        if (!IMP::checkAuthentication(true)) {
+            return '';
+        }
+
+        /* Filter on INBOX display, if requested. */
+        if ($GLOBALS['prefs']->getValue('filter_on_display')) {
+            IMP_Filter::filter('INBOX');
+        }
+
+        // @todo
+        $query = new IMAP_Search_Query();
+        $query->seen(false);
+        $ids = $GLOBALS['imp_search']->runSearchQuery($query, IMP::serverString('INBOX'), SORTARRIVAL, 1);
+
+        $html = '<table cellspacing="0" width="100%">';
+        if (empty($ids)) {
+            $html .= '<tr><td><em>' . _("No unread messages") . '</em></td></tr>';
+        } else {
+            require_once 'Horde/Identity.php';
+            require_once 'Horde/Text.php';
+
+            $charset = NLS::getCharset();
+            $identity = &Identity::singleton(array('imp', 'imp'));
+            $imp_ui = new IMP_UI_Mailbox('INBOX', $charset, $identity);
+            $shown = empty($this->_params['msgs_shown']) ? 3 : $this->_params['msgs_shown'];
+
+            // @todo
+            $msg_cache = &IMP_MessageCache::singleton();
+            $overview = $msg_cache->retrieve('INBOX', array_slice($ids, 0, $shown), 1 | 128);
+            foreach ($overview as $ob) {
+                $date = $imp_ui->getDate((isset($ob->date)) ? $ob->date : null);
+                $from_res = $imp_ui->getFrom($ob);
+                $subject = (empty($ob->subject)) ? _("[No Subject]") : $imp_ui->getSubject($ob->subject);
+
+                $html .= '<tr style="cursor:pointer" class="text" onclick="DimpBase.go(\'msg:INBOX:' . $ob->uid . '\');return false;"><td>' .
+                    '<strong>' . htmlspecialchars($from_res['from'], ENT_QUOTES, $charset) . '</strong><br />' .
+                    str_replace('&nbsp;', '&#160;', Text::htmlSpaces($subject)) . '</td>' .
+                    '<td>' . htmlspecialchars($date, ENT_QUOTES, $charset) . '</td></tr>';
+            }
+
+            $more_msgs = count($ids) - $shown;
+            if ($more_msgs) {
+                $text = sprintf(ngettext("%d more unseen message...", "%d more unseen messages...", $more_msgs), $more_msgs);
+            } else {
+                $text = _("Go to your Inbox...");
+            }
+            $html .= '<tr><td colspan="2" style="cursor:pointer" align="right" onclick="DimpBase.go(\'folder:INBOX\');return false;">' . $text . '</td></tr>';
+        }
+
+        return $html . '</table>';
+    }
+
+}
diff --git a/imp/lib/DIMP.php b/imp/lib/DIMP.php
new file mode 100644 (file)
index 0000000..b81c7cf
--- /dev/null
@@ -0,0 +1,530 @@
+<?php
+/**
+ * DIMP Base Class - provides dynamic view functions.
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package IMP
+ */
+class DIMP
+{
+    /**
+     * Output a dimp-style action (menubar) link.
+     *
+     * @param array $params  A list of parameters.
+     * <pre>
+     * 'app' - The application to load the icon from.
+     * 'class' - The CSS classname to use for the link.
+     * 'icon' - The icon filename to display.
+     * 'id' - The DOM ID of the link.
+     * 'title' - The title string.
+     * 'tooltip' - Tooltip text to use.
+     * </pre>
+     *
+     * @return string  An HTML link to $url.
+     */
+    public function actionButton($params = array())
+    {
+        $tooltip = (empty($params['tooltip'])) ? '' : $params['tooltip'];
+
+        if (empty($params['title'])) {
+            static $charset;
+            if (!isset($charset)) {
+                $charset = NLS::getCharset();
+            }
+            $old_error = error_reporting(0);
+            $tooltip = nl2br(htmlspecialchars($tooltip, ENT_QUOTES, $charset));
+            $title = $ak = '';
+        } else {
+            $title = $params['title'];
+            $ak = Horde::getAccessKey($title);
+        }
+
+        return Horde::link('', $tooltip,
+                           empty($params['class']) ? '' : $params['class'],
+                           '', '', '', $ak,
+                           empty($params['id']) ? array() : array('id' => $params['id']),
+                           !empty($title))
+            . (!empty($params['icon'])
+               ? Horde::img($params['icon'], $title, '',
+                            $GLOBALS['registry']->getImageDir(empty($params['app']) ? 'imp' : $params['app']))
+               : '')
+            . $title . '</a>';
+    }
+
+    /**
+     * Output everything up to but not including the <body> tag.
+     *
+     * @param string $title   The title of the page.
+     * @param array $scripts  Any additional scripts that need to be loaded.
+     *                        Each entry contains the three elements necessary
+     *                        for a Horde::addScriptFile() call.
+     */
+    public function header($title, $scripts = array())
+    {
+        // Don't autoload any javascript files.
+        Horde::disableAutoloadHordeJS();
+
+        // Need to include script files before we start output
+        Horde::addScriptFile('prototype.js', 'horde', true);
+        Horde::addScriptFile('effects.js', 'horde', true);
+
+        // ContextSensitive must be loaded before DimpCore.
+        while (list($key, $val) = each($scripts)) {
+            if (($val[0] == 'ContextSensitive.js') &&
+                ($val[1] == 'imp')) {
+                Horde::addScriptFile($val[0], $val[1], $val[2]);
+                unset($scripts[$key]);
+                break;
+            }
+        }
+        Horde::addScriptFile('DimpCore.js', 'imp', true);
+
+        // Add other scripts now
+        foreach ($scripts as $val) {
+            call_user_func_array(array('Horde', 'addScriptFile'), $val);
+        }
+
+        $page_title = $GLOBALS['registry']->get('name');
+        if (!empty($title)) {
+            $page_title .= ' :: ' . $title;
+        }
+
+        if (isset($GLOBALS['language'])) {
+            header('Content-type: text/html; charset=' . NLS::getCharset());
+            header('Vary: Accept-Language');
+        }
+
+        echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">' . "\n" .
+             (!empty($GLOBALS['language']) ? '<html lang="' . strtr($GLOBALS['language'], '_', '-') . '"' : '<html') . ">\n".
+             "<head>\n" .
+             '<title>' . htmlspecialchars($page_title) . "</title>\n" .
+             '<link href="' . $GLOBALS['registry']->getImageDir() . "/favicon.ico\" rel=\"SHORTCUT ICON\" />\n".
+             IMP::wrapInlineScript(DIMP::_includeDIMPJSVars());
+
+        IMP::includeStylesheetFiles(true, 'dimp');
+
+        echo "</head>\n";
+
+        // Send what we have currently output so the browser can start
+        // loading CSS/JS. See:
+        // http://developer.yahoo.com/performance/rules.html#flush
+        flush();
+    }
+
+    /**
+     * Outputs the javascript code which defines all javascript variables
+     * that are dependent on the local user's account.
+     *
+     * @return string  TODO
+     */
+    protected function _includeDIMPJSVars()
+    {
+        global $browser, $conf, $dimp_conf, $dimp_prefs, $prefs, $registry;
+
+        $compose_mode = (strstr($_SERVER['PHP_SELF'], 'compose-dimp.php') || strstr($_SERVER['PHP_SELF'], 'message-dimp.php'));
+        $horde_webroot = $registry->get('webroot', 'horde');
+
+        $app_urls = $code = array();
+
+        foreach (DIMP::menuList() as $app) {
+            $app_urls[$app] = Horde::url($registry->getInitialPage($app), true);
+        }
+
+        /* Variables used in core javascript files. */
+        $code['conf'] = array(
+            'URI_DIMP_INBOX' => Horde::applicationUrl('index-dimp.php', true, -1),
+            'URI_IMP' => Horde::applicationUrl('imp-dimp.php', true, -1),
+            'URI_PREFS' => Horde::url($horde_webroot . '/services/prefs/', true, -1),
+            'URI_VIEW' => Util::addParameter(Horde::applicationUrl('view.php', true, -1), array('actionID' => 'view_source', 'id' => 0), null, false),
+
+            'SESSION_ID' => defined('SID') ? SID : '',
+
+            'app_urls' => $app_urls,
+            'timeout_url' => Auth::addLogoutParameters($horde_webroot . '/login.php', AUTH_REASON_SESSION),
+            'message_url' => Horde::applicationUrl('message-dimp.php'),
+            'compose_url' => Horde::applicationUrl('compose-dimp.php'),
+            'prefs_url' => str_replace('&amp;', '&', Horde::getServiceLink('options', 'imp')),
+
+            'sortthread' => Horde_Imap_Client::SORT_THREAD,
+            'sortdate' => Horde_Imap_Client::SORT_DATE,
+
+            'popup_width' => 820,
+            'popup_height' => 610,
+
+            'forward_default' => $prefs->getValue('forward_default'),
+            'spam_folder' => IMP::folderPref($prefs->getValue('spam_folder'), true),
+            'spam_reporting' => (int) !empty($conf['spam']['reporting']),
+            'spam_spamfolder' => (int) !empty($conf['spam']['spamfolder']),
+            'ham_reporting' => (int) !empty($conf['notspam']['reporting']),
+            'ham_spamfolder' => (int) !empty($conf['notspam']['spamfolder']),
+            'refresh_time' => (int) $prefs->getValue('refresh_time'),
+            'search_all' => (int) !empty($conf['search']['search_all']),
+
+            'fixed_folders' => empty($conf['server']['fixed_folders'])
+                ? array()
+                : array_map(array('DIMP', '_appendedFolderPref'), $conf['server']['fixed_folders']),
+
+            'name' => $registry->get('name', 'dimp'),
+
+            'preview_pref' => (bool)$dimp_prefs->getValue('show_preview'),
+
+            'is_ie6' => ($browser->isBrowser('msie') && ($browser->getMajor() < 7)),
+
+            'buffer_pages' => intval($dimp_conf['viewport']['buffer_pages']),
+            'limit_factor' => intval($dimp_conf['viewport']['limit_factor']),
+            'viewport_wait' => intval($dimp_conf['viewport']['viewport_wait']),
+            'login_view' => $dimp_prefs->getValue('login_view'),
+            'background_inbox' => !empty($dimp_conf['viewport']['background_inbox']),
+
+            // Turn debugging on?
+            'debug' => !empty($dimp_conf['js']['debug']),
+        );
+
+        /* Gettext strings used in core javascript files. */
+        $code['text'] = array_map('addslashes', array(
+            'portal' => ("Portal"),
+            'prefs' => _("User Options"),
+            'search' => _("Search"),
+            'resfound' => _("results found"),
+            'message' => _("Message"),
+            'messages' => _("Messages"),
+            'of' => _("of"),
+            'nomessages' => _("No Messages"),
+            'ok' => _("Ok"),
+            'copyto' => _("Copy %s to %s"),
+            'moveto' => _("Move %s to %s"),
+            'baselevel' => _("base level of the folder tree"),
+            'cancel' => _("Cancel"),
+            'loading' => _("Loading..."),
+            'check' => _("Checking..."),
+            'ajax_timeout' => _("There has been no contact with the remote server for several minutes. The server may be temporarily unavailable or network problems may be interrupting your session. You will not see any updates until the connection is restored."),
+            'ajax_recover' => _("The connection to the remote server has been restored."),
+            'listmsg_wait' => _("The server is still generating the message list."),
+            'listmsg_timeout' => _("The server was unable to generate the message list. Please try again later."),
+            'popup_block' => _("A popup window could not be opened. Your browser may be blocking popups."),
+            'hide_preview' => _("Hide Preview"),
+            'show_preview' => _("Show Preview"),
+            'rename_prompt' => _("Rename folder to:"),
+            'create_prompt' => _("Create folder:"),
+            'createsub_prompt' => _("Create subfolder:"),
+            'empty_folder' => _("Permanently delete all messages?"),
+            'delete_folder' => _("Permanently delete this folder?"),
+            'hidealog' => _("Hide Alerts Log"),
+            'alog_error' => _("Error"),
+            'alog_message' => _("Message"),
+            'alog_success' => _("Success"),
+            'alog_warning' => _("Warning"),
+        ));
+
+        /* Gettext strings with individual escaping. */
+        $code['text']['getmail'] = Horde::highlightAccessKey(addslashes(_("_Get Mail")), Horde::getAccessKey(_("_Get Mail"), true));
+        $code['text']['showalog'] = Horde::highlightAccessKey(addslashes(_("_Alerts Log")), Horde::getAccessKey(_("_Alerts Log"), true));
+
+        if ($compose_mode) {
+            /* Variables used in compose page. */
+            $compose_cursor = $GLOBALS['prefs']->getValue('compose_cursor');
+            $code['conf_compose'] = array(
+                'rte_avail' => $browser->hasFeature('rte'),
+                'cc' => (bool) $prefs->getValue('compose_cc'),
+                'bcc' => (bool) $prefs->getValue('compose_bcc'),
+                'attach_limit' => ($conf['compose']['attach_count_limit'] ? intval($conf['compose']['attach_count_limit']) : -1),
+                'close_draft' => $prefs->getValue('close_draft'),
+                'compose_cursor' => ($compose_cursor ? $compose_cursor : 'top'),
+
+                'abook_url' => Horde::url($imp_webroot . '/contacts.php'),
+                'specialchars_url' => Horde::url($horde_webroot . '/services/keyboard.php'),
+            );
+
+            /* Gettext strings used in compose page. */
+            $code['text_compose'] = array_map('addslashes', array(
+                'cancel' => _("Cancelling this message will permanently discard its contents and will delete auto-saved drafts.\nAre you sure you want to do this?"),
+                'fillform' => _("You have already changed the message body, are you sure you want to drop the changes?"),
+                'remove' => _("remove"),
+                'uploading' => _("Uploading..."),
+                'attachment_limit' => _("The attachment limit has been reached."),
+                'sending' => _("Sending..."),
+                'saving' => _("Saving..."),
+                'toggle_html' => _("Really discard all formatting information? This operation cannot be undone."),
+            ));
+        }
+
+        return array('var DIMP = ' . Horde_Serialize::serialize($code, SERIALIZE_JSON, NLS::getCharset()) . ';');
+    }
+
+    /**
+     * Return an appended IMP folder string
+     */
+    function _appendedFolderPref($folder)
+    {
+        return IMP::folderPref($folder, true);
+    }
+
+    /**
+     * Return the javascript code necessary to display notification popups.
+     *
+     * @return string  The notification JS code.
+     */
+    function notify()
+    {
+        $GLOBALS['notification']->notify(array('listeners' => 'status'));
+        $msgs = $GLOBALS['dimp_listener']->getStack(true);
+        if (!count($msgs)) {
+            return '';
+        }
+
+        return 'DimpCore.showNotifications(' . Horde_Serialize::serialize($msgs, SERIALIZE_JSON) . ')';
+    }
+
+    /**
+     * Formats the response to send to javascript code when dealing with
+     * folder operations.
+     *
+     * @param IMP_Tree $imptree  An IMP_Tree object.
+     * @param array $changes     An array with three sub arrays - to be used
+     *                           instead of the return from
+     *                           $imptree->eltDiff():
+     *                           'a' - a list of folders to add
+     *                           'c' - a list of changed folders
+     *                           'd' - a list of folders to delete
+     *
+     * @return array  The object used by the JS code to update the folder tree.
+     */
+    function getFolderResponse($imptree, $changes = null)
+    {
+        if ($changes === null) {
+            $changes = $imptree->eltDiff();
+        }
+        if (empty($changes)) {
+            return false;
+        }
+
+        $result = array();
+
+        if (!empty($changes['a'])) {
+            $result['a'] = array();
+            foreach ($changes['a'] as $val) {
+                $result['a'][] = DIMP::_createFolderElt($imptree->element($val));
+            }
+        }
+
+        if (!empty($changes['c'])) {
+            $result['c'] = array();
+            foreach ($changes['c'] as $val) {
+                // Skip the base element, since any change there won't ever be
+                // updated on-screen.
+                if ($val != IMPTREE_BASE_ELT) {
+                    $result['c'][] = DIMP::_createFolderElt($imptree->element($val));
+                }
+            }
+        }
+
+        if (!empty($changes['d'])) {
+            $result['d'] = array_map('rawurlencode', array_reverse($changes['d']));
+        }
+
+        return $result;
+    }
+
+    /**
+     * Create an object used by DimpCore to generate the folder tree.
+     *
+     * @access private
+     *
+     * @param array $elt  The output from IMP_Tree::element().
+     *
+     * @return stdClass  The element object. Contains the following items:
+     * <pre>
+     * 'ch' (children) = Does the folder contain children? [boolean]
+     *                   [DEFAULT: no]
+     * 'cl' (class) = The CSS class. [string] [DEFAULT: 'base']
+     * 'co' (container) = Is this folder a container element? [boolean]
+     *                    [DEFAULT: no]
+     * 'i' (icon) = A user defined icon to use. [string] [DEFAULT: none]
+     * 'l' (label) = The folder display label. [string]
+     * 'm' (mbox) = The mailbox value. [string]
+     * 'pa' (parent) = The parent element. [string]
+     * 'po' (polled) = Is the element polled? [boolean] [DEFAULT: no]
+     * 's' (special) = Is this a "special" element? [boolean] [DEFAULT: no]
+     * 'u' (unseen) = The number of unseen messages. [integer]
+     * 'v' (virtual) = Is this a virtual folder? [boolean] [DEFAULT: no]
+     * </pre>
+     */
+    function _createFolderElt($elt)
+    {
+        $ob = new stdClass;
+        if ($elt['children']) {
+           $ob->ch = 1;
+        }
+        $ob->l = $elt['base_elt']['l'];
+        $ob->m = rawurlencode($elt['value']);
+        $ob->pa = rawurlencode($elt['parent']);
+        if ($elt['polled']) {
+            $ob->po = 1;
+        }
+        if ($elt['vfolder']) {
+            $ob->v = 1;
+        }
+
+        if ($elt['container']) {
+            $ob->co = 1;
+            $ob->cl = 'exp';
+        } else {
+            if ($elt['polled']) {
+                $ob->u = $elt['unseen'];
+            }
+
+            switch ($elt['special']) {
+            case IMPTREE_SPECIAL_INBOX:
+                $ob->cl = 'inbox';
+                $ob->s = 1;
+                break;
+
+            case IMPTREE_SPECIAL_TRASH:
+                $ob->cl = 'trash';
+                $ob->s = 1;
+                break;
+
+            case IMPTREE_SPECIAL_SPAM:
+                $ob->cl = 'spam';
+                $ob->s = 1;
+                break;
+
+            case IMPTREE_SPECIAL_DRAFT:
+                $ob->cl = 'drafts';
+                $ob->s = 1;
+                break;
+
+            case IMPTREE_SPECIAL_SENT:
+                $ob->cl = 'sent';
+                $ob->s = 1;
+                break;
+
+            default:
+                if ($elt['vfolder']) {
+                    if ($GLOBALS['imp_search']->isVTrashFolder($elt['value'])) {
+                        $ob->cl = 'trash';
+                    } elseif ($GLOBALS['imp_search']->isVINBOXFolder($elt['value'])) {
+                        $ob->cl = 'inbox';
+                    }
+                } elseif ($elt['children']) {
+                    $ob->cl = 'exp';
+                }
+                break;
+            }
+        }
+
+        if ($elt['user_icon']) {
+            $ob->cl = 'custom';
+            $ob->i = Horde::img($elt['icon'], $elt['alt'], '', $elt['icondir']);
+        }
+
+        return $ob;
+    }
+
+    /**
+     * Returns a stdClass response object with added notification information.
+     *
+     * @param string $data     The 'response' data.
+     * @param boolean $notify  If true, adds notification information to
+     *                         object.
+     * @param boolean $auto    If true, DimpCore will automatically display the
+     *                         notification.  If false, the callback handler
+     *                         is responsible for displaying the notification.
+     */
+    function prepareResponse($data = null, $notify = true, $auto = true)
+    {
+        $response = new stdClass();
+        $response->response = $data;
+        if ($notify) {
+            $GLOBALS['notification']->notify(array('listeners' => 'status'));
+            $stack = $GLOBALS['dimp_listener']->getStack();
+            if (!empty($stack)) {
+                $response->msgs = $GLOBALS['dimp_listener']->getStack();
+                if (!(bool)$auto) {
+                    $response->msgs_noauto = true;
+                }
+            }
+        }
+        return $response;
+    }
+
+    /**
+     * Return information about the current attachments for a message
+     *
+     * @var object IMP_Compose $imp_compose  An IMP_Compose object.
+     *
+     * @return array  An array of arrays with the following keys:
+     * <pre>
+     * 'number' - The current attachment number
+     * 'name' - The HTML encoded attachment name
+     * 'type' - The MIME type of the attachment
+     * 'size' - The size of the attachment in KB (string)
+     * </pre>
+     */
+    function getAttachmentInfo($imp_compose)
+    {
+        $fwd_list = array();
+
+        if ($imp_compose->numberOfAttachments()) {
+            foreach ($imp_compose->getAttachments() as $file_num => $mime) {
+                $fwd_list[] = array(
+                    'number' => $file_num,
+                    'name' => htmlspecialchars($mime->getName(true, true)),
+                    'type' => $mime->getType(),
+                    'size' => $mime->getSize()
+                );
+            }
+        }
+
+        return $fwd_list;
+    }
+
+    /**
+     * Return a list of DIMP specific menu items.
+     *
+     * @return array  The array of menu items.
+     */
+    function menuList()
+    {
+        if (isset($GLOBALS['dimp_conf']['menu']['apps'])) {
+            $apps = $GLOBALS['dimp_conf']['menu']['apps'];
+            if (is_array($apps) && count($apps)) {
+                return $apps;
+            }
+        }
+        return array();
+    }
+
+    /**
+     * Returns the appropriate link to call the message composition screen.
+     *
+     * @param mixed $args   List of arguments to pass to compose.php. If this
+     *                      is passed in as a string, it will be parsed as a
+     *                      toaddress?subject=foo&cc=ccaddress (mailto-style)
+     *                      string.
+     * @param array $extra  Hash of extra, non-standard arguments to pass to
+     *                      compose.php.
+     *
+     * @return string  The link to the message composition screen.
+     */
+    function composeLink($args = array(), $extra = array())
+    {
+        // IE 6 & 7 handles window.open() URL param strings differently if
+        // triggered via an href or an onclick.  Since we have no hint
+        // at this point where this link will be used, we have to always
+        // encode the params and explicitly call rawurlencode() in
+        // compose.php.
+        $args = IMP::composeLinkArgs($args, $extra);
+        $encode_args = array();
+        foreach ($args as $k => $v) {
+            $encode_args[$k] = rawurlencode($v);
+        }
+        return 'javascript:void(window.open(\'' . Util::addParameter(Horde::applicationUrl('compose.php'), $encode_args, null, false) . '\', \'\', \'width=820,height=610,status=1,scrollbars=yes,resizable=yes\'));';
+    }
+
+}
diff --git a/imp/lib/Notification/Listener/status-dimp.php b/imp/lib/Notification/Listener/status-dimp.php
new file mode 100644 (file)
index 0000000..1c88283
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+require_once IMP_BASE . '/lib/Notification/Listener/status.php';
+require_once 'Horde/Notification/Event.php';
+
+/**
+ * The Notification_Listener_status_dimp:: class extends the
+ * Notification_Listener_status_imp:: class to return all messages instead of
+ * printing them.
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Notification
+ */
+class Notification_Listener_status_dimp extends Notification_Listener_status_imp {
+
+    /**
+     * The notified message stack.
+     *
+     * @var array
+     */
+    var $_messageStack = array();
+
+    /**
+     * Returns all status message if there are any on the 'status' message
+     * stack.
+     *
+     * @param array &$messageStack  The stack of messages.
+     * @param array $options        An array of options.
+     */
+    function notify(&$messageStack, $options = array())
+    {
+        while ($message = array_shift($messageStack)) {
+            $event = @unserialize($message['event']);
+            $this->_messageStack[] = array('type' => $message['type'],
+                                           'flags' => $message['flags'],
+                                           'message' => is_object($event)
+                                               ? $event->getMessage()
+                                               : null);
+        }
+    }
+
+    /**
+     * Handle every message of type dimp.*; otherwise delegate back to
+     * the parent.
+     *
+     * @param string $type  The message type in question.
+     *
+     * @return boolean  Whether this listener handles the type.
+     */
+    function handles($type)
+    {
+        if (substr($type, 0, 5) == 'dimp.') {
+            return true;
+        }
+        return parent::handles($type);
+    }
+
+    /**
+     * Returns the message stack.
+     * To return something useful, notify() needs to be called first.
+     *
+     * @param boolean $encode  Encode HTML entities?
+     *
+     * @return array  List of message hashes.
+     */
+    function getStack($encode = false)
+    {
+        $msgs = $this->_messageStack;
+        if (!$encode) {
+            return $msgs;
+        }
+
+        for ($i = 0, $mcount = count($msgs); $i < $mcount; ++$i) {
+            if (!in_array('content.raw', $this->getFlags($msgs[$i]))) {
+                $msgs[$i]['message'] = htmlspecialchars($msgs[$i]['message'], ENT_COMPAT, NLS::getCharset());
+            }
+        }
+
+        return $msgs;
+    }
+
+}
diff --git a/imp/lib/Views/Compose.php b/imp/lib/Views/Compose.php
new file mode 100644 (file)
index 0000000..ef98717
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * DIMP compose view logic.
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package IMP
+ */
+class DIMP_Views_Compose
+{
+    /**
+     * Create content needed to output the compose screen.
+     *
+     * @param array $args  Configuration parameters:
+     * <pre>
+     * 'composeCache' - The cache ID of the IMP_Compose object.
+     * 'qreply' - Is this a quickreply view?
+     * </pre>
+     *
+     * @return array  Array with the following keys:
+     * <pre>
+     * 'html' - The rendered HTML content.
+     * 'js' - An array of javascript code to run immediately.
+     * 'jsappend' - Javascript code to append at bottom of page.
+     * 'jsonload' - An array of javascript code to run on load.
+     * </pre>
+     */
+    static public function showCompose($args)
+    {
+        $result = array(
+            'html' => '',
+            'jsappend' => '',
+            'jsonload' => array()
+        );
+
+        /* Load Identity. */
+        require_once 'Horde/Identity.php';
+        $identity = &Identity::singleton(array('imp', 'imp'));
+        $selected_identity = $identity->getDefault();
+        $sent_mail_folder = $identity->getValue('sent_mail_folder');
+        if (!empty($sent_mail_folder)) {
+            $sent_mail_folder = htmlspecialchars('"' . IMP::displayFolder($sent_mail_folder) . '"');
+        }
+
+        /* Get user identities. */
+        require_once 'Horde/Text/Filter.php';
+        $all_sigs = $identity->getAllSignatures();
+        foreach ($all_sigs as $ident => $sig) {
+            $identities[] = array(
+                // 0 = Plain text signature
+                $sig,
+                // 1 = HTML signature
+                str_replace(' target="_blank"', '', Text_Filter::filter($sig, 'text2html', array('parselevel' => TEXT_HTML_MICRO_LINKURL, 'class' => null, 'callback' => null))),
+                // 2 = Signature location
+                (bool)$identity->getValue('sig_first', $ident),
+                // 3 = Sent mail folder name
+                $identity->getValue('sent_mail_folder', $ident),
+                // 4 = Save in sent mail folder by default?
+                (bool)$identity->saveSentmail($ident),
+                // 5 = Sent mail display name
+                IMP::displayFolder($identity->getValue('sent_mail_folder', $ident)),
+                // 6 = Bcc addresses to add
+                Horde_Mime_Address::addrArray2String($identity->getBccAddresses($ident))
+            );
+        }
+
+        $draft_index = $composeCache = null;
+        if (!empty($args['composeCache'])) {
+            $imp_compose = &IMP_Compose::singleton($args['composeCache']);
+            $draft_index = intval($imp_compose->saveDraftIndex());
+            $composeCache = $args['composeCache'];
+
+            if ($imp_compose->numberOfAttachments()) {
+                foreach ($imp_compose->getAttachments() as $num => $mime) {
+                    $result['js_onload'][] = 'DimpCompose.addAttach(' . $num . ', \'' . addslashes($mime->getName(true)) . '\', \'' . addslashes($mime->getType()) . '\', \'' . addslashes($mime->getSize()) . "')";
+                }
+            }
+        }
+
+        $result['js'] = array(
+            'DIMP.conf_compose.auto_save_interval_val = ' . intval($GLOBALS['prefs']->getValue('auto_save_drafts')),
+            'DIMP.conf_compose.identities = ' . Horde_Serialize::serialize($identities, SERIALIZE_JSON),
+            'DIMP.conf_compose.qreply = ' . intval(!empty($args['qreply'])),
+        );
+
+        $compose_html = $rte = false;
+        if ($GLOBALS['browser']->hasFeature('rte')) {
+            $compose_html = $GLOBALS['prefs']->getValue('compose_html');
+            $rte = true;
+
+            $imp_ui = new IMP_UI_Compose();
+            $result['jsappend'] .= $imp_ui->initRTE('dimp');
+        }
+
+        // Buffer output so that we can return a string from this function
+        ob_start();
+        require IMP_TEMPLATES . '/chunks/compose.php';
+        $result['html'] .= ob_get_contents();
+        ob_clean();
+
+        return $result;
+    }
+
+}
diff --git a/imp/lib/Views/ListMessages.php b/imp/lib/Views/ListMessages.php
new file mode 100644 (file)
index 0000000..842b8f3
--- /dev/null
@@ -0,0 +1,353 @@
+<?php
+/**
+ * DIMP ListMessages view logic.  Abstracted out here to prevent imp.php from
+ * becoming too cluttered.
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package IMP
+ */
+class DIMP_Views_ListMessages
+{
+    /**
+     * Returns a list of messages for use with ViewPort.
+     *
+     * @var array $args  TODO
+     *
+     * @return array  TODO
+     */
+    public function ListMessages($args)
+    {
+        $c_ptr = &$_SESSION['imp']['cache'];
+        $folder = $args['folder'];
+        $search_id = null;
+
+        $sortpref = IMP::getSort($folder);
+
+        /* If we're searching, do search. */
+        if (!empty($args['filter']) &&
+            !empty($args['searchfolder']) &&
+            !empty($args['searchmsg'])) {
+            /* Create the search query. */
+            $query = new Horde_Imap_Client_Search_Query();
+
+            /* Create message search list. */
+            switch ($args['searchmsg']) {
+            case 'msgall':
+                $query->text($args['filter'], false);
+                break;
+
+            case 'from':
+                $query->headerText('From', $args['filter']);
+                break;
+
+            case 'to':
+                $query->headerText('To', $args['filter']);
+                break;
+
+            case 'subject':
+                $query->headerText('Subject', $args['filter']);
+                break;
+            }
+
+            /* Create folder search list. */
+            switch ($args['searchfolder']) {
+            case 'all':
+                $imptree = &IMP_IMAP_Tree::singleton();
+                $folder_list = $imptree->folderList();
+                break;
+
+            case 'current':
+                $folder_list = array($folder);
+                break;
+            }
+
+            /* Set the search in the IMP session. */
+            $search_id = $GLOBALS['imp_search']->createSearchQuery($query, $folder_list, array(), _("Search Results"), isset($c_ptr['dimp_searchquery']) ? $c_ptr['dimp_searchquery'] : null);
+
+            /* Folder is now the search folder. */
+            $folder = $c_ptr['dimp_searchquery'] = $GLOBALS['imp_search']->createSearchID($search_id);
+        }
+
+        $label = IMP::getLabel($folder);
+
+        /* Set the current time zone. */
+        NLS::setTimeZone();
+
+        /* Run filters now. */
+        if (!empty($_SESSION['imp']['filteravail']) &&
+            ($folder == 'INBOX') &&
+            $GLOBALS['prefs']->getValue('filter_on_display')) {
+            $imp_filter = new IMP_Filter();
+            $imp_filter->filter($folder);
+        }
+
+        /* Generate the sorted mailbox list now. */
+        $imp_mailbox = &IMP_Mailbox::singleton($folder);
+        $sorted_list = $imp_mailbox->getSortedList();
+        $msgcount = count($sorted_list['s']);
+
+        /* Create the base object. */
+        $result = new stdClass;
+        $result->id = $folder;
+        $result->totalrows = $msgcount;
+        $result->label = $label;
+        // @todo
+        $result->cacheid = time();
+
+        /* Determine the row slice to process. */
+        if (isset($args['slice_rownum'])) {
+            $rownum = max(1, $args['slice_rownum']);
+            $slice_start = $args['slice_start'];
+            $slice_end = $args['slice_end'];
+        } else {
+            list($u_uid, $u_mbox) = explode(IMP_IDX_SEP, $args['search_uid']);
+            $result->rownum = $rownum = 1;
+            foreach (array_keys($sorted_list['s'], $u_uid) as $val) {
+                if (empty($sorted_list['m'][$val]) ||
+                    ($sorted_list['m'][$val] == $u_mbox)) {
+                    $rownum = $val;
+                    break;
+                }
+            }
+
+            $slice_start = $rownum - $args['search_before'];
+            $slice_end = $rownum + $args['search_after'];
+            if ($slice_start < 1) {
+                $slice_end += abs($slice_start) + 1;
+            } elseif ($slice_end > $msgcount) {
+                $slice_start -= $slice_end - $msgcount;
+            }
+        }
+        $slice_start = max(1, $slice_start);
+        $slice_end = min($msgcount, $slice_end);
+
+        /* Mail-specific viewport information. */
+        $result->other = new stdClass;
+        $md = &$result->other;
+        if (!IMP::threadSortAvailable($folder)) {
+            $md->nothread = 1;
+        }
+        $md->sortby = intval($sortpref['by']);
+        $md->sortdir = intval($sortpref['dir']);
+        if ($sortpref['limit']) {
+            $md->sortlimit = 1;
+        }
+        if (IMP::isSpecialFolder($folder)) {
+            $md->special = 1;
+        }
+
+        /* Check for mailbox existence now. If there are no messages, there
+         * is a chance that the mailbox doesn't exist. If there is at least
+         * 1 message, we don't need this check. */
+        if (empty($msgcount) && is_null($search_id)) {
+            $imp_folder = &IMP_Folder::singleton();
+            if (!$imp_folder->exists($folder)) {
+                $GLOBALS['notification']->push(sprintf(_("Mailbox %s does not exist."), $label), 'horde.error');
+            }
+
+            $result->data = $result->rowlist = array();
+            return $result;
+        }
+
+        /* Generate the message list and the UID -> rownumber list. */
+        $msglist = $rowlist = array();
+        foreach (range($slice_start, $slice_end) as $key) {
+            $msglist[$key] = $sorted_list['s'][$key];
+            $rowlist[$msglist[$key] . ((isset($sorted_list['m'][$key]['m'])) ? $sorted_list['m'][$key]['m'] : $folder)] = $key;
+        }
+        $result->rowlist = $rowlist;
+
+        /* Determine the list of UIDs that are currently cached on the
+         * browser. Not technically necessary for ViewPort to work, but saves
+         * a bunch of duplicative info being sent to browser. */
+        $cached = array();
+        if (is_null($search_id)) {
+            if (!isset($c_ptr['dimp_msglist'])) {
+                $c_ptr['dimp_msglist'] = array();
+            }
+            if (!empty($args['cacheid']) &&
+                isset($c_ptr['dimp_msglist'][$folder])) {
+                $cachestr = IMP::parseRangeString($c_ptr['dimp_msglist'][$folder]);
+                $cached = reset($cachestr);
+            }
+            $c_ptr['dimp_msglist'][$folder] = IMP::toRangeString(array($folder => array_keys(array_flip(array_merge($cached, array_values($msglist))))));
+        }
+
+        /* Build the overview list. */
+        $result->data = $this->_getOverviewData($imp_mailbox, $folder, array_keys(array_diff($msglist, $cached)));
+
+        /* Get unseen/thread information. */
+        if (is_null($search_id)) {
+            $imptree = &IMP_IMAP_Tree::singleton();
+            $info = $imptree->getElementInfo($folder);
+            if (!empty($info)) {
+                $md->unseen = $info['unseen'];
+            }
+
+            if ($sortpref['by'] == SORTTHREAD) {
+                $threadob = $imp_mailbox->getThreadOb();
+                $md->thread = array_filter($threadob->getThreadTreeOb($msglist, $sortpref['dir']));
+            }
+        } else {
+            $result->search = true;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Return a reduced message list for use with ViewPort -- only a unique
+     * ID/Rownum/UID/Mailbox mapping.  Used to select slices without needing
+     * to obtain IMAP information for all messages in the slice.
+     *
+     * @var string $folder   The current folder.
+     * @var integer $start   Starting row number.
+     * @var integer $length  Slice length.
+     *
+     * @return array  The minimal message list.
+     */
+    public function getSlice($folder, $start, $length)
+    {
+        $start += 1;
+        $end = $start + $length;
+
+        $imp_mailbox = &IMP_Mailbox::singleton($folder);
+        $sorted_list = $imp_mailbox->getSortedList();
+        $data = array();
+        for ($i = $start; $i < $end; ++$i) {
+            $mbox = (empty($sorted_list['m'][$i])) ? $folder : $sorted_list['m'][$i];
+            $id = $sorted_list['s'][$i];
+            $data[$id . $mbox] = array(
+                'imapuid' => $id,
+                'rownum' => $i
+            );
+        }
+
+        $result = new stdClass;
+        $result->data = $data;
+        $result->id = $folder;
+        return $result;
+    }
+
+    /**
+     * Obtains IMAP overview data for a given set of message UIDs.
+     *
+     * @var object IMP_Mailbox $imp_mailbox  An IMP_Mailbox:: object.
+     * @var string $folder                   The current folder.
+     * @var array $msglist                   The list of message sequence
+     *                                       numbers to process.
+     *
+     * @return array TODO
+     */
+    private function _getOverviewData($imp_mailbox, $folder, $msglist)
+    {
+        $msgs = array();
+
+        if (empty($msglist)) {
+            return $msgs;
+        }
+
+        require_once 'Horde/Identity.php';
+        require_once 'Horde/Text.php';
+
+        /* Get mailbox information. */
+        $overview = $imp_mailbox->getMailboxArray($msglist, false, array('list-post'));
+
+        $charset = NLS::getCharset();
+        $identity = &Identity::singleton(array('imp', 'imp'));
+        $imp_ui = new IMP_UI_Mailbox($folder, $charset, $identity);
+
+        /* Display message information. */
+        reset($overview['overview']);
+        while (list($msgIndex, $ob) = each($overview['overview'])) {
+            /* Initialize the header fields. */
+            $msg = array(
+                'imapuid' => $ob['uid'],
+                'menutype' => 'message',
+                'rownum' => $msgIndex,
+                'view' => $ob['mailbox'],
+            );
+
+            /* Get all the flag information. */
+            $bg = array('msgRow');
+            if ($_SESSION['imp']['protocol'] != 'pop') {
+                if (!in_array('\\seen', $ob['flags'])) {
+                    $bg[] = 'unseen';
+                }
+                if (in_array('\\answered', $ob['flags'])) {
+                    $bg[] = 'answered';
+                }
+                if (in_array('\\draft', $ob['flags'])) {
+                    $bg[] = 'draft';
+                    $msg['menutype'] = 'draft';
+                    $msg['draft'] = 1;
+                }
+                if (in_array('\\flagged', $ob['flags'])) {
+                    $bg[] = 'flagged';
+                }
+                if (in_array('\\deleted', $ob['flags'])) {
+                    $bg[] = 'deletedmsg';
+                }
+            }
+
+            $attachment = '';
+            if (!empty($GLOBALS['dimp_conf']['hooks']['msglist_format'])) {
+                $ob_f = Horde::callHook('_dimp_hook_msglist_format', array($ob['mailbox'], $ob['uid']), 'dimp');
+                if (is_a($ob_f, 'PEAR_Error')) {
+                    Horde::logMessage($ob_f, __FILE__, __LINE__, PEAR_LOG_ERR);
+                } else {
+                    $attachment = empty($ob_f['atc']) ? '' : $ob_f['atc'];
+                    if (!empty($ob_f['class'])) {
+                        $bg = array_merge($bg, $ob_f['class']);
+                    }
+                }
+            }
+
+            $msg['bg'] = $bg;
+
+            /* Format attachment information. */
+            if (!empty($attachment)) {
+                $msg['atc'] = $attachment;
+            }
+
+            /* Format size information. */
+            $msg['size'] = htmlspecialchars($imp_ui->getSize($ob['size']), ENT_QUOTES, $charset);
+
+            /* Format the Date: Header. */
+            $msg['date'] = htmlspecialchars($imp_ui->getDate($ob['envelope']['date']), ENT_QUOTES, $charset);
+
+            /* Format the From: Header. */
+            $getfrom = $imp_ui->getFrom($ob['envelope'], false);
+            $msg['from'] = htmlspecialchars($getfrom['from'], ENT_QUOTES, $charset);
+
+            /* Format the Subject: Header. */
+            $msg['subject'] = str_replace('&nbsp;', '&#160;', Text::htmlSpaces($imp_ui->getSubject($ob['envelope']['subject'])));
+
+            if (!empty($GLOBALS['conf']['hooks']['mailboxarray'])) {
+                $result = Horde::callHook('_dimp_hook_mailboxarray', array($msg, $ob), 'dimp');
+                if (is_a($result, 'PEAR_Error')) {
+                    Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                } else {
+                    $msg = $result;
+                }
+            }
+
+            /* Check to see if this is a list message. Namely, we want to
+             * check for 'List-Post' information because that is the header
+             * that gives the e-mail address to reply to, which is all we
+             * care about. */
+            if ($ob['headers']->getValue('list-post')) {
+                $msg['listmsg'] = 1;
+            }
+
+            /* Need both UID and mailbox to create a unique ID string. */
+            $msgs[$ob['uid'] . $ob['mailbox']] = $msg;
+        }
+
+        return $msgs;
+    }
+}
diff --git a/imp/lib/Views/ShowMessage.php b/imp/lib/Views/ShowMessage.php
new file mode 100644 (file)
index 0000000..dcdb420
--- /dev/null
@@ -0,0 +1,328 @@
+<?php
+/**
+ * Dimp show message view logic.
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @package IMP
+ */
+class DIMP_Views_ShowMessage
+{
+    /**
+     * Builds a list of addresses from header information.
+     *
+     * @param IMP_Headers &$headers  The headers object.
+     * @param array $addrlist        The list of addresses from
+     *                               MIME::parseAddressList().
+     *
+     * @return array  Array with the following keys: address, display, inner,
+     *                personal, raw.
+     */
+    private function _buildAddressList(&$headers, $addrlist)
+    {
+        if (empty($addrlist) || !is_array($addrlist)) {
+            return;
+        }
+
+        $addr_array = array();
+        $call_hook = !empty($GLOBALS['conf']['hooks']['addressformatting']);
+
+        foreach (Horde_Mime_Address::getAddressesFromObject($addrlist) as $ob) {
+            if (empty($ob->address) || empty($ob->inner)) {
+                continue;
+            }
+
+            /* If this is an incomplete e-mail address, don't link to
+             * anything. */
+            if ($call_hook) {
+                $result = Horde::callHook('_dimp_hook_addressformatting', array($ob), 'dimp');
+                if (is_a($result, 'PEAR_Error')) {
+                    Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
+                } else {
+                    $addr_array[] = array('raw' => $result);
+                }
+            } elseif (stristr($ob->host, 'UNKNOWN') !== false) {
+                $addr_array[] = array('raw' => htmlspecialchars($ob->address));
+            } else {
+                $tmp = array();
+                foreach (array('address', 'display', 'inner', 'personal') as $val) {
+                    if ($val == 'display') {
+                        $ob->display = htmlspecialchars($ob->display);
+                        if ($ob->display == $ob->address) {
+                            continue;
+                        }
+                    }
+                    if (!empty($ob->$val)) {
+                        $tmp[$val] = $ob->$val;
+                    }
+                }
+                $addr_array[] = $tmp;
+            }
+        }
+
+        return $addr_array;
+    }
+
+    /**
+     * Create the object used to display the message.
+     *
+     * @param array $args  Configuration parameters.
+     * <pre>
+     * 'headers' - The headers desired in the returned headers array (only used
+     *             with non-preview view)
+     * 'folder' - The folder name
+     * 'index' - The folder index
+     * 'preview' - Is this the preview view?
+     * </pre>
+     *
+     * @return array  Array with the following keys:
+     * <pre>
+     * FOR BOTH MODES:
+     * 'atc_download' - The download all link
+     * 'atc_label' - The label to use for Attachments
+     * 'atc_list' - The list (HTML code) of attachments
+     * 'cc' - The CC addresses
+     * 'error' - Contains an error message (only on error)
+     * 'errortype' - Contains the error type (only on error)
+     * 'from' - The From addresses
+     * 'folder' - The IMAP folder
+     * 'index' - The IMAP UID
+     * 'msgtext' - The text of the message
+     * 'priority' - The X-Priority of the message ('low', 'high', 'normal')
+     * 'to' - The To addresses
+     * 'uid' - The unique UID of this message
+     *
+     * FOR PREVIEW MODE:
+     * 'fulldate' - The fully formatted date
+     * 'js' - Javascript code to run on display (only if the previewview
+     *        hook is active)
+     * 'minidate' - A miniature date
+     *
+     * FOR NON-PREVIEW MODE:
+     * 'bcc' - The Bcc addresses
+     * 'headers' - An array of headers (not including basic headers)
+     * 'replyTo' - The Reply-to addresses
+     * </pre>
+     */
+    public function showMessage($args)
+    {
+        $preview = !empty($args['preview']);
+        $folder = $args['folder'];
+        $index = $args['index'];
+        $error_msg = _("Requested message not found.");
+
+        $result = array(
+            'folder' => $folder,
+            'index' => $index,
+            'uid' => $index . $folder,
+        );
+
+        /* Set the current time zone. */
+        NLS::setTimeZone();
+
+        /* Get envelope/flag/header information. */
+        try {
+            $fetch_ret = $GLOBALS['imp_imap']->ob->fetch($folder, array(
+                Horde_Imap_Client::FETCH_ENVELOPE => true,
+                Horde_Imap_Client::FETCH_FLAGS => true,
+                Horde_Imap_Client::FETCH_HEADERTEXT => array(array('parse' => true, 'peek' => true))
+            ), array('ids' => array($index)));
+            $ob = $fetch_ret[$index];
+        } catch (Horde_Imap_Client_Exception $e) {
+            $result['error'] = $error_msg;
+            $result['errortype'] = 'horde.error';
+            return $result;
+        }
+
+        /* Parse MIME info and create the body of the message. */
+        $imp_contents = &IMP_Contents::singleton($index . IMP_IDX_SEP . $folder);
+        if (is_a($imp_contents, 'PEAR_Error') ||
+            !$imp_contents->buildMessage()) {
+            $result['error'] = $error_msg;
+            $result['errortype'] = 'horde.error';
+            return $result;
+        }
+
+        /* Get the IMP_UI_Message:: object. */
+        $imp_ui = new IMP_UI_Message();
+
+        /* Update the message flag, if necessary. */
+        if (($_SESSION['imp']['protocol'] == 'imap') &&
+            !in_array('\\seen', $ob['flags'])) {
+            $imp_mailbox = &IMP_Mailbox::singleton($folder, $index);
+            $imp_message = &IMP_Message::singleton();
+            $imp_message->flag(array('\\seen'), array($folder => array($index)), true);
+        }
+
+        /* Determine if we should generate the attachment strip links or
+         * not. */
+        if ($GLOBALS['prefs']->getValue('strip_attachments')) {
+            $imp_contents->setStripLink(true);
+        }
+
+        /* Show summary links. */
+        $imp_contents->showSummaryLinks(true);
+
+        $attachments = $imp_contents->getAttachments();
+        $result['msgtext'] = $imp_contents->getMessage();
+
+        /* Develop the list of Headers to display now. Deal with the 'basic'
+         * header information first since there are various manipulations
+         * done to them. */
+        $headers_list = $imp_ui->basicHeaders();
+        if (empty($args['headers'])) {
+            $args['headers'] = array('from', 'date', 'to', 'cc');
+        }
+
+        $basic_headers = array_intersect_key($headers_list, array_flip($args['headers']));
+
+        /* Build From/To/Cc/Bcc/Reply-To links. */
+        foreach (array('from', 'to', 'cc', 'bcc', 'reply-to') as $val) {
+            if (isset($basic_headers[$val]) &&
+                (!$preview || !in_array($val, array('bcc', 'reply-to')))) {
+                $tmp = $this->_buildAddressList($imp_headers, $ob->addrlist[$val]);
+                if (!empty($tmp)) {
+                    $result[$val] = $tmp;
+                } elseif ($val == 'to') {
+                    $result[$val] = array(array('raw' => _("Undisclosed Recipients")));
+                }
+                if ($preview) {
+                    unset($basic_headers[$val]);
+                }
+            }
+        }
+
+        if (empty($result['reply-to']) ||
+            ($result['from'] == $result['reply-to'])) {
+            unset($result['reply-to']);
+        }
+
+        /* Build the rest of the headers. */
+        foreach ($basic_headers as $head => $str) {
+            if (!$preview && isset($result[$head])) {
+                /* JS requires camelized name for reply-to. */
+                if ($head == 'reply-to') {
+                    $head = 'replyTo';
+                    $result[$head] = $result['reply-to'];
+                    unset($result['reply-to']);
+                }
+                $headers[] = array('id' => String::ucfirst($head), 'name' => $str, 'value' => '');
+            } elseif ($val = $imp_headers->getValue($head)) {
+                if ($head == 'date') {
+                    /* Add local time to date header. */
+                    $val = nl2br($imp_headers->addLocalTime(htmlspecialchars($val)));
+                    if ($preview) {
+                        $result['fulldate'] = $val;
+                    }
+                } elseif (!$preview) {
+                    $val = htmlspecialchars($val);
+                }
+                if (!$preview) {
+                    $headers[] = array('id' => String::ucfirst($head), 'name' => $str, 'value' => $val);
+                }
+            }
+        }
+
+        /* Display the user-specified headers for the current identity. */
+        if (!$preview) {
+            $user_hdrs = $imp_ui->getUserHeaders();
+            if (!empty($user_hdrs)) {
+                $full_h = $imp_headers->getAllHeaders();
+                foreach ($user_hdrs as $user_hdr) {
+                    foreach ($full_h as $head => $val) {
+                        if (stristr($head, $user_hdr) !== false) {
+                            $headers[] = array('name' => $head, 'value' => htmlspecialchars($val));
+                        }
+                    }
+                }
+            }
+            $result['headers'] = $headers;
+        }
+
+        /* Process the subject. */
+        if (($subject = $imp_headers->getValue('subject'))) {
+            require_once 'Horde/Text.php';
+            $subject = Text::htmlSpaces(IMP::filterText($subject));
+        } else {
+            $subject = htmlspecialchars(_("[No Subject]"));
+        }
+        $result['subject'] = $subject;
+
+        /* Get X-Priority/ */
+        $result['priority'] = $imp_headers->getXpriority();
+
+
+        /* Add attachment info. */
+        $atc_display = $GLOBALS['prefs']->getValue('attachment_display');
+        $show_parts = (!empty($attachments) && (($atc_display == 'list') || ($atc_display == 'both')));
+        $downloadall_link = $imp_contents->getDownloadAllLink();
+
+        if ($attachments && ($show_parts || $downloadall_link)) {
+            $result['atc_label'] = sprintf(ngettext("%d Attachment", "%d Attachments",
+                                         $imp_contents->attachmentCount()),
+                                         $imp_contents->attachmentCount());
+            $result['atc_download'] = ($downloadall_link) ? Horde::link($downloadall_link) . _("Save All") . '</a>' : '';
+        }
+        if ($show_parts) {
+            $result['atc_list'] = $attachments;
+        }
+
+        if ($preview) {
+            $curr_time = time();
+            $curr_time -= $curr_time % 60;
+            $ltime_val = localtime();
+            $today_start = mktime(0, 0, 0, $ltime_val[4] + 1, $ltime_val[3], 1900 + $ltime_val[5]);
+            $today_end = $today_start + 86400;
+            if (empty($ob->date)) {
+                $udate = false;
+            } else {
+                $ob->date = preg_replace('/\s+\(\w+\)$/', '', $ob->date);
+                $udate = strtotime($ob->date, $curr_time);
+            }
+            if ($udate === false || $udate === -1) {
+                $result['minidate'] = _("Unknown Date");
+            } elseif (($udate < $today_start) || ($udate > $today_end)) {
+                /* Not today, use the date. */
+                $result['minidate'] = strftime($GLOBALS['prefs']->getValue('date_format'), $udate);
+            } else {
+                /* Else, it's today, use the time. */
+                $result['minidate'] = strftime($GLOBALS['prefs']->getValue('time_format'), $udate);
+            }
+        }
+
+        if ($preview && !empty($GLOBALS['conf']['hooks']['previewview'])) {
+            $res = Horde::callHook('_dimp_hook_previewview', array($result), 'dimp');
+            if (is_a($res, 'PEAR_Error')) {
+                Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR);
+            } else {
+                $result = $res[0];
+                $result['js'] = $res[1];
+            }
+        } elseif (!$preview && !empty($GLOBALS['conf']['hooks']['messageview'])) {
+            $res = Horde::callHook('_dimp_hook_messageview', array($result), 'dimp');
+            if (is_a($res, 'PEAR_Error')) {
+                Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR);
+            } else {
+                $result = $res;
+            }
+        }
+
+        /* Retrieve any history information for this message. */
+        if (!empty($GLOBALS['conf']['maillog']['use_maillog'])) {
+            if (!$preview) {
+                IMP_Maillog::displayLog($imp_headers->getValue('message-id'));
+            }
+
+            /* Do MDN processing now. */
+            if ($imp_ui->MDNCheck($ob->header)) {
+                $confirm_link = Horde::link('', '', '', '', 'DimpCore.doAction(\'SendMDN\',{folder:\'' . $folder . '\',index:' . $index . '}); return false;', '', '') . _("HERE") . '</a>';
+                $GLOBALS['notification']->push(sprintf(_("The sender of this message is requesting a Message Disposition Notification from you when you have read this message. Click %s to send the notification message."), $confirm_link), 'dimp.request', array('content.raw'));
+            }
+        }
+
+        return $result;
+    }
+}
index 8b68c95..86c249e 100644 (file)
@@ -186,11 +186,14 @@ function _imp_compose($args = array(), $extra = array())
 function _imp_batchCompose($args = array(), $extra = array())
 {
     $GLOBALS['authentication'] = 'none';
-    $GLOBALS['noset_view'] = true;
     require_once dirname(__FILE__) . '/base.php';
 
     $links = array();
     foreach ($args as $i => $arg) {
+        // @todo - for DIMP
+        // $extra[$i]['type'] = 'new';
+        // $extra[$i]['popup'] = true;
+        // $links[$i] = DIMP::composeLink($arg, $extra[$i]);
         $links[$i] = IMP::composeLink($arg, !empty($extra[$i]) ? $extra[$i] : array());
     }
 
@@ -205,7 +208,6 @@ function _imp_batchCompose($args = array(), $extra = array())
 function _imp_folderlist()
 {
     $GLOBALS['authentication'] = 'none';
-    $GLOBALS['noset_view'] = true;
     require_once dirname(__FILE__) . '/base.php';
 
     $result = false;
@@ -234,7 +236,6 @@ function _imp_folderlist()
 function _imp_createFolder($folder)
 {
     $GLOBALS['authentication'] = 'none';
-    $GLOBALS['noset_view'] = true;
     require_once dirname(__FILE__) . '/base.php';
 
     $result = false;
@@ -256,7 +257,6 @@ function _imp_createFolder($folder)
 function _imp_server()
 {
     $GLOBALS['authentication'] = 'none';
-    $GLOBALS['noset_view'] = true;
     require_once dirname(__FILE__) . '/base.php';
     return (IMP::checkAuthentication(true)) ? $_SESSION['imp']['server'] : null;
 }
@@ -274,7 +274,6 @@ function _imp_favouriteRecipients($limit,
                                   $filter = array('new', 'forward', 'reply', 'redirect'))
 {
     $GLOBALS['authentication'] = 'none';
-    $GLOBALS['noset_view'] = true;
     require_once dirname(__FILE__) . '/base.php';
 
     if ($GLOBALS['conf']['sentmail']['driver'] != 'none') {
@@ -291,7 +290,6 @@ function _imp_favouriteRecipients($limit,
 function _imp_changeLanguage()
 {
     $GLOBALS['authentication'] = 'none';
-    $GLOBALS['noset_view'] = true;
     require_once dirname(__FILE__) . '/base.php';
 
     if (IMP::checkAuthentication(true)) {
index 7469bd2..057a439 100644 (file)
@@ -10,6 +10,7 @@
  *                      'none'  - Do not authenticate
  *                      Default - Authenticate to IMAP/POP server
  *   $compose_page    - If true, we are on IMP's compose page
+ *   $dimp_logout      - Logout and redirect to the login page.
  *   $login_page      - If true, we are on IMP's login page
  *   $mimp_debug      - If true, output text/plain version of page.
  *   $no_compress     - Controls whether the page should be compressed
@@ -45,23 +46,24 @@ require_once HORDE_BASE . '/lib/core.php';
 require_once 'Horde/Autoloader.php';
 Horde_Autoloader::addClassPattern('/^IMP_/', IMP_BASE . '/lib/');
 
-$session_control = Util::nonInputVar('session_control');
-switch ($session_control) {
+// Registry.
+$s_ctrl = null;
+switch (Util::nonInputVar('session_control')) {
 case 'netscape':
     if ($browser->isBrowser('mozilla')) {
         session_cache_limiter('private, must-revalidate');
     }
     break;
-}
 
-// Registry.
-if ($session_control == 'none') {
-    $registry = &Registry::singleton(HORDE_SESSION_NONE);
-} elseif ($session_control == 'readonly') {
-    $registry = &Registry::singleton(HORDE_SESSION_READONLY);
-} else {
-    $registry = &Registry::singleton();
+case 'none':
+    $s_ctrl = HORDE_SESSION_NONE;
+    break;
+
+case 'readonly':
+    $s_ctrl = HORDE_SESSION_READONLY;
+    break;
 }
+$registry = &Registry::singleton();
 
 // Need to explicitly load IMP.php
 require_once IMP_BASE . '/lib/IMP.php';
@@ -102,6 +104,11 @@ if (!(Auth::isAuthenticated() || (Auth::getProvider() == 'imp'))) {
     Horde::authenticationFailureRedirect();
 }
 
+// Determine view mode.
+$viewmode = isset($_SESSION['imp']['view'])
+    ? $_SESSION['imp']['view']
+    : 'imp';
+
 $authentication = Util::nonInputVar('authentication');
 if ($authentication === null) {
     $authentication = 0;
@@ -130,6 +137,21 @@ if ($authentication !== 'none') {
             require IMP_BASE . '/login.php';
             exit;
         }
+    } elseif ($viewmode == 'dimp') {
+        // Handle session timeouts
+        if (!IMP::checkAuthentication(true)) {
+            switch (Util::nonInputVar('session_timeout')) {
+            case 'json':
+                $notification->push(null, 'dimp.timeout');
+                IMP::sendHTTPResponse(DIMP::prepareResponse(), 'json');
+
+            case 'none':
+                exit;
+
+            default:
+                Horde::redirect(Util::addParameter(Horde::url($GLOBALS['registry']->get('webroot', 'imp') . '/redirect.php'), 'url', Horde::selfUrl(true)));
+            }
+        }
     } else {
         IMP::checkAuthentication(false, ($authentication === 'horde'));
     }
@@ -137,10 +159,13 @@ if ($authentication !== 'none') {
 
 // Notification system.
 $notification = &Notification::singleton();
-if ((Util::nonInputVar('login_page') && $GLOBALS['browser']->isMobile()) ||
-    (isset($_SESSION['imp']['view']) && ($_SESSION['imp']['view'] == 'mimp'))) {
+if (($viewmode == 'mimp') ||
+    (Util::nonInputVar('login_page') && $GLOBALS['browser']->isMobile())) {
     require_once 'Horde/Notification/Listener/mobile.php';
     $GLOBALS['mimp_notify'] = &$notification->attach('status', null, 'Notification_Listener_mobile');
+} elseif ($viewmode == 'dimp') {
+    require_once IMP_BASE . '/lib/Notification/Listener/status-dimp.php';
+    $GLOBALS['dimp_notify'] = &$notification->attach('status', null, 'Notification_Listener_status_dimp');
 } else {
     require_once IMP_BASE . '/lib/Notification/Listener/status.php';
     require_once 'Horde/Notification/Listener/audio.php';
@@ -148,6 +173,11 @@ if ((Util::nonInputVar('login_page') && $GLOBALS['browser']->isMobile()) ||
     $notification->attach('audio');
 }
 
+// Handle logout requests
+if (($viewmode == 'dimp') && Util::nonInputVar('dimp_logout')) {
+    Horde::redirect(str_replace('&amp;', '&', IMP::getLogoutUrl()));
+}
+
 // Horde libraries.
 require_once 'Horde/Secret.php';
 
@@ -155,11 +185,9 @@ require_once 'Horde/Secret.php';
 $GLOBALS['imp_mbox'] = IMP::getCurrentMailboxInfo();
 
 // Initialize IMP_Search object.
-if (isset($_SESSION['imp']) && strpos($GLOBALS['imp_mbox']['mailbox'], IMP::SEARCH_MBOX) === 0) {
-    $GLOBALS['imp_search'] = new IMP_Search(array('id' => $GLOBALS['imp_mbox']['mailbox']));
-} else {
-    $GLOBALS['imp_search'] = new IMP_Search();
-}
+$GLOBALS['imp_search'] = ((isset($_SESSION['imp']) && strpos($GLOBALS['imp_mbox']['mailbox'], IMP::SEARCH_MBOX) === 0)
+    ? new IMP_Search(array('id' => $GLOBALS['imp_mbox']['mailbox'])
+    : new IMP_Search();
 
 if ((IMP::loginTasksFlag() === 2) &&
     !defined('AUTH_HANDLER') &&
@@ -167,7 +195,7 @@ if ((IMP::loginTasksFlag() === 2) &&
     IMP_Session::loginTasks();
 }
 
-if (isset($_SESSION['imp']['view']) && ($_SESSION['imp']['view'] == 'mimp')) {
+if ($viewmode == 'mimp') {
     // Need to explicitly load MIMP.php
     require_once IMP_BASE . '/lib/MIMP.php';
 
diff --git a/imp/message-dimp.php b/imp/message-dimp.php
new file mode 100644 (file)
index 0000000..d9d1a55
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * $Horde: dimp/message.php,v 1.73 2008/09/05 06:38:48 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Jan Schneider <jan@horde.org>
+ */
+
+$load_imp = true;
+@define('DIMP_BASE', dirname(__FILE__));
+require_once DIMP_BASE . '/lib/base.php';
+
+$folder = Util::getFormData('folder');
+$index = Util::getFormData('uid');
+if (!$index || !$folder) {
+    exit;
+}
+
+require_once IMP_BASE . '/lib/UI/Message.php';
+$imp_ui = new IMP_UI_Message();
+
+$args = array(
+    'headers' => array_diff(array_keys($imp_ui->basicHeaders()), array('subject')),
+    'folder' => $folder,
+    'index' => $index,
+    'preview' => false,
+);
+
+require_once DIMP_BASE . '/lib/Views/ShowMessage.php';
+$show_msg = new DIMP_Views_ShowMessage();
+$show_msg_result = $show_msg->ShowMessage($args);
+if (isset($show_msg_result['error'])) {
+    echo IMP::wrapInlineScript(array(
+        DIMP::notify(false, 'parent.opener.document', 'parent.opener.DimpCore'),
+        'parent.close()'
+    ));
+    exit;
+}
+
+$compose_args = array(
+    'folder' => $folder,
+    'index' => $index,
+    'messageCache' => '',
+    'popup' => false,
+    'qreply' => true,
+);
+$compose_result = DIMP_Views_Compose::showCompose($compose_args);
+
+/* Need the Header object to check for list information. */
+$msg_cache = &IMP_MessageCache::singleton();
+$cache_entry = $msg_cache->retrieve($folder, array($index), 32);
+$ob = reset($cache_entry);
+
+/* Init IMP_UI_Compose:: object. */
+require_once IMP_BASE . '/lib/UI/Compose.php';
+$imp_ui = new IMP_UI_Compose();
+
+/* Attach spellchecker & auto completer. */
+require_once DIMP_BASE . '/lib/Dimple.php';
+$imp_ui->attachAutoCompleter('Dimple', array('to', 'cc', 'bcc'));
+$imp_ui->attachSpellChecker('dimp');
+
+$compose_result['js'] = array_merge($compose_result['js'], array(
+    'DIMP.conf.msg_index = "' . $show_msg_result['index'] . '"',
+    'DIMP.conf.msg_folder = "' . $show_msg_result['folder'] . '"'
+));
+
+require_once 'Horde/Serialize.php';
+foreach (array('from', 'to', 'cc', 'bcc', 'replyTo') as $val) {
+    if (!empty($show_msg_result[$val])) {
+        $compose_result['js'][] = 'DimpFullmessage.' . $val . ' = ' . Horde_Serialize::serialize($show_msg_result[$val], SERIALIZE_JSON);
+    }
+}
+IMP::addInlineScript($compose_result['js']);
+IMP::addInlineScript($compose_result['jsonload'], 'load');
+IMP::addInlineScript(array(DIMP::notify()), 'dom');
+
+$scripts = array(
+    array('ContextSensitive.js', 'dimp', true),
+    array('fullmessage.js', 'dimp', true),
+    array('compose.js', 'dimp', true),
+    array('unblockImages.js', 'imp', true)
+);
+
+DIMP::header($show_msg_result['subject'], $scripts);
+echo "<body>\n";
+require DIMP_TEMPLATES . '/chunks/message.php';
+IMP::includeScriptFiles();
+IMP::outputInlineScript();
+echo $compose_result['jsappend'];
+$notification->notify(array('listeners' => array('javascript')));
+echo "</body>\n</html>";
diff --git a/imp/templates/chunks/compose.php b/imp/templates/chunks/compose.php
new file mode 100644 (file)
index 0000000..83bdbe3
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+/**
+ * compose.php - Used by DIMP_Views_Compose:: to render the compose screen.
+ *
+ * Variables passed in from calling code:
+ *   $args('folder', 'index'), $compose_html, $draft_index, $from, $id,
+ *   $identity, $composeCache, $rte, $selected_identity, $sent_mail_folder
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+$d_read = $GLOBALS['prefs']->getValue('disposition_request_read');
+$save_attach = $GLOBALS['prefs']->getValue('save_attachments');
+
+$loading_img = Horde::img('loading.gif', _("Loading..."), array(), $GLOBALS['registry']->getImageDir('dimp'));
+
+// Small utility function to simplify creating dimpactions buttons.
+// As of right now, we don't show text only links.
+function _createDAcompose($text, $image, $id)
+{
+    $params = array('icon' => $image, 'id' => $id);
+    if (!in_array($GLOBALS['prefs']->getValue('menu_view'), array('text', 'both'))) {
+        $params['tooltip'] = $text;
+    } else {
+        $params['title'] = $text;
+    }
+    echo DIMP::actionButton($params);
+}
+
+?>
+<div id="composeloading"></div>
+<form id="compose" name="compose" enctype="multipart/form-data" action="compose.php" method="post" target="submit_frame">
+<?php echo Util::formInput() ?>
+<input type="hidden" id="action" name="action" />
+<input type="hidden" id="last_identity" name="last_identity" value="<?php echo (int)$selected_identity ?>" />
+<input type="hidden" id="html" name="html" value="<?php echo intval($rte && $compose_html) ?>" />
+<input type="hidden" id="in_reply_to" name="in_reply_to" />
+<input type="hidden" id="references" name="references" />
+<input type="hidden" id="folder" name="folder" value="<?php echo $args['folder'] ?>" />
+<input type="hidden" id="index" name="index" value="<?php echo $args['index'] ?>" />
+<input type="hidden" id="draft_index" name="draft_index" value="<?php echo $draft_index ?>" />
+<input type="hidden" id="reply_type" name="reply_type" />
+<input type="hidden" id="composeCache" name="composeCache" value="<?php echo $composeCache ?>" />
+
+<div class="dimpActions dimpActionsCompose">
+ <?php _createDAcompose(_("Send"), 'forward_menu.png', 'send_button') ?>
+<?php if ($GLOBALS['conf']['compose']['allow_receipts'] && $d_read != 'never'): ?>
+ <label><input type="checkbox" class="checkbox" name="request_read_receipt"<?php if ($d_read != 'ask') echo ' checked="checked"' ?> /> <?php echo _("Read Receipt") ?></label>
+<?php endif; ?>
+<?php if ($GLOBALS['conf']['user']['allow_folders'] && !$GLOBALS['prefs']->isLocked('save_sent_mail')): ?>
+  <label><input type="checkbox" class="checkbox" id="save_sent_mail" name="save_sent_mail"<?php if ($identity->saveSentmail()) echo ' checked="checked"' ?> /> <?php echo _("Save in ") ?><span id="sent_mail_folder_label"><?php echo $sent_mail_folder ?></span></label>
+<?php endif; ?>
+ <?php _createDAcompose(_("Check Spelling"), 'spellcheck_menu.png', 'spellcheck') ?>
+ <?php _createDAcompose(_("Save as Draft"), 'drafts_menu.png', 'draft_button') ?>
+</div>
+
+<div id="writemsg" class="noprint">
+ <div class="msgwrite">
+  <div class="dimpOptions">
+   <label><input id="togglecc" name="togglecc" type="checkbox" class="checkbox" /> <?php echo _("Show Cc") ?></label>
+   <label><input id="togglebcc" name="togglebcc" type="checkbox" class="checkbox" /> <?php echo _("Show Bcc") ?></label>
+<?php if ($rte): ?>
+   <label><input id="htmlcheckbox" type="checkbox" class="checkbox" <?php if ($compose_html) echo 'checked="checked"' ?> /> <?php echo _("HTML composition") ?></label>
+<?php endif; ?>
+<?php if (!empty($GLOBALS['conf']['compose']['special_characters'])): ?>
+   <div id="compose_specialchars"><?php echo Horde::img('keyboard.png', _("Special Characters"), array(), $GLOBALS['registry']->getImageDir('horde')) ?><a><?php echo _("Special Characters") ?></a></div>
+<?php endif; ?>
+  </div>
+  <table cellspacing="0">
+   <thead>
+    <tr>
+     <td class="label"><?php echo _("From: ") ?></td>
+     <td>
+      <select id="identity" name="identity">
+<?php foreach ($identity->getSelectList() as $id => $from): ?>
+       <option value="<?php echo htmlspecialchars($id) ?>"<?php if ($id == $selected_identity) echo ' selected="selected"' ?>><?php echo htmlspecialchars($from) ?></option>
+<?php endforeach; ?>
+      </select>
+     </td>
+    </tr>
+    <tr>
+     <td class="label"><?php echo Horde::link('', '', 'composeAddrbook') . _("To: ") . '</a>' ?></td>
+     <td>
+      <textarea id="to" name="to" rows="1" cols="75"></textarea>
+      <div id="to_results" class="autocomplete" style="display:none"></div>
+     </td>
+     <td>
+      <span id="to_loading_img" style="display:none"><?php echo $loading_img ?></span>
+     </td>
+    </tr>
+   </thead>
+  </table>
+  <table cellspacing="0" id="sendcc" style="display:none">
+   <thead>
+    <tr>
+     <td class="label"><?php echo Horde::link('', '', 'composeAddrbook') . _("Cc: ") . '</a>' ?></td>
+     <td>
+      <textarea id="cc" name="cc" rows="1" cols="75"></textarea>
+      <div id="cc_results" class="autocomplete" style="display:none"></div>
+     </td>
+     <td>
+      <span id="cc_loading_img" style="display:none"><?php echo $loading_img ?></span>
+     </td>
+    </tr>
+   </thead>
+  </table>
+  <table cellspacing="0" id="sendbcc" style="display:none">
+   <thead>
+    <tr>
+     <td class="label"><?php echo Horde::link('', '', 'composeAddrbook') . _("Bcc: ") . '</a>' ?></td>
+     <td>
+      <textarea id="bcc" name="bcc" rows="1" cols="75"></textarea>
+      <div id="bcc_results" class="autocomplete" style="display:none"></div>
+     </td>
+     <td>
+      <span id="bcc_loading_img" style="display:none"><?php echo $loading_img ?></span>
+     </td>
+    </tr>
+   </thead>
+  </table>
+  <table cellspacing="0">
+   <thead>
+    <tr>
+     <td class="label"><?php echo _("Subject: ") ?></td>
+     <td class="subject"><input type="text" id="subject" name="subject" /></td>
+    </tr>
+   </thead>
+  </table>
+  <table cellspacing="0">
+   <thead>
+    <tr class="atcrow">
+     <td class="label"><?php echo Horde::img('attachment.png', '', '', $GLOBALS['registry']->getImageDir('imp')) . ': ' ?></td>
+     <td id="attach_cell">
+      <input type="file" id="upload" name="file_1" />
+<?php if (strpos($save_attach, 'prompt') !== false): ?>
+      <label><input type="checkbox" class="checkbox" name="save_attachments_select"<?php if (strpos($save_attach, 'yes') !== false) echo ' checked="checked"' ?> /> <?php echo _("Save Attachments in sent folder") ?></label><br />
+<?php endif; ?>
+      <div id="attach_list"></div>
+     </td>
+    </tr>
+   </thead>
+  </table>
+ </div>
+
+ <div id="messageParent">
+  <textarea name="message" rows="20" id="message" class="fixed"></textarea>
+ </div>
+</div>
+</form>
+
+<iframe name="submit_frame" id="submit_frame" style="display:none" src="javascript:false;"></iframe>
diff --git a/imp/templates/chunks/message.php b/imp/templates/chunks/message.php
new file mode 100644 (file)
index 0000000..057755a
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+/**
+ * $Horde: dimp/templates/chunks/message.php,v 1.12 2008/10/22 22:59:38 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+$dimp_img = $registry->getImageDir('dimp');
+$horde_img = $registry->getImageDir('horde');
+$menu_view = $prefs->getValue('menu_view');
+$show_text = ($menu_view == 'text' || $menu_view == 'both');
+
+$attachment = Horde::img('attachment.png', '', array('class' => 'attachmentImage'), $dimp_img);
+
+// Small utility function to simplify creating dimpactions buttons.
+// As of right now, we don't show text only links.
+function _createDAfmsg($text, $image, $id, $class = '', $show_text = true)
+{
+    $params = array('icon' => $image, 'id' => $id, 'class' => $class);
+    if ($show_text) {
+        $params['title'] = $text;
+    } else {
+        $params['tooltip'] = $text;
+    }
+    echo DIMP::actionButton($params);
+}
+
+?>
+<div id="pageContainer">
+ <div id="msgData">
+  <div class="noprint">
+   <div class="header">
+    <div class="headercloseimg" id="windowclose"><?php echo Horde::img('close.png', 'X', array(), $horde_img) ?></div>
+    <div><?php echo _("Message:") . ' ' . $show_msg_result['subject'] ?></div>
+   </div>
+
+   <div class="dimpActions">
+    <span id="button_reply_cont"><?php _createDAfmsg(_("Reply"), 'reply_menu.png', 'reply_link', 'hasmenu', $show_text) ?></span>
+    <span id="button_forward_cont"><?php _createDAfmsg(_("Forward"), 'forward_menu.png', 'forward_link', 'hasmenu', $show_text) ?></span>
+<?php if (!empty($conf['spam']['reporting']) && (!$conf['spam']['spamfolder'] || ($folder != IMP::folderPref($prefs->getValue('spam_folder'), true)))): ?>
+    <span><?php _createDAfmsg(_("Report Spam"), 'spam_menu.png', 'button_spam', '', $show_text) ?></span>
+<?php endif; ?>
+<?php if (!empty($conf['notspam']['reporting']) && (!$conf['notspam']['spamfolder'] || ($folder == IMP::folderPref($prefs->getValue('spam_folder'), true)))): ?>
+    <span><?php _createDAfmsg(_("Report Innocent"), 'ham_menu.png', 'button_ham', '', $show_text) ?></span>
+<?php endif; ?>
+    <span><?php _createDAfmsg(_("Delete"), 'delete_menu.png', 'button_deleted', '', $show_text) ?></span>
+   </div>
+  </div>
+
+  <div class="msgfullread">
+   <div class="msgHeaders">
+    <div id="msgHeaders">
+     <div class="dimpOptions noprint">
+      <div id="msg_print"><?php echo Horde::img('print.png', '', '', $horde_img) ?><a><?php echo _("Print") ?></a></div>
+<?php if (!empty($conf['user']['allow_view_source'])): ?>
+      <div id="msg_view_source"><?php echo Horde::img('message_source.png', '', '', $dimp_img) ?><a><?php echo _("View Message Source") ?></a></div>
+<?php endif; ?>
+     </div>
+     <div id="msgHeadersContent">
+      <table cellspacing="0">
+       <thead>
+        <tr>
+         <td class="label"><?php echo _("Subject") ?>:</td>
+         <td class="subject"><?php echo $show_msg_result['subject'] ?></td>
+        </tr>
+<?php foreach($show_msg_result['headers'] as $val): ?>
+        <tr<?php if (isset($val['id'])): ?> id="msgHeader<?php echo $val['id'] ?>"<?php endif; ?>>
+         <td class="label"><?php echo $val['name'] ?>:</td>
+         <td><?php echo $val['value'] ?></td>
+        </tr>
+<?php endforeach; ?>
+<?php if (isset($show_msg_result['atc_label'])): ?>
+        <tr id="msgAtc">
+         <td class="label"><?php if ($show_msg_result['atc_list']): ?><?php echo Horde::link('') . Horde::img('arrow_collapsed.png', '', array('id' => 'partlist_col'), $dimp_img) . Horde::img('arrow_expanded.png', '', array('id' => 'partlist_exp', 'style' => 'display:none'), $dimp_img) . ' ' . $attachment ?></a><?php else: echo $attachment; endif; ?></td>
+         <td>
+          <?php echo $show_msg_result['atc_label'] . ' ' . $show_msg_result['atc_download'] ?>
+<?php if (isset($show_msg_result['atc_list'])): ?>
+          <table id="partlist" cellspacing="2">
+           <?php echo $show_msg_result['atc_list'] ?>
+          </table>
+<?php endif; ?>
+         </td>
+        </tr>
+<?php endif; ?>
+       </thead>
+      </table>
+     </div>
+    </div>
+   </div>
+   <div class="msgBody">
+    <table width="100%" cellspacing="0">
+     <?php echo $show_msg_result['msgtext'] ?>
+    </table>
+   </div>
+  </div>
+ </div>
+
+ <div id="qreply" style="display:none">
+  <div class="header">
+   <div class="headercloseimg"><?php echo Horde::img('close.png', 'X', array(), $horde_img) ?></div>
+   <div><?php echo _("Message:") . ' ' . $show_msg_result['subject'] ?></div>
+  </div>
+  <?php echo $compose_result['html']; ?>
+ </div>
+</div>
+
+<div class="context" id="ctx_replypopdown" style="display:none">
+ <div><?php _createDAfmsg(_("To Sender"), 'reply.png', 'ctx_replypopdown_reply') ?></div>
+ <div><?php _createDAfmsg(_("To All"), 'replyall.png', 'ctx_replypopdown_reply_all') ?></div>
+<?php if ($ob->header->listHeadersExist()): ?>
+ <div><?php _createDAfmsg(_("To List"), 'replyall.png', 'ctx_replypopdown_reply_list') ?></div>
+<?php endif; ?>
+</div>
+
+<div class="context" id="ctx_fwdpopdown" style="display:none">
+ <div><?php _createDAfmsg(_("Entire Message"), 'forward.png', 'ctx_fwdpopdown_forward_all') ?></div>
+ <div><?php _createDAfmsg(_("Body Text Only"), 'forward.png', 'ctx_fwdpopdown_forward_body') ?></div>
+ <div><?php _createDAfmsg(_("Attachments Only"), 'forward.png', 'ctx_fwdpopdown_forward_attachments') ?></div>
+</div>
+
+<div class="context" id="ctx_contacts" style="display:none">
+ <div><?php _createDAfmsg(_("New Message"), 'compose.png', 'ctx_contacts_new') ?></div>
+ <div><?php _createDAfmsg(_("Add to Address Book"), 'add_contact.png', 'ctx_contacts_add') ?></div>
+</div>
+
+<?php echo Horde::img('popdown.png', 'v', array('class' => 'popdown', 'id' => 'popdown_img', 'style' => 'display:none'), $dimp_img) ?>
diff --git a/imp/templates/imp/compose.html b/imp/templates/imp/compose.html
new file mode 100644 (file)
index 0000000..77d6e2c
--- /dev/null
@@ -0,0 +1,10 @@
+<body>
+ <div id="pageContainer">
+  <div>
+   <div class="header">
+    <div class="headercloseimg"><tag:closelink /></div>
+    <div class="headertitle"><tag:title /></div>
+   </div>
+  </div>
+  <tag:compose_html />
+ </div>
diff --git a/imp/templates/imp/portal.html b/imp/templates/imp/portal.html
new file mode 100644 (file)
index 0000000..8110283
--- /dev/null
@@ -0,0 +1,9 @@
+<div id="portalchunk">
+<loop:block>
+ <div class="<tag:block.class />"<if:block.domid> id="<tag:block.domid />"</if:block.domid>>
+  <h1 class="header"><a app="<tag:block.app />"><tag:block.title /></a></h1>
+  <tag:block.content />
+ </div>
+  <br class="clear" /><br />
+</loop:block>
+</div>
diff --git a/imp/templates/index/index.inc b/imp/templates/index/index.inc
new file mode 100644 (file)
index 0000000..9a9326e
--- /dev/null
@@ -0,0 +1,489 @@
+<?php
+// $Horde: dimp/templates/index/index.inc,v 1.238 2008/10/09 18:49:09 chuck Exp $
+
+// Generate various dimp graphics used in multiple locations
+$imagedir = $registry->getImageDir('dimp');
+$hordeimg = $registry->getImageDir('horde');
+$mailseen = Horde::img('mail_seen.png');
+$mailunseen = Horde::img('mail_unseen.png');
+$delete = Horde::img('delete.png', '', '', $imagedir);
+$folder_create = Horde::img('folders/folder_create.png');
+$folder_edit = Horde::img('folders/folder_edit.png');
+$forward = Horde::img('forward.png', '', '', $imagedir);
+$flagged = Horde::img('mail_flagged.png');
+$clearflag = Horde::img('mail_clearflag.png');
+$preview = Horde::img('preview.png', '', '', $imagedir);
+$tick = Horde::img('tick.png', '', '', $imagedir);
+$newwin = Horde::img('newwin.png', _("Open in new window"), '', $imagedir);
+$attachment = Horde::img('attachment.png', '', array('class' => 'attachmentImage'));
+
+// Thread images
+require_once IMP_BASE . '/lib/IMAP/Thread.php';
+$thread_imgs = IMP_Thread::getImageUrls();
+
+// Attachment images
+require_once IMP_BASE . '/lib/UI/Mailbox.php';
+$imp_ui = new IMP_UI_Mailbox();
+$atc_imgs = array();
+foreach ($imp_ui->getAttachmentAltList() as $k => $v) {
+    $atc_imgs[] = Horde::img($k . '.png', $v, array('id' => 'atc_img_' . $k));
+}
+
+$usetrash = $prefs->getValue('use_trash');
+$menu_view = $prefs->getValue('menu_view');
+$show_text = ($menu_view == 'text' || $menu_view == 'both');
+$loading_text = _("Loading...");
+$is_ie6 = ($browser->isBrowser('msie') && ($browser->getMajor() < 7));
+
+$has_blacklist = $registry->hasMethod('mail/blacklistFrom');
+$has_whitelist = $registry->hasMethod('mail/whitelistFrom');
+
+// Sidebar width
+$sidebar_width = max((int)$prefs->getValue('sidebar_width') - 50, 150) . 'px';
+
+if (isset($_SESSION['imp']['quota']) && is_array($_SESSION['imp']['quota'])) {
+    $quotadata = IMP::quotaData(false);
+    if (!empty($quotadata)) {
+        $quota_img = Horde::img('quotauncover.gif', '', array('width' => round(99 - $quotadata['percent']), 'height' => 10), $imagedir);
+    }
+}
+
+// Small utility functions to simplify creating dimpactions buttons.
+// As of right now, we don't show text only links.
+function _createDA($text, $image, $id = null, $class = '', $show_text = true)
+{
+    $params = array('icon' => $image, 'id' => $id, 'class' => $class);
+    if ($show_text) {
+        $params['title'] = $text;
+    } else {
+        $params['tooltip'] = $text;
+    }
+    echo DIMP::actionButton($params);
+}
+
+function _simpleButton($id, $text, $image, $imagedir = null)
+{
+    $ak = Horde::getAccessKey($text, true);
+    return '<li class="servicelink"'
+        . (strlen($id) ? ' id="' . $id . '"' : '')
+        . (strlen($ak) ? ' accesskey="' . $ak . '"' : '') . '>'
+        . ($imagedir
+            ? Horde::img($image, Horde::stripAccessKey($text), '', $imagedir)
+            : Horde::img($image, Horde::stripAccessKey($text)))
+        . '<a>'
+        . Horde::highlightAccessKey($text, $ak) . '</a></li>';
+}
+?>
+<div id="dimpLoading"><?php echo $loading_text ?></div>
+<div id="dimpPage" style="display:none">
+ <div id="header"></div>
+ <div id="pageContainer">
+  <div id="sidebarPanel" class="noprint" style="width:<?php echo $sidebar_width ?>">
+   <div id="logo"><h1><a><?php echo _("Horde") ?></a></h1></div>
+   <ul id="dimpbarActions">
+    <?php echo _simpleButton('composelink', _("_New Message"), 'compose.png') ?>
+    <?php echo _simpleButton('checkmaillink', _("_Get Mail"), 'checkmail.png', $imagedir) ?>
+<?php if (!$is_ie6): ?>
+    <?php echo _simpleButton('alertsloglink', _("Alerts _Log"), 'info_icon.png', $hordeimg) ?>
+<?php endif; ?>
+   </ul>
+   <div class="sepfull" style="width:<?php echo $sidebar_width ?>"></div>
+   <ul id="serviceActions">
+<?php if ($registry->get('status', 'horde') != 'hidden' && $registry->get('status', 'horde') != 'notoolbar'): ?>
+    <?php echo _simpleButton('appportal', _("_Portal"), 'horde.png', $hordeimg) ?>
+<?php endif; ?>
+<?php if (Horde::showService('options')): ?>
+    <?php echo _simpleButton('appoptions', _("_Options"), 'prefs.png', $hordeimg) ?>
+<?php endif; ?>
+<?php if (Horde::showService('logout')): ?>
+    <?php echo _simpleButton('applogout', _("_Log Out"), 'logout.png', $hordeimg) ?>
+<?php endif; ?>
+   </ul>
+   <div class="sepfull" style="width:<?php echo $sidebar_width ?>"></div>
+   <div id="foldersLoading"><?php echo $loading_text ?></div>
+   <div id="foldersSidebar" style="display:none">
+    <ul class="folderlist" id="specialfolders"></ul>
+<?php if (!empty($application_folders)): ?>
+    <div class="sepfull" style="width:<?php echo $sidebar_width ?>"></div>
+    <ul id="applicationfolders">
+<?php foreach ($application_folders as $val): ?>
+     <li class="custom">
+      <img src="<?php echo $val['icon'] ?>" alt="<?php echo $val['name'] ?>" />
+      <a title="<?php echo $val['name'] ?>" id="app<?php echo $val['app'] ?>" app="<?php echo $val['app'] ?>"><?php echo $val['name'] ?></a>
+     </li>
+<?php endforeach; ?>
+    </ul>
+<?php endif; ?>
+<?php if (!empty($site_menu)): ?>
+    <div id="sitemenu">
+     <div class="sepfull" style="width:<?php echo $sidebar_width ?>"></div>
+     <ul>
+<?php foreach ($site_menu as $key => $menu_item): ?>
+<?php if ($menu_item == 'separator'): ?>
+     </ul>
+     <div class="sepfull" style="width:<?php echo $sidebar_width ?>"></div>
+     <ul>
+<?php else: ?>
+      <li class="custom">
+       <img src="<?php echo $menu_item['icon'] ?>" alt="<?php echo $menu_item['text'] ?>" />
+       <a title="<?php echo $menu_item['text'] ?>" id="menu<?php echo $key ?>"><?php echo $menu_item['text'] ?></a>
+      </li>
+<?php endif; ?>
+<?php endforeach; ?>
+     </ul>
+    </div>
+<?php endif; ?>
+    <div class="sepfull" style="width:<?php echo $sidebar_width ?>"></div>
+    <div id="myfolders"><?php echo _("My Folders") ?></div>
+    <ul class="folderlist" id="normalfolders">
+     <li class="folder" id="newfolder">
+      <div class="create"></div>
+      <a title="<?php echo _("New Folder") ?>"><?php echo _("New Folder") ?></a>
+     </li>
+     <li class="folder" id="dropbase" style="display:none">
+      <div class="base"></div>
+      <a title="<?php echo _("Base Level") ?>"><?php echo _("Base Level") ?></a>
+     </li>
+    </ul>
+   </div>
+  </div>
+
+  <div id="dimpmain" style="left:<?php echo $sidebar_width ?>">
+   <div id="dimpmain_portal"><?php echo $loading_text ?></div>
+   <div id="dimpmain_folder" style="display:none">
+    <div id="dimpmain_folder_top" class="noprint">
+     <div id="tabbar">
+<?php if (!empty($application_folders) || !empty($site_menu)): ?>
+      <div class="tabset">
+       <ul>
+        <li>
+         <a id="hometab"><img src="<?php echo $registry->get('icon', 'horde') ?>" alt="<?php echo _("Home") ?>" /><?php echo _("Home") ?></a>
+        </li>
+<?php foreach ($application_folders as $val): ?>
+        <li<?php if ($val['app'] == 'dimp') echo ' class="activeTab"' ?>>
+         <a class="applicationtab" app="<?php echo $val['app'] ?>"><img src="<?php echo $val['icon'] ?>" alt="<?php echo $val['name'] ?>" /><?php echo $val['name'] ?></a>
+        </li>
+<?php endforeach; ?>
+<?php foreach ($site_menu as $key => $menu_item): if ($menu_item == 'separator') continue; ?>
+        <li>
+         <a id="tab<?php echo $key ?>"><img src="<?php echo $menu_item['icon'] ?>" alt="<?php echo $menu_item['text'] ?>" /><?php echo $menu_item['text'] ?></a>
+        </li>
+<?php endforeach; ?>
+<?php if (!empty($quotadata)): ?>
+        <li id="quota">
+         <span class="used"><?php echo $quota_img ?></span>
+         <?php echo $quotadata['message'] ?>
+        </li>
+<?php endif; ?>
+       </ul>
+      </div>
+<?php elseif (!empty($quotadata)): ?>
+      <div id="quota">
+       <span class="used"><?php echo $quota_img ?></span>
+       <?php echo $quotadata['message'] ?>
+      </div>
+<?php endif; ?>
+     </div>
+     <br class="clear" />
+     <div id="mailboxHeader" class="header">
+      <div>
+       <span class="rightFloat" id="msgHeader"></span>
+       <?php echo Horde::img('loading.gif', $loading_text, array('id' => 'folderLoading', 'style' => 'display:none'), $imagedir) ?>
+       <span id="folderName"></span>
+      </div>
+     </div>
+
+     <div class="dimpActions">
+      <form action="#" method="post" onsubmit="return false;">
+       <input type="text" name="msgList_filter" id="msgList_filter" size="30" />
+      </form>
+      <span>
+       <?php _createDA(_("Reply"), 'reply_menu.png', 'button_reply', '', $show_text) ?>
+      </span>
+      <span>
+       <?php _createDA(_("Forward"), 'forward_menu.png', 'button_forward', '', $show_text) ?>
+      </span>
+<?php if (!empty($conf['spam']['reporting'])): ?>
+      <span>
+       <?php _createDA(_("Spam"), 'spam_menu.png', 'button_spam', '', $show_text) ?>
+      </span>
+<?php endif; ?>
+<?php if (!empty($conf['notspam']['reporting'])): ?>
+      <span style="display:none">
+       <?php _createDA(_("Innocent"), 'ham_menu.png', 'button_ham', '', $show_text) ?>
+      </span>
+<?php endif; ?>
+      <span id="button_compose">
+       <?php _createDA(_("Compose"), 'compose_menu.png', '', '', $show_text) ?>
+      </span>
+      <span id="button_checkmail">
+       <?php _createDA(_("Get Mail"), 'checkmail_menu.png', '', '', $show_text) ?>
+      </span>
+      <span>
+       <?php _createDA(_("Delete"), 'delete_menu.png', 'button_deleted', '', $show_text) ?>
+      </span>
+      <span>
+       <?php _createDA(_("Other Actions"), 'plus_menu.png', 'button_other', '', $show_text) ?>
+      </span>
+     </div>
+
+     <div style="display:none">
+      <div id="qoptions">
+       <span class="qclose"><a>x</a></span>
+       <span class="qlabel"><?php echo _("Folders: ") ?></span>
+<?php if (!empty($conf['search']['search_all'])): ?>
+       <a id="sf_all"><?php echo _("All") ?></a>
+<?php endif; ?>
+       <a id="sf_current"></a>
+       <span class="qlabel">|&nbsp;</span>
+       <span class="qlabel"><?php echo _("Message: ") ?></span>
+       <a id="sf_msgall"><?php echo _("All") ?></a>
+       <a id="sf_from"><?php echo _("From") ?></a>
+       <a id="sf_to"><?php echo _("To") ?></a>
+       <a id="sf_subject"><?php echo _("Subject") ?></a>
+      </div>
+     </div>
+
+     <div id="msglistHeader" class="item">
+      <div class="msgStatus">&nbsp;</div>
+      <div class="msgFrom">
+       <a class="widget" sortby="<?php echo SORTFROM ?>"><?php echo _("From") ?></a>
+       <a class="widget" style="display:none" sortby="<?php echo SORTTO ?>"><?php echo _("To") ?></a>
+      </div>
+      <div class="msgSubject">
+       <a class="widget" sortby="<?php echo SORTSUBJECT ?>"><?php echo _("Subject") ?>
+        <small sortby="<?php echo SORTTHREAD ?>">[<?php echo _("Thread") ?>]</small>
+       </a>
+       <a class="widget" style="display:none" sortby="<?php echo SORTTHREAD ?>"><?php echo _("Thread") ?>
+        <small sortby="<?php echo SORTSUBJECT ?>">[<?php echo _("Subject") ?>]</small>
+       </a>
+      </div>
+      <div class="msgDate">
+       <a class="widget" sortby="<?php echo SORTDATE ?>"><?php echo _("Date") ?>
+        <small sortby="<?php echo SORTARRIVAL ?>">[<?php echo _("Arrival") ?>]</small>
+       </a>
+       <a class="widget" style="display:none" sortby="<?php echo SORTARRIVAL ?>"><?php echo _("Arrival") ?>
+        <small sortby="<?php echo SORTDATE ?>">[<?php echo _("Date") ?>]</small>
+       </a>
+      </div>
+      <div class="msgSize"><a class="widget" sortby="<?php echo SORTSIZE ?>"><?php echo _("Size") ?></a></div>
+     </div>
+     <div class="clear" id="mlistHeaderClear">&nbsp;</div>
+    </div>
+
+    <div id="msgSplitPane">
+     <div id="msgListDiv" class="noprint">
+      <div id="msgList_empty" style="display:none"><em><?php echo _("No messages to display") ?></em></div>
+      <div id="msgList_error" style="display:none"><em><?php echo _("Could not get message list from server") ?></em></div>
+      <div id="msgList" class="msglist">&nbsp;</div>
+     </div>
+     <div id="splitBar" style="display:none" class="splitBar noprint"></div>
+     <div id="previewPane" style="display:none">
+      <?php echo Horde::img('loading.gif', $loading_text, array('id' => 'msgLoading', 'style' => 'display:none'), $imagedir) ?>
+      <div id="previewInfo" style="display:none">
+       <?php echo _("To preview a message, select it from the list above. A right-click on the messages will display available actions.") ?><br />
+       <?php printf(_("Click on a message while holding down the %s key to select multiple messages.  To select a range of messages, click the first message of the range, navigate to the last message of the range, and then click on the last message while holding down the %s key."), '<span class="kbd">' . _("Ctrl") . '</span>', '<span class="kbd">' . _("Shift") . '</span>') ?><br /><br />
+       <?php echo _("The following keyboard shortcuts are available:") ?><br />
+       <?php echo Horde::img('key_up.png', '', '', $imagedir) ?> / <?php echo Horde::img('key_down.png', '', '', $imagedir) . ' : ' . _("Move up/down through the message list.") ?><br />
+       <span class="kbd"><?php echo _("PgUp") ?></span> / <span class="kbd"><?php echo _("PgDown") ?></span> : <?php echo _("Move one page up/down through the message list.") ?><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 _("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>
+      <div id="previewMsg" style="display:none">
+       <div class="msgHeaders">
+        <div id="toggleHeaders" class="noprint">
+         <a><?php echo Horde::img('arrow_collapsed.png', '>', array('title' => _("Expand Headers")), $imagedir) ?></a>
+         <a style="display:none"><?php echo Horde::img('arrow_expanded.png', 'v', array('title' => _("Collapse Headers")), $imagedir) ?></a>
+         <?php echo Horde::img('attachment.png', '', array('class' => 'attachmentImage', 'style' => 'display:none')) ?>
+        </div>
+        <div id="msgHeadersColl">
+         <a id="msg_newwin"><?php echo $newwin ?></a>
+         <span class="date"></span>
+         <span class="subject"></span>
+         <span class="fromcontainer"><?php echo _("from") ?> <span class="from"></span></span>
+        </div>
+        <div id="msgHeaders" style="display:none">
+         <div class="dimpOptions noprint">
+          <div id="msg_newwin_options"><?php echo $newwin ?><a><?php echo _("Open in new window") ?></a></div>
+          <div id="msg_print"><?php echo Horde::img('print.png', '', '', $hordeimg) ?><a><?php echo _("Print") ?></a></div>
+<?php if (!empty($conf['user']['allow_view_source'])): ?>
+          <div id="msg_view_source"><?php echo Horde::img('message_source.png', '', '', $imagedir) ?><a><?php echo _("View Message Source") ?></a></div>
+<?php endif; ?>
+         </div>
+         <div id="msgHeadersContent">
+          <table cellspacing="0">
+           <thead>
+            <tr>
+             <td class="label"><?php echo _("Subject") ?>:</td>
+             <td class="subject"></td>
+            </tr>
+            <tr id="msgHeaderFrom">
+             <td class="label"><?php echo _("From") ?>:</td>
+             <td class="from"></td>
+            </tr>
+            <tr id="msgHeaderDate">
+             <td class="label"><?php echo _("Date") ?>:</td>
+             <td class="date"></td>
+            </tr>
+            <tr id="msgHeaderTo">
+             <td class="label"><?php echo _("To") ?>:</td>
+             <td class="to"></td>
+            </tr>
+            <tr id="msgHeaderCc">
+             <td class="label"><?php echo _("Cc") ?>:</td>
+             <td class="cc"></td>
+            </tr>
+            <tr id="msgAtc" style="display:none">
+             <td class="label"><?php echo $attachment ?><?php echo Horde::link('') . Horde::img('arrow_collapsed.png', '', array('id' => 'partlist_col'), $imagedir) . Horde::img('arrow_expanded.png', '', array('id' => 'partlist_exp', 'style' => 'display:none'), $imagedir) . ' ' . $attachment ?></a></td>
+             <td>
+              <div></div>
+              <table id="partlist" style="display:none" cellspacing="2">
+              </table>
+             </td>
+            </tr>
+           </thead>
+          </table>
+         </div>
+        </div>
+       </div>
+       <div id="msgBody" class="msgBody">
+        <table width="100%" cellspacing="0"></table>
+       </div>
+      </div>
+     </div>
+     <div style="clear:left" />
+    </div>
+   </div>
+  </div>
+ </div>
+</div>
+
+<div class="context" id="ctx_folder" style="display:none">
+ <a id="ctx_folder_create"><?php echo $folder_create . _("Create subfolder") ?></a>
+ <a id="ctx_folder_rename"><?php echo $folder_edit . _("Rename Folder") ?></a>
+ <a id="ctx_folder_empty"><?php echo $delete . _("Empty Folder") ?></a>
+ <a id="ctx_folder_delete"><?php echo Horde::img('folders/folder_delete.png') . _("Delete Folder") ?></a>
+ <div id="ctx_folder_seen_sep" class="sep"></div>
+ <a id="ctx_folder_seen"><?php echo $mailseen . _("Mark all as Read") ?></a>
+ <a id="ctx_folder_unseen"><?php echo $mailunseen . _("Mark all as New") ?></a>
+ <div id="ctx_folder_poll_sep" class="sep"></div>
+ <a id="ctx_folder_poll"><?php echo $mailseen . _("Check for New Mail") ?></a>
+ <a id="ctx_folder_nopoll"><?php echo $mailseen . _("Do Not Check for New Mail") ?></a>
+</div>
+
+<div class="context" id="ctx_container" style="display:none">
+ <a id="ctx_container_create"><?php echo $folder_create . _("Create subfolder") ?></a>
+ <a id="ctx_container_rename"><?php echo $folder_edit . _("Rename Folder") ?></a>
+</div>
+
+<div class="context" id="ctx_message" style="display:none">
+ <a id="ctx_message_reply"><?php echo Horde::img('reply.png', '', '', $imagedir) . _("Reply to Sender") ?></a>
+ <a id="ctx_message_reply_all"><?php echo Horde::img('replyall.png', '', '', $imagedir) . _("Reply to All") ?></a>
+ <a id="ctx_message_reply_list"><?php echo Horde::img('replyall.png', '', '', $imagedir) . _("Reply to List") ?></a>
+ <div class="sep"></div>
+ <a id="ctx_message_forward_all"><?php echo $forward . _("Forward Entire Message") ?></a>
+ <a id="ctx_message_forward_body"><?php echo $forward . _("Forward Body Text Only") ?></a>
+ <a id="ctx_message_forward_attachments"><?php echo $forward . _("Forward Attachments Only") ?></a>
+ <div class="sep"></div>
+ <a id="ctx_message_seen"><?php echo $mailseen . _("Mark as Read") ?></a>
+ <a id="ctx_message_unseen"><?php echo $mailunseen . _("Mark as New") ?></a>
+ <a id="ctx_message_flagged"><?php echo $flagged . _("Flag Message") ?></a>
+ <a id="ctx_message_clear"><?php echo $clearflag . _("Clear Flag") ?></a>
+ <div class="sep"></div>
+<?php if (!empty($conf['spam']['reporting'])): ?>
+ <a id="ctx_message_spam"><?php echo Horde::img('spam.png', '', '', $imagedir) . _("Mark as Spam") ?></a>
+<?php endif; ?>
+<?php if (!empty($conf['notspam']['reporting'])): ?>
+ <a id="ctx_message_ham"><?php echo Horde::img('ham.png', '', '', $imagedir) . _("Mark as Innocent") ?></a>
+<?php endif; ?>
+<?php if ($has_blacklist): ?>
+ <a id="ctx_message_blacklist"><?php echo Horde::img('blacklist.png', '', '', $imagedir) . _("Blacklist") ?></a>
+<?php endif; ?>
+<?php if ($has_whitelist): ?>
+ <a id="ctx_message_whitelist"><?php echo Horde::img('whitelist.png', '', '', $imagedir) . _("Whitelist") ?></a>
+<?php endif; ?>
+ <a id="ctx_message_deleted"><?php echo $delete . _("Delete") ?></a>
+<?php if (!$usetrash): ?>
+ <a id="ctx_message_undeleted"><?php echo $delete . _("Undelete") ?></a>
+<?php endif; ?>
+</div>
+
+<div class="context" id="ctx_draft" style="display:none">
+ <a id="ctx_draft_resume"><?php echo Horde::img('mail_draft.png', '', '', $imagedir) . _("Resume Draft") ?></a>
+ <div class="sep"></div>
+ <a id="ctx_draft_flagged"><?php echo $flagged . _("Flag Message") ?></a>
+ <a id="ctx_draft_clear"><?php echo $clearflag . _("Clear Flag") ?></a>
+ <div class="sep"></div>
+ <a id="ctx_draft_deleted"><?php echo $delete . _("Delete") ?></a>
+<?php if (!$usetrash): ?>
+ <a id="ctx_draft_undeleted"><?php echo $delete . _("Undelete") ?></a>
+<?php endif; ?>
+</div>
+
+<div class="context" id="ctx_reply" style="display:none;">
+ <a id="ctx_reply_reply"><?php echo Horde::img('replyall.png', '', '', $imagedir) . _("To Sender") ?></a>
+ <a id="ctx_reply_reply_all"><?php echo Horde::img('replyall.png', '', '', $imagedir) . _("To All") ?></a>
+ <a id="ctx_reply_reply_list"><?php echo Horde::img('replyall.png', '', '', $imagedir) . _("To List") ?></a>
+</div>
+
+<div class="context" id="ctx_forward" style="display:none">
+ <a id="ctx_forward_forward_all"><?php echo $forward . _("Entire Message") ?></a>
+ <a id="ctx_forward_forward_body"><?php echo $forward . _("Body Text Only") ?></a>
+ <a id="ctx_forward_forward_attachments"><?php echo $forward . _("Attachments Only") ?></a>
+</div>
+
+<div class="context" id="ctx_otheractions" style="display:none">
+ <a id="previewtoggle"><?php echo $preview; echo ($dimp_prefs->getValue('show_preview') ? _("Hide Preview") : _("Show Preview")); ?></a>
+ <div class="sep"></div>
+ <a id="oa_seen"><?php echo $mailseen . _("Mark as Read") ?></a>
+ <a id="oa_unseen"><?php echo $mailunseen . _("Mark as New") ?></a>
+ <a id="oa_flagged"><?php echo $flagged . _("Flag Message") ?></a>
+ <a id="oa_clear"><?php echo $clearflag . _("Clear Flag") ?></a>
+ <div class="sep" id="oa_sep1"></div>
+<?php if ($has_blacklist || $has_whitelist): ?>
+<?php if ($has_blacklist): ?>
+ <a id="oa_blacklist"><?php echo Horde::img('blacklist.png', '', '', $imagedir) . _("Blacklist") ?></a>
+<?php endif; ?>
+<?php if ($has_whitelist): ?>
+ <a id="oa_whitelist"><?php echo Horde::img('whitelist.png', '', '', $imagedir) . _("Whitelist") ?></a>
+<?php endif; ?>
+ <div class="sep" id="oa_sep2"></div>
+<?php endif; ?>
+ <a id="oa_selectall"><?php echo $tick . _("Select All") ?></a>
+<?php if (!$prefs->getValue('use_trash')): ?>
+ <div class="sep"></div>
+ <a id="oa_purge_deleted"><?php echo $delete . _("Purge Deleted") ?></a>
+<?php endif; ?>
+</div>
+
+<div class="context" id="ctx_contacts" style="display:none">
+ <div><?php _createDA(_("New Message"), 'compose.png', 'ctx_contacts_new') ?></div>
+ <div><?php _createDA(_("Add to Address Book"), 'add_contact.png', 'ctx_contacts_add') ?></div>
+</div>
+
+<div style="display:none">
+ <?php echo Horde::img('popdown.png', 'v', array('class' => 'popdown', 'id' => 'popdown_img'), $imagedir) ?>
+ <?php echo Horde::img('mail_priority_high.png', _("High Priority"), array('id' => 'high_priority_img')) ?>
+ <?php echo Horde::img('mail_priority_low.png', _("Low Priority"), array('id' => 'low_priority_img')) ?>
+ <?php foreach ($thread_imgs as $val) { echo $val; } ?>
+ <?php foreach ($atc_imgs as $val) { echo $val; } ?>
+ <span id="largeaddrspan">
+  <span class="largeaddrtoggle">
+   <span class="largeaddrlist">[<?php echo _("Show Addresses - %d recipients") ?>]</span>
+   <span class="largeaddrlist" style="display:none">[<?php echo _("Hide Addresses") ?>]</span>
+  </span>
+  <span class="dispaddrlist" style="display:none"></span>
+ </span>
+</div>
+
+<?php if (!$is_ie6): ?>
+<div id="alertslog">
+ <div style="display:none">
+  <ul>
+   <li class="noalerts"><strong><?php echo _("No Alerts") ?></strong></li>
+  </ul>
+ </div>
+</div>
+<?php endif; ?>
diff --git a/imp/templates/javascript/mailbox.js b/imp/templates/javascript/mailbox.js
new file mode 100644 (file)
index 0000000..040cc0c
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * mailbox.js - Template used to format the rows in the message list display.
+ *
+ * See the documentation of prototypejs - Template for the template format:
+ *   http://www.prototypejs.org/api/template
+ *
+ * $Horde: dimp/templates/javascript/mailbox.js,v 1.8 2008/09/04 17:55:59 slusarz Exp $
+ *
+ * Copyright 2005-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+DimpBase.message_list_template =
+'<div id="#{domid}" title="#{subject}" class="#{bg_string}">' +
+ '<div class="msgStatus">' +
+  '<div class="msCheck"></div>' +
+  '<div class="msState"></div>' +
+  '<div class="msCompose"></div>' +
+  '<div class="msPri"></div>' +
+ '</div>' +
+ '<div class="msgFrom">#{from}</div>' +
+ '<div class="msgSubject">#{subject}</div>' +
+ '<div class="msgDate">#{date}</div>' +
+ '<div class="msgSize">#{size}</div>' +
+ '<div class="clear">&nbsp;</div>' +
+'</div>';
diff --git a/imp/themes/bluewhite/screen-dimp.css b/imp/themes/bluewhite/screen-dimp.css
new file mode 100644 (file)
index 0000000..7666195
--- /dev/null
@@ -0,0 +1,10 @@
+/**
+ * $Horde: dimp/themes/bluewhite/screen.css,v 1.2 2007/05/09 16:21:55 jan Exp $
+ */
+
+#sidebarPanel a:hover, #sidebarPanel a:hover span {
+    color: #006;
+}
+.tabset {
+    border-bottom: none;
+}
diff --git a/imp/themes/graphics/add_contact.png b/imp/themes/graphics/add_contact.png
new file mode 100644 (file)
index 0000000..8b2555f
Binary files /dev/null and b/imp/themes/graphics/add_contact.png differ
diff --git a/imp/themes/graphics/arrow_collapsed.png b/imp/themes/graphics/arrow_collapsed.png
new file mode 100644 (file)
index 0000000..c93bb81
Binary files /dev/null and b/imp/themes/graphics/arrow_collapsed.png differ
diff --git a/imp/themes/graphics/arrow_expanded.png b/imp/themes/graphics/arrow_expanded.png
new file mode 100644 (file)
index 0000000..35abef5
Binary files /dev/null and b/imp/themes/graphics/arrow_expanded.png differ
diff --git a/imp/themes/graphics/backhead_orderby.png b/imp/themes/graphics/backhead_orderby.png
new file mode 100644 (file)
index 0000000..5a52843
Binary files /dev/null and b/imp/themes/graphics/backhead_orderby.png differ
diff --git a/imp/themes/graphics/backhead_r.png b/imp/themes/graphics/backhead_r.png
new file mode 100644 (file)
index 0000000..873ea39
Binary files /dev/null and b/imp/themes/graphics/backhead_r.png differ
diff --git a/imp/themes/graphics/backhead_s2.png b/imp/themes/graphics/backhead_s2.png
new file mode 100644 (file)
index 0000000..5959a6a
Binary files /dev/null and b/imp/themes/graphics/backhead_s2.png differ
diff --git a/imp/themes/graphics/backhead_shadow.png b/imp/themes/graphics/backhead_shadow.png
new file mode 100644 (file)
index 0000000..1e2e52a
Binary files /dev/null and b/imp/themes/graphics/backhead_shadow.png differ
diff --git a/imp/themes/graphics/blacklist.png b/imp/themes/graphics/blacklist.png
new file mode 100644 (file)
index 0000000..eac4395
Binary files /dev/null and b/imp/themes/graphics/blacklist.png differ
diff --git a/imp/themes/graphics/checkbox_off.png b/imp/themes/graphics/checkbox_off.png
new file mode 100755 (executable)
index 0000000..1a78b26
Binary files /dev/null and b/imp/themes/graphics/checkbox_off.png differ
diff --git a/imp/themes/graphics/checkbox_on.png b/imp/themes/graphics/checkbox_on.png
new file mode 100755 (executable)
index 0000000..564a0de
Binary files /dev/null and b/imp/themes/graphics/checkbox_on.png differ
diff --git a/imp/themes/graphics/checkbox_over.png b/imp/themes/graphics/checkbox_over.png
new file mode 100644 (file)
index 0000000..86c0894
Binary files /dev/null and b/imp/themes/graphics/checkbox_over.png differ
diff --git a/imp/themes/graphics/checkmail.png b/imp/themes/graphics/checkmail.png
new file mode 100644 (file)
index 0000000..e3690a0
Binary files /dev/null and b/imp/themes/graphics/checkmail.png differ
diff --git a/imp/themes/graphics/checkmail_menu.png b/imp/themes/graphics/checkmail_menu.png
new file mode 100644 (file)
index 0000000..e3690a0
Binary files /dev/null and b/imp/themes/graphics/checkmail_menu.png differ
diff --git a/imp/themes/graphics/compose_menu.png b/imp/themes/graphics/compose_menu.png
new file mode 100644 (file)
index 0000000..238463b
Binary files /dev/null and b/imp/themes/graphics/compose_menu.png differ
diff --git a/imp/themes/graphics/delete_menu.png b/imp/themes/graphics/delete_menu.png
new file mode 100644 (file)
index 0000000..f3076bc
Binary files /dev/null and b/imp/themes/graphics/delete_menu.png differ
diff --git a/imp/themes/graphics/dimp.png b/imp/themes/graphics/dimp.png
new file mode 100644 (file)
index 0000000..3b5085a
Binary files /dev/null and b/imp/themes/graphics/dimp.png differ
diff --git a/imp/themes/graphics/drafts.png b/imp/themes/graphics/drafts.png
new file mode 100644 (file)
index 0000000..bce9e4a
Binary files /dev/null and b/imp/themes/graphics/drafts.png differ
diff --git a/imp/themes/graphics/drafts_menu.png b/imp/themes/graphics/drafts_menu.png
new file mode 100644 (file)
index 0000000..bce9e4a
Binary files /dev/null and b/imp/themes/graphics/drafts_menu.png differ
diff --git a/imp/themes/graphics/dragHandle.png b/imp/themes/graphics/dragHandle.png
new file mode 100644 (file)
index 0000000..9075213
Binary files /dev/null and b/imp/themes/graphics/dragHandle.png differ
diff --git a/imp/themes/graphics/error.png b/imp/themes/graphics/error.png
new file mode 100644 (file)
index 0000000..d1c6785
Binary files /dev/null and b/imp/themes/graphics/error.png differ
diff --git a/imp/themes/graphics/folder.png b/imp/themes/graphics/folder.png
new file mode 100644 (file)
index 0000000..6317fa3
Binary files /dev/null and b/imp/themes/graphics/folder.png differ
diff --git a/imp/themes/graphics/folder_create.png b/imp/themes/graphics/folder_create.png
new file mode 100644 (file)
index 0000000..5b428d2
Binary files /dev/null and b/imp/themes/graphics/folder_create.png differ
diff --git a/imp/themes/graphics/folder_drafts.png b/imp/themes/graphics/folder_drafts.png
new file mode 100644 (file)
index 0000000..bce9e4a
Binary files /dev/null and b/imp/themes/graphics/folder_drafts.png differ
diff --git a/imp/themes/graphics/folder_inbox.png b/imp/themes/graphics/folder_inbox.png
new file mode 100644 (file)
index 0000000..3b5085a
Binary files /dev/null and b/imp/themes/graphics/folder_inbox.png differ
diff --git a/imp/themes/graphics/folder_minus.png b/imp/themes/graphics/folder_minus.png
new file mode 100644 (file)
index 0000000..755d4f8
Binary files /dev/null and b/imp/themes/graphics/folder_minus.png differ
diff --git a/imp/themes/graphics/folder_plus.png b/imp/themes/graphics/folder_plus.png
new file mode 100644 (file)
index 0000000..c88dca2
Binary files /dev/null and b/imp/themes/graphics/folder_plus.png differ
diff --git a/imp/themes/graphics/folder_sent.png b/imp/themes/graphics/folder_sent.png
new file mode 100644 (file)
index 0000000..640ef45
Binary files /dev/null and b/imp/themes/graphics/folder_sent.png differ
diff --git a/imp/themes/graphics/folder_spam.png b/imp/themes/graphics/folder_spam.png
new file mode 100644 (file)
index 0000000..145608e
Binary files /dev/null and b/imp/themes/graphics/folder_spam.png differ
diff --git a/imp/themes/graphics/folder_trash.png b/imp/themes/graphics/folder_trash.png
new file mode 100644 (file)
index 0000000..2b1bc92
Binary files /dev/null and b/imp/themes/graphics/folder_trash.png differ
diff --git a/imp/themes/graphics/forward.png b/imp/themes/graphics/forward.png
new file mode 100644 (file)
index 0000000..559d65d
Binary files /dev/null and b/imp/themes/graphics/forward.png differ
diff --git a/imp/themes/graphics/forward_menu.png b/imp/themes/graphics/forward_menu.png
new file mode 100644 (file)
index 0000000..559d65d
Binary files /dev/null and b/imp/themes/graphics/forward_menu.png differ
diff --git a/imp/themes/graphics/ham.png b/imp/themes/graphics/ham.png
new file mode 100644 (file)
index 0000000..9cdb1cb
Binary files /dev/null and b/imp/themes/graphics/ham.png differ
diff --git a/imp/themes/graphics/ham_menu.png b/imp/themes/graphics/ham_menu.png
new file mode 100644 (file)
index 0000000..9cdb1cb
Binary files /dev/null and b/imp/themes/graphics/ham_menu.png differ
diff --git a/imp/themes/graphics/ico_message_off.png b/imp/themes/graphics/ico_message_off.png
new file mode 100644 (file)
index 0000000..ead9f63
Binary files /dev/null and b/imp/themes/graphics/ico_message_off.png differ
diff --git a/imp/themes/graphics/ie6_or_less-dimp.css b/imp/themes/graphics/ie6_or_less-dimp.css
new file mode 100644 (file)
index 0000000..0b6bbee
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * $Horde: dimp/themes/ie6_or_less.css,v 1.37 2008/08/19 23:04:37 slusarz Exp $
+ *
+ * CSS corrections for IE 6 and below.
+ */
+
+#pageContainer {
+    height: auto;
+}
+
+#sidebarPanel, #normalfolders {
+    width: 150px;
+}
+#normalfolders {
+    /* IE 6 does not support max-height. To auto-size folder list display, set
+     * height to 0px. */
+    height: 0px;
+    overflow: auto;
+}
+
+#sidebarPanel li {
+    width: 100%;
+}
+.context {
+    width: 180px;
+}
+
+div.msgSubject {
+    width: 49%;
+}
+
+div.msgmiddle {
+    margin-bottom: -4px;
+}
+
+.splitBar {
+    background-attachment: fixed;
+    font-size: 0px;
+}
+
+form#compose {
+    margin-top: -13px;
+}
+#to, #cc, #bcc {
+    overflow: auto;
+}
+#messageParent {
+    width: auto !important;
+}
+#messageParent textarea#message {
+    width: 97% !important;
+}
+
+#alerts {
+    width: 500px;
+}
+#alerts div.ie6alertsfix {
+    padding: 0;
+    border: 0;
+    background: url("graphics/clear.gif") repeat;
+}
+
+.block-monthgrid table {
+    border-width: 2px;
+}
+
+#msglistHeader {
+    height: 15px;
+}
+#mlistHeaderClear {
+    display: none;
+}
+
+/* Fixes li & ol list numbering issues in HTML messages. */
+#html-message ul li, #html-message ol li {
+    margin-left: 2em;
+}
diff --git a/imp/themes/graphics/ie7-dimp.css b/imp/themes/graphics/ie7-dimp.css
new file mode 100644 (file)
index 0000000..29ebfe1
--- /dev/null
@@ -0,0 +1,65 @@
+/**
+ * $Horde: dimp/themes/ie7.css,v 1.26 2008/08/20 04:30:18 slusarz Exp $
+ *
+ * CSS corrections for IE 7.
+ */
+
+#pageContainer {
+    height: 1px;
+}
+
+#sidebarPanel li {
+    overflow: hidden;
+    max-width: 133px;
+    text-overflow: ellipsis;
+}
+#sidebarPanel .folderlist {
+    overflow-y: auto;
+}
+
+#msgSplitPane {
+    margin-top: -4px;
+    width: 99.7%;
+}
+
+div.msgSize {
+    width: auto;
+    overflow: hidden;
+}
+
+form#compose {
+    margin-top: -13px;
+}
+#to, #cc, #bcc {
+    overflow: auto;
+}
+
+.msgBody {
+    width: expression(document.body.clientWidth-220);
+}
+
+#alerts {
+    width: expression(document.body.clientWidth/2);
+}
+
+.block-monthgrid table {
+    border-width: 2px;
+}
+
+#sidebarPanel li.custom img, #sidebarPanel li.servicelink img {
+    padding-top: 1px;
+}
+
+#sidebarPanel li.folder a:hover, #sidebarPanel li.custom a:hover, #sidebarPanel li.servicelink a:hover {
+    font-weight: bold;
+    text-decoration: underline;
+}
+
+.dimpOptions div a:hover {
+    text-decoration: underline;
+}
+
+/* Fixes li & ol list numbering issues in HTML messages. */
+#html-message ul li , #html-message ol li {
+    margin-left: 2em;
+}
diff --git a/imp/themes/graphics/key_down.png b/imp/themes/graphics/key_down.png
new file mode 100644 (file)
index 0000000..d157561
Binary files /dev/null and b/imp/themes/graphics/key_down.png differ
diff --git a/imp/themes/graphics/key_up.png b/imp/themes/graphics/key_up.png
new file mode 100644 (file)
index 0000000..2f46d0e
Binary files /dev/null and b/imp/themes/graphics/key_up.png differ
diff --git a/imp/themes/graphics/logo.png b/imp/themes/graphics/logo.png
new file mode 100644 (file)
index 0000000..9a7ee17
Binary files /dev/null and b/imp/themes/graphics/logo.png differ
diff --git a/imp/themes/graphics/logout.png b/imp/themes/graphics/logout.png
new file mode 100644 (file)
index 0000000..e40c2b5
Binary files /dev/null and b/imp/themes/graphics/logout.png differ
index 4a1f3b7..559d65d 100644 (file)
Binary files a/imp/themes/graphics/mail_forwarded.png and b/imp/themes/graphics/mail_forwarded.png differ
diff --git a/imp/themes/graphics/message.png b/imp/themes/graphics/message.png
new file mode 100644 (file)
index 0000000..1cae1e8
Binary files /dev/null and b/imp/themes/graphics/message.png differ
diff --git a/imp/themes/graphics/message_source.png b/imp/themes/graphics/message_source.png
new file mode 100644 (file)
index 0000000..3e82c45
Binary files /dev/null and b/imp/themes/graphics/message_source.png differ
diff --git a/imp/themes/graphics/newwin.png b/imp/themes/graphics/newwin.png
new file mode 100644 (file)
index 0000000..99e9ae7
Binary files /dev/null and b/imp/themes/graphics/newwin.png differ
diff --git a/imp/themes/graphics/plus_menu.png b/imp/themes/graphics/plus_menu.png
new file mode 100644 (file)
index 0000000..f2d6413
Binary files /dev/null and b/imp/themes/graphics/plus_menu.png differ
diff --git a/imp/themes/graphics/preview.png b/imp/themes/graphics/preview.png
new file mode 100644 (file)
index 0000000..2932279
Binary files /dev/null and b/imp/themes/graphics/preview.png differ
diff --git a/imp/themes/graphics/print-dimp.css b/imp/themes/graphics/print-dimp.css
new file mode 100644 (file)
index 0000000..7ea5cdc
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * DIMP print CSS.
+ *
+ * $Horde: dimp/themes/print.css,v 1.27 2008/08/11 05:16:29 slusarz Exp $
+ */
+
+body {
+    background: #fff;
+}
+
+a, a:visited {
+    color: #000;
+}
+
+#pageContainer {
+    margin-left: 20px;
+    margin-right: 20px;
+}
+
+#dimpmain {
+    position: relative;
+    left: 0 !important;
+    margin-left: 0;
+    top: 0;
+}
+
+#previewPane {
+    height: auto !important;
+}
+
+#msgHeaders, .dispaddrlist {
+    display: block !important;
+}
+
+.msgfullread .msgBody {
+    border-top: 1px solid silver;
+}
+
+.label {
+    min-width: 60px;
+    text-align: right;
+    font-weight: bold;
+}
+
+#msgHeadersColl, .context, .address img, .noprint, .mimeStatusMessage, .largeaddrlist, #alertslog {
+    display: none !important;
+}
+a.address {
+    left: 0;
+    padding: 0;
+    text-decoration: none;
+}
diff --git a/imp/themes/graphics/reply.png b/imp/themes/graphics/reply.png
new file mode 100644 (file)
index 0000000..d31da90
Binary files /dev/null and b/imp/themes/graphics/reply.png differ
diff --git a/imp/themes/graphics/reply_menu.png b/imp/themes/graphics/reply_menu.png
new file mode 100644 (file)
index 0000000..d31da90
Binary files /dev/null and b/imp/themes/graphics/reply_menu.png differ
diff --git a/imp/themes/graphics/replyall.png b/imp/themes/graphics/replyall.png
new file mode 100644 (file)
index 0000000..d31da90
Binary files /dev/null and b/imp/themes/graphics/replyall.png differ
diff --git a/imp/themes/graphics/sbcursor_bottom.png b/imp/themes/graphics/sbcursor_bottom.png
new file mode 100644 (file)
index 0000000..e6e3401
Binary files /dev/null and b/imp/themes/graphics/sbcursor_bottom.png differ
diff --git a/imp/themes/graphics/sbcursor_top.png b/imp/themes/graphics/sbcursor_top.png
new file mode 100644 (file)
index 0000000..5638272
Binary files /dev/null and b/imp/themes/graphics/sbcursor_top.png differ
diff --git a/imp/themes/graphics/screen-dimp.css b/imp/themes/graphics/screen-dimp.css
new file mode 100644 (file)
index 0000000..b4af29a
--- /dev/null
@@ -0,0 +1,1186 @@
+/**
+ * DIMP core CSS.
+ *
+ * $Horde: dimp/themes/screen.css,v 1.315 2008/09/12 17:30:56 slusarz Exp $
+ */
+
+/* Positioning */
+#pageContainer {
+    clear: both;
+}
+
+/* Popdown styles. */
+a.popdown {
+    cursor: pointer;
+    background: url("graphics/popdown.png") no-repeat center center;
+    padding: 5px !important;
+}
+a.popdown:hover {
+    padding: 4px !important;
+    border-left: 1px solid #fff;
+    border-top: 1px solid #fff;
+    border-right: 1px solid #000;
+    border-bottom: 1px solid #000;
+}
+
+/* ini header. */
+#quota {
+    float: right;
+    font-weight: normal;
+}
+#quota .used {
+    float: left;
+    width: 100px;
+    background: maroon url('graphics/quotaback.jpg') repeat-y;
+    text-align: right;
+    border: 1px #000 solid;
+    margin-right: 2px;
+}
+#quota .used img {
+    display: inline;
+    float: none;
+    padding: 0;
+    border-left: 1px #000 solid;
+    height: 14px;
+}
+div#quota {
+    padding-bottom: 3px;
+}
+
+#mailboxHeader {
+    clear: right;
+}
+
+#logo {
+    display: none;
+}
+/* end header */
+
+#sidebarPanel {
+    position: absolute;
+    cursor: default;
+    top: 0;
+    left: 0;
+    padding: 7px 0 0 7px;
+    width: 150px;
+    bottom: 0;
+    z-index: 1;
+    white-space: normal;
+    -moz-border-radius-bottomright: 0;
+    -webkit-border-bottom-right-radius: 0;
+    border-bottom: 0px;
+}
+#dimpmain {
+    position: absolute;
+    margin-left: 13px;
+    left: 150px;
+    top: 4px;
+    bottom: 0;
+    right: 4px;
+}
+#dimpmain_portal h1.header a {
+    cursor: pointer;
+}
+
+/* Autocomplete styles. */
+.autocomplete {
+    background: #f4f4f4;
+    border: 1px solid #d4d4d4;
+    position: absolute;
+    padding: 2px;
+}
+.autocomplete ul {
+    margin: 1px;
+    padding: 1px;
+    list-style-type: none;
+}
+.autocomplete ul li {
+    background: transparent;
+    padding: 3px 4px 4px;
+    cursor: pointer;
+    color: #000;
+}
+.autocomplete ul .selected {
+    background: gray;
+    color: #fff;
+}
+
+/* SpellChecker styles. */
+.Checking {
+    color: red;
+}
+.incorrect {
+    text-decoration: underline;
+    cursor: pointer;
+    color: red;
+}
+.corrected {
+    text-decoration: underline;
+    cursor: pointer;
+    color: #090;
+}
+
+/* SplitPane styles. */
+#msgSplitPane {
+    background-color: #fff;
+    border-left: 1px silver solid;
+    border-right: 1px silver solid;
+    border-bottom: 1px silver solid;
+}
+.splitBar {
+    background: #e9e9e9 url("graphics/dragHandle.png") no-repeat scroll center top;
+    border: 1px solid;
+    border-color: ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight;
+    cursor: n-resize;
+    height: 5px;
+    clear: left;
+    z-index: 10;
+}
+
+/* Message List */
+div.loading img, #folderLoading, #msgLoading {
+    z-index: 1000;
+    background: #fff;
+    border: 1px #e0e0e0 solid;
+    padding: 2px;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+}
+
+/* Columns */
+div.msgStatus, div.msgFrom, div.msgSubject, div.msgDate, div.msgSize {
+    float: left;
+    overflow: hidden;
+}
+div.msgStatus {
+    width: 6%;
+}
+div.msgStatus .msCheck, div.msgStatus .msState, div.msgStatus .msCompose, div.msgStatus .msPri {
+    width: 16px;
+    float: left;
+}
+div.msgFrom {
+    width: 20%;
+}
+div.msgSubject {
+    width: 50%;
+}
+div.msgDate {
+    width: 15%;
+}
+div.msgSize {
+    width: 9%;
+}
+
+/* Message List Header and Column Header */
+#msglistHeader {
+    font-weight: bold;
+    overflow: hidden;
+    border: 1px solid silver;
+    background: transparent url("graphics/backhead_orderby.png") repeat-x;
+    height: 16px;
+}
+#msglistHeader a {
+    cursor: pointer;
+    display: block;
+}
+#msglistHeader div {
+    padding-top: 1px;
+    height: 15px;
+}
+#msglistHeader div.sortup {
+    padding-left: 0;
+    background-position: 2px;
+}
+#msglistHeader div.sortup a {
+    padding-left: 14px;
+}
+#msglistHeader div.sortdown {
+    padding-left: 0;
+    background-position: 2px;
+}
+#msglistHeader div.sortdown a {
+    padding-left: 14px;
+}
+#msglistHeader a small {
+    padding-left: 2px;
+    font-weight: normal;
+}
+
+/* Message ViewPort */
+.msglist {
+    width: 100%;
+    overflow: hidden;
+    border-bottom: 1px silver solid;
+    float: left;
+}
+.msglist em {
+    font-weight: bold;
+}
+.msglistNoPreview {
+    border-bottom: 0;
+}
+
+/* Rows */
+.msglist div.msgRow, .msglist div div {
+    cursor: pointer;
+    font-size: 95%;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    height: 16px;
+    border-bottom: 2px #fff solid;
+}
+/* Flags */
+.msglist div.unseen {
+    font-weight: bold;
+}
+.msglist div.important, .msglist div.flagged {
+    background: #fcc;
+}
+.msglist div.unimportant {
+    font-weight: normal;
+}
+.msglist div.answered {
+    background: #cfc;
+}
+.msglist div.deletedmsg {
+    background: #999;
+}
+.msglist div.deletedmsg div.msgFrom, .msglist div.deletedmsg div.msgSubject, .msglist div.deletedmsg div.msgDate, .msglist div.deletedmsg div.msgSize {
+    text-decoration: line-through;
+}
+.msglist div.selectedRow {
+    background: #ffc;
+}
+/* Checkbox images */
+.msglist .msCheck {
+    background: transparent url("graphics/checkbox_off.png") center center no-repeat;
+}
+.msglist .msCheck:hover {
+    background: transparent url("graphics/checkbox_over.png") center center no-repeat;
+}
+.msglist div.selectedRow .msCheck {
+    background: transparent url("graphics/checkbox_on.png") center center no-repeat;
+}
+
+/* Status images and flags selectors: status column */
+.msglist div.unseen .msState {
+    background: transparent url("graphics/mail_unseen.png") center center no-repeat;
+}
+.msglist div.flagged .msState {
+    background: transparent url("graphics/mail_flagged.png") center center no-repeat;
+}
+.msglist div.deletedmsg .msState {
+    background: transparent url("graphics/mail_deleted.png") center center no-repeat;
+}
+/* Status images and flags selectors: priority column */
+.msglist div.unimportant .msPri {
+    background: transparent url("graphics/mail_priority_low.png") center center no-repeat;
+}
+.msglist div.important .msPri {
+    background: transparent url("graphics/mail_priority_high.png") center center no-repeat;
+}
+/* Status images and flags selectors: compose status column */
+.msglist div.answered .msCompose {
+    background: transparent url("graphics/mail_answered.png") center center no-repeat;
+}
+.msglist div.draft .msCompose {
+    background: transparent url("graphics/mail_draft.png") center center no-repeat;
+}
+/* Thread images. */
+div.msgSubject img {
+    margin-top: -4px;
+}
+/* Size images. */
+div.msgSize img {
+    margin-right: 2px;
+}
+
+/* Scroller */
+.sbdiv {
+    position: relative;
+    right: 0;
+    width: 14px;
+    overflow: auto;
+    background-image: url("graphics/scroller_back.png");
+    background-repeat: repeat-y;
+}
+.sbcursor {
+    width: 13px;
+    border-left: 1px solid silver;
+    background-image: url("graphics/scroller.png");
+    background-repeat: repeat-y;
+}
+.sbup {
+    width: 13px;
+    height: 16px;
+    border-left: 1px solid silver;
+    background: transparent url("graphics/sbcursor_top.png") center center no-repeat;
+}
+.sbdown {
+    width: 13px;
+    height: 17px;
+    border-left: 1px solid silver;
+    background: transparent url("graphics/sbcursor_bottom.png") center center no-repeat;
+}
+
+/* IFRAMEs */
+.iframe {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    right: 0;
+    border: none;
+    width: 100%;
+    height: 100%;
+}
+
+/* Sidebar */
+#sidebarPanel span {
+    white-space: normal;
+}
+#sidebarPanel a {
+    cursor: pointer;
+    padding: 0;
+}
+#sidebarPanel a:hover {
+    background: transparent;
+}
+#sidebarPanel p {
+    margin: 0 0 5px;
+}
+#sidebarPanel p a {
+    text-decoration: none;
+    font-weight: bold;
+}
+#sidebarPanel p a img {
+    padding-right: 3px;
+}
+#sidebarPanel .sepfull {
+    font-size: 1px !important;
+    line-height: 1px !important;
+    height: 1px !important;
+    margin: 5px 0;
+    overflow: hidden;
+    width: 150px;
+    background: silver;
+}
+#sidebarPanel .count, #sidebarPanel a:hover .count {
+    color: silver;
+}
+#sidebarPanel li {
+    list-style: none;
+    width: 100%;
+    height: 17px;
+    line-height: 17px;
+    margin: 1px 0 0;
+    white-space: nowrap;
+}
+#sidebarPanel li.folder, #sidebarPanel li.custom, #sidebarPanel li.servicelink {
+    float: none;
+    width: 100%;
+    text-decoration: none;
+    padding: 0;
+}
+#sidebarPanel li.custom, #sidebarPanel li.servicelink {
+    display: block;
+}
+#sidebarPanel li a {
+    padding-top: 1px;
+    width: 130px;
+    text-decoration: none;
+}
+#sidebarPanel li.custom a, #sidebarPanel li.servicelink a {
+    padding-top: 0;
+}
+#sidebarPanel li.servicelink a {
+    font-weight: bold;
+}
+#sidebarPanel li.custom {
+    background: transparent none;
+}
+#sidebarPanel li.custom img, #sidebarPanel li.servicelink img {
+    float: left;
+    padding: 1px 4px 0 0;
+}
+#sidebarPanel li.folder a {
+    display: inline;
+}
+#sidebarPanel .base {
+    display: inline;
+    float: left;
+    width: 20px;
+    height: 100%;
+    background: transparent url("graphics/folder.png") center left no-repeat;
+}
+#sidebarPanel .col {
+    display: inline;
+    float: left;
+    width: 20px;
+    height: 100%;
+    background: transparent url("graphics/folder_minus.png") center left no-repeat;
+}
+#sidebarPanel .exp {
+    display: inline;
+    float: left;
+    width: 20px;
+    height: 100%;
+    background: transparent url("graphics/folder_plus.png") center left no-repeat;
+}
+#sidebarPanel .create {
+    display: inline;
+    float: left;
+    width: 20px;
+    height: 100%;
+    background: transparent url("graphics/folder_create.png") center left no-repeat;
+}
+#sidebarPanel .drafts, #sidebarPanel .inbox, #sidebarPanel .sent, #sidebarPanel .spam, #sidebarPanel .trash {
+    display: inline;
+    float: left;
+    width: 20px;
+    height: 100%;
+    padding: 0;
+    background-color: transparent;
+    background-position: center left;
+    background-repeat: no-repeat;
+}
+#sidebarPanel .drafts {
+    background-image: url("graphics/folder_drafts.png");
+}
+#sidebarPanel .inbox {
+    background-image: url("graphics/folder_inbox.png");
+}
+#sidebarPanel .sent {
+    background-image: url("graphics/folder_sent.png");
+}
+#sidebarPanel .spam {
+    background-image: url("graphics/folder_spam.png");
+}
+#sidebarPanel .trash {
+    background-image: url("graphics/folder_trash.png");
+}
+#sidebarPanel #normalfolders {
+    overflow-x: hidden;
+}
+#sidebarPanel #myfolders {
+    display: none;
+}
+#foldersSidebar {
+    overflow: hidden;
+}
+#sidebarPanel .folderlist li a {
+    width: auto;
+    overflow: hidden;
+    display: block;
+    float: none;
+}
+#sidebarPanel li div {
+    cursor: pointer;
+}
+#sidebarPanel li.subfolders {
+    height: auto;
+    margin: 0;
+}
+#sidebarPanel li.subfolders ul {
+    padding-left: 10px;
+}
+#sidebarPanel li.subfolders li a {
+    width: auto;
+}
+#sidebarPanel li.on {
+    background: #ffc;
+}
+#sidebarPanel .over a, #sidebarPanel li.folder:hover, #sidebarPanel li.custom:hover, #sidebarPanel li.servicelink:hover {
+    font-weight: bold;
+    text-decoration: underline;
+}
+#sidebarPanel li a.drop {
+    border: 1px solid orange;
+    padding: 1px 1px 1px 19px;
+}
+
+/* Options */
+.dimpOptions {
+    float: right;
+    padding-right: 5px;
+}
+.dimpOptions div {
+    clear: left;
+    cursor: pointer;
+    line-height: 17px;
+}
+.dimpOptions div a, .dimpOptions div a:visited {
+    text-decoration: none;
+    color: #000;
+}
+.dimpOptions div:hover {
+    text-decoration: underline;
+}
+.dimpOptions div img {
+    float: left;
+    padding: 1px 4px 0 0;
+}
+
+/* Actions */
+.dimpActions {
+    background: #efefef;
+    padding: 4px 4px 4px 2px;
+    border: 1px silver solid;
+    border-bottom: 0px;
+    background: transparent url("graphics/backhead_s2.png") repeat-x;
+}
+.dimpActions span {
+    cursor: pointer;
+}
+.dimpActions a, .dimpActions a:visited, .dimpActions label {
+    color: #000;
+    font-size: 90%;
+    font-weight: bold;
+    white-space: nowrap;
+    padding: 2px 4px 4px 4px;
+}
+.dimpActions a img {
+    vertical-align: middle;
+    padding-right: 2px;
+}
+.dimpActions a:hover {
+    text-decoration: none;
+    border-left: 1px solid #fff;
+    border-top: 1px solid #fff;
+    border-right: 1px solid #000;
+    border-bottom: 1px solid #000;
+    padding: 1px 3px 3px 3px;
+    cursor: pointer;
+}
+.dimpActions form {
+    float: right;
+    padding: 0;
+}
+.dimpActions .disabled a, .dimpActions .disabled a:visited, .dimpActions .disabled a:active, .dimpActions .disabled a:hover {
+    cursor: default;
+    text-decoration: none;
+    color: silver;
+}
+.dimpActions .disabled a:hover {
+    border: 0;
+    padding-left: 4px;
+    padding-right: 4px;
+}
+.dimpActions .disabled img {
+    filter: alpha(opacity=25);
+    -moz-opacity: .25;
+    opacity: .25;
+}
+.dimpActions .popdown {
+    border: 0;
+    padding: 2px;
+}
+.dimpActions .popdown:hover {
+    border-left: 1px solid #fff;
+    border-top: 1px solid #fff;
+    border-right: 1px solid #000;
+    border-bottom: 1px solid #000;
+    padding: 1px;
+}
+.dimpActions .disabled .popdown:hover {
+    border: 0;
+    cursor: default;
+    padding-left: 2px;
+    padding-right: 2px;
+}
+#button_compose, #button_checkmail {
+    display: none;
+}
+
+div.dimpActionsCompose {
+    margin-top: 0;
+}
+.dimpActionsCompose a.popdown {
+    margin-right: 4px;
+}
+
+#previewPane {
+    overflow: auto;
+    height: 250px;
+    border: none;
+    border-top: 1px silver solid;
+    background: #fff;
+}
+
+.msgHeaders {
+    background: transparent url("graphics/backhead_r.png") repeat-x;
+    border-bottom: 1px #b9b9b3 solid;
+    padding: 2px 0;
+    font-size: 90%;
+    overflow: hidden;
+}
+.msgfullread .msgHeaders {
+    border: 1px #b9b9b3 solid;
+}
+
+.msgHeaders .dimpOptions {
+    float: right;
+    padding: 0 10px 10px;
+    margin-top: 3px;
+}
+
+.msgHeaders table {
+    margin: 0;
+}
+.msgHeaders table thead tr {
+    vertical-align: top;
+}
+.msgHeaders table thead td {
+    padding: 0 3px 3px 0;
+}
+.msgHeaders table thead td img {
+    vertical-align: middle;
+}
+.msgHeaders table thead td.label {
+    font-weight: bold;
+    white-space: nowrap;
+    text-align: right;
+    padding-left: 3px;
+}
+.msgHeaders table thead td.subject {
+    font-weight: bold;
+}
+#msgAtc td {
+    padding-bottom: 0;
+}
+#partlist td {
+    padding-bottom: 3px;
+}
+
+#msgHeadersContent {
+    overflow: hidden;
+}
+
+/* ini msg small header */
+#toggleHeaders {
+    float: left;
+}
+#toggleHeaders a img {
+    padding: 3px 0 0 2px;
+}
+#msgHeadersColl {
+    height: 17px;
+    padding: 0;
+    margin: 0 6px;
+    white-space: nowrap;
+}
+#msgHeadersColl span.date {
+    float: right;
+    text-align: right;
+}
+#msg_newwin img {
+    float: right;
+    padding-left: 4px;
+    vertical-align: middle;
+}
+#msgHeadersColl span.subject {
+    float: left;
+    font-weight: bold;
+    overflow: hidden;
+    padding-left: 4px;
+    max-width: 40%;
+}
+#msgHeadersColl span.fromcontainer {
+    padding-left: 5px;
+}
+#msgHeadersColl span.from {
+    max-width: 50%;
+    overflow: hidden;
+}
+#msgHeadersColl span.from a:hover {
+    border: none;
+    background: none;
+    padding: 2px;
+    text-decoration: underline;
+    -moz-border-radius: 0;
+    -webkit-border-radius: 0;
+}
+#msgHeadersColl span.from a:hover img {
+    display: none;
+}
+/* end msg small header */
+
+.msgBody {
+    background: #fff url("graphics/backhead_shadow.png") top repeat-x;
+    padding: 7px 5px 5px;
+}
+.msgBody table td {
+    padding: 0;
+}
+.msgfullread .msgBody {
+    border-left: 1px silver solid;
+    border-right: 1px silver solid;
+    border-bottom: 1px silver solid;
+    min-height: 420px;
+}
+
+/* mime status messages */
+.mimeStatusMessage, .mimeStatusWarning, .mimePartInfo {
+    color: #000;
+    margin: 2px;
+}
+.mimeStatusMessage {
+    background: #ffc;
+    border: 1px #fffba4 solid;
+}
+.mimePartInfo {
+    background: #efefef;
+    border: 1px #c0c0c0 solid;
+}
+.mimeStatusWarning {
+    background: #e81222;
+    border: 1px maroon solid;
+    color: #fff;
+}
+table.mimeStatusMessage td, table.mimeStatusWarning td, table.mimePartInfo td {
+    padding: 0;
+}
+table.mimeStatusMessage td table td, table.mimeStatusWarning td table td, table.mimePartInfo td table td {
+    padding: 0 2px 0 0;
+    white-space: nowrap;
+}
+
+/* Message Composition. */
+.msgwrite {
+    cursor: default;
+    background: #e2e2e2 url("graphics/backhead_r.png") repeat-x;
+    border: 1px #b9b9b3 solid;
+    padding: 2px 0;
+}
+.msgwrite .dimpOptions {
+    float: right;
+    padding: 10px;
+    font-size: 90%;
+    line-height: 90%;
+}
+.msgwrite .dimpOptions label {
+    display: block;
+    cursor: pointer;
+}
+.msgwrite thead tr {
+    vertical-align: top;
+}
+.msgwrite thead td {
+    padding: 3px 5px 1px 0;
+    font-size: 90%;
+}
+.msgwrite thead td.label {
+    height: 15px;
+    white-space: nowrap;
+    text-align: right;
+    width: 65px;
+    padding-top: 4px;
+    padding-left: 2px;
+}
+.msgwrite thead td.subject {
+    font-weight: bold;
+}
+.msgwrite .atcrow {
+    line-height: 130%;
+}
+
+#messageParent {
+    background: #fff url("graphics/backhead_shadow.png") top repeat-x;
+    border-left: 1px silver solid;
+    border-right: 1px silver solid;
+    border-bottom: 1px silver solid;
+    padding: 1%;
+    margin: 0;
+}
+#messageParent .htmlarea {
+    width: 98% !important;
+    border: none;
+}
+#message {
+    border: none;
+    margin: 0;
+    padding: 0;
+    width: 100%;
+}
+
+#previewInfo {
+    background: transparent url(graphics/ico_message_off.png) no-repeat scroll 10px 15px;
+    line-height: 18px;
+    text-align: left;
+    padding: 15px 80px 10px 35px;
+    color: #a0a0a0;
+}
+
+div.spellcheck {
+    white-space: pre;
+    white-space: -moz-pre-wrap;
+    white-space: -hp-pre-wrap;
+    white-space: -o-pre-wrap;
+    white-space: -pre-wrap;
+    white-space: pre-wrap;
+    white-space: pre-line;
+    word-wrap: break-word;
+}
+
+.htmledit {
+    display: none;
+    padding: 4px;
+}
+
+#subject {
+    width: 40em;
+}
+
+#composeloading, #rteloading {
+    position: absolute;
+    z-index: 100;
+    background-color: #000;
+    filter: alpha(opacity=50);
+    -moz-opacity: .5;
+    opacity: .5;
+}
+#rteloadingtxt {
+    position: absolute;
+    background-color: #fff;
+    z-index: 101;
+}
+#composeloading {
+    width: 100%;
+    height: 100%;
+    top: 0;
+    left: 0;
+}
+
+/* attachment file list */
+#attach_list {
+    padding-top: 5px;
+}
+#attach_list input {
+    background: none;
+    border: none;
+    font-weight: bold;
+    text-decoration: underline;
+}
+#attach_list div {
+    background: transparent url("graphics/attachment.png") left 2px no-repeat;
+    height: 20px;
+    padding-left: 20px;
+}
+
+/* Context Menus */
+.context {
+    min-width: 180px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 10000;
+    border: 1px #d4d4d4 solid;
+    padding: 1px;
+    background: #f4f4f4;
+    color: #000;
+    font-size: 90%;
+}
+.context a, .context a:visited {
+    white-space: nowrap;
+    display: block;
+    padding: 4px 4px 3px 2px;
+    text-decoration: none;
+    color: #000;
+}
+.context a:hover {
+    background: gray;
+    color: #fff;
+}
+.context a img {
+    vertical-align: middle;
+    padding-right: 4px;
+}
+.context div.sep {
+    font-size: 1px;
+    line-height: 1px;
+    height: 1px;
+    border-bottom: 1px #d4d4d4 solid;
+}
+
+/* Email Addresses. */
+a.address {
+    left: -2px;
+    position: static;
+    padding: 2px;
+    text-decoration: underline;
+    white-space: nowrap;
+}
+a.address img {
+    display: none;
+    vertical-align: top;
+}
+a.address:hover {
+    text-decoration: none;
+    padding: 2px 1px;
+    border: 1px silver solid;
+    background: #fff;
+}
+a.address:hover img {
+    display: inline;
+    padding-left: 4px;
+}
+
+#alerts {
+    cursor: pointer;
+    position: absolute;
+    top: 20%;
+    left: 25%;
+    right: 25%;
+    width: 50%;
+    /* Must always be the highest z-index on the page to ensure we can always
+       click the alert box to close. */
+    z-index: 10001;
+    margin: 0 0 10px;
+    font-weight: bold;
+    color: #fff;
+}
+#alerts div {
+    padding: 6px 20px 6px 25px;
+    border: 1px #316600 solid;
+    background-color: #5db110;
+    background-position: 6px 6px;
+    background-repeat: no-repeat;
+}
+#alerts div.horde-error {
+    background: #e81222 url("graphics/error.png") 99% 50% no-repeat;
+    border-color: maroon;
+}
+#alerts div.horde-message {
+    background-image: url("graphics/message.png");
+}
+#alerts div.horde-success {
+    background-image: url("graphics/success.png");
+}
+#alerts div.horde-warning, #alerts div.dimp-sticky, #alerts div.dimp-sticky {
+    background-color: #ebe20c;
+    background-image: url("graphics/warning.png");
+    border-color: #807b00;
+    color: #000;
+}
+#alerts div.imp-reply {
+    background-image: url("graphics/mail_answered.png");
+}
+#alerts div.imp-forward, #alerts div.imp-redirect {
+    background-image: url("graphics/mail_forwarded.png");
+}
+
+#alertslog {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    max-height: 200px;
+    z-index: 10000;
+}
+#alertslog ul {
+    background: #999;
+    overflow: auto;
+    filter: alpha(opacity=80);
+    -moz-opacity: .8;
+    opacity: .8;
+}
+#alertslog ul li {
+    color: #333;
+    font-size: 12px;
+    list-style-type: none;
+    overflow: hidden;
+    padding: 10px 20px;
+    border-bottom: 1px dotted;
+}
+#alertslog ul li:last-child {
+    border: 0;
+}
+#alertslog ul li p {
+    margin-bottom: 0;
+}
+#alertslog p.label {
+    float: left;
+    font-weight: bold;
+}
+#alertslog p.indent {
+    margin: 0 100px 0 75px;
+}
+#alertslog span.alertdate {
+    font-size: 90%;
+    font-style: italic;
+    padding-left: 10px;
+}
+
+.tooltip, .nicetitle {
+    z-index: 1001;
+}
+
+/* Drag and drop styles. */
+.drag, .dragdrop {
+    position: absolute;
+    background: #eee;
+    border: 1px solid #ccc;
+    font: 12px Geneva,Arial,Helvetica,sans-serif;
+    padding: 2px;
+    overflow: hidden;
+    z-index: 999;
+}
+.dragdrop {
+    color: red;
+}
+
+/* Message Search Filter bar */
+#qoptions {
+    background: gray;
+    color: #fff;
+    font-size: 90%;
+    padding: 5px 5px 5px 10px;
+}
+#qoptions a, #qoptions a:visited {
+    text-decoration: none;
+    font-weight: bold;
+    color: #fff;
+    margin: 0 5px;
+    padding: 2px 5px;
+}
+#qoptions a.qselected, #qoptions a.qselected:visited, #qoptions a:hover {
+    background: #fafafa;
+    color: #000;
+    font-weight: bold;
+    border-top: 1px silver solid;
+    border-left: 1px silver solid;
+    border-bottom: 1px #e0e0e0 solid;
+    border-right: 1px #e0e0e0 solid;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    padding: 1px 4px;
+}
+#qoptions .qclose a, #qoptions .qclose a:visited {
+    float: right;
+    margin-top: -2px;
+    width: 10px;
+    background: #efefef;
+    color: #000;
+    font-weight: bold;
+    border-top: 1px silver solid;
+    border-left: 1px silver solid;
+    border-bottom: 1px #e0e0e0 solid;
+    border-right: 1px #e0e0e0 solid;
+    text-align: center;
+    vertical-align: middle;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    padding: 0px 2px 2px 4px;
+    text-decoration: none;
+}
+#qoptions .qclose a:hover {
+    background: #fff;
+}
+#qoptions .qlabel {
+    font-weight: bold;
+}
+
+.msgFilterDefault {
+    color: #a0a0a0;
+}
+
+.searchMatch {
+    font-weight: bold;
+    text-decoration: underline;
+}
+
+/* Redbox styles. */
+#RB_overlay {
+    position: absolute;
+    z-index: 100;
+    width: 100%;
+    height: 100%;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    min-height: 100%;
+    background-color: #000;
+    filter: alpha(opacity=60);
+    -moz-opacity: .6;
+    opacity: .6;
+}
+#RB_window {
+    z-index: 102;
+    display: block;
+    text-align: left;
+    overflow: hidden;
+    margin: 20px auto 0 auto;
+    position: absolute;
+}
+
+#RB_folder {
+    width: 20em;
+    padding: 6px;
+    border: 1px solid #ccc;
+}
+#RB_folder input {
+    margin: .2em;
+}
+
+/* Popup message styling. */
+.headercloseimg {
+    float: right;
+    padding-right: 2px;
+    cursor: pointer;
+}
+.headertitle {
+    border-bottom: none;
+}
+
+/* Based on the Mozilla default definitions */
+#html-message td {
+    padding: 0;
+}
+#html-message ul {
+    display: block;
+    list-style-type: disc;
+    margin: 1em 0;
+    -moz-padding-start: 40px;
+}
+#html-message ol {
+    display: block;
+    list-style-type: decimal;
+    margin: 1em 0;
+    -moz-padding-start: 40px;
+}
+#html-message p {
+    display: block;
+    margin: 1em 0;
+}
+#html-message blockquote {
+    margin: 1em 40px;
+}
+#html-message blockquote[type="cite"] {
+    margin-left: 0;
+    margin-right: 0;
+    padding-left: 1em;
+}
+
+/* Keyboard key style */
+.kbd {
+    background: #eee;
+    color: #000;
+    white-space: nowrap;
+    padding: 1px 2px;
+    border: 1px solid #888;
+    font-size: 90%;
+    -moz-border-radius: 3px;
+    -webkit-border-radius: 3px;
+}
+
+.largeaddrlist {
+    cursor: pointer;
+    background: #aaa;
+    margin-right: 5px;
+}
+.largeaddrlist:hover {
+    text-decoration: underline;
+}
diff --git a/imp/themes/graphics/scroller.png b/imp/themes/graphics/scroller.png
new file mode 100644 (file)
index 0000000..0645427
Binary files /dev/null and b/imp/themes/graphics/scroller.png differ
diff --git a/imp/themes/graphics/scroller_back.png b/imp/themes/graphics/scroller_back.png
new file mode 100644 (file)
index 0000000..4dcab36
Binary files /dev/null and b/imp/themes/graphics/scroller_back.png differ
diff --git a/imp/themes/graphics/select.png b/imp/themes/graphics/select.png
new file mode 100644 (file)
index 0000000..f21cbd6
Binary files /dev/null and b/imp/themes/graphics/select.png differ
diff --git a/imp/themes/graphics/sortdown.png b/imp/themes/graphics/sortdown.png
new file mode 100644 (file)
index 0000000..2b646f9
Binary files /dev/null and b/imp/themes/graphics/sortdown.png differ
diff --git a/imp/themes/graphics/sortup.png b/imp/themes/graphics/sortup.png
new file mode 100644 (file)
index 0000000..a154237
Binary files /dev/null and b/imp/themes/graphics/sortup.png differ
diff --git a/imp/themes/graphics/spam.png b/imp/themes/graphics/spam.png
new file mode 100644 (file)
index 0000000..bfe45af
Binary files /dev/null and b/imp/themes/graphics/spam.png differ
diff --git a/imp/themes/graphics/spam_menu.png b/imp/themes/graphics/spam_menu.png
new file mode 100644 (file)
index 0000000..bfe45af
Binary files /dev/null and b/imp/themes/graphics/spam_menu.png differ
diff --git a/imp/themes/graphics/spellcheck_menu.png b/imp/themes/graphics/spellcheck_menu.png
new file mode 100644 (file)
index 0000000..bf94cab
Binary files /dev/null and b/imp/themes/graphics/spellcheck_menu.png differ
diff --git a/imp/themes/graphics/success.png b/imp/themes/graphics/success.png
new file mode 100644 (file)
index 0000000..4af9076
Binary files /dev/null and b/imp/themes/graphics/success.png differ
diff --git a/imp/themes/graphics/tick.png b/imp/themes/graphics/tick.png
new file mode 100644 (file)
index 0000000..418e603
Binary files /dev/null and b/imp/themes/graphics/tick.png differ
diff --git a/imp/themes/graphics/warning.png b/imp/themes/graphics/warning.png
new file mode 100644 (file)
index 0000000..7f6d50f
Binary files /dev/null and b/imp/themes/graphics/warning.png differ
diff --git a/imp/themes/graphics/whitelist.png b/imp/themes/graphics/whitelist.png
new file mode 100644 (file)
index 0000000..ed490aa
Binary files /dev/null and b/imp/themes/graphics/whitelist.png differ
diff --git a/imp/themes/silver/graphics/add_contact.png b/imp/themes/silver/graphics/add_contact.png
new file mode 100644 (file)
index 0000000..2a68453
Binary files /dev/null and b/imp/themes/silver/graphics/add_contact.png differ
diff --git a/imp/themes/silver/graphics/arrow_collapsed.png b/imp/themes/silver/graphics/arrow_collapsed.png
new file mode 100644 (file)
index 0000000..9ab4a89
Binary files /dev/null and b/imp/themes/silver/graphics/arrow_collapsed.png differ
diff --git a/imp/themes/silver/graphics/arrow_expanded.png b/imp/themes/silver/graphics/arrow_expanded.png
new file mode 100644 (file)
index 0000000..b47ce55
Binary files /dev/null and b/imp/themes/silver/graphics/arrow_expanded.png differ
diff --git a/imp/themes/silver/graphics/backhead_orderby.png b/imp/themes/silver/graphics/backhead_orderby.png
new file mode 100644 (file)
index 0000000..5a52843
Binary files /dev/null and b/imp/themes/silver/graphics/backhead_orderby.png differ
diff --git a/imp/themes/silver/graphics/backhead_r.png b/imp/themes/silver/graphics/backhead_r.png
new file mode 100644 (file)
index 0000000..873ea39
Binary files /dev/null and b/imp/themes/silver/graphics/backhead_r.png differ
diff --git a/imp/themes/silver/graphics/backhead_s2.png b/imp/themes/silver/graphics/backhead_s2.png
new file mode 100644 (file)
index 0000000..5959a6a
Binary files /dev/null and b/imp/themes/silver/graphics/backhead_s2.png differ
diff --git a/imp/themes/silver/graphics/backhead_shadow.png b/imp/themes/silver/graphics/backhead_shadow.png
new file mode 100644 (file)
index 0000000..1e2e52a
Binary files /dev/null and b/imp/themes/silver/graphics/backhead_shadow.png differ
diff --git a/imp/themes/silver/graphics/blacklist.png b/imp/themes/silver/graphics/blacklist.png
new file mode 100644 (file)
index 0000000..1514d51
Binary files /dev/null and b/imp/themes/silver/graphics/blacklist.png differ
diff --git a/imp/themes/silver/graphics/checkmail.png b/imp/themes/silver/graphics/checkmail.png
new file mode 100644 (file)
index 0000000..6c93368
Binary files /dev/null and b/imp/themes/silver/graphics/checkmail.png differ
diff --git a/imp/themes/silver/graphics/checkmail_menu.png b/imp/themes/silver/graphics/checkmail_menu.png
new file mode 100644 (file)
index 0000000..6c93368
Binary files /dev/null and b/imp/themes/silver/graphics/checkmail_menu.png differ
index 6c93368..244f04a 100644 (file)
Binary files a/imp/themes/silver/graphics/compose.png and b/imp/themes/silver/graphics/compose.png differ
diff --git a/imp/themes/silver/graphics/compose_menu.png b/imp/themes/silver/graphics/compose_menu.png
new file mode 100644 (file)
index 0000000..244f04a
Binary files /dev/null and b/imp/themes/silver/graphics/compose_menu.png differ
diff --git a/imp/themes/silver/graphics/delete.png b/imp/themes/silver/graphics/delete.png
new file mode 100644 (file)
index 0000000..08f2493
Binary files /dev/null and b/imp/themes/silver/graphics/delete.png differ
diff --git a/imp/themes/silver/graphics/delete_menu.png b/imp/themes/silver/graphics/delete_menu.png
new file mode 100644 (file)
index 0000000..08f2493
Binary files /dev/null and b/imp/themes/silver/graphics/delete_menu.png differ
diff --git a/imp/themes/silver/graphics/dimp.png b/imp/themes/silver/graphics/dimp.png
new file mode 100644 (file)
index 0000000..e588e2f
Binary files /dev/null and b/imp/themes/silver/graphics/dimp.png differ
diff --git a/imp/themes/silver/graphics/drafts.png b/imp/themes/silver/graphics/drafts.png
new file mode 100644 (file)
index 0000000..53420f0
Binary files /dev/null and b/imp/themes/silver/graphics/drafts.png differ
diff --git a/imp/themes/silver/graphics/drafts_menu.png b/imp/themes/silver/graphics/drafts_menu.png
new file mode 100644 (file)
index 0000000..53420f0
Binary files /dev/null and b/imp/themes/silver/graphics/drafts_menu.png differ
diff --git a/imp/themes/silver/graphics/dragHandle.png b/imp/themes/silver/graphics/dragHandle.png
new file mode 100644 (file)
index 0000000..9075213
Binary files /dev/null and b/imp/themes/silver/graphics/dragHandle.png differ
diff --git a/imp/themes/silver/graphics/error.png b/imp/themes/silver/graphics/error.png
new file mode 100644 (file)
index 0000000..c37bd06
Binary files /dev/null and b/imp/themes/silver/graphics/error.png differ
diff --git a/imp/themes/silver/graphics/folder.png b/imp/themes/silver/graphics/folder.png
new file mode 100644 (file)
index 0000000..784e8fa
Binary files /dev/null and b/imp/themes/silver/graphics/folder.png differ
diff --git a/imp/themes/silver/graphics/folder_create.png b/imp/themes/silver/graphics/folder_create.png
new file mode 100644 (file)
index 0000000..529fe8f
Binary files /dev/null and b/imp/themes/silver/graphics/folder_create.png differ
diff --git a/imp/themes/silver/graphics/folder_delete.png b/imp/themes/silver/graphics/folder_delete.png
new file mode 100644 (file)
index 0000000..112b016
Binary files /dev/null and b/imp/themes/silver/graphics/folder_delete.png differ
diff --git a/imp/themes/silver/graphics/folder_drafts.png b/imp/themes/silver/graphics/folder_drafts.png
new file mode 100644 (file)
index 0000000..53420f0
Binary files /dev/null and b/imp/themes/silver/graphics/folder_drafts.png differ
diff --git a/imp/themes/silver/graphics/folder_edit.png b/imp/themes/silver/graphics/folder_edit.png
new file mode 100644 (file)
index 0000000..ad669cc
Binary files /dev/null and b/imp/themes/silver/graphics/folder_edit.png differ
diff --git a/imp/themes/silver/graphics/folder_inbox.png b/imp/themes/silver/graphics/folder_inbox.png
new file mode 100644 (file)
index 0000000..7348aed
Binary files /dev/null and b/imp/themes/silver/graphics/folder_inbox.png differ
diff --git a/imp/themes/silver/graphics/folder_minus.png b/imp/themes/silver/graphics/folder_minus.png
new file mode 100644 (file)
index 0000000..138c280
Binary files /dev/null and b/imp/themes/silver/graphics/folder_minus.png differ
diff --git a/imp/themes/silver/graphics/folder_open.png b/imp/themes/silver/graphics/folder_open.png
new file mode 100644 (file)
index 0000000..784e8fa
Binary files /dev/null and b/imp/themes/silver/graphics/folder_open.png differ
diff --git a/imp/themes/silver/graphics/folder_plus.png b/imp/themes/silver/graphics/folder_plus.png
new file mode 100644 (file)
index 0000000..28076e2
Binary files /dev/null and b/imp/themes/silver/graphics/folder_plus.png differ
diff --git a/imp/themes/silver/graphics/folder_sent.png b/imp/themes/silver/graphics/folder_sent.png
new file mode 100644 (file)
index 0000000..34a736f
Binary files /dev/null and b/imp/themes/silver/graphics/folder_sent.png differ
diff --git a/imp/themes/silver/graphics/folder_spam.png b/imp/themes/silver/graphics/folder_spam.png
new file mode 100644 (file)
index 0000000..b29042c
Binary files /dev/null and b/imp/themes/silver/graphics/folder_spam.png differ
diff --git a/imp/themes/silver/graphics/folder_trash.png b/imp/themes/silver/graphics/folder_trash.png
new file mode 100644 (file)
index 0000000..ebad933
Binary files /dev/null and b/imp/themes/silver/graphics/folder_trash.png differ
diff --git a/imp/themes/silver/graphics/forward.png b/imp/themes/silver/graphics/forward.png
new file mode 100644 (file)
index 0000000..4a6c5d3
Binary files /dev/null and b/imp/themes/silver/graphics/forward.png differ
diff --git a/imp/themes/silver/graphics/forward_menu.png b/imp/themes/silver/graphics/forward_menu.png
new file mode 100644 (file)
index 0000000..4a6c5d3
Binary files /dev/null and b/imp/themes/silver/graphics/forward_menu.png differ
diff --git a/imp/themes/silver/graphics/ham.png b/imp/themes/silver/graphics/ham.png
new file mode 100644 (file)
index 0000000..6e4ade1
Binary files /dev/null and b/imp/themes/silver/graphics/ham.png differ
diff --git a/imp/themes/silver/graphics/ham_menu.png b/imp/themes/silver/graphics/ham_menu.png
new file mode 100644 (file)
index 0000000..6e4ade1
Binary files /dev/null and b/imp/themes/silver/graphics/ham_menu.png differ
diff --git a/imp/themes/silver/graphics/logo.png b/imp/themes/silver/graphics/logo.png
new file mode 100644 (file)
index 0000000..9a7ee17
Binary files /dev/null and b/imp/themes/silver/graphics/logo.png differ
diff --git a/imp/themes/silver/graphics/logout.png b/imp/themes/silver/graphics/logout.png
new file mode 100644 (file)
index 0000000..41676a0
Binary files /dev/null and b/imp/themes/silver/graphics/logout.png differ
diff --git a/imp/themes/silver/graphics/message.png b/imp/themes/silver/graphics/message.png
new file mode 100644 (file)
index 0000000..12cd1ae
Binary files /dev/null and b/imp/themes/silver/graphics/message.png differ
diff --git a/imp/themes/silver/graphics/message_source.png b/imp/themes/silver/graphics/message_source.png
new file mode 100644 (file)
index 0000000..efd97e8
Binary files /dev/null and b/imp/themes/silver/graphics/message_source.png differ
diff --git a/imp/themes/silver/graphics/newwin.png b/imp/themes/silver/graphics/newwin.png
new file mode 100644 (file)
index 0000000..647592f
Binary files /dev/null and b/imp/themes/silver/graphics/newwin.png differ
diff --git a/imp/themes/silver/graphics/plus_menu.png b/imp/themes/silver/graphics/plus_menu.png
new file mode 100644 (file)
index 0000000..6332fef
Binary files /dev/null and b/imp/themes/silver/graphics/plus_menu.png differ
diff --git a/imp/themes/silver/graphics/preview.png b/imp/themes/silver/graphics/preview.png
new file mode 100644 (file)
index 0000000..a91c78a
Binary files /dev/null and b/imp/themes/silver/graphics/preview.png differ
diff --git a/imp/themes/silver/graphics/reply.png b/imp/themes/silver/graphics/reply.png
new file mode 100644 (file)
index 0000000..cfa6907
Binary files /dev/null and b/imp/themes/silver/graphics/reply.png differ
diff --git a/imp/themes/silver/graphics/reply_menu.png b/imp/themes/silver/graphics/reply_menu.png
new file mode 100644 (file)
index 0000000..cfa6907
Binary files /dev/null and b/imp/themes/silver/graphics/reply_menu.png differ
diff --git a/imp/themes/silver/graphics/replyall.png b/imp/themes/silver/graphics/replyall.png
new file mode 100644 (file)
index 0000000..cfa6907
Binary files /dev/null and b/imp/themes/silver/graphics/replyall.png differ
diff --git a/imp/themes/silver/graphics/select.png b/imp/themes/silver/graphics/select.png
new file mode 100644 (file)
index 0000000..f21cbd6
Binary files /dev/null and b/imp/themes/silver/graphics/select.png differ
diff --git a/imp/themes/silver/graphics/sortdown.png b/imp/themes/silver/graphics/sortdown.png
new file mode 100644 (file)
index 0000000..f52e828
Binary files /dev/null and b/imp/themes/silver/graphics/sortdown.png differ
diff --git a/imp/themes/silver/graphics/sortup.png b/imp/themes/silver/graphics/sortup.png
new file mode 100644 (file)
index 0000000..32e427a
Binary files /dev/null and b/imp/themes/silver/graphics/sortup.png differ
diff --git a/imp/themes/silver/graphics/spam.png b/imp/themes/silver/graphics/spam.png
new file mode 100644 (file)
index 0000000..a9d7266
Binary files /dev/null and b/imp/themes/silver/graphics/spam.png differ
diff --git a/imp/themes/silver/graphics/spam_menu.png b/imp/themes/silver/graphics/spam_menu.png
new file mode 100644 (file)
index 0000000..a9d7266
Binary files /dev/null and b/imp/themes/silver/graphics/spam_menu.png differ
diff --git a/imp/themes/silver/graphics/spellcheck_menu.png b/imp/themes/silver/graphics/spellcheck_menu.png
new file mode 100644 (file)
index 0000000..ebc632d
Binary files /dev/null and b/imp/themes/silver/graphics/spellcheck_menu.png differ
diff --git a/imp/themes/silver/graphics/success.png b/imp/themes/silver/graphics/success.png
new file mode 100644 (file)
index 0000000..89c8129
Binary files /dev/null and b/imp/themes/silver/graphics/success.png differ
diff --git a/imp/themes/silver/graphics/tick.png b/imp/themes/silver/graphics/tick.png
new file mode 100644 (file)
index 0000000..c531e31
Binary files /dev/null and b/imp/themes/silver/graphics/tick.png differ
diff --git a/imp/themes/silver/graphics/warning.png b/imp/themes/silver/graphics/warning.png
new file mode 100644 (file)
index 0000000..628cf2d
Binary files /dev/null and b/imp/themes/silver/graphics/warning.png differ
diff --git a/imp/themes/silver/graphics/whitelist.png b/imp/themes/silver/graphics/whitelist.png
new file mode 100644 (file)
index 0000000..a9925a0
Binary files /dev/null and b/imp/themes/silver/graphics/whitelist.png differ
diff --git a/imp/themes/silver/screen-dimp.css b/imp/themes/silver/screen-dimp.css
new file mode 100644 (file)
index 0000000..8099e5a
--- /dev/null
@@ -0,0 +1,87 @@
+/* Background images. */
+.msglist div.unseen .msState {
+    background-image: transparent url("graphics/mail_unseen.png");
+}
+.msglist div.flagged .msState {
+    background-image: url("graphics/mail_flagged.png");
+}
+.msglist div.deletedmsg .msState {
+    background-image: url("graphics/mail_deleted.png");
+}
+.msglist div.unimportant .msPri {
+    background-image: url("graphics/mail_priority_low.png");
+}
+.msglist div.important .msPri {
+    background-image: url("graphics/mail_priority_high.png");
+}
+.msglist div.answered .msCompose {
+    background-image: url("graphics/mail_answered.png");
+}
+.msglist div.draft .msCompose {
+    background-image: url("graphics/mail_draft.png");
+}
+
+#sidebarPanel {
+    border-bottom: none;
+}
+#sidebarPanel .base {
+    background-image: url("graphics/folder.png");
+}
+#sidebarPanel .col {
+    background-image: url("graphics/folder_minus.png");
+}
+#sidebarPanel .exp {
+    background-image: url("graphics/folder_plus.png");
+}
+#sidebarPanel .create {
+    background-image: url("graphics/folder_create.png");
+}
+#sidebarPanel .drafts {
+    background-image: url("graphics/folder_drafts.png");
+}
+#sidebarPanel .inbox {
+    background-image: url("graphics/folder_inbox.png");
+}
+#sidebarPanel .sent {
+    background-image: url("graphics/folder_sent.png");
+}
+#sidebarPanel .spam {
+    background-image: url("graphics/folder_spam.png");
+}
+#sidebarPanel .trash {
+    background-image: url("graphics/folder_trash.png");
+}
+#attach_list div {
+    background-image: url("graphics/attachment.png");
+}
+#alerts div.horde-error {
+    background-image: url("graphics/error.png");
+}
+#alerts div.horde-message {
+    background-image: url("graphics/message.png");
+}
+#alerts div.horde-success {
+    background-image: url("graphics/success.png");
+}
+#alerts div.horde-warning, #alerts div.dimp-sticky, #alerts div.dimp-sticky {
+    background-image: url("graphics/warning.png");
+}
+#alerts div.imp-reply {
+    background-image: url("graphics/mail_answered.png");
+}
+#alerts div.imp-forward, #alerts div.imp-redirect {
+    background-image: url("graphics/mail_forwarded.png");
+}
+
+.header {
+    border-left: 1px solid #999;
+    border-right: 1px solid #999;
+}
+
+.tabset li#quota {
+    background: none;
+}
+
+.dimpActions, .msgfullread .msgHeaders, .msgwrite {
+    border-top: none;
+}
diff --git a/imp/themes/tango-blue/screen-dimp.css b/imp/themes/tango-blue/screen-dimp.css
new file mode 100644 (file)
index 0000000..849e451
--- /dev/null
@@ -0,0 +1,20 @@
+/**
+ * $Horde: dimp/themes/tango-blue/screen.css,v 1.1 2007/09/29 02:58:29 chuck Exp $
+ */
+
+.mboxcontrol form {
+    background: #ccc;
+}
+
+.text {
+    color: #000;
+    background: #fff;
+}
+
+#sidebarPanel a:hover {
+    color: black;
+}
+
+#dimpmain_portal {
+    background-color: #c9daed;
+}