1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 ZmSignaturesPage = function(parent, section, controller) { 25 26 ZmPreferencesPage.call(this, parent, section, controller); 27 28 this._minEntries = appCtxt.get(ZmSetting.SIGNATURES_MIN); // added for Comcast 29 this._maxEntries = appCtxt.get(ZmSetting.SIGNATURES_MAX); 30 31 this.addControlListener(this._resetSize.bind(this)); 32 }; 33 34 ZmSignaturesPage.prototype = new ZmPreferencesPage; 35 ZmSignaturesPage.prototype.constructor = ZmSignaturesPage; 36 37 ZmSignaturesPage.prototype.toString = function() { 38 return "ZmSignaturesPage"; 39 }; 40 41 // 42 // Constants 43 // 44 45 ZmSignaturesPage.SIGNATURE_TEMPLATE = "prefs.Pages#SignatureSplitView"; 46 47 ZmSignaturesPage.SIG_TYPES = [ZmIdentity.SIGNATURE, ZmIdentity.REPLY_SIGNATURE]; 48 49 // 50 // Public methods 51 // 52 53 ZmSignaturesPage.prototype.showMe = 54 function() { 55 56 ZmPreferencesPage.prototype.showMe.call(this); 57 58 // bug #41719 & #94845 - always update size on display 59 this._resetSize(); 60 61 if (!this._firstTime) { 62 this._firstTime = true; 63 } 64 65 // bug fix #31849 - reset the signature html editor when in multi-account mode 66 // since the view gets re-rendered whenever the account changes 67 if (appCtxt.multiAccounts) { 68 this._signatureEditor = null; 69 } 70 }; 71 72 ZmSignaturesPage.prototype._rehashByName = 73 function() { 74 this._byName = {}; 75 for (var id in this._signatures) { 76 var signature = this._signatures[id]; 77 if (!signature._new) { // avoid messing with existing signatures with same names 78 this._byName[signature.name] = signature; 79 } 80 } 81 }; 82 83 ZmSignaturesPage.prototype.getNewSignatures = 84 function(onlyValid) { 85 var list = []; 86 this._rehashByName(); 87 for (var id in this._signatures) { 88 var signature = this._signatures[id], 89 isEmpty = signature._autoAdded && !(AjxStringUtil._NON_WHITESPACE.test(signature.getValue()) || AjxStringUtil._NON_WHITESPACE.test(signature.name)); 90 if (signature._new && !isEmpty && !(onlyValid && this._isInvalidSig(signature, true))) { 91 list.push(signature); 92 } 93 } 94 return list; 95 }; 96 97 ZmSignaturesPage.prototype.getDeletedSignatures = 98 function() { 99 return AjxUtil.values(this._deletedSignatures); 100 }; 101 102 ZmSignaturesPage.prototype.getModifiedSignatures = 103 function() { 104 105 var array = []; 106 for (var id in this._signatures) { 107 var signature = this._signatures[id]; 108 if (signature._new) { 109 continue; 110 } 111 112 if (this._hasChanged(signature)) { 113 array.push(signature); 114 } 115 } 116 return array; 117 }; 118 119 ZmSignaturesPage.SIG_FIELDS = [ZmIdentity.SIGNATURE, ZmIdentity.REPLY_SIGNATURE]; 120 121 // returns a hash representing the current usage, based on form selects 122 ZmSignaturesPage.prototype._getUsage = 123 function(newOnly) { 124 125 var usage = {}; 126 var foundOne; 127 for (var identityId in this._sigSelect) { 128 usage[identityId] = {}; 129 for (var j = 0; j < ZmSignaturesPage.SIG_FIELDS.length; j++) { 130 var field = ZmSignaturesPage.SIG_FIELDS[j]; 131 var select = this._sigSelect[identityId] && this._sigSelect[identityId][field]; 132 if (select) { 133 var sigId = select.getValue(); 134 if (newOnly && this._newSigId[sigId]) { 135 return true; 136 } 137 else { 138 usage[identityId][field] = sigId; 139 } 140 } 141 } 142 } 143 return newOnly ? false : usage; 144 }; 145 146 // returns a hash representing the current usage, based on identity data 147 ZmSignaturesPage.prototype._getUsageFromIdentities = 148 function() { 149 150 var usage = {}; 151 var collection = appCtxt.getIdentityCollection(); 152 var identities = collection && collection.getIdentities(); 153 for (var i = 0, len = identities.length; i < len; i++) { 154 var identity = identities[i]; 155 usage[identity.id] = {}; 156 for (var j = 0; j < ZmSignaturesPage.SIG_FIELDS.length; j++) { 157 var field = ZmSignaturesPage.SIG_FIELDS[j]; 158 usage[identity.id][field] = identity.getField(field) || ""; 159 } 160 } 161 return usage; 162 }; 163 164 /** 165 * Returns a list of usage changes. Each item in the list details the identity, 166 * which type of signature, and the ID of the new signature. 167 */ 168 ZmSignaturesPage.prototype.getChangedUsage = 169 function() { 170 171 var list = []; 172 var usage = this._getUsage(); 173 for (var identityId in usage) { 174 var u1 = this._origUsage[identityId]; 175 var u2 = usage[identityId]; 176 if (u1 && u2){ 177 for (var j = 0; j < ZmSignaturesPage.SIG_FIELDS.length; j++) { 178 var field = ZmSignaturesPage.SIG_FIELDS[j]; 179 var savedSigId = (u1[field]) || ((field === ZmIdentity.REPLY_SIGNATURE) ? ZmIdentity.SIG_ID_NONE : u1[field]); 180 var curSigId = this._newSigId[u2[field]] || u2[field]; 181 if (savedSigId !== curSigId) { 182 list.push({identity:identityId, sig:field, value:curSigId}); 183 } 184 } 185 } 186 } 187 return list; 188 }; 189 190 ZmSignaturesPage.prototype.reset = 191 function(useDefaults) { 192 this._updateSignature(); 193 ZmPreferencesPage.prototype.reset.apply(this, arguments); 194 this._populateSignatures(true); 195 }; 196 197 ZmSignaturesPage.prototype.resetOnAccountChange = 198 function() { 199 ZmPreferencesPage.prototype.resetOnAccountChange.apply(this, arguments); 200 this._selSignature = null; 201 this._firstTime = false; 202 }; 203 204 ZmSignaturesPage.prototype.isDirty = 205 function() { 206 207 this._updateSignature(); 208 209 var printSigs = function(sig) { 210 if (AjxUtil.isArray(sig)) { 211 return AjxUtil.map(sig, printSigs).join("\n"); 212 } 213 return [sig.name, " (", ((sig._orig && sig._orig.value !== sig.value) ? (sig._orig.value+" changed to ") : ""), sig.value, ")"].join(""); 214 } 215 216 var printUsages = function(usage) { 217 if (AjxUtil.isArray(usage)) { 218 return AjxUtil.map(usage, printUsages).join("\n"); 219 } 220 return ["identityId: ", usage.identity, ", type: ", usage.sig, ", signatureId: ", usage.value].join(""); 221 } 222 223 if (this.getNewSignatures(false).length > 0) { 224 AjxDebug.println(AjxDebug.PREFS, "Dirty preferences:\nNew signatures:\n" + printSigs(this.getNewSignatures(false))); 225 return true; 226 } 227 if (this.getDeletedSignatures().length > 0) { 228 AjxDebug.println(AjxDebug.PREFS, "Dirty preferences:\nDeleted signatures:\n" + printSigs(this.getDeletedSignatures())); 229 return true; 230 } 231 if (this.getModifiedSignatures().length > 0) { 232 AjxDebug.println(AjxDebug.PREFS, "Dirty preferences:\nModified signatures:\n" + printSigs(this.getModifiedSignatures())); 233 return true; 234 } 235 if (this.getChangedUsage().length > 0) { 236 AjxDebug.println(AjxDebug.PREFS, "Dirty preferences:\nSignature usage changed:\n" + printUsages(this.getChangedUsage())); 237 return true; 238 } 239 }; 240 241 ZmSignaturesPage.prototype.validate = 242 function() { 243 this._updateSignature(); 244 this._rehashByName(); 245 246 for (var id in this._signatures) { 247 var error = this._isInvalidSig(this._signatures[id]); 248 if (error) { 249 this._errorMsg = error; 250 return false; 251 } 252 } 253 return true; 254 }; 255 256 // The 'strict' parameter will make the function return true if a signature is not 257 // saveable, even if it can be safely ignored. Without it, the function returns an error 258 // only if there's bad user input. 259 ZmSignaturesPage.prototype._isInvalidSig = 260 function(signature, strict) { 261 262 var hasName = AjxStringUtil._NON_WHITESPACE.test(signature.name); 263 var hasContact = Boolean(signature.contactId); 264 var hasValue = AjxStringUtil._NON_WHITESPACE.test(signature.getValue()) || hasContact; 265 if (!hasName && !hasValue) { 266 this._deleteSignature(signature); 267 if (strict) { 268 return true; 269 } 270 } 271 else if (!hasName || !hasValue) { 272 return !hasName ? ZmMsg.signatureNameMissingRequired : ZmMsg.signatureValueMissingRequired; 273 } 274 else if (strict && !hasValue) { 275 return true; 276 } 277 if (hasName && this._byName[signature.name]) { 278 // If its a new signature with a in-use name or a existing signature whose name the user is trying to edit to a existing name value 279 if (signature._new || (this._byName[signature.name].id !== signature.id)) { 280 return AjxMessageFormat.format(ZmMsg.signatureNameDuplicate, AjxStringUtil.htmlEncode(signature.name)); 281 } 282 } 283 var sigValue = signature.value; 284 var maxLength = appCtxt.get(ZmSetting.SIGNATURE_MAX_LENGTH); 285 if (maxLength > 0 && sigValue.length > maxLength) { 286 return AjxMessageFormat.format((signature.contentType === ZmMimeTable.TEXT_HTML) 287 ? ZmMsg.errorHtmlSignatureTooLong 288 : ZmMsg.errorSignatureTooLong, maxLength); 289 } 290 291 return false; 292 }; 293 294 ZmSignaturesPage.prototype.getErrorMessage = 295 function() { 296 return this._errorMsg; 297 }; 298 299 ZmSignaturesPage.prototype.addCommand = 300 function(batchCommand) { 301 302 // delete signatures 303 var deletedSigs = this.getDeletedSignatures(); 304 for (var i = 0; i < deletedSigs.length; i++) { 305 var signature = deletedSigs[i]; 306 var callback = this._handleDeleteResponse.bind(this, signature); 307 signature.doDelete(callback, null, batchCommand); 308 } 309 310 // modify signatures 311 var modifiedSigs = this.getModifiedSignatures(); 312 for (var i = 0; i < modifiedSigs.length; i++) { 313 var signature = modifiedSigs[i]; 314 var comps = this._signatures[signature._htmlElId]; 315 var callback = this._handleModifyResponse.bind(this, signature); 316 var errorCallback = this._handleModifyError.bind(this, signature); 317 signature.save(callback, errorCallback, batchCommand); 318 } 319 320 // add signatures 321 var newSigs = this.getNewSignatures(true); 322 for (var i = 0; i < newSigs.length; i++) { 323 var signature = newSigs[i]; 324 signature._id = signature.id; // Clearing existing dummy id 325 signature.id = null; 326 var callback = this._handleNewResponse.bind(this, signature); 327 signature.create(callback, null, batchCommand); 328 } 329 330 // signature usage 331 var sigChanges = this.getChangedUsage(); 332 if (sigChanges.length) { 333 var collection = appCtxt.getIdentityCollection(); 334 if (collection) { 335 for (var i = 0; i < sigChanges.length; i++) { 336 var usage = sigChanges[i]; 337 var identity = collection.getById(usage.identity); 338 // don't save usage of new signature just yet 339 if (identity && !this._isTempId[usage.value]) { 340 identity.setField(usage.sig, usage.value); 341 identity.save(null, null, batchCommand); 342 } 343 } 344 } 345 } 346 }; 347 348 ZmSignaturesPage.prototype.getPostSaveCallback = 349 function() { 350 return this._postSave.bind(this); 351 }; 352 353 // if a new sig has been assigned to an identity, we need to save again since 354 // we only now know the sig's ID 355 ZmSignaturesPage.prototype._postSave = 356 function() { 357 358 var newUsage = this._getUsage(true); 359 if (newUsage) { 360 var respCallback = this._handleResponsePostSave.bind(this); 361 this._controller.save(respCallback, true); 362 } 363 }; 364 365 ZmSignaturesPage.prototype._handleResponsePostSave = 366 function() { 367 368 this._newSigId = {}; // clear this to prevent request loop 369 this._resetOperations(); 370 this._origUsage = this._getUsage(); // form selects and data in identities should be in sync now 371 }; 372 373 ZmSignaturesPage.prototype.setContact = 374 function(contact) { 375 if (this._selSignature) { 376 this._selSignature.contactId = contact.id; 377 } 378 this._vcardField.value = contact.getFileAs() || contact.getFileAsNoName(); 379 }; 380 381 // 382 // Protected methods 383 // 384 385 ZmSignaturesPage.prototype._initialize = 386 function(container) { 387 388 container.getHtmlElement().innerHTML = AjxTemplate.expand(ZmSignaturesPage.SIGNATURE_TEMPLATE, {id:this._htmlElId}); 389 390 // Signature list 391 var listEl = document.getElementById(this._htmlElId + "_SIG_LIST"); 392 var list = new ZmSignatureListView(this); 393 this._replaceControlElement(listEl, list); 394 list.setMultiSelect(false); 395 list.addSelectionListener(this._selectionListener.bind(this)); 396 list.setUI(null, true); // renders headers and empty list 397 this._sigList = list; 398 399 // Signature ADD 400 var addEl = document.getElementById(this._htmlElId + "_SIG_NEW"); 401 var button = new DwtButton(this); 402 button.setText(ZmMsg.newSignature); 403 button.addSelectionListener(this._handleAddButton.bind(this)); 404 this._replaceControlElement(addEl, button); 405 this._sigAddBtn = button; 406 407 // Signature DELETE 408 var deleteEl = document.getElementById(this._htmlElId + "_SIG_DELETE"); 409 var button = new DwtButton(this); 410 button.setText(ZmMsg.del); 411 button.addSelectionListener(this._handleDeleteButton.bind(this)); 412 this._replaceControlElement(deleteEl, button); 413 this._deleteBtn = button; 414 415 // vCard INPUT 416 this._vcardField = document.getElementById(this._htmlElId + "_SIG_VCARD"); 417 418 // vCard BROWSE 419 var el = document.getElementById(this._htmlElId + "_SIG_VCARD_BROWSE"); 420 var button = new DwtButton(this); 421 button.setText(ZmMsg.browse); 422 button.addSelectionListener(this._handleVcardBrowseButton.bind(this)); 423 this._replaceControlElement(el, button); 424 this._vcardBrowseBtn = button; 425 426 // vCard CLEAR 427 var el = document.getElementById(this._htmlElId + "_SIG_VCARD_CLEAR"); 428 var button = new DwtButton(this); 429 button.setText(ZmMsg.clear); 430 button.addSelectionListener(this._handleVcardClearButton.bind(this)); 431 this._replaceControlElement(el, button); 432 this._vcardClearBtn = button; 433 434 // Signature Name 435 var nameEl = document.getElementById(this._htmlElId + "_SIG_NAME"); 436 var params = { 437 parent: this, 438 type: DwtInputField.STRING, 439 required: false, 440 validationStyle: DwtInputField.CONTINUAL_VALIDATION, 441 }; 442 var input = this._sigName = new DwtInputField(params); 443 input.setValidationCallback(this._updateName.bind(this)); 444 this._replaceControlElement(nameEl, input); 445 446 // Signature FORMAT 447 var formatEl = document.getElementById(this._htmlElId + "_SIG_FORMAT"); 448 if (formatEl && appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED)) { 449 var select = new DwtSelect(this); 450 select.setToolTipContent(ZmMsg.formatTooltip); 451 select.addOption(ZmMsg.formatAsText, 1 , true); 452 select.addOption(ZmMsg.formatAsHtml, 0, false); 453 select.addChangeListener(this._handleFormatSelect.bind(this)); 454 this._replaceControlElement(formatEl, select); 455 this._sigFormat = select; 456 } 457 458 // Signature CONTENT - editor added by ZmPref.regenerateSignatureEditor 459 460 // Signature use by identity 461 var collection = appCtxt.getIdentityCollection(); 462 if (collection) { 463 collection.addChangeListener(this._identityChangeListener.bind(this)); 464 } 465 466 this._initialized = true; 467 }; 468 469 // generate usage selects based on identity data 470 ZmSignaturesPage.prototype._resetUsageSelects = 471 function(addSigs) { 472 473 this._clearUsageSelects(); 474 475 var table = document.getElementById(this._htmlElId + "_SIG_TABLE"); 476 this._sigSelect = {}; 477 var signatures; 478 if (addSigs) { 479 signatures = appCtxt.getSignatureCollection().getSignatures(true); 480 } 481 var ic = appCtxt.getIdentityCollection(); 482 var identities = ic && ic.getIdentities(true); 483 if (identities && identities.length) { 484 for (var i = 0, len = identities.length; i < len; i++) { 485 this._addUsageSelects(identities[i], table, signatures); 486 } 487 } 488 }; 489 490 ZmSignaturesPage.prototype._clearUsageSelects = 491 function() { 492 493 var table = document.getElementById(this._htmlElId + "_SIG_TABLE"); 494 while (table.rows.length > 1) { 495 table.deleteRow(-1); 496 } 497 for (var id in this._sigSelect) { 498 for (var field in this._sigSelect[id]) { 499 var select = this._sigSelect[id][field]; 500 if (select) { 501 select.dispose(); 502 } 503 } 504 } 505 }; 506 507 ZmSignaturesPage.prototype._addUsageSelects = 508 function(identity, table, signatures, index) { 509 510 table = table || document.getElementById(this._htmlElId + "_SIG_TABLE"); 511 index = (index != null) ? index : -1; 512 var row = table.insertRow(index); 513 row.id = identity.id + "_row"; 514 var name = identity.getField(ZmIdentity.NAME); 515 if (name === ZmIdentity.DEFAULT_NAME) { 516 name = ZmMsg.accountDefault; 517 } 518 var cell = row.insertCell(-1); 519 cell.className = "ZOptionsLabel"; 520 var id = identity.id + "_name"; 521 cell.innerHTML = "<span id='" + id + "'>" + AjxStringUtil.htmlEncode(name) + ":</span>"; 522 523 this._sigSelect[identity.id] = {}; 524 for (var i = 0; i < ZmSignaturesPage.SIG_FIELDS.length; i++) { 525 this._addUsageSelect(row, identity, signatures, ZmSignaturesPage.SIG_FIELDS[i]); 526 } 527 }; 528 529 ZmSignaturesPage.prototype._addUsageSelect = 530 function(row, identity, signatures, sigType) { 531 532 var select = this._sigSelect[identity.id][sigType] = new DwtSelect(this); 533 var curSigId = identity.getField(sigType); 534 var noSigId = (sigType === ZmIdentity.REPLY_SIGNATURE) ? ZmIdentity.SIG_ID_NONE : ""; 535 this._addUsageSelectOption(select, {name:ZmMsg.noSignature, id:noSigId}, sigType, identity); 536 if (signatures) { 537 for (var i = 0, len = signatures.length; i < len; i++) { 538 this._addUsageSelectOption(select, signatures[i], sigType, identity); 539 } 540 } 541 var cell = row.insertCell(-1); 542 select.reparentHtmlElement(cell); 543 }; 544 545 // Bug 86217, Don't apply any default to the Reply Signature if it is not set. 546 ZmSignaturesPage.prototype._addUsageSelectOption = 547 function(select, signature, sigType, identity) { 548 549 var curSigId = identity.getField(sigType); 550 DBG.println(AjxDebug.DBG3, "Adding " + sigType + " option for " + identity.name + ": " + signature.name + " / " + signature.id + " (" + (curSigId === signature.id) + ")"); 551 // a new signature starts with an empty name; use a space so that option gets added 552 select.addOption(signature.name || ' ', (curSigId === signature.id), signature.id); 553 }; 554 555 // handles addition, removal, or rename of a signature within the form 556 ZmSignaturesPage.prototype._updateUsageSelects = 557 function(signature, action) { 558 559 if (!this._initialized) { 560 return; 561 } 562 563 for (var id in this._sigSelect) { 564 for (var sigType in this._sigSelect[id]) { 565 var select = this._sigSelect[id][sigType]; 566 if (select) { 567 var hasOption = !!(select.getOptionWithValue(signature.id)); 568 var collection = appCtxt.getIdentityCollection(); 569 var identity = collection && collection.getById(id); 570 if (action === ZmEvent.E_CREATE && !hasOption && identity) { 571 this._addUsageSelectOption(select, signature, sigType, identity); 572 } 573 else if (action === ZmEvent.E_DELETE && hasOption && identity) { 574 select.removeOptionWithValue(signature.id); 575 var curSigId = identity.getField(sigType); 576 if (curSigId === signature.id) { 577 var noSigId = (sigType === ZmIdentity.REPLY_SIGNATURE) ? ZmIdentity.SIG_ID_NONE : ""; 578 select.setSelectedValue(noSigId); 579 } 580 } 581 else if (action === ZmEvent.E_MODIFY && hasOption) { 582 select.rename(signature.id, signature.name); 583 } 584 } 585 } 586 } 587 }; 588 589 ZmSignaturesPage.prototype._identityChangeListener = 590 function(ev) { 591 592 var identity = ev.getDetail("item"); 593 if (!identity) { 594 return; 595 } 596 597 var collection = appCtxt.getIdentityCollection(); 598 var id = identity.id; 599 var signatures = appCtxt.getSignatureCollection().getSignatures(true); 600 var table = document.getElementById(this._htmlElId + "_SIG_TABLE"); 601 if (ev.event === ZmEvent.E_CREATE) { 602 var index = collection.getSortIndex(identity); 603 this._addUsageSelects(identity, table, signatures, index); 604 for (var i = 0; i < ZmSignaturesPage.SIG_FIELDS.length; i++) { 605 var field = ZmSignaturesPage.SIG_FIELDS[i]; 606 var select = this._sigSelect[id] && this._sigSelect[id][field]; 607 if (select) { 608 this._origUsage[id] = this._origUsage[id] || {}; 609 this._origUsage[id][field] = select.getValue(); 610 } 611 } 612 } 613 else if (ev.event === ZmEvent.E_DELETE) { 614 var row = document.getElementById(id + "_row"); 615 if (row) { 616 table.deleteRow(row.rowIndex); 617 } 618 delete this._origUsage[id]; 619 } 620 else if (ev.event === ZmEvent.E_MODIFY) { 621 var row = document.getElementById(id + "_row"); 622 if (row) { 623 var index = collection.getSortIndex(identity); 624 if (index === row.rowIndex - 1) { // header row doesn't count 625 var span = document.getElementById(id + "_name"); 626 if (span) { 627 span.innerHTML = identity.name + ":"; 628 } 629 } 630 else { 631 table.deleteRow(row.rowIndex); 632 this._addUsageSelects(identity, table, signatures, index); 633 } 634 } 635 } 636 }; 637 638 // Sets the height of the editor and the list 639 ZmSignaturesPage.prototype._resetSize = 640 function() { 641 if (!this._sigEditor || !this._sigList) { 642 return; 643 } 644 645 // resize editor and list to fit appropriately -- in order to get this 646 // right, we size them to a minimum, and then apply the sizes of their 647 // containing table cells 648 AjxUtil.foreach([this._sigEditor, this._sigList], function(ctrl) { 649 ctrl.setSize(0, 0); 650 651 var bounds = Dwt.getInsetBounds(ctrl.getHtmlElement().parentNode); 652 ctrl.setSize(bounds.width, bounds.height); 653 }); 654 }; 655 656 ZmSignaturesPage.prototype._setupCustom = 657 function(id, setup, value) { 658 if (id === ZmSetting.SIGNATURES) { 659 // create container control 660 var container = new DwtComposite(this); 661 this.setFormObject(id, container); 662 663 // create radio group for defaults 664 this._defaultRadioGroup = new DwtRadioButtonGroup(); 665 666 this._initialize(container); 667 668 return container; 669 } 670 671 return ZmPreferencesPage.prototype._setupCustom.apply(this, arguments); 672 }; 673 674 ZmSignaturesPage.prototype._selectionListener = 675 function(ev) { 676 677 this._updateSignature(); 678 679 var signature = this._sigList.getSelection()[0]; 680 if (signature) { 681 this._resetSignature(this._signatures[signature.id]); 682 } 683 this._resetOperations(); 684 }; 685 686 ZmSignaturesPage.prototype._insertImagesListener = 687 function(ev) { 688 AjxDispatcher.require("BriefcaseCore"); 689 appCtxt.getApp(ZmApp.BRIEFCASE)._createDeferredFolders(); 690 var callback = this._sigEditor._imageUploaded.bind(this._sigEditor); 691 var cFolder = appCtxt.getById(ZmOrganizer.ID_BRIEFCASE); 692 var dialog = appCtxt.getUploadDialog(); 693 dialog.popup(null, cFolder, callback, ZmMsg.uploadImage, null, true); 694 }; 695 696 // Updates name and format of selected sig based on form fields 697 ZmSignaturesPage.prototype._updateSignature = 698 function(select) { 699 700 if (!this._selSignature) { 701 return; 702 } 703 704 var sig = this._selSignature; 705 var newName = AjxStringUtil.trim(this._sigName.getValue()); 706 var isNameModified = (newName !== sig.name); 707 708 sig.name = newName; 709 710 var isText = this._sigFormat ? this._sigFormat.getValue() : true; 711 sig.setContentType(isText ? ZmMimeTable.TEXT_PLAIN : ZmMimeTable.TEXT_HTML); 712 713 sig.value = this._sigEditor.getContent(false, true); 714 715 if (isNameModified) { 716 this._sigList.redrawItem(sig); 717 this._updateUsageSelects(sig, ZmEvent.E_MODIFY); 718 } 719 }; 720 721 ZmSignaturesPage.prototype._populateSignatures = 722 function(reset) { 723 724 this._signatures = {}; 725 this._deletedSignatures = {}; 726 this._origUsage = {}; 727 this._isTempId = {}; 728 this._newSigId = {}; 729 this._byName = {}; 730 731 this._selSignature = null; 732 this._sigList.removeAll(true); 733 this._sigList._resetList(); 734 this._resetUsageSelects(); // signature options will be added via _addSignature 735 736 var signatures = appCtxt.getSignatureCollection().getSignatures(true); 737 var count = Math.min(signatures.length, this._maxEntries); 738 for (var i = 0; i < count; i++) { 739 var signature = signatures[i]; 740 this._addSignature(signature, true, reset); 741 } 742 for (var i = count; i < this._minEntries; i++) { 743 this._addNewSignature(true, true); // autoAdded 744 } 745 746 var selectSig = this._sigList.getList().get(0); 747 this._sigList.setSelection(selectSig); 748 749 this._origUsage = this._getUsageFromIdentities(); 750 }; 751 752 ZmSignaturesPage.prototype._getNewSignature = 753 function() { 754 var signature = new ZmSignature(null); 755 signature.id = Dwt.getNextId(); 756 signature.name = ''; 757 signature._new = true; 758 this._isTempId[signature.id] = true; 759 return signature; 760 }; 761 762 ZmSignaturesPage.prototype._addNewSignature = 763 function(skipControls, autoAdded) { 764 // add new signature 765 var signature = this._getNewSignature(); 766 var sigEditor = this._sigEditor; 767 if (sigEditor) { 768 sigEditor.setContent(''); 769 } 770 signature._autoAdded = autoAdded; 771 signature = this._addSignature(signature, skipControls); 772 setTimeout(this._sigName.focus.bind(this._sigName), 100); 773 774 return signature; 775 }; 776 777 ZmSignaturesPage.prototype._addSignature = 778 function(signature, skipControls, reset, index, skipNotify) { 779 780 if (!signature._new) { 781 if (reset) { 782 this._restoreFromOrig(signature); 783 } else if (!signature._orig) { 784 this._setOrig(signature); 785 } 786 } 787 788 this._signatures[signature.id] = signature; 789 790 if (this._sigList.getItemIndex(signature) === null) { 791 this._sigList.addItem(signature, index); 792 if (!skipNotify) { 793 this._updateUsageSelects(signature, ZmEvent.E_CREATE); 794 } 795 } 796 797 if (!skipControls) { 798 this._resetSignature(signature); // initialize state 799 } 800 801 this._resetOperations(); 802 if (signature.name) { 803 this._byName[signature.name] = signature; 804 } 805 806 return signature; 807 }; 808 809 ZmSignaturesPage.prototype._fixSignatureInlineImages_onTimer = 810 function(msg) { 811 // first time the editor is initialized, idoc.getElementsByTagName("img") is empty 812 // Instead of waiting for 500ms, trying to add this callback. Risky but works. 813 if (!this._firstTimeFixImages) { 814 this._sigEditor.addOnContentInitializedListener(this._fixSignatureInlineImages.bind(this)); 815 } 816 else { 817 this._fixSignatureInlineImages(); 818 } 819 }; 820 821 ZmSignaturesPage.prototype._fixSignatureInlineImages = 822 function() { 823 var idoc = this._sigEditor.getIframeDoc(); 824 if (idoc) { 825 if (!this._firstTimeFixImages) { 826 this._firstTimeFixImages = true; 827 this._sigEditor.clearOnContentInitializedListeners(); 828 } 829 830 var images = idoc.getElementsByTagName("img"); 831 var path = appCtxt.get(ZmSetting.REST_URL) + ZmFolder.SEP; 832 var img; 833 for (var i = 0; i < images.length; i++) { 834 img = images[i]; 835 var dfsrc = img.getAttribute("dfsrc"); 836 if (dfsrc && dfsrc.indexOf("doc:") === 0) { 837 var url = [path, dfsrc.substring(4)].join(''); 838 img.src = AjxStringUtil.fixCrossDomainReference(url, false, true); 839 } 840 } 841 } 842 }; 843 844 ZmSignaturesPage.prototype._restoreSignatureInlineImages = 845 function() { 846 var idoc = this._sigEditor.getIframeDoc(); 847 if (idoc) { 848 var images = idoc.getElementsByTagName("img"); 849 var img; 850 for (var i = 0; i < images.length; i++) { 851 img = images[i]; 852 var dfsrc = img.getAttribute("dfsrc"); 853 if (dfsrc && dfsrc.substring(0, 4) === "doc:") { 854 img.removeAttribute("src"); 855 } 856 } 857 } 858 }; 859 860 ZmSignaturesPage.prototype._resetSignature = 861 function(signature, clear) { 862 this._selSignature = signature; 863 if (!signature) { 864 return; 865 } 866 867 this._sigList.setSelection(signature, true); 868 this._sigName.setValue(signature.name, true); 869 if (this._sigFormat) { 870 this._sigFormat.setSelectedValue(signature.getContentType() === ZmMimeTable.TEXT_PLAIN); 871 } 872 var vcardName = ""; 873 if (signature.contactId) { 874 var contactsApp = appCtxt.getApp(ZmApp.CONTACTS); 875 var contact = contactsApp && contactsApp.getContactList().getById(signature.contactId); 876 if (contact) { 877 vcardName = contact.getFileAs() || contact.getFileAsNoName(); 878 } 879 } 880 this._vcardField.value = vcardName; 881 882 this._sigEditor.clear(); 883 var editorMode = (appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED) && signature.getContentType() === ZmMimeTable.TEXT_HTML) 884 ? Dwt.HTML : Dwt.TEXT; 885 var htmlModeInited = this._sigEditor.isHtmlModeInited(); 886 if (editorMode !== this._sigEditor.getMode()) { 887 this._sigEditor.setMode(editorMode); 888 this._resetSize(); 889 } 890 this._sigEditor.setContent(signature.getValue(editorMode === Dwt.HTML ? ZmMimeTable.TEXT_HTML : ZmMimeTable.TEXT_PLAIN)); 891 if (editorMode === Dwt.HTML) { 892 this._fixSignatureInlineImages_onTimer(); 893 } 894 }; 895 896 ZmSignaturesPage.prototype._resetOperations = 897 function() { 898 if (this._sigAddBtn) { 899 var hasEmptyNewSig = false; 900 for (var id in this._signatures) { 901 var signature = this._signatures[id]; 902 if (signature._new && !signature.name) { 903 hasEmptyNewSig = true; 904 break; 905 } 906 } 907 this._sigAddBtn.setEnabled(!hasEmptyNewSig && this._sigList.size() < this._maxEntries); 908 } 909 }; 910 911 ZmSignaturesPage.prototype._setFormat = 912 function(isText) { 913 this._sigEditor.setMode(isText ? Dwt.TEXT : Dwt.HTML, true); 914 this._selSignature.setContentType(isText ? ZmMimeTable.TEXT_PLAIN : ZmMimeTable.TEXT_HTML); 915 this._resetSize(); 916 }; 917 918 ZmSignaturesPage.prototype._formatOkCallback = 919 function(isText) { 920 this._formatWarningDialog.popdown(); 921 this._setFormat(isText); 922 }; 923 924 ZmSignaturesPage.prototype._formatCancelCallback = 925 function(isText) { 926 this._formatWarningDialog.popdown(); 927 // reset the option 928 this._sigFormat.setSelectedValue(!isText); 929 }; 930 931 932 // buttons 933 ZmSignaturesPage.prototype._handleFormatSelect = 934 function(ev) { 935 var isText = this._sigFormat ? this._sigFormat.getValue() : true; 936 var currentIsText = this._sigEditor.getMode() === Dwt.TEXT; 937 if (isText === currentIsText) { 938 return; 939 } 940 941 var content = this._sigEditor.getContent(); 942 var contentIsEmpty = (content === "<html><body><br></body></html>" || content === ""); 943 944 if (!contentIsEmpty && isText) { 945 if (!this._formatWarningDialog) { 946 this._formatWarningDialog = new DwtMessageDialog({parent : appCtxt.getShell(), buttons : [DwtDialog.OK_BUTTON, DwtDialog.CANCEL_BUTTON]}); 947 } 948 var dialog = this._formatWarningDialog; 949 dialog.registerCallback(DwtDialog.OK_BUTTON, this._formatOkCallback, this, [isText]); 950 dialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._formatCancelCallback, this, [isText]); 951 dialog.setMessage(ZmMsg.switchToText, DwtMessageDialog.WARNING_STYLE); 952 dialog.popup(); 953 return; 954 } 955 this._setFormat(isText); 956 957 }; 958 959 ZmSignaturesPage.prototype._handleAddButton = 960 function(ev) { 961 this._updateSignature(); 962 this._addNewSignature(); 963 }; 964 965 ZmSignaturesPage.prototype._deleteSignature = 966 function(signature, skipNotify) { 967 signature = signature || this._selSignature; 968 if (this._selSignature && !skipNotify) { 969 this._sigName.clear(); 970 } 971 this._sigList.removeItem(signature); 972 if (!skipNotify) { 973 this._updateUsageSelects(signature, ZmEvent.E_DELETE); 974 } 975 delete this._signatures[signature.id]; 976 if (!signature._new) { 977 this._deletedSignatures[signature.id] = signature; 978 } 979 delete this._byName[signature.name]; 980 }; 981 982 ZmSignaturesPage.prototype._handleDeleteButton = 983 function(evt) { 984 var sigEditor = this._sigEditor; 985 this._deleteSignature(); 986 this._selSignature = null; 987 988 if (sigEditor) { 989 sigEditor.setContent(''); 990 } 991 if (this._sigList.size() > 0) { 992 var sel = this._sigList.getList().get(0); 993 if (sel) { 994 this._sigList.setSelection(sel); 995 } 996 } 997 else { 998 for (var i = 0; i < this._minEntries; i++) { 999 this._addNewSignature(false, true); //autoAdded 1000 } 1001 } 1002 this._resetOperations(); 1003 }; 1004 1005 // saving 1006 1007 ZmSignaturesPage.prototype._handleDeleteResponse = 1008 function(signature, resp) { 1009 delete this._deletedSignatures[signature.id]; 1010 }; 1011 1012 ZmSignaturesPage.prototype._handleModifyResponse = 1013 function(signature, resp) { 1014 delete this._byName[signature._orig.name]; 1015 this._byName[signature.name] = signature; 1016 this._setOrig(signature); 1017 }; 1018 1019 ZmSignaturesPage.prototype._handleModifyError = 1020 function(signature) { 1021 this._restoreFromOrig(signature); 1022 if (this._selSignature.id === signature.id) { 1023 this._resetSignature(signature); 1024 } 1025 return true; 1026 }; 1027 1028 ZmSignaturesPage.prototype._handleNewResponse = 1029 function(signature, resp) { 1030 var id = signature.id; 1031 signature.id = signature._id; 1032 1033 // delete and add so that ID of row in list view is updated 1034 var index = this._sigList.getItemIndex(signature); 1035 this._deleteSignature(signature, true); 1036 signature.id = id; 1037 this._addSignature(signature, false, false, index, true); 1038 1039 this._newSigId[signature._id] = signature.id; 1040 delete signature._new; 1041 1042 this._setOrig(signature); 1043 }; 1044 1045 ZmSignaturesPage.prototype._handleVcardBrowseButton = 1046 function(ev) { 1047 1048 var query; 1049 if (!this._vcardPicker) { 1050 AjxDispatcher.require(["ContactsCore", "Contacts"]); 1051 this._vcardPicker = new ZmVcardPicker({sigPage:this}); 1052 var user = appCtxt.getUsername(); 1053 query = user.substr(0, user.indexOf('@')); 1054 } 1055 this._vcardPicker.popup(query); 1056 }; 1057 1058 ZmSignaturesPage.prototype._handleVcardClearButton = 1059 function(ev) { 1060 this._vcardField.value = ""; 1061 if (this._selSignature) { 1062 this._selSignature.contactId = null; 1063 } 1064 }; 1065 1066 // validation 1067 1068 ZmSignaturesPage.prototype._updateName = 1069 function(field, isValid) { 1070 1071 var signature = this._selSignature; 1072 if (!signature) { 1073 return; 1074 } 1075 1076 if (signature.name !== field.getValue()) { 1077 signature.name = field.getValue(); 1078 this._sigList.redrawItem(signature); 1079 this._sigList.setSelection(signature, true); 1080 this._resetOperations(); 1081 this._updateUsageSelects(signature, ZmEvent.E_MODIFY); 1082 } 1083 }; 1084 1085 ZmSignaturesPage.prototype._hasChanged = 1086 function(signature) { 1087 1088 var o = signature._orig; 1089 return (o.name !== signature.name || 1090 o.contactId !== signature.contactId || 1091 o.contentType !== signature.getContentType() || 1092 !AjxStringUtil.equalsHtmlPlatformIndependent(AjxStringUtil.htmlEncode(o.value), AjxStringUtil.htmlEncode(signature.getValue()))); 1093 }; 1094 1095 ZmSignaturesPage.prototype._setOrig = 1096 function(signature) { 1097 signature._orig = { 1098 name: signature.name, 1099 contactId: signature.contactId, 1100 value: signature.getValue(), 1101 contentType: signature.getContentType() 1102 }; 1103 }; 1104 1105 ZmSignaturesPage.prototype._restoreFromOrig = 1106 function(signature) { 1107 var o = signature._orig; 1108 signature.name = o.name; 1109 signature.contactId = o.contactId; 1110 signature.value = o.value; 1111 signature.setContentType(o.contentType); 1112 }; 1113 1114 // 1115 // Classes 1116 // 1117 1118 //ZmSignatureListView: Signatures List 1119 1120 ZmSignatureListView = function(parent) { 1121 if (arguments.length === 0) { return; } 1122 1123 DwtListView.call(this, {parent:parent, className:"ZmSignatureListView"}); 1124 }; 1125 1126 ZmSignatureListView.prototype = new DwtListView; 1127 ZmSignatureListView.prototype.constructor = ZmSignatureListView; 1128 1129 ZmSignatureListView.prototype.toString = 1130 function() { 1131 return "ZmSignatureListView"; 1132 }; 1133 1134 ZmSignatureListView.prototype._getCellContents = 1135 function(html, idx, signature, field, colIdx, params) { 1136 html[idx++] = signature.name ? AjxStringUtil.htmlEncode(signature.name, true) : ZmMsg.signatureNameHint; 1137 return idx; 1138 }; 1139 1140 ZmSignatureListView.prototype._getItemId = 1141 function(signature) { 1142 return (signature && signature.id) ? signature.id : Dwt.getNextId(); 1143 }; 1144 1145 ZmSignatureListView.prototype.setSignatures = 1146 function(signatures) { 1147 this._resetList(); 1148 this.addItems(signatures); 1149 var list = this.getList(); 1150 if (list && list.size() > 0) { 1151 this.setSelection(list.get(0)); 1152 } 1153 }; 1154 1155 1156 // ZmVcardPicker 1157 1158 ZmVcardPicker = function(params) { 1159 1160 params = params || {}; 1161 params.parent = appCtxt.getShell(); 1162 params.title = ZmMsg.selectContact; 1163 DwtDialog.call(this, params); 1164 1165 this._sigPage = params.sigPage; 1166 this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._okButtonListener)); 1167 }; 1168 1169 ZmVcardPicker.prototype = new DwtDialog; 1170 ZmVcardPicker.prototype.constructor = ZmVcardPicker; 1171 1172 ZmVcardPicker.prototype.popup = 1173 function(query, account) { 1174 1175 if (!this._initialized) { 1176 this._initialize(); 1177 } 1178 this._contactSearch.reset(query, account); 1179 if (query) { 1180 this._contactSearch.search(); 1181 } 1182 1183 DwtDialog.prototype.popup.call(this); 1184 }; 1185 1186 ZmVcardPicker.prototype._initialize = 1187 function(account) { 1188 1189 var options = {preamble: ZmMsg.vcardContactSearch}; 1190 this._contactSearch = new ZmContactSearch({options:options}); 1191 this.setView(this._contactSearch); 1192 this._initialized = true; 1193 }; 1194 1195 ZmVcardPicker.prototype._okButtonListener = 1196 function(ev) { 1197 1198 var data = this._contactSearch.getContacts(); 1199 var contact = data && data[0]; 1200 if (contact) { 1201 this._sigPage.setContact(contact); 1202 } 1203 1204 this.popdown(); 1205 }; 1206