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