1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 */ 27 28 /** 29 * Creates a folder properties view for the folder dialog 30 * @class 31 * This class represents a folder properties view 32 * 33 * @param {DwtControl} parent the parent (dialog) 34 * @param {String} className the class name 35 * 36 * @extends DwtDialog 37 */ 38 ZmFolderPropertyView = function(dialog, parent) { 39 if (arguments.length == 0) return; 40 ZmFolderDialogTabView.call(this, parent, "ZmFolderPropertyView"); 41 this._dialog = dialog; 42 43 }; 44 45 ZmFolderPropertyView.prototype = new ZmFolderDialogTabView; 46 ZmFolderPropertyView.prototype.constructor = ZmFolderPropertyView; 47 48 49 // Public methods 50 51 ZmFolderPropertyView.prototype.toString = 52 function() { 53 return "ZmFolderPropertyView"; 54 }; 55 56 ZmFolderPropertyView.prototype.getTitle = 57 function() { 58 return ZmMsg.folderTabProperties; 59 } 60 61 ZmFolderPropertyView.prototype.showMe = 62 function() { 63 DwtTabViewPage.prototype.showMe.call(this); 64 if (appCtxt.get(ZmSetting.SHARING_ENABLED)) { 65 this._dialog.setButtonVisible(ZmFolderPropsDialog.ADD_SHARE_BUTTON, true); 66 } 67 68 this.setSize(Dwt.DEFAULT, "100"); 69 if (Dwt.getVisible(this._nameInputEl)) { 70 this._nameInputEl.focus(); 71 } 72 }; 73 74 75 /** doSave will be invoked for each tab view. 76 * 77 * @param {BatchCommand} batchCommand Accumulates updates from all tabs 78 * @param {Object} saveState Accumulates error messages and indication of any update 79 */ 80 ZmFolderPropertyView.prototype.doSave = 81 function(batchCommand, saveState) { 82 if (!this._handleErrorCallback) { 83 this._handleErrorCallback = this._handleError.bind(this); 84 this._handleRenameErrorCallback = this._handleRenameError.bind(this); 85 } 86 87 // rename folder followed by attribute processing 88 var organizer = this._organizer; 89 90 if (!organizer.isSystem() && !organizer.isDataSource()) { 91 var name = this._nameInputEl.value; 92 if (organizer.name != name) { 93 var error = ZmOrganizer.checkName(name); 94 if (error) { 95 saveState.errorMessage.push(error); 96 // Only error checking for now. If additional, should not return here 97 return; 98 } 99 batchCommand.add(new AjxCallback(organizer, organizer.rename, [name, null, this._handleRenameErrorCallback])); 100 saveState.commandCount++; 101 } 102 } 103 104 if (!organizer.isDataSource() && appCtxt.isWebClientOfflineSupported) { 105 var webOfflineSyncDays = $('#folderOfflineLblId').val() || 0; 106 if (organizer.webOfflineSyncDays != webOfflineSyncDays) { 107 var error = ZmOrganizer.checkWebOfflineSyncDays(webOfflineSyncDays); 108 if (error) { 109 saveState.errorMessage.push(error); 110 // Only error checking for now. If additional, should not return here 111 return; 112 } 113 batchCommand.add(new AjxCallback(organizer, organizer.setOfflineSyncInterval, [webOfflineSyncDays, null, this._handleErrorCallback])); 114 saveState.commandCount++; 115 } 116 } 117 118 var color = this._color.getValue() || ZmOrganizer.DEFAULT_COLOR[organizer.type]; 119 if (organizer.isColorChanged(color, organizer.color, organizer.rgb)) { 120 if (ZmOrganizer.getStandardColorNumber(color) === -1) { 121 batchCommand.add(new AjxCallback(organizer, organizer.setRGB, [color, null, this._handleErrorCallback])); 122 } 123 else { 124 batchCommand.add(new AjxCallback(organizer, organizer.setColor, [color, null, this._handleErrorCallback])); 125 } 126 saveState.commandCount++; 127 } 128 129 if (Dwt.getVisible(this._excludeFbEl) && organizer.setFreeBusy) { 130 var excludeFreeBusy = this._excludeFbCheckbox.checked; 131 if (organizer.excludeFreeBusy != excludeFreeBusy) { 132 batchCommand.add(new AjxCallback(organizer, organizer.setFreeBusy, [excludeFreeBusy, null, this._handleErrorCallback])); 133 saveState.commandCount++; 134 } 135 } 136 137 // Mail Folders only 138 if (Dwt.getVisible(this._globalMarkReadEl) && organizer.globalMarkRead) { 139 var globalMarkRead = this._globalMarkReadCheckbox.checked; 140 if (organizer.globalMarkRead != globalMarkRead) { 141 batchCommand.add(new AjxCallback(organizer, organizer.setGlobalMarkRead, [globalMarkRead, null, this._handleErrorCallback])); 142 saveState.commandCount++; 143 } 144 } 145 146 // Saved searches 147 if (Dwt.getVisible(this._queryInputEl) && organizer.type === ZmOrganizer.SEARCH) { 148 var query = this._queryInputEl.value; 149 if (organizer.search.query !== query) { 150 batchCommand.add(new AjxCallback(organizer, organizer.setQuery, [query, null, this._handleErrorCallback])); 151 saveState.commandCount++; 152 } 153 } 154 }; 155 156 ZmFolderPropertyView.prototype._handleFolderChange = 157 function(event) { 158 var organizer = this._organizer; 159 160 var colorCode = 0; 161 if (this._color) { 162 var icon = organizer.getIcon(); 163 this._color.setImage(icon); 164 165 var colorCode = organizer.isColorCustom ? organizer.rgb : organizer.color; 166 167 var defaultColorCode = ZmOrganizer.DEFAULT_COLOR[organizer.type], 168 defaultColor = ZmOrganizer.COLOR_VALUES[defaultColorCode], 169 colorMenu = this._color.getMenu(), 170 moreColorMenu; 171 if (colorMenu) { 172 moreColorMenu = (colorMenu.toString() == "ZmMoreColorMenu") ? colorMenu : colorMenu._getMoreColorMenu(); 173 if (moreColorMenu) { 174 moreColorMenu.setDefaultColor(defaultColor); 175 } 176 } 177 this._color.setValue(colorCode); 178 var folderId = organizer.getSystemEquivalentFolderId() || organizer.id; 179 this._color.setEnabled(folderId != ZmFolder.ID_DRAFTS); 180 var isVisible = (organizer.type != ZmOrganizer.FOLDER || 181 (organizer.type == ZmOrganizer.FOLDER && appCtxt.get(ZmSetting.MAIL_FOLDER_COLORS_ENABLED))); 182 this._props.setPropertyVisible(this._colorId, isVisible); 183 } 184 185 if (organizer.isSystem() || organizer.isDataSource()) { 186 this._nameOutputEl.innerHTML = AjxStringUtil.htmlEncode(organizer.name); 187 Dwt.setVisible(this._nameOutputEl, true); 188 Dwt.setVisible(this._nameInputEl, false); 189 } 190 else { 191 this._nameInputEl.value = organizer.name; 192 Dwt.setVisible(this._nameOutputEl, false); 193 Dwt.setVisible(this._nameInputEl, true); 194 } 195 196 var hasFolderInfo = !!organizer.getToolTip(); 197 if (hasFolderInfo) { 198 var unreadProp = this._props.getProperty(this._unreadId), 199 unreadLabel = unreadProp && document.getElementById(unreadProp.labelId); 200 if (unreadLabel) { 201 unreadLabel.innerHTML = AjxMessageFormat.format(ZmMsg.makeLabel, organizer._getUnreadLabel()); 202 } 203 this._unreadEl.innerHTML = organizer.numUnread; 204 var totalProp = this._props.getProperty(this._totalId), 205 totalLabel = totalProp && document.getElementById(totalProp.labelId); 206 if (totalLabel) { 207 totalLabel.innerHTML = AjxMessageFormat.format(ZmMsg.makeLabel, organizer._getItemsText()); 208 } 209 this._totalEl.innerHTML = organizer.numTotal; 210 this._sizeEl.innerHTML = AjxUtil.formatSize(organizer.sizeTotal); 211 } 212 this._props.setPropertyVisible(this._unreadId, hasFolderInfo && organizer.numUnread); 213 this._props.setPropertyVisible(this._totalId, hasFolderInfo); 214 this._props.setPropertyVisible(this._sizeId, hasFolderInfo && organizer.sizeTotal); 215 216 if (organizer.type === ZmOrganizer.SEARCH) { 217 this._queryInputEl.value = organizer.search.query; 218 this._props.setPropertyVisible(this._queryId, true); 219 } 220 else { 221 this._props.setPropertyVisible(this._queryId, false); 222 } 223 224 this._ownerEl.innerHTML = AjxStringUtil.htmlEncode(organizer.owner); 225 this._typeEl.innerHTML = ZmMsg[ZmOrganizer.FOLDER_KEY[organizer.type]] || ZmMsg.folder; 226 227 this._excludeFbCheckbox.checked = organizer.excludeFreeBusy; 228 229 var showPerm = organizer.isMountpoint; 230 if (showPerm) { 231 AjxDispatcher.require("Share"); 232 var share = ZmShare.getShareFromLink(organizer); 233 var role = share && share.link && share.link.role; 234 this._permEl.innerHTML = ZmShare.getRoleActions(role); 235 } 236 237 var url = organizer.url ? AjxStringUtil.htmlEncode(organizer.url).replace(/&/g,'%26') : null; 238 var urlDisplayString = url; 239 if (urlDisplayString) { 240 urlDisplayString = [ '<a target=_new href="',url,'">', AjxStringUtil.clipByLength(urlDisplayString, 50), '</a>' ].join(""); 241 } 242 243 this._urlEl.innerHTML = urlDisplayString || ""; 244 245 this._props.setPropertyVisible(this._ownerId, organizer.owner != null); 246 247 this._props.setPropertyVisible(this._urlId, organizer.url); 248 this._props.setPropertyVisible(this._permId, showPerm); 249 $('#folderOfflineLblId').val(organizer.webOfflineSyncDays || 0) 250 251 Dwt.setVisible(this._excludeFbEl, !organizer.link && (organizer.type == ZmOrganizer.CALENDAR)); 252 // TODO: False until server handling of the flag is added 253 //Dwt.setVisible(this._globalMarkReadEl, organizer.type == ZmOrganizer.FOLDER); 254 Dwt.setVisible(this._globalMarkReadEl, false); 255 256 if (this._offlineId) { 257 var enabled = false; 258 if (!organizer.isMountpoint) { 259 enabled = (this._organizer.type == ZmOrganizer.FOLDER); 260 } 261 this._props.setPropertyVisible(this._offlineId, enabled); 262 } 263 }; 264 265 266 ZmFolderPropertyView.prototype._handleRenameError = 267 function(response) { 268 var value = this._nameInputEl.value; 269 var type = appCtxt.getFolderTree(appCtxt.getActiveAccount()).getFolderTypeByName(value); 270 var msg; 271 var noDetails = false; 272 if (response.code == ZmCsfeException.MAIL_ALREADY_EXISTS) { 273 msg = AjxMessageFormat.format(ZmMsg.errorAlreadyExists, [value,ZmMsg[type.toLowerCase()]]); 274 } else if (response.code == ZmCsfeException.MAIL_IMMUTABLE) { 275 msg = AjxMessageFormat.format(ZmMsg.errorCannotRename, [value]); 276 } else if (response.code == ZmCsfeException.SVC_INVALID_REQUEST) { 277 msg = response.msg; // triggered on an empty name 278 } else if (response.code == ZmCsfeException.MAIL_INVALID_NAME) { 279 //I add this here despite checking upfront using ZmOrganizer.checkName, since there might be more restrictions for different types of organizers. so just in case the server still returns an error in the name. 280 msg = AjxMessageFormat.format(ZmMsg.invalidName, [AjxStringUtil.htmlEncode(value)]); 281 noDetails = true; 282 } 283 appCtxt.getAppController().popupErrorDialog(msg, noDetails ? null : response.msg, null, true); 284 return true; 285 }; 286 287 // TODO: This seems awkward. Should use a template. 288 ZmFolderPropertyView.prototype._createView = function() { 289 290 // create html elements 291 this._nameOutputEl = document.createElement("SPAN"); 292 this._nameInputEl = document.createElement("INPUT"); 293 this._nameInputEl.style.width = "20em"; 294 this._nameInputEl._dialog = this; 295 var nameElement = this._nameInputEl; 296 297 this._queryInputEl = document.createElement("INPUT"); 298 this._queryInputEl.style.width = "20em"; 299 this._queryInputEl._dialog = this; 300 var queryElement = this._queryInputEl; 301 302 this._ownerEl = document.createElement("DIV"); 303 this._typeEl = document.createElement("DIV"); 304 this._urlEl = document.createElement("DIV"); 305 this._permEl = document.createElement("DIV"); 306 307 this._unreadEl = document.createElement("SPAN"); 308 this._totalEl = document.createElement("SPAN"); 309 this._sizeEl = document.createElement("SPAN"); 310 311 var nameEl = document.createElement("DIV"); 312 nameEl.appendChild(this._nameOutputEl); 313 nameEl.appendChild(nameElement); 314 315 var queryEl = document.createElement("DIV"); 316 queryEl.appendChild(queryElement); 317 318 var excludeFbEl = this._createCheckboxItem("excludeFb", ZmMsg.excludeFromFreeBusy); 319 var globalMarkReadEl = this._createCheckboxItem("globalMarkRead", ZmMsg.globalMarkRead); 320 321 this._props = new DwtPropertySheet(this); 322 this._color = new ZmColorButton({parent:this}); 323 324 var namePropId = this._props.addProperty(ZmMsg.nameLabel, nameEl); 325 this._props.addProperty(ZmMsg.typeLabel, this._typeEl); 326 this._queryId = this._props.addProperty(ZmMsg.queryLabel, queryEl); 327 this._ownerId = this._props.addProperty(ZmMsg.ownerLabel, this._ownerEl); 328 this._urlId = this._props.addProperty(ZmMsg.urlLabel, this._urlEl); 329 this._permId = this._props.addProperty(ZmMsg.permissions, this._permEl); 330 this._colorId = this._props.addProperty(ZmMsg.colorLabel, this._color); 331 this._totalId = this._props.addProperty(AjxMessageFormat.format(ZmMsg.makeLabel, ZmMsg.messages), this._totalEl); 332 this._unreadId = this._props.addProperty(AjxMessageFormat.format(ZmMsg.makeLabel, ZmMsg.unread), this._unreadEl); 333 this._sizeId = this._props.addProperty(ZmMsg.sizeLabel, this._sizeEl); 334 335 if (appCtxt.isWebClientOfflineSupported) { 336 this._offlineEl = document.createElement("DIV"); 337 this._offlineEl.style.whiteSpace = "nowrap"; 338 this._offlineEl.innerHTML = ZmMsg.offlineFolderSyncInterval; 339 this._offlineId = this._props.addProperty(ZmMsg.offlineLabel, this._offlineEl); 340 } 341 342 var container = this.getHtmlElement(); 343 container.appendChild(this._props.getHtmlElement()); 344 container.appendChild(excludeFbEl); 345 container.appendChild(globalMarkReadEl); 346 this._contentEl = container; 347 348 this._tabGroup.addMember(this._props.getTabGroupMember()); 349 }; 350