Source of “iphone.js”.
1133 lines, 29.4 KBytes.   Last modified 9:27 pm, 1st November 2015 PST.
1 // Emacs settings: -*- mode: Fundamental; tab-width: 4; -*- 2 3 //////////////////////////////////////////////////////////////////////////// 4 // // 5 // Pachylet: Andrew's Web Mail Interface // 6 // // 7 // Copyright (c) 2002-2014 // 8 // // 9 // See http://birrell.org/pachylet/help.html // 10 // // 11 // Javascript for iPhone // 12 // // 13 //////////////////////////////////////////////////////////////////////////// 14 15 16 // 17 // Data types 18 // 19 20 // Query is in pachyletV2.js 21 22 // State is in pachyletV2.js 23 24 // Toc is in pachyletV2.js 25 26 // Contact is in pachyletV2.js 27 28 // Button is in pachyletV2.js, but has overrides: 29 30 Button.prototype.enable = function() { 31 if (!this.enabled) this.element.style.opacity = 1.0; 32 this.enabled = true; 33 } 34 35 Button.prototype.disable = function() { 36 if (this.enabled) this.element.style.opacity = 0.5; 37 this.enabled = false; 38 } 39 40 41 // 42 // Global state 43 // 44 45 // Most of the global state is declared in pachyletV2.js 46 var theFQ; // sorted array of folders plus saved queries 47 48 // Widget state 49 var draftScrollbar; // scrollbar on draft screen 50 noMessageSelected = "<b>No message selected</b>"; 51 viewingUnsentFolder = "<b>Click on a message to resume composing it</b>"; 52 noMessagesAtAll = ""; 53 var tocItemHeight = 46; // pixel height of item in the TOC 54 55 function setPersistentCookie(key, value, path, domain, secure) { 56 // Set a persistent cookie 57 var date = new Date(); 58 date.setTime(date.getTime() + 365.25*24*60*60*1000); 59 setCookie(key, value, date, path, domain, secure); 60 } 61 62 63 // 64 // Modal message screens 65 // 66 67 function reportSuccess(str, action) { 68 // Report success modally; cleared by a user click; then do action 69 // 70 alert(str); 71 action(); 72 } 73 74 function reportError(str) { 75 // Report an error to the user, modally 76 // 77 alert(str); 78 } 79 80 function askConfirm(str, action) { 81 // Ask for confirmation modally, and on "yes" do the action 82 // 83 if (confirm(str)) action(); 84 } 85 86 87 // 88 // Making server-side requests 89 // 90 91 // urlForOp is in pachyletV2.js 92 93 // The constructor for "Getter" is in pachyletV2.js 94 // We override methods ".handleFailure", ".handleResult" 95 96 Getter.prototype.handleFailure = function() { 97 if (this.labelOp) doneLabelOp(); 98 if (this.state != theState) { 99 // User has logged out since the request - ignore the response 100 } else if (this.op == "saveDraft" && this.draft.id != curDraftId()) { 101 // saveDraft on a draft we've abandoned 102 } else { 103 if (this.op == "login" || this.op == "logout") { 104 screens.login.show(); 105 } else if (this.toc == theToc && (this.op == "fetchMail" || 106 this.op == "getMsgs" || 107 this.op == "scan")) { 108 showFolderScreen(); 109 } else if (this.composeOp) { 110 returnFromDraft(); 111 } else if (this.sendOp) { 112 showDraftScreen(); 113 } 114 alert("HTTP request" + 115 (this.op ? (" \"" + this.op + "\"") : "") + " failed."); 116 } 117 } 118 119 Getter.prototype.handleResult = function(xmlhttp) { 120 // Handle successful result of async HTTP request 121 // alert(xmlhttp.responseText); 122 if (this.labelOp) doneLabelOp(); 123 var responseXML = xmlhttp.responseXML; 124 var doc = (responseXML ? responseXML.documentElement : null); 125 if (this.state != theState) { 126 // User has logged out since the request - ignore the response 127 } else if (this.op == "saveDraft" && this.draft.id != curDraftId()) { 128 // saveDraft on a draft we've abandoned 129 } else if (!doc) { 130 alert("Internal error: response isn't valid XML\n\n" + 131 xmlhttp.responseText); 132 } else if (doc.nodeName == "loginFailed") { 133 doLogout(); 134 reportError("Login failed: " + doc.getAttribute("status")); 135 } else if (doc.nodeName == "unknownOp") { 136 alert("Unknown operation: " + doc.getAttribute("op")); 137 } else { 138 this.callback(doc); 139 } 140 } 141 142 function pachyGet(op) { 143 // Patch out pachyGet from pachyletV2, since it shouldn't be called. 144 // 145 alert("Internal error: pachyGet(" + op + ")"); 146 } 147 148 function iPachyGet(op, callback, offset, selected, dest, startAt) { 149 // Call the client-side script to do something, asynchronously, 150 // with a GET request. 151 // 152 var getter = new Getter(op); 153 if (getter.labelOp) labelOps++; 154 getter.url = urlForOp(op, offset, selected, dest, startAt); 155 getter.callback = (callback ? callback : dummyCallback); 156 initiateXMLHttp(getter); 157 } 158 159 function doneLabelOp() { 160 // Completion of a label op 161 labelOps--; 162 if (labelOps < 0) alert("negative label op count"); 163 if (labelOps == 0) { 164 if (deferredOp) { 165 var op = deferredOp; 166 deferredOp = null; 167 iPachyGet(op, deferredCallback); 168 } 169 } 170 } 171 172 173 // 174 // Login screen 175 // 176 177 function doLogin() { 178 user = utf8(document.getElementById("loginUser").value); 179 var loginPwd = utf8(document.getElementById("loginPwd").value); 180 document.getElementById("loginPwd").value = ""; 181 document.getElementById("loginUser").blur(); 182 document.getElementById("loginPwd").blur(); 183 pachyPostPwd("login", gotLogin, loginPwd); 184 loginPwd = ""; 185 screens.loggingIn.show(); 186 return false; 187 } 188 189 function autoLogin() { 190 // If appropriate, log the user in automatically 191 // 192 var autoUser = getCookie("pachydkuser"); 193 if (autoUser && autoUser == user) { 194 if (slidingScreens) setTimeout(doLogin, 0); else doLogin(); 195 } 196 } 197 198 // receiveAccounts is in pachyletV2.js 199 200 // receiveFolders is in pachyletV2.js 201 202 // receiveQueries is in pachyletV2.js 203 204 function folderListHTML(fn, id) { 205 // Construct HTML for entire list, calling fn onclick, and 206 // insert it at given element id. 207 var fText = ""; 208 for (var i = 0; i < theFQ.length; i++) { 209 if (i != 0) fText += '<hr>\n'; 210 var name = theFQ[i]; 211 fText += '<a href="#" onclick="return ' + fn + '(\'' + 212 htmlspecials(name) + 213 '\')">' + 214 htmlspecials(name) + 215 '</a>\n'; 216 } 217 document.getElementById(id).innerHTML = fText; 218 } 219 220 function gotLogin(doc) { 221 if (checkResult(doc, "user")) { 222 receiveAccounts(doc); 223 var fixedFolders = new Array("Inbox", "Dropped", "Trash", "Unsent"); 224 var recvdFolders = receiveFolders(doc); 225 var recvdQueries = receiveQueries(doc); 226 updateAccountsUI(); 227 theFQ = fixedFolders.concat( 228 recvdFolders.concat(recvdQueries).sort(caseSort)); 229 folderListHTML("doOpen", "folderList"); 230 folderListHTML("chooseMoveOp", "moveDestList"); 231 truncateOptions('findFolder', 1); 232 var elt = document.getElementById('findFolder'); 233 for (var i = 0; i < theFQ.length; i++) { 234 appendOption(elt, theFQ[i], theFQ[i]); 235 } 236 if (theState.query.folder == "") { 237 screens.loggingIn.seq = screens.folder.seq; 238 showFolderScreen(); 239 } else { 240 screens.loggingIn.seq = screens.toc.seq; 241 jumpTo(theState.query); 242 } 243 } 244 } 245 246 function doLogoutUI() { 247 // Clean up the UI after logout 248 screens.loggingOut.show(); 249 } 250 251 function doLogoutAtServer() { 252 // Ask server to erase our authentication cookie 253 // 254 iPachyGet("logout", doneLogout); 255 } 256 257 function doneLogout(doc) { 258 // Completion of doLogoutAtServer 259 // 260 screens.login.show(); 261 } 262 263 // doLogout is in pachyletV2.js 264 265 266 // 267 // Folder list and moveDest screens 268 // 269 270 function showFolderScreen() { 271 // Show top-level folder list screen, and abandon previous query and 272 // its TOC. 273 screens.folder.show(); 274 disableMsgBtns(); 275 deleteCookie("ipachyfolder"); 276 if (theState.query.folder != "") theState.oldFolder = 277 theState.query.folder; 278 theState.query = new Query(""); 279 abandonToc(); 280 return false; 281 } 282 283 function chooseMoveDest() { 284 // Show moveDest screen 285 screens.moveDest.show(); 286 return false; 287 } 288 289 290 // 291 // TOC operations 292 // 293 294 function showTocScreen() { 295 // Show the TOC screen 296 screens.toc.show(); 297 tocScrollbar.set(tocScrollbar.visible, tocScrollbar.pos); 298 return false; 299 } 300 301 function setTocPrompt(prompt) { 302 // Set a user prompt on the TOC area 303 document.getElementById("toc").innerHTML = prompt; 304 theToc.prevHTML = prompt; 305 document.getElementById('tocCounters').innerHTML = ""; 306 } 307 308 function getToc(fetch) { 309 // Execute a query on the server to fetch theState.tocPageSize TOC lines 310 // Can get deferred because of outstanding labelOps. 311 var op = (fetch ? "fetchMail" : "getMsgs"); 312 if (theToc.total < 0) { 313 // First use of query 314 var fName = htmlspecials(theState.query.folder); 315 setTocPrompt("Reading ..."); 316 document.getElementById('tocTitle').innerHTML = "&nbsp;" + fName; 317 document.getElementById('msgTitle').innerHTML = "&nbsp;" + fName; 318 } 319 if (labelOps > 0) { 320 deferredOp = op; 321 deferredCallback = gotToc; 322 } else { 323 iPachyGet(op, gotToc); 324 } 325 } 326 327 // receiveToc is in pachyletV2.js 328 329 function gotToc(doc) { 330 // Response to fetchMail or getMsgs 331 if (this.toc != theToc) { 332 // Query has changed - ignore the response 333 } else if (checkResult(doc, "toc")) { 334 var recvdToc = receiveToc(doc); 335 if (theState.selected < 0) { 336 theState.selected = recvdToc.selected; 337 theState.selOffset = recvdToc.selOffset; 338 prefetchToc(); 339 } 340 // unlike non-iPhone, we don't call "show" because that would 341 // flip us to the message screen. 342 showToc(); 343 xableMiscBtns(); 344 } 345 } 346 347 function disableMsgBtns() { 348 // Disable buttons that require a message to be selected 349 btns.resend.disable(); 350 btns.forward.disable(); 351 btns.replyAll.disable(); 352 btns.reply.disable(); 353 } 354 355 function abandonToc() { 356 // Discard TOC in preparation for new query 357 // Implicitly discards responses to related requests 358 theState.offset = 0; 359 theState.selected = -1; 360 theToc = new Toc(); // discard old TOC data, and also old requests 361 showContents(noMessagesAtAll); 362 tocScrollbar.set(1.0, 0.0); 363 btns.tocEarlier.disable(); 364 btns.tocLater.disable(); 365 btns.next.disable(); 366 btns.next2.disable(); 367 btns.prev.disable(); 368 disableMsgBtns(); 369 } 370 371 function fetchMail() { 372 // Manual prod at incorporating new mail 373 // In this version, this happens on the folder screen, with no current 374 // folder. 375 doOpen("Inbox", true); 376 return false; 377 } 378 379 function jumpTo(query, fetch) { 380 // Execute the given query, optionally first fetching new mail 381 if (query.folder != theState.query.folder && 382 theState.query.folder != "") { 383 theState.oldFolder = theState.query.folder; 384 } 385 theState.query = query; 386 abandonToc(); 387 getToc(fetch); 388 showTocScreen(); 389 setPersistentCookie("ipachyfolder", theState.query.folder); 390 } 391 392 // fetchMail is in pachyletV2.js 393 394 function fixFindPrompts() { 395 // Override pachyletV2.js 396 } 397 398 // setFind is in pachyletV2.js 399 400 function chooseFind() { 401 setFind(theState.query); 402 screens.find.show(); 403 return false; 404 } 405 406 function findFromFolders() { 407 findClear(); 408 screens.find.show(); 409 return false; 410 } 411 412 // findClear is in pachyletV2.js 413 414 function findClose() { 415 // Override pachyletV2.js 416 } 417 418 // doFind is in pachyletV2.js 419 420 // doOpen is in pachyletV2.js 421 422 function doScan() { 423 // Scan smart folders for unread messages 424 abandonToc(); 425 setTocPrompt("Scanning ..."); 426 document.getElementById('tocTitle').innerHTML = "&nbsp;"; 427 showTocScreen(); 428 if (labelOps > 0) { 429 deferredOp = "scan"; 430 deferredCallback = gotScan; 431 } else { 432 iPachyGet("scan", gotScan); 433 } 434 return false; 435 } 436 437 function gotScan(doc) { 438 // Scan result from server 439 if (this.toc != theToc) { 440 // Query has changed - ignore the response 441 } else if (checkResult(doc, "scanned")) { 442 var found = doc.getAttribute("folder"); 443 if (found == "") { 444 showFolderScreen(); 445 } else { 446 doOpen(found); 447 } 448 } 449 } 450 451 function showToc() { 452 // Show the TOC at its current scroll offset 453 var last = theToc.total - theState.offset; 454 var first = last - theState.tocPageSize + 1; 455 if (first <= 0) first = 1; 456 document.getElementById('tocCounters').innerHTML = 457 "&nbsp;" + 458 (theToc.total == 0 ? "Empty" : 459 ((last - first + 1 == theToc.total ? 460 "All " + (last - first + 1) : 461 (theState.offset == 0 ? "Last " + (last - first + 1) : 462 ("" + first + "-" + last))) + 463 "&nbsp;\nof " + theToc.total)); 464 var temp = ""; 465 var gotEverything = true; 466 for (var i = theState.offset + theState.tocPageSize - 1; 467 i >= theState.offset; i--) { 468 var tocLine = theToc.lines[i]; 469 if (temp != "") temp += "<hr>"; 470 if (tocLine) { 471 var flag = (tocLine.id == theState.selected ? "&gt;" : 472 (tocLine.unread ? "?" : "&nbsp;")); 473 // Date is right-justified. Left is better with this layout 474 var lDate = tocLine.date; 475 for (;;) { 476 if (lDate.indexOf(' ') == 0) { 477 lDate = lDate.substring(1) + "&nbsp;"; 478 } else if (lDate.indexOf('&nbsp;') == 0) { 479 lDate = lDate.substring(6) + "&nbsp;"; 480 } else { 481 break; 482 } 483 } 484 temp += "<a href=\"#\" onclick=\"return show(" + tocLine.id + 485 "," + i + ")\">"; 486 temp += flag + " <b>" + tocLine.subject + "</b>"; 487 temp += "\n"; 488 temp += " " + lDate + " " + tocLine.from; 489 temp += "</a>"; 490 } else if (i < theToc.total) { 491 temp += "<a href=\"#\" onclick=\"return false\">"; 492 temp += " Message " + (theToc.total-i) + " ...\n "; 493 temp += "</a>"; 494 gotEverything = false; 495 } 496 } 497 if (temp != theToc.prevHTML) { // there are redundant calls of showToc 498 document.getElementById("toc").innerHTML = temp; 499 theToc.prevHTML = temp; 500 } 501 tocScrollbar.set( 502 (theToc.total == 0 ? 1.0 : (last-first+1)/theToc.total), 503 (first == 1 ? 0.0 : (first-1)/(theToc.total-(last-first+1)))); 504 if (theState.offset == 0) { 505 btns.tocLater.disable(); 506 } else { 507 btns.tocLater.enable(); 508 } 509 if (theState.offset >= theToc.total - theState.tocPageSize) { 510 btns.tocEarlier.disable(); 511 } else { 512 btns.tocEarlier.enable(); 513 } 514 return gotEverything; 515 } 516 517 // tocPageKnown is in pachyletV2.js 518 519 function prefetchToc() { 520 var nextBefore = theState.offset + theState.tocPageSize; 521 if (nextBefore >= theToc.total) nextBefore = theToc.total-1; 522 if (nextBefore > theState.offset && !tocPageKnown(nextBefore)) { 523 iPachyGet("getMsgs", gotToc, nextBefore); 524 } 525 var nextAfter = theState.offset - theState.tocPageSize; 526 if (nextAfter < 0) nextAfter = 0; 527 if (nextAfter < theState.offset && !tocPageKnown(nextAfter)) { 528 iPachyGet("getMsgs", gotToc, nextAfter); 529 } 530 } 531 532 // scrollTocTo is in pachyletV2.js 533 534 // goToStart is in pachyletV2.js 535 536 // goToEnd is in pachyletV2.js 537 538 // newTocScrollbar is in pachyletV2.js 539 540 541 // 542 // Message display 543 // 544 545 // Summary of the main message display functions: 546 // 547 // showContents ... places given HTML in the message display area. 548 // showMessageContents ... displays a message object (which might be a 549 // sub-message); calls showContents; initiates message 550 // pre-fetch if appropriate. 551 // showSelectedMessage ... shows the currently selected message, from 552 // cache or by fetching it. Does no TOC manipulation. 553 // show ... makes the given message ID be the one we're trying to 554 // display. Calls showSelectedMessage and adjusts the 555 // TOC appropriately. 556 // showAltMsg ... initiates read of a sub-message or alternative part. 557 // receiveMsg ... takes message response from server, and builds message 558 // object; caches it if appropriate, and displays it if 559 // appropriate (by calling showMessageContents). 560 // 561 // Note that "show" is the only thing that changes the shown message ID. The 562 // others gets used to display sub-messages, alternative views, or 563 // miscellanous prompts within that message ID. 564 565 function showMsgScreen() { 566 screens.msg.show(); 567 } 568 569 function returnToToc() { 570 // Return from message contents screen to TOC screen, re-displaying TOC 571 // (which can change during message screen operations). 572 if (!showToc()) getToc(); 573 showTocScreen(); 574 return false; 575 } 576 577 function showContents(head, body) { 578 // Display some HTML in the contents area 579 document.getElementById("msgContents").scrollTop = 0; 580 document.getElementById("msgContents").scrollLeft = 0; 581 document.getElementById("msgHead").innerHTML = head; 582 document.getElementById("msgBody").innerHTML = (body ? body : ""); 583 } 584 585 function getTruncatedPrompt(msgContent) { 586 // Return "truncated" prompt for inclusion in header display 587 var truncated; 588 if (msgContent.truncated) { 589 var clickHere = "<a href=\"#\" " + 590 "onClick=\"return showAltMsg(" + msgContent.id + "," + 591 msgContent.startAt + ", '" + 592 msgContent.selectedPart + "', true)\" " + 593 ">view all " + msgContent.fullLength + " bytes</a>"; 594 truncated = "<br><b>Truncated: </b>" + clickHere; 595 } else { 596 truncated = ""; 597 } 598 return truncated; 599 } 600 601 function showMessageContents(msgContent) { 602 // Display received message contents, and pre-fetch next message 603 showContents(msgContent.header + getTruncatedPrompt(msgContent), 604 msgContent.body); 605 if (theState.selOffset > 0) { 606 var nextLine = theToc.lines[theState.selOffset - 1]; 607 if (nextLine && !msgCache.read(""+nextLine.id)) { 608 iPachyGet("getMsg", gotMsg, null, nextLine.id); 609 } 610 } 611 } 612 613 function showSelectedMessage(tocLine) { 614 // Show the selected message if it's cached, or fetch it. 615 // "tocLine" is optional hint for some of the message's header. 616 // 617 // Assumes this is the selected message and that TOC is set up properly. 618 // 619 var content = msgCache.read(""+theState.selected); 620 if (content) { 621 showMessageContents(content); 622 } else { 623 var prompt = "<b>Reading ...</b><br>"; 624 if (tocLine) { 625 prompt += "<b>Subject: &nbsp;</b>" + tocLine.subject; 626 } else { 627 prompt += "&nbsp;"; 628 } 629 showContents(prompt); 630 iPachyGet("getMsg", gotMsg); 631 } 632 return false; 633 } 634 635 function show(id, offset) { 636 // Show given message, which lives at given offset in TOC 637 if (theState.query.folder == "Unsent") { 638 theState.selected = 0; 639 theState.selOffset = 0; 640 } else { 641 theState.selected = id; 642 theState.selOffset = offset; 643 } 644 if (offset < theState.offset) { 645 scrollTocTo(theState.offset - theState.tocPageSize); 646 } else if (offset >= theState.offset + theState.tocPageSize) { 647 scrollTocTo(theState.offset + theState.tocPageSize); 648 } 649 if (theState.query.folder == "Unsent") { 650 if (id == 0) { 651 showContents(theToc.total > 0 ? viewingUnsentFolder : 652 noMessagesAtAll); 653 } else { 654 return doReopenDraft(id); 655 } 656 } else if (theState.selected <= 0) { 657 showContents(theToc.total > 0 ? noMessageSelected : 658 noMessagesAtAll); 659 } else { 660 var tocLine = theToc.lines[theState.selOffset]; 661 if (tocLine.unread) { 662 iPachyGet("markRead"); 663 theState.totalUnread--; 664 } 665 tocLine.unread = false; 666 showSelectedMessage(tocLine); 667 } 668 document.getElementById('msgCounters').innerHTML = 669 "&nbsp;" + (theState.selected <= 0 ? "" : 670 (theToc.total - theState.selOffset) + 671 "\n&nbsp;of " + theToc.total); 672 showMsgScreen(); 673 xableMiscBtns(); 674 return false; 675 } 676 677 function showAltMsg(id, startAt, chosenPartNo, full) { 678 // Show an attached message, or non-default part of main message 679 showContents(readingContents); 680 iPachyGet((full ? "getFullMsg" : "getMsg"), gotMsg, null, null, 681 chosenPartNo, startAt); 682 return false; 683 } 684 685 function showHeader(id, startAt) { 686 // Show a message's header 687 showContents(readingContents); 688 iPachyGet("getRawHeader", gotRaw, null, null, null, startAt); 689 return false; 690 } 691 692 function showRawMessage(id, startAt) { 693 // Show re-assembled raw message 694 showContents(readingContents); 695 iPachyGet("getRawMessage", gotRaw, null, null, null, startAt); 696 return false; 697 } 698 699 // receiveMsg is in pachyletV2.js 700 701 function gotRaw(doc) { 702 // Server response to getRawHeader or getRawMessage 703 if (doc.nodeName == "rawHeader" || checkResult(doc, "rawMessage")) { 704 var id = parseInt(doc.getAttribute("id")); 705 if (id == theState.selected) { 706 var startAt = parseInt(doc.getAttribute("startAt")); 707 var content = getBulkData(doc); 708 var tempHdr = "<b>Raw " + 709 (doc.nodeName == "rawHeader" ? "Header:" : "Message:") + 710 " </b>"; 711 if (startAt != 0) { 712 tempHdr += "<a href=\"#\" " + 713 "onClick=\"return showAltMsg(" + id + "," + 714 startAt + ", '')\" " + 715 "title=\"Return to attached message\"" + 716 ">sub-message #" + startAt + "</a> of "; 717 } 718 tempHdr += "<a href=\"#\" onClick=\"return " + 719 "showSelectedMessage()\" title=\"Return to message #" + id + 720 "\">message #" + id + "</a>"; 721 showContents(tempHdr, 722 "<div class=fixedNoWrap>" + 723 htmlspecials(content) + 724 "</div>"); 725 } 726 } 727 } 728 729 function gotMsg(doc) { 730 // Server response to getMsg 731 if (checkResult(doc, "msg")) { 732 receiveMsg(this, doc, false); 733 } 734 } 735 736 // doNext and doPrev are in pachyletV2.js 737 738 739 function xableMiscBtns() { 740 // called when displaying a message, or after getting a query result 741 if (theState.selected <= 0) { 742 disableMsgBtns(); 743 } else { 744 btns.resend.enable(); 745 btns.forward.enable(); 746 btns.replyAll.enable(); 747 btns.reply.enable(); 748 } 749 if (theState.query.folder == "Trash") { 750 btns.drop.disable(); 751 btns.trash.disable(); 752 } else if (theState.query.folder == "Dropped") { 753 btns.drop.disable(); 754 btns.trash.enable(); 755 } else { 756 btns.drop.enable(); 757 btns.trash.enable(); 758 } 759 if (theState.selOffset > 0 || 760 (theState.selected <= 0 && theToc.total > 0)) { 761 showHide(btns.next2.id, true); 762 showHide(btns.scan3.id, false); 763 btns.next.enable(); 764 btns.next2.enable(); 765 } else { 766 showHide(btns.next2.id, false); 767 showHide(btns.scan3.id, true); 768 btns.next.disable(); 769 btns.next2.disable(); 770 } 771 if (theState.selOffset + 1 < theToc.total) { 772 btns.prev.enable(); 773 } else { 774 btns.prev.disable(); 775 } 776 if (theState.totalUnread > 0 || true) { 777 btns.scan1.enable(); 778 } else { 779 btns.scan1.disable(); 780 } 781 } 782 783 784 // 785 // Label operations 786 // 787 788 function doMoveCopy(moveOrCopy, dest) { 789 // Move or copy to dest folder 790 if (theState.selected <= 0) { 791 reportError("No message selected"); 792 } else { 793 if (!dest || dest == "-") { 794 reportError("No destination folder selected"); 795 } else if (dest == theState.query.folder) { 796 reportError("You can't move or copy a message to the " + 797 "folder you're currently looking at"); 798 } else { 799 iPachyGet((moveOrCopy ? "move" : "copy"), 800 null, null, null, dest); 801 if (moveOrCopy) { 802 // Remove from our cached TOC, and repaint 803 theToc.lines.splice(theState.selOffset, 1); 804 theToc.total--; 805 } 806 if (theToc.total == 0) { 807 show(0, 0); 808 } else { 809 if (theState.selOffset == 0) theState.selOffset = 1; 810 doNext(); 811 } 812 } 813 } 814 return false; 815 } 816 817 var moveCopyDest = null; 818 819 function chooseMoveOp(folder) { 820 moveCopyDest = folder; 821 document.getElementById('moveOpDest').innerHTML = htmlspecials(folder); 822 screens.moveOp.show(); 823 return false; 824 } 825 826 function doMoveCopyOp(moveOrCopy, all) { 827 // Do requested move/copy with previously chosen destination 828 if (!all) { 829 doMoveCopy(moveOrCopy, moveCopyDest); 830 } else if (theToc.total < 0) { 831 // nothing to move 832 } else if (!confirm("Really " + (moveOrCopy ? "move" : "copy") + 833 " all " + theToc.total + " messages to \"" + 834 moveCopyDest + "\"?")) { 835 // just walk away 836 } else if (moveOrCopy) { 837 iPachyGet("moveAll", null, null, null, moveCopyDest); 838 theToc.lines = new Array(); 839 theToc.total = 0; 840 show(0,0); 841 returnToToc(); 842 } else { 843 iPachyGet("copyAll", null, null, null, moveCopyDest); 844 } 845 screens.moveOp.goBack(); 846 return false; 847 } 848 849 function chooseMarkOp() { 850 screens.markOp.show(); 851 return false; 852 } 853 854 function doMark(unread, all) { 855 // Mark selected/all as unread/read 856 if (theToc.total >= 0) { 857 if (!all && theState.selected <= 0) { 858 reportError("No message selected"); 859 } else { 860 iPachyGet((unread ? (all ? "markAllUnread" : "markUnread") : 861 (all ? "markAllRead" : "markRead"))); 862 if (all) { 863 jumpTo(theState.query); // to recompute unread count 864 // ... which also calls abandonToc, so we needn't update it 865 } else { 866 theState.totalUnread += (unread ? 1 : 0) - 867 (theToc.lines[theState.selOffset].unread ? 1 : 0); 868 theToc.lines[theState.selOffset].unread = unread; 869 if (theState.selOffset > 0) doNext(); else show(0,0); 870 } 871 } 872 } 873 if (all || theState.selected <= 0) { 874 returnToToc(); 875 } else { 876 screens.markOp.goBack(); 877 } 878 return false; 879 } 880 881 function chooseSendOp() { 882 screens.sendOp.show(); 883 return false; 884 } 885 886 887 // 888 // Message composition operations 889 // 890 891 function setupDBodyHeight() { 892 // Set the height of the draft body text area 893 // 894 var dBody = document.getElementById("draftBody"); 895 dBody.style.height = "" + 896 (dBody.offsetParent.clientHeight - dBody.offsetTop) + 897 "px"; 898 // That calculation assumed dBody has no padding/border/margin 899 } 900 901 // setupDraftFrom, curDraftId, curDraft, 902 // installAttachments, installDraft, receiveAttachments, 903 // receiveDraft are in pachyletV2.js 904 905 function openDraft(op, draftId) { 906 // Commence opening a draft, new or old 907 // 908 showComposingScreen(draftId ? "Reading draft ..." : 909 "Composing draft ..."); 910 iPachyGet(op, gotDraft, null, null, draftId); 911 } 912 913 function showDraftScreen() { 914 // Switch to the draft screen, from modal state 915 // 916 screens.draft.show(); 917 setupDBodyHeight(); 918 } 919 920 function showComposingScreen(prompt) { 921 // Move to modal state from the draft screen 922 // 923 document.getElementById("composing").innerHTML = prompt; 924 screens.composing.show(); 925 } 926 927 function returnFromDraft() { 928 // Exit from the draft screen (which might be modal at the time) 929 // 930 if (screens.composing.prev == screens.toc && 931 theState.query.folder == 'Unsent') { 932 jumpTo(theState.query); // re-evaluate query after send/discard 933 } else { 934 screens.composing.goBack(); 935 } 936 return false; 937 } 938 939 // reportSavedTime, gotDraft, doComposition, 940 // doReopenDraft are in pachyletV2.js 941 942 function doResend() { 943 reportError("Resend not implemented"); 944 return false; 945 } 946 947 function showRecipients() { 948 // Show the "add recipient" screen 949 // 950 screens.recipient.show(); 951 if (!theContacts) { 952 document.getElementById("recipientList").innerHTML = "Reading ..."; 953 iPachyGet("getContacts", gotContacts); 954 } 955 return false; 956 } 957 958 // draftContactsFind, draftContactsAll, draftFindFocus, draftFindBlur 959 // are in pachyletV2.js 960 961 function addRecipientDone() { 962 // Cleanup after addRecipient 963 // 964 screens.recipient.goBack(); 965 } 966 967 // addRecipient is in pachyletV2.js 968 969 function attach() { 970 // Show the "attach" screen 971 // 972 screens.attach.show(); 973 return false; 974 } 975 976 // doAttach is in pachyletV2.js 977 978 function attachDone() { 979 // onload handler for the attachment upload iframe 980 // 981 document.getElementById("attachFrame").onload = function() { }; 982 showComposingScreen("Enumerating the attachments ..."); 983 iPachyGet("reopenDraft", gotAttachEnum, null, null, curDraftId()); 984 } 985 986 // gotAttachEnum is in pachyletV2.js 987 988 function attachDelete(draftId, part) { 989 // Remove an attachment from a draft 990 // 991 showComposingScreen("Deleting the attachment ..."); 992 iPachyGet("attachDelete", gotAttachEnum, null, null, draftId, part); 993 return false; 994 } 995 996 // checkSendDone, saveIfNeeded, startAutoSave are in pachyletV2.js 997 998 function autoSaveDraft(id) { 999 // Timed saving of draft, if modified 1000 if (screens.cur == screens.draft && lastSavedDraft.id == id) { 1001 if (!saveIfNeeded(autoSaveDone)) startAutoSave(); 1002 } 1003 } 1004 1005 // autoSaveDone, saveDraft, draftChange, saveDone, confirmSendDraft, 1006 // sendDraft, ackAbandonDraft, abandonDraft, confirmDeleteDraft, 1007 // deleteDraft, doneWithDraft are in pachyletV2.js 1008 1009 // Not used 1010 // 1011 function onBeforeUnload() { 1012 // Save before exit from the program 1013 if (screens.cur == screens.draft) { 1014 if (saveIfNeeded()) { 1015 return "The draft will remain in your Unsent folder"; 1016 } 1017 } 1018 } 1019 1020 1021 // 1022 // Contacts (!) 1023 // 1024 1025 function keepContact(email, person) { 1026 // "keep" in a message's recipient list 1027 reportError("Keep contact not implemented"); 1028 return false; 1029 } 1030 1031 1032 // 1033 // Initialisation 1034 // 1035 1036 function adjustContentsHeight(id, contentsPx) { 1037 // Adjust screen contents height to fit the display 1038 var elt = document.getElementById(id); 1039 elt.style.height = contentsPx; 1040 } 1041 1042 function setupScreen(id, seq) { 1043 // Create one screen object, and adjust its height 1044 new Screen(id, seq); 1045 } 1046 1047 function setupScreens() { 1048 // Create screen objects 1049 setupScreen('login', 1); 1050 setupScreen('loggingIn', 2); 1051 setupScreen('loggingOut', 2); 1052 setupScreen('folder', 3); 1053 setupScreen('toc', 4); 1054 setupScreen('msg', 5); 1055 setupScreen('find', 6); 1056 setupScreen('moveDest', 7.1); 1057 setupScreen('moveOp', 7.2); 1058 setupScreen('markOp', 8); 1059 setupScreen('sendOp', 9.1); 1060 setupScreen('composing', 9.2); 1061 setupScreen('draft', 9.2); 1062 setupScreen('recipient', 10); 1063 setupScreen('attach', 10); 1064 screens.cur = screens.login; 1065 } 1066 1067 function setupHeights() { 1068 // Adjust elements that are sized to fit the current screen height 1069 window.scrollTo(0, 0); // scrolls Mobile Safari's header off-screen 1070 var contentsHeight = windowSize().y - 2 * 43; 1071 var contentsPx = "" + contentsHeight + "px"; 1072 adjustContentsHeight("login", contentsPx); 1073 adjustContentsHeight("loggingIn", contentsPx); 1074 adjustContentsHeight("loggingOut", contentsPx); 1075 adjustContentsHeight("folderList", contentsPx); 1076 adjustContentsHeight("tocWrapper", contentsPx); 1077 adjustContentsHeight("findWrapper", contentsPx); 1078 adjustContentsHeight("msgContents", contentsPx); 1079 adjustContentsHeight("moveDestList", contentsPx); 1080 // The "*opWrapper" elements exist because their children have padding 1081 adjustContentsHeight("moveOpWrapper", contentsPx); 1082 adjustContentsHeight("markOpWrapper", contentsPx); 1083 adjustContentsHeight("sendOpWrapper", contentsPx); 1084 adjustContentsHeight("composing", contentsPx); 1085 adjustContentsHeight("draftWrapper", contentsPx); 1086 adjustContentsHeight("recipientList", contentsPx); 1087 adjustContentsHeight("attachForm", contentsPx); 1088 tocScrollbar = newTocScrollbar(); 1089 theState.tocPageSize = 1090 Math.floor((contentsHeight + 3) / tocItemHeight); 1091 } 1092 1093 function rotated() { 1094 setupHeights(); 1095 // Force TOC redisplay for new page size, also re-applying bounds for 1096 // new page size. 1097 if (theToc.total >= 0) { 1098 var dest = theState.offset; 1099 theState.offset = -1; 1100 scrollTocTo(dest, false); // always sets theState.offset 1101 } 1102 if (screens.cur == screens.draft) setupDBodyHeight(); 1103 } 1104 1105 function init() { 1106 new Button("tocLater"); 1107 new Button("tocEarlier"); 1108 new Button("scan1"); 1109 new Button("scan2"); 1110 new Button("scan3"); 1111 new Button("prev"); 1112 new Button("next"); 1113 new Button("next2"); 1114 new Button("drop"); 1115 new Button("trash"); 1116 new Button("reply"); 1117 new Button("replyAll"); 1118 new Button("forward"); 1119 new Button("resend"); 1120 window.onbeforeunload = onBeforeUnload; 1121 user = getQueryArg("user"); 1122 if (!user) user = getCookie("pachydkuser"); 1123 if (!user) user = ""; 1124 document.getElementById("loginUser").value = user; 1125 document.getElementById("loginTable").style.visibility = "visible"; 1126 var prevFolder = getCookie("ipachyfolder"); 1127 if (typeof(prevFolder) != "string") prevFolder = ""; 1128 theState = new State(new Query(prevFolder)); 1129 1130 autoLogin(); 1131 setupScreens(); 1132 setTimeout(setupHeights, 0); 1133 }
End of listing