1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2007, 2008, 2009, 2010, 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) 2007, 2008, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Lite Html Editor
 26  *
 27  * It uses an text area as an editor where text is plain or all with the same style.
 28  *
 29  * @author Rajesh Segu
 30  * @class
 31  * @constructor
 32  */
 33 ZmLiteHtmlEditor = function(params) {
 34 	if (arguments.length == 0) return;
 35 
 36 	params.className = params.className || "ZmLiteHtmlEditor";
 37 	DwtComposite.call(this, params);
 38 
 39 	this._mode = params.mode || ZmLiteHtmlEditor.TEXT;
 40 	this._initialize();
 41 
 42 
 43 };
 44 
 45 ZmLiteHtmlEditor.prototype = new DwtComposite();
 46 ZmLiteHtmlEditor.prototype.constructor = ZmLiteHtmlEditor;
 47 
 48 //Constants
 49 
 50 ZmLiteHtmlEditor.HTML = 1;
 51 ZmLiteHtmlEditor.TEXT = 2;
 52 
 53 ZmLiteHtmlEditor._VALUE = "value";
 54 
 55 // Font Styles
 56 ZmLiteHtmlEditor.BOLD_STYLE = "bold";
 57 ZmLiteHtmlEditor.ITALIC_STYLE = "italic";
 58 ZmLiteHtmlEditor.UNDERLINE_STYLE = "underline";
 59 
 60 ZmLiteHtmlEditor.FONT_SIZE_STYLE = "fontsize";
 61 ZmLiteHtmlEditor.FONT_FAMILY_STYLE = "fontfamily";
 62 ZmLiteHtmlEditor.FONT_SIZE_VALUES = ["8pt", "10pt", "12pt", "14pt", "18pt", "24pt", "36pt"];
 63 
 64 ZmLiteHtmlEditor.FONT_COLOR = "fontcolor";
 65 
 66 ZmLiteHtmlEditor.FONT_SIZE_VALUES = ["8pt", "10pt", "12pt", "14pt", "18pt", "24pt", "36pt"];
 67 ZmLiteHtmlEditor.FONT_FAMILY = [
 68         {name:"Tahoma",           value:"Tahoma, Verdana, Arial, Helvetica, sans-serif" },
 69         {name:"Arial",            value:"Arial, Helvetica, sans-serif" },
 70         {name:"Times New Roman",  value:"Times New Roman, Times, serif" },
 71         {name:"Courier",          value:"Courier, Courier New, mono" },
 72         {name:"Verdana",          value:"Verdana, Tahoma, Arial, Helvetica, sans-serif" }
 73 ];
 74 
 75 ZmLiteHtmlEditor.STYLE = {};
 76 ZmLiteHtmlEditor.STYLE[ZmLiteHtmlEditor.BOLD_STYLE] = "fontWeight";
 77 ZmLiteHtmlEditor.STYLE[ZmLiteHtmlEditor.ITALIC_STYLE] = "fontStyle";
 78 ZmLiteHtmlEditor.STYLE[ZmLiteHtmlEditor.UNDERLINE_STYLE] = "textDecoration";
 79 ZmLiteHtmlEditor.STYLE[ZmLiteHtmlEditor.FONT_SIZE_STYLE] = "fontSize";
 80 ZmLiteHtmlEditor.STYLE[ZmLiteHtmlEditor.FONT_FAMILY_STYLE] = "fontFamily";
 81 ZmLiteHtmlEditor.STYLE[ZmLiteHtmlEditor.FONT_COLOR] = "color";
 82 
 83 
 84 // Public methods
 85 
 86 ZmLiteHtmlEditor.prototype.toString =
 87 function() {
 88 	return "ZmLiteHtmlEditor";
 89 };
 90 
 91 ZmLiteHtmlEditor.prototype.getEditor =
 92 function(){
 93 	return this._textArea;
 94 };
 95 
 96 ZmLiteHtmlEditor.prototype.getContent =
 97 function(){
 98 	return this._mode == ZmLiteHtmlEditor.HTML
 99 	        ? this.getHtmlContent()
100 	        : this.getTextContent();
101 };
102 
103 ZmLiteHtmlEditor.prototype.getTextContent =
104 function(){
105 	return this._textArea.value || "";
106 };
107 
108 //<B><U><I><font color="#FF9900"><font face="arial"><font size=16>RAJESH SEGU IS HERE</I></U></B>
109 //width: 100%; height: 40px; font-family: Courier,Courier New,mono; font-size: 18pt; color: rgb(0, 0, 0); font-weight: bold; font-style: italic; text-decoration: underline;
110 
111 ZmLiteHtmlEditor.prototype.getHtmlContent =
112 function(tag){
113 
114 	var html = ["<span style='",this.getCSS(),"'>",
115 				AjxStringUtil.htmlEncode(this.getTextContent(), true).replace(/\r?\n/g, "<br/>"),
116 				"</span>"];
117 
118 	return html.join("");
119 };
120 
121 ZmLiteHtmlEditor.prototype.getCSS =
122 function() {
123 	var style = this._textArea.style;
124 	var css = [];
125 
126 	if (style.fontFamily)
127 	        css.push("font-family: " + style.fontFamily);
128 	if (style.fontSize)
129 	        css.push("font-size: " + style.fontSize);
130 	if (style.color)
131 	        css.push("color: " + style.color);
132 	if (style.fontWeight)
133 	        css.push("font-weight: " + style.fontWeight);
134 	if (style.fontStyle)
135 	        css.push("font-style: " + style.fontStyle);
136 	if (style.textDecoration)
137 	        css.push("text-decoration: " + style.textDecoration);
138 	if (style.color)
139 		css.push("color: " + style.color);
140 
141 	return css.join(";");
142 };
143 
144 //Supports only text content
145 ZmLiteHtmlEditor.prototype.setContent =
146 function(content){
147 	this._textArea.value = (content || "");
148 };
149 
150 ZmLiteHtmlEditor.prototype.setSelectionText = function(text) {
151         Dwt.setSelectionText(this._textArea, text);
152 };
153 
154 ZmLiteHtmlEditor.prototype.clear =
155 function() {
156 	this.setContent("");
157 	this._setDefaultStyles();
158 }
159 
160 ZmLiteHtmlEditor.prototype.addModeChangeListener =
161 function(listener){
162 	this.addListener(DwtEvent.STATE_CHANGE, listener);
163 };
164 
165 ZmLiteHtmlEditor.prototype.getMode =
166 function(){
167 	return this._mode;
168 };
169 
170 ZmLiteHtmlEditor.prototype.setMode =
171 function(mode, force) {
172 
173 	if ( (!force && mode == this._mode) ||
174 		(mode != ZmLiteHtmlEditor.HTML && mode != ZmLiteHtmlEditor.TEXT) )
175 	{
176 		return;
177 	}
178 
179 	this._mode = mode;
180 
181 	if(mode == ZmLiteHtmlEditor.HTML) {
182 		this._createFormatToolBar();
183 		this._enableToolbar(true);
184 		this._setDefaultStyles();
185 	}else{
186 		this._enableToolbar(false);
187 		this._clearAllStyles();
188 	}
189 
190 	this.resetSize();
191 
192 	// Notify mode change listeners.
193 	this.notifyListeners(DwtEvent.STATE_CHANGE);
194 };
195 
196 ZmLiteHtmlEditor.prototype.isHtmlMode =
197 function(){
198 	return ( this._mode == ZmLiteHtmlEditor.HTML );
199 };
200 
201 ZmLiteHtmlEditor.prototype.reverseMode =
202 function(){
203 	this.setMode(( (this._mode == ZmLiteHtmlEditor.HTML)? ZmLiteHtmlEditor.TEXT : ZmLiteHtmlEditor.HTML),false);
204 };
205 
206 ZmLiteHtmlEditor.prototype.setSize =
207 function( width, height ){
208 	DwtComposite.prototype.setSize.call(this, width, height);
209 	this.resetSize();
210 };
211 
212 ZmLiteHtmlEditor.prototype.resetSize = function(){
213 	var height = this.getHtmlElement().offsetHeight;
214 	var toolbarHeight = this._basicToolBar.getSize().y;
215 	if (this._mode == ZmLiteHtmlEditor.HTML) {
216 		toolbarHeight += this._formatToolBar.getSize().y;
217 	}
218 	this._textArea.style.width = "100%";
219 	this._textArea.style.height = height - toolbarHeight - 2 + "px";
220 };
221 
222 //KeyPress event listener
223 ZmLiteHtmlEditor.prototype.addKeyPressListener =
224 function(listener){
225 	this._keyPressListener = listener;
226 };
227 
228 
229 ZmLiteHtmlEditor.prototype.enable =
230 function(enable){
231 	if(this._textArea)
232 		this._textArea.disabled = (!enable);
233 };
234 
235 ZmLiteHtmlEditor.prototype.insertText =
236 function(text) {
237 	this._textArea.focus();
238 	this._textArea.value += text;
239 };
240 
241 ZmLiteHtmlEditor.prototype.getBasicToolBar =
242 function() {
243 	return this._basicToolBar;
244 };
245 
246 ZmLiteHtmlEditor.prototype.getFormatToolBar =
247 function() {
248 	return this._formatToolBar;
249 };
250 
251 //Private Methods
252 
253 ZmLiteHtmlEditor.prototype._initialize = function() {
254 	var id = this.getHTMLElId();
255 	this._createHtmlFromTemplate(this.TEMPLATE, { id: id });
256 	this._textArea = this._initEditor();
257 
258 	this._textArea[ AjxEnv.isIE ? "onkeydown" : "onkeypress" ] = AjxCallback.simpleClosure(this._keyPressHandler,this);
259 
260 	var toolBarArgs = {
261 		parent:this,
262 		parentElement: id + "_toolBar",
263 		posStyle:Dwt.RELATIVE_STYLE,
264 		buttons: [ZmOperation.IM_HTML],
265 		index:0
266 	};
267 	this._basicToolBar = new ZmButtonToolBar(toolBarArgs);
268 	this._basicToolBar.addSelectionListener(ZmOperation.IM_HTML, new AjxListener(this, this._changeEditorModeListener));
269 	
270 	this.setMode(this._mode, true);
271 };
272 
273 ZmLiteHtmlEditor.prototype._initEditor = function(){
274 	var htmlEl = Dwt.byId(this.getHTMLElId() + "_textarea");
275 
276 	this._textAreaId = "textarea_" + Dwt.getNextId();
277 	var html = [
278 			"<textarea id='",
279 			this._textAreaId,
280 			"' class='ZmHtmlEditorTextArea' style='width:100%;'></textarea>"
281 	].join("");
282 	htmlEl.innerHTML = html; 
283 	return Dwt.byId(this._textAreaId);
284 };
285 
286 ZmLiteHtmlEditor.prototype._keyPressHandler =
287 function(ev){
288 	if (AjxEnv.isIE)
289 		ev = window.event;
290 
291 	if(this._keyPressListener){
292 		this._keyPressListener.run(ev);
293 	}
294 
295 };
296 
297 ZmLiteHtmlEditor.prototype._changeEditorModeListener = function() {
298 	this.reverseMode();
299 	this.focus();
300 };
301 
302 //Styles
303 
304 ZmLiteHtmlEditor.prototype._clearAllStyles =
305 function(){
306 	var style = this._textArea.style;
307 	style.cssText = "";
308 	this.resetSize();
309 };
310 
311 ZmLiteHtmlEditor.prototype._setDefaultStyles =
312 function(){
313 // 	var style = this._textArea.style;
314 // 	style.fontFamily = appCtxt.get(ZmSetting.COMPOSE_INIT_FONT_FAMILY);
315 // 	style.fontSize = appCtxt.get(ZmSetting.COMPOSE_INIT_FONT_SIZE);
316 // 	style.color = appCtxt.get(ZmSetting.COMPOSE_INIT_FONT_COLOR);
317 // 	style.fontWeight = "normal";
318 // 	style.fontStyle = "normal";
319 // 	style.textDecoration = "none";
320 };
321 
322 ZmLiteHtmlEditor.prototype.setStyle =
323 function(property,value){
324 	this._textArea.style[property] = value;
325 };
326 
327 ZmLiteHtmlEditor.prototype.getStyle =
328 function(property){
329 	return this._textArea.style[property];
330 };
331 
332 //Toolbar
333 
334 ZmLiteHtmlEditor.prototype._createFormatToolBar = function(){
335 	if (!this._formatToolBar) {
336 		var formatToolBar = this._formatToolBar = new DwtToolBar({parent:this, className:"ZToolbar",
337 												  posStyle:DwtControl.RELATIVE_STYLE, cellSpacing:2, index:0});
338 		this._initFormatToolBar(formatToolBar);
339 		ZmLiteHtmlEditor._toolbarHeight = ZmLiteHtmlEditor._toolbarHeight || this._formatToolBar.getHtmlElement().offsetHeight;
340 		this.setSize(Dwt.DEFAULT, this.getH() + ZmLiteHtmlEditor._toolbarHeight);
341 	}
342 };
343 
344 ZmLiteHtmlEditor.prototype._enableToolbar =
345 function(enable) {
346 	var visible = !!enable;
347 	if (this._formatToolBar && (visible != !!this._formatToolBar.getVisible())) {
348 		this._formatToolBar.setVisible(visible);
349 		var sizeDiff = visible ? ZmLiteHtmlEditor._toolbarHeight : -ZmLiteHtmlEditor._toolbarHeight;
350 		this.setSize(Dwt.DEFAULT, this.getH() + sizeDiff);
351 	}
352 };
353 
354 ZmLiteHtmlEditor.prototype._initFormatToolBar = function(tb){
355 
356 	this._createFontFamilyMenu(tb);
357 
358 	this._createFontSizeMenu(tb);
359 
360 	new DwtControl({parent:tb, className:"vertSep"});
361 
362 	var listener = new AjxListener(this, this._fontStyleListener);
363 	var params = {parent:tb, style:DwtButton.TOGGLE_STYLE};
364 	this._boldButton = new DwtToolBarButton(params);
365 	this._boldButton.setImage("Bold");
366 	this._boldButton.setToolTipContent(ZmMsg.boldText);
367 	this._boldButton.setData(ZmLiteHtmlEditor._VALUE, ZmLiteHtmlEditor.BOLD_STYLE);
368 	this._boldButton.addSelectionListener(listener);
369 
370 	this._italicButton = new DwtToolBarButton(params);
371 	this._italicButton.setImage("Italics");
372 	this._italicButton.setToolTipContent(ZmMsg.italicText);
373 	this._italicButton.setData(ZmLiteHtmlEditor._VALUE, ZmLiteHtmlEditor.ITALIC_STYLE);
374 	this._italicButton.addSelectionListener(listener);
375 
376 	this._underlineButton = new DwtToolBarButton(params);
377 	this._underlineButton.setImage("Underline");
378 	this._underlineButton.setToolTipContent(ZmMsg.underlineText);
379 	this._underlineButton.setData(ZmLiteHtmlEditor._VALUE, ZmLiteHtmlEditor.UNDERLINE_STYLE);
380 	this._underlineButton.addSelectionListener(listener);
381 
382 	new DwtControl({parent:tb, className:"vertSep"});
383 
384 	this._fontColorButton = new ZmLiteHtmlEditorColorPicker(tb,null,"ZToolbarButton");
385 	this._fontColorButton.dontStealFocus();
386 	this._fontColorButton.setImage("FontColor");
387 	this._fontColorButton.showColorDisplay(true);
388 	this._fontColorButton.setToolTipContent(ZmMsg.fontColor);
389 	this._fontColorButton.setData(ZmLiteHtmlEditor._VALUE, ZmLiteHtmlEditor.FONT_COLOR);
390 	this._fontColorButton.setColor("#000000");
391 	this._fontColorButton.addSelectionListener(new AjxListener(this, this._fontStyleListener));
392 };
393 
394 ZmLiteHtmlEditor.prototype._createFontFamilyMenu =
395 function(tb) {
396 	this._fontFamilyButton = new DwtToolBarButton({parent:tb});
397 	this._fontFamilyButton.dontStealFocus();
398 	this._fontFamilyButton.setAlign(DwtLabel.ALIGN_LEFT);
399 	var menu = new ZmPopupMenu(this._fontFamilyButton);
400 	var listener = new AjxListener(this, this._fontFamilyListener);
401 
402 	for (var i = 0; i < ZmLiteHtmlEditor.FONT_FAMILY.length; i++) {
403 		var item = ZmLiteHtmlEditor.FONT_FAMILY[i];
404 		var mi = menu.createMenuItem(item.name, {text:item.name});
405 		mi.addSelectionListener(listener);
406 		mi.setData(ZmLiteHtmlEditor._VALUE, i);
407 		if(i == 0){
408 			this._fontFamilyButton.setText(item.name);
409 		}
410 	}
411 
412 	this._fontFamilyButton.setMenu(menu);
413 };
414 
415 ZmLiteHtmlEditor.prototype._createFontSizeMenu =
416 function(tb) {
417 	this._fontSizeButton = new DwtToolBarButton({parent:tb});
418 	this._fontSizeButton.dontStealFocus();
419 	var menu = new ZmPopupMenu(this._fontSizeButton);
420 	var listener = new AjxListener(this, this._fontSizeListener);
421 
422 	for (var i = 0; i < ZmLiteHtmlEditor.FONT_SIZE_VALUES.length; i++) {
423 		var item = ZmLiteHtmlEditor.FONT_SIZE_VALUES[i];
424 		var num = i+1;
425 		var text = num + " (" + item + ")";
426 		var mi = menu.createMenuItem(i, {text:text});
427 		mi.addSelectionListener(listener);
428 		mi.setData(ZmHtmlEditor.VALUE, num);
429 		if(i == 0){
430 				this._fontSizeButton.setText(text);
431 		}
432 	}
433 
434 	this._fontSizeButton.setMenu(menu);
435 
436 };
437 
438 ZmLiteHtmlEditor.prototype._fontFamilyListener =
439 function(ev) {
440 	var id = ev.item.getData(ZmLiteHtmlEditor._VALUE);
441 	this.setStyle("fontFamily",ZmLiteHtmlEditor.FONT_FAMILY[id].value);
442 	this._fontFamilyButton.setText(ZmLiteHtmlEditor.FONT_FAMILY[id].name);
443 	this.focus();
444 };
445 
446 ZmLiteHtmlEditor.prototype._fontSizeListener =
447 function(ev) {
448 	var num = ev.item.getData(ZmLiteHtmlEditor._VALUE);
449 	var size = ZmLiteHtmlEditor.FONT_SIZE_VALUES[num-1];
450 	this.setStyle("fontSize",size);
451 	this._fontSizeButton.setText(num + " (" + size + ")");
452 	this.focus();
453 };
454 
455 ZmLiteHtmlEditor.prototype._fontStyleListener =
456 function(ev) {
457 
458 	var styleType = ev.item.getData(ZmHtmlEditor.VALUE);
459 	var style = ZmLiteHtmlEditor.STYLE[styleType];
460 	if(!style) return;
461 
462 	var value = this.getStyle(style);
463 	if(styleType == ZmLiteHtmlEditor.UNDERLINE_STYLE){
464 		this.setStyle( style , (( !value || value == "none" ) ? "underline" : "none"));
465 	}else if (styleType == ZmLiteHtmlEditor.BOLD_STYLE){
466 		this.setStyle( style , (( !value || value == "normal" ) ? "bold" : "normal"));
467 	}else if(styleType == ZmLiteHtmlEditor.ITALIC_STYLE){
468 		this.setStyle( style , (( !value || value == "normal" ) ? "italic" : "normal"));
469 	}else if(styleType == ZmLiteHtmlEditor.FONT_COLOR){
470 		this.setStyle( style, ( ev.item.getColor() || "#000000" ) );
471 	}
472 	this.focus();
473 };
474 
475 ZmLiteHtmlEditor.prototype.focus =
476 function() {
477 	this.getEditor().focus();
478 };
479 
480 ZmLiteHtmlEditorColorPicker = function(parent,style,className) {
481     DwtButtonColorPicker.call(this, parent,style,className);
482 }
483 
484 ZmLiteHtmlEditorColorPicker.prototype = new DwtButtonColorPicker;
485 ZmLiteHtmlEditorColorPicker.prototype.constructor = ZmLiteHtmlEditorColorPicker;
486 
487 ZmLiteHtmlEditorColorPicker.prototype.TEMPLATE = "dwt.Widgets#ZToolbarButton";
488