1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * This file defines the tag class.
 27  */
 28 
 29 /**
 30  * Creates a tag
 31  * @class
 32  * This class represents a tag.
 33  * 
 34  * @param	{Hash}	params		a hash of parameters
 35  * @extends	ZmOrganizer
 36  */
 37 ZmTag = function(params) {
 38 	params.type = ZmOrganizer.TAG;
 39 	ZmOrganizer.call(this, params);
 40 	this.notLocal = params.notLocal;
 41 };
 42 
 43 ZmTag.prototype = new ZmOrganizer;
 44 ZmTag.prototype.constructor = ZmTag;
 45 ZmTag.prototype.isZmTag = true;
 46 
 47 /**
 48  * Returns a string representation of the object.
 49  * 
 50  * @return		{String}		a string representation of the object
 51  */
 52 ZmTag.prototype.toString = 
 53 function() {
 54 	return "ZmTag";
 55 };
 56 
 57 // color icons
 58 ZmTag.COLOR_ICON = new Object();
 59 ZmTag.COLOR_ICON[ZmOrganizer.C_ORANGE]	= "TagOrange";
 60 ZmTag.COLOR_ICON[ZmOrganizer.C_BLUE]	= "TagBlue";
 61 ZmTag.COLOR_ICON[ZmOrganizer.C_CYAN]	= "TagCyan";
 62 ZmTag.COLOR_ICON[ZmOrganizer.C_GREEN]	= "TagGreen";
 63 ZmTag.COLOR_ICON[ZmOrganizer.C_PURPLE]	= "TagPurple";
 64 ZmTag.COLOR_ICON[ZmOrganizer.C_RED]		= "TagRed";
 65 ZmTag.COLOR_ICON[ZmOrganizer.C_YELLOW]	= "TagYellow";
 66 
 67 
 68 // system tags
 69 ZmTag.ID_ROOT = ZmOrganizer.ID_ROOT;
 70 ZmTag.ID_UNREAD		= 32;
 71 ZmTag.ID_FLAGGED	= 33;
 72 ZmTag.ID_FROM_ME	= 34;
 73 ZmTag.ID_REPLIED	= 35;
 74 ZmTag.ID_FORWARDED	= 36;
 75 ZmTag.ID_ATTACHED	= 37;
 76 
 77 /**
 78  * Tags come from back end as a flat list, and we manually create a root tag, so all tags
 79  * have the root as parent. If tags ever have a tree structure, then this should do what
 80  * ZmFolder does (recursively create children).
 81  * 
 82  * @private
 83  */
 84 ZmTag.createFromJs =
 85 function(parent, obj, tree, sorted, account) {
 86 	var tag;
 87 	var nId = ZmOrganizer.normalizeId(obj.id);
 88 	if (nId < ZmOrganizer.FIRST_USER_ID[ZmOrganizer.TAG]) { return; }
 89 	tag = tree.getById(obj.id);
 90 	if (tag) { return tag; }
 91 
 92 	var params = {
 93 		id: obj.id,
 94 		name: obj.name,
 95 		color: ZmTag.checkColor(obj.color),
 96 		rgb: obj.rgb,
 97 		parent: parent,
 98 		tree: tree,
 99 		numUnread: obj.u,
100 		account: account
101 	};
102 	tag = new ZmTag(params);
103 	var index = sorted ? ZmOrganizer.getSortIndex(tag, ZmTag.sortCompare) : null;
104 	parent.children.add(tag, index);
105 
106 	var tagNameMap = parent.getTagNameMap();
107 	tagNameMap[obj.name] = tag;
108 
109 	return tag;
110 };
111 
112 ZmTag.createNotLocalTag =
113 function(name) {
114 	//cache so we don't create many objects in case many items are tagged by non local tags.
115 	var cache = ZmTag.notLocalCache = ZmTag.notLocalCache || [];
116 	var tag = cache[name];
117 	if (tag) {
118 		return tag;
119 	}
120 	tag = new ZmTag({notLocal: true, id: "notLocal_" + name, name: name});
121 	cache[name] = tag;
122 	return tag;
123 };
124 
125 /**
126  * Compares the tags by name.
127  * 
128  * @param	{ZmTag}	tagA		the first tag
129  * @param	{ZmTag}	tagB		the second tag
130  * @return	{int}	0 if the tag names match (case-insensitive); 1 if "a" is before "b"; -1 if "b" is before "a"
131  */
132 ZmTag.sortCompare = 
133 function(tagA, tagB) {
134 	var check = ZmOrganizer.checkSortArgs(tagA, tagB);
135 	if (check != null) return check;
136 
137 	if (tagA.name.toLowerCase() > tagB.name.toLowerCase()) return 1;
138 	if (tagA.name.toLowerCase() < tagB.name.toLowerCase()) return -1;
139 	return 0;
140 };
141 
142 /**
143  * Checks the tag name.
144  * 
145  * @param	{String}	name		the name
146  * @return	{String}	<code>null</code> if the name is valid or a error message
147  */
148 ZmTag.checkName =
149 function(name) {
150 	var msg = ZmOrganizer.checkName(name);
151 	if (msg) { return msg; }
152 
153 	if (name.indexOf('\\') == 0) {
154 		return AjxMessageFormat.format(ZmMsg.errorInvalidName, AjxStringUtil.htmlEncode(name));
155 	}
156 
157 	return null;
158 };
159 
160 /**
161  * Checks the color.
162  * 
163  * @param	{String}	color	the color
164  * @return	{Number}	the valid color
165  */
166 ZmTag.checkColor =
167 function(color) {
168 	color = Number(color);
169 	return ((color != null) && (color >= 0 && color <= ZmOrganizer.MAX_COLOR)) ? color : ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.TAG];
170 };
171 
172 ZmTag.getIcon = function(color) {
173     var object = { getIcon:ZmTag.prototype.getIcon, getColor:ZmTag.prototype.getColor, color:color };
174     if (String(color).match(/^#/)) {
175         object.rgb = color;
176         object.color = null;
177     }
178     return ZmTag.prototype.getIconWithColor.call(object);
179 }
180 
181 /**
182  * Creates a tag.
183  * 
184  * @param	{Hash}	params	a hash of parameters
185  */
186 ZmTag.create =
187 function(params) {
188 	var request = {_jsns: "urn:zimbraMail"};
189 	var jsonObj = {CreateTagRequest: request};
190 	request.tag = {name: params.name}
191 
192     if (params.rgb) {
193         request.tag.rgb = params.rgb;
194     }
195     else {
196         request.tag.color = ZmOrganizer.checkColor(params.color) || ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.TAG];
197     }
198 	var errorCallback = new AjxCallback(null, ZmTag._handleErrorCreate, params);
199 	appCtxt.getAppController().sendRequest({
200 			jsonObj: jsonObj,
201 			asyncMode: true,
202 			errorCallback: errorCallback,
203 			accountName: params.accountName
204 	});
205 };
206 
207 /**
208  * @private
209  */
210 ZmTag._handleErrorCreate =
211 function(params, ex) {
212 	if (ex.code == ZmCsfeException.MAIL_INVALID_NAME) {
213 		var msg = AjxMessageFormat.format(ZmMsg.errorInvalidName, AjxStringUtil.htmlEncode(params.name));
214 		var msgDialog = appCtxt.getMsgDialog();
215 		msgDialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE);
216 		msgDialog.popup();
217 		return true;
218 	}
219 	return false;
220 };
221 
222 /**
223  * Gets the icon.
224  * 
225  * @return	{String}	the icon or <code>null</code> for no icon
226  */
227 ZmTag.prototype.getIcon = 
228 function() {
229 	if (this.notLocal) {
230 		return "TagShared";
231 	}
232 	
233 	return (this.id == ZmOrganizer.ID_ROOT) ? null : "Tag";
234 };
235 
236 /**
237  * map from tag names to tags. used by getByNameOrRemote
238  */
239 ZmTag.prototype.getTagNameMap =
240 function() {
241 	if (!this.tagNameMap) {
242 		this.tagNameMap = {};
243 	}
244 	return this.tagNameMap;
245 };
246 
247 /**
248  * Creates a query for this tag.
249  * 
250  * @return	{String}	the tag query
251  */
252 ZmTag.prototype.createQuery =
253 function() {
254 	return ['tag:"', this.name, '"'].join("");
255 };
256 
257 /**
258  * Gets the tool tip.
259  * 
260  * @return	{String}	the tool tip
261  */
262 ZmTag.prototype.getToolTip = function() {};
263 
264 /**
265  * @private
266  */
267 ZmTag.prototype.notifyCreate =
268 function(obj) {
269 	var child = ZmTag.createFromJs(this, obj, this.tree, true);
270 	child._notify(ZmEvent.E_CREATE);
271 };
272 
273 
274 ZmTag.prototype.notifyModify =
275 function(obj) {
276 	if (obj.name) {
277 		//this is a rename - update the tagNameMap
278 		var oldName = this.name;
279 		var nameMap = this.parent.getTagNameMap();
280 		delete nameMap[oldName];
281 		nameMap[obj.name] = this;
282 		//we don't change the name on this ZmTag object here, it is done in ZmOrganizer.prototype.notifyModify
283 	}
284 	ZmOrganizer.prototype.notifyModify.call(this, obj);
285 };
286 
287 
288 ZmTag.prototype.notifyDelete =
289 function() {
290 	var nameMap = this.parent.getTagNameMap();
291 	delete nameMap[this.name];  //remove from name map
292 	
293 	ZmOrganizer.prototype.notifyDelete.call(this);
294 };
295 
296 /**
297  * Checks if the tag supports sharing.
298  * 
299  * @return	{Boolean}	always returns <code>false</code>. Tags cannot be shared.
300  */
301 ZmTag.prototype.supportsSharing =
302 function() {
303 	// tags cannot be shared
304 	return false;
305 };
306 
307 ZmTag.prototype.getByNameOrRemote =
308 function(name) {
309 	var tagNameMap = this.getTagNameMap();
310 	var tag = tagNameMap[name];
311 	if (tag) {
312 		return tag;
313 	}
314 	return ZmTag.createNotLocalTag(name);
315 };
316 
317 
318 
319