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