39a8d852b0e7ea11d65bad62d32f061a3a0b256e
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / ContextMenu / context-menu.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * Copyright (c) 2003 dynarch.com. Authored by Mihai Bazon. Sponsored by www.americanbible.org.
5 * Copyright (c) 2004-2008 Stanislas Rolland <typo3(arobas)sjbr.ca>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This script is a modified version of a script published under the htmlArea License.
26 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /*
31 * Context Menu Plugin for TYPO3 htmlArea RTE
32 *
33 * TYPO3 SVN ID: $Id$
34 */
35
36 ContextMenu = HTMLArea.Plugin.extend({
37
38 constructor : function(editor, pluginName) {
39 this.base(editor, pluginName);
40 },
41
42 /*
43 * This function gets called by the class constructor
44 */
45 configurePlugin : function(editor) {
46 this.currentMenu = null;
47 this.keys = [];
48 this.eventHandlers = {};
49
50 /*
51 * Registering plugin "About" information
52 */
53 var pluginInformation = {
54 version : "2.0",
55 developer : "Mihai Bazon & Stanislas Rolland",
56 developerUrl : "http://www.sjbr.ca/",
57 copyrightOwner : "dynarch.com & Stanislas Rolland",
58 sponsor : "American Bible Society & SJBR",
59 sponsorUrl : "http://www.sjbr.ca/",
60 license : "GPL"
61 };
62 this.registerPluginInformation(pluginInformation);
63
64 return true;
65 },
66
67 /*
68 * This function gets called when the editor gets generated
69 */
70 onGenerate : function() {
71 this.editor.eventHandlers["contextMenu"] = this.makeFunctionReference("popupMenu");
72 HTMLArea._addEvent((HTMLArea.is_ie ? this.editor._doc.body : this.editor._doc), (HTMLArea.is_opera ? "mousedown" : "contextmenu"), this.editor.eventHandlers["contextMenu"]);
73 },
74
75 popupMenu : function(ev, target) {
76 if (HTMLArea.is_opera && ev.button < 2) {
77 return false;
78 }
79 var editor = this.editor;
80 if (!ev) var ev = window.event;
81 if (!target) var target = (ev.target) ? ev.target : ev.srcElement;
82 if (this.currentMenu) this.currentMenu.parentNode.removeChild(this.currentMenu);
83 this.keys = [];
84 var ifpos = ContextMenu.getPos(this.editor._iframe);
85 var x = ev.clientX + ifpos.x;
86 var y = ev.clientY + ifpos.y;
87
88 var doc, list, separator = false;
89
90 if (!HTMLArea.is_ie) {
91 doc = document;
92 } else {
93 var popup = this.iePopup = window.createPopup();
94 doc = popup.document;
95 var head = doc.getElementsByTagName("head")[0];
96 var link = doc.createElement("link");
97 link.rel = "stylesheet";
98 link.type = "text/css";
99 if ( _editor_CSS.indexOf("http") == -1 ) link.href = _typo3_host_url + _editor_CSS;
100 else link.href = _editor_CSS;
101 head.appendChild(link);
102 }
103
104 list = doc.createElement("ul");
105 list.className = "htmlarea-context-menu";
106 doc.body.appendChild(list);
107
108 var options = this.getContextMenu(target);
109 var n = options.length;
110 for (var i=0; i < n; ++i) {
111 var option = options[i];
112 if (!option){
113 separator = true;
114 } else {
115 var item = doc.createElement("li");
116 list.appendChild(item);
117 var label = option[0];
118 if (separator) {
119 HTMLArea._addClass(item, "separator");
120 separator = false;
121 }
122 item.__msh = {
123 item: item,
124 label: label,
125 action: option[1],
126 tooltip: option[2] || null,
127 icon: option[3] || null,
128 activate: ContextMenu.activateHandler(item, this),
129 cmd: option[4] || null
130 };
131 label = label.replace(/_([a-zA-Z0-9])/, "<u>$1</u>");
132 if (label != option[0]) this.keys.push([ RegExp.$1, item ]);
133 label = label.replace(/__/, "_");
134 var button = doc.createElement("button");
135 HTMLArea._addClass(button, "button");
136 if (item.__msh.cmd) {
137 HTMLArea._addClass(button, item.__msh.cmd);
138 if (editor._toolbarObjects[item.__msh.cmd] && editor._toolbarObjects[item.__msh.cmd].active) {
139 HTMLArea._addClass(button, "buttonActive");
140 }
141 } else if (item.__msh.icon) {
142 button.innerHTML = "<img src='" + item.__msh.icon + "' />";
143 }
144 item.appendChild(button);
145 item.innerHTML = item.innerHTML + label;
146 // Setting event handlers on the menu items
147 item.__msh.mouseover = ContextMenu.mouseOverHandler(editor, item);
148 HTMLArea._addEvent(item, "mouseover", item.__msh.mouseover);
149 item.__msh.mouseout = ContextMenu.mouseOutHandler(item);
150 HTMLArea._addEvent(item, "mouseout", item.__msh.mouseout);
151 item.__msh.contextmenu = ContextMenu.itemContextMenuHandler(item);
152 HTMLArea._addEvent(item, "contextmenu", item.__msh.contextmenu);
153 if (!HTMLArea.is_ie) {
154 item.__msh.mousedown = ContextMenu.mouseDownHandler(item);
155 HTMLArea._addEvent(item, "mousedown", item.__msh.mousedown);
156 }
157 item.__msh.mouseup = ContextMenu.mouseUpHandler(item, this);
158 HTMLArea._addEvent(item, "mouseup", item.__msh.mouseup);
159 }
160 }
161 if (n) {
162 if(!HTMLArea.is_ie) {
163 var dx = x + list.offsetWidth - window.innerWidth - window.pageXOffset + 4;
164 var dy = y + list.offsetHeight - window.innerHeight - window.pageYOffset + 4;
165 if(dx > 0) x -= dx;
166 if(dy > 0) y -= dy;
167 list.style.left = x + "px";
168 list.style.top = y + "px";
169 } else {
170 // determine the size
171 list.style.left = "0px";
172 list.style.top = "0px";
173 var foobar = document.createElement("ul");
174 foobar.className = "htmlarea-context-menu";
175 foobar.innerHTML = list.innerHTML;
176 editor._iframe.contentWindow.parent.document.body.appendChild(foobar);
177 this.iePopup.show(ev.screenX, ev.screenY, foobar.clientWidth+2, foobar.clientHeight+2);
178 editor._iframe.contentWindow.parent.document.body.removeChild(foobar);
179 }
180 this.currentMenu = list;
181 this.timeStamp = (new Date()).getTime();
182 this.eventHandlers["documentClick"] = this.makeFunctionReference("documentClickHandler");
183 HTMLArea._addEvent((HTMLArea.is_ie ? document.body : document), "mousedown", this.eventHandlers["documentClick"]);
184 HTMLArea._addEvent((HTMLArea.is_ie ? editor._doc.body : editor._doc), "mousedown", this.eventHandlers["documentClick"]);
185 if (this.keys.length > 0) {
186 this.eventHandlers["keyPress"] = ContextMenu.keyPressHandler(this);
187 HTMLArea._addEvents((HTMLArea.is_ie ? editor._doc.body : editor._doc), ["keypress", "keydown"], this.eventHandlers["keyPress"]);
188 }
189 }
190 HTMLArea._stopEvent(ev);
191 return false;
192 },
193
194 pushOperations : function (opcodes, elmenus, pluginId) {
195 var editor = this.editor;
196 var pluginInstance = this.editor.plugins[pluginId];
197 if (pluginInstance) {
198 pluginInstance = pluginInstance.instance;
199 }
200 var toolbarObjects = editor._toolbarObjects;
201 var btnList = editor.config.btnList;
202 var enabled = false, opcode, opEnabled = [], i = opcodes.length;
203 for (i; i > 0;) {
204 opcode = opcodes[--i];
205 opEnabled[opcode] = toolbarObjects[opcode] && toolbarObjects[opcode].enabled;
206 enabled = enabled || opEnabled[opcode];
207 }
208 if (enabled && elmenus.length) {
209 elmenus.push(null);
210 }
211 for (i = opcodes.length; i > 0;) {
212 opcode = opcodes[--i];
213 if (opEnabled[opcode]) {
214 switch (pluginId) {
215 case "TableOperations" :
216 elmenus.push([this.localize(opcode + "-title"),
217 ContextMenu.tableOperationsHandler(editor, pluginInstance, opcode),
218 this.localize(opcode + "-tooltip"),
219 btnList[opcode][1], opcode]);
220 break;
221 case "BlockElements" :
222 elmenus.push([this.localize(opcode + "-title"),
223 ContextMenu.blockElementsHandler(editor, null, opcode),
224 this.localize(opcode + "-tooltip"),
225 btnList[opcode][1], opcode]);
226 break;
227 default :
228 elmenus.push([this.localize(opcode + "-title"),
229 ContextMenu.execCommandHandler(editor, opcode),
230 this.localize(opcode + "-tooltip"),
231 btnList[opcode][1], opcode]);
232 break;
233 }
234 }
235 }
236 },
237
238 getContextMenu : function(target) {
239 var editor = this.editor;
240 var toolbarObjects = editor._toolbarObjects;
241 var config = editor.config;
242 var btnList = config.btnList;
243 var menu = [], opcode;
244 var tbo = this.editor.plugins["TableOperations"];
245 if (tbo) tbo = "TableOperations";
246
247 var selection = editor.hasSelectedText();
248 if(selection) {
249 if (toolbarObjects['Cut'] && toolbarObjects['Cut'].enabled) {
250 opcode = "Cut";
251 menu.push([this.localize(opcode), ContextMenu.execCommandHandler(editor, opcode), null, btnList[opcode][1], opcode]);
252 }
253 if (toolbarObjects['Copy'] && toolbarObjects['Copy'].enabled) {
254 opcode = "Copy";
255 menu.push([this.localize(opcode), ContextMenu.execCommandHandler(editor, opcode), null, btnList[opcode][1], opcode]);
256 }
257 }
258 if (toolbarObjects['Paste'] && toolbarObjects['Paste'].enabled) {
259 opcode = "Paste";
260 menu.push([this.localize(opcode), ContextMenu.execCommandHandler(editor, opcode), null, btnList[opcode][1], opcode]);
261 }
262
263 var currentTarget = target,
264 tmp, tag, link = false,
265 table = null, tr = null, td = null, img = null, list = null, div = null;
266
267 for(; target; target = target.parentNode) {
268 tag = target.nodeName;
269 if(!tag) continue;
270 tag = tag.toLowerCase();
271 switch (tag) {
272 case "img":
273 img = target;
274 if (toolbarObjects["InsertImage"] && toolbarObjects["InsertImage"].enabled) {
275 if (menu.length) menu.push(null);
276 menu.push(
277 [this.localize("Image Properties"),
278 ContextMenu.imageHandler(editor, img),
279 this.localize("Show the image properties dialog"),
280 btnList["InsertImage"][1], "InsertImage"]
281 );
282 }
283 break;
284 case "a":
285 link = target;
286 if (toolbarObjects["CreateLink"]) {
287 if (menu.length) menu.push(null);
288 menu.push(
289 [this.localize("Modify Link"),
290 this.linkHandler(link, "ModifyLink"),
291 this.localize("Current URL is") + ': ' + link.href,
292 btnList["CreateLink"][1], "CreateLink"],
293 [this.localize("Check Link"),
294 this.linkHandler(link, "CheckLink"),
295 this.localize("Opens this link in a new window"),
296 null, null],
297 [this.localize("Remove Link"),
298 this.linkHandler(link, "RemoveLink"),
299 this.localize("Unlink the current element"),
300 editor.imgURL("ed_unlink.gif"), "UnLink"]
301 );
302 }
303 break;
304 case "td":
305 case "th":
306 td = target;
307 if(!tbo) break;
308 this.pushOperations(["TO-cell-split", "TO-cell-delete", "TO-cell-insert-after", "TO-cell-insert-before", "TO-cell-prop"], menu, tbo);
309 break;
310 case "tr":
311 tr = target;
312 if(!tbo) break;
313 opcode = "TO-cell-merge";
314 if (toolbarObjects[opcode] && toolbarObjects[opcode].enabled) {
315 menu.push([this.localize(opcode + "-title"),
316 ContextMenu.tableOperationsHandler(editor, this.editor.plugins.TableOperations.instance, opcode),
317 this.localize(opcode + "-tooltip"),
318 btnList[opcode][1], opcode]);
319 }
320 this.pushOperations(["TO-row-split", "TO-row-delete", "TO-row-insert-under", "TO-row-insert-above", "TO-row-prop"], menu, tbo);
321 break;
322 case "table":
323 table = target;
324 if(!tbo) break;
325 this.pushOperations(["TO-col-split", "TO-col-delete", "TO-col-insert-after", "TO-col-insert-before", "TO-col-prop"], menu, tbo);
326 this.pushOperations(["TO-toggle-borders", "TO-table-restyle", "TO-table-prop"], menu, tbo);
327 break;
328 case "ol":
329 case "ul":
330 case "dl":
331 list = target;
332 break;
333 case "div":
334 div = target;
335 break;
336 case "body":
337 this.pushOperations(["JustifyFull", "JustifyRight", "JustifyCenter", "JustifyLeft"], menu, "BlockElements");
338 break;
339 }
340 }
341
342 if (selection && !link) {
343 if (menu.length) menu.push(null);
344 menu.push([this.localize("Make link"),
345 this.linkHandler(link, "MakeLink"),
346 this.localize("Create a link"),
347 btnList["CreateLink"][1],"CreateLink"]);
348 }
349
350 if (!/^(html|body)$/i.test(currentTarget.nodeName)) {
351 if (/^(table|thead|tbody|tr|td|th|tfoot)$/i.test(currentTarget.nodeName)) {
352 tmp = table;
353 table = null;
354 } else if(list) {
355 tmp = list;
356 list = null;
357 } else {
358 tmp = currentTarget;
359 }
360 if (menu.length) menu.push(null);
361 menu.push(
362 [this.localize("Remove the") + " &lt;" + tmp.tagName.toLowerCase() + "&gt; " + this.localize("Element"),
363 this.deleteElementHandler(tmp, table), this.localize("Remove this node from the document")],
364 [this.localize("Insert paragraph before"),
365 ContextMenu.blockElementsHandler(editor, tmp, "InsertParagraphBefore"), this.localize("Insert a paragraph before the current node"), null, "InsertParagraphBefore"],
366 [this.localize("Insert paragraph after"),
367 ContextMenu.blockElementsHandler(editor, tmp, "InsertParagraphAfter"), this.localize("Insert a paragraph after the current node"), null, "InsertParagraphAfter"]
368 );
369 }
370 return menu;
371 },
372
373 closeMenu : function() {
374 HTMLArea._removeEvent((HTMLArea.is_ie ? document.body : document), "mousedown", this.eventHandlers["documentClick"]);
375 HTMLArea._removeEvent((HTMLArea.is_ie ? this.editor._doc.body : this.editor._doc), "mousedown", this.eventHandlers["documentClick"]);
376 if (this.keys.length > 0) HTMLArea._removeEvent((HTMLArea.is_ie ? this.editor._doc.body : this.editor._doc), "keypress", this.eventHandlers["keyPress"]);
377 for (var handler in this.eventHandlers) this.eventHandlers[handler] = null;
378 var e, items = document.getElementsByTagName("li");
379 if (HTMLArea.is_ie) items = this.iePopup.document.getElementsByTagName("li");;
380 for (var i = items.length; --i >= 0 ;) {
381 e = items[i];
382 if ( e.__msh ) {
383 HTMLArea._removeEvent(e, "mouseover", e.__msh.mouseover);
384 e.__msh.mouseover = null;
385 HTMLArea._removeEvent(e, "mouseout", e.__msh.mouseout);
386 e.__msh.mouseout = null;
387 HTMLArea._removeEvent(e, "contextmenu", e.__msh.contextmenu);
388 e.__msh.contextmenu = null;
389 if (!HTMLArea.is_ie) HTMLArea._removeEvent(e, "mousedown", e.__msh.mousedown);
390 e.__msh.mousedown = null;
391 HTMLArea._removeEvent(e, "mouseup", e.__msh.mouseup);
392 e.__msh.mouseup = null;
393 e.__msh.action = null;
394 e.__msh.activate = null;
395 e.__msh = null;
396 }
397 }
398 this.currentMenu.parentNode.removeChild(this.currentMenu);
399 this.currentMenu = null;
400 this.keys = [];
401 if (HTMLArea.is_ie) this.iePopup.hide();
402 },
403
404 linkHandler : function (link, opcode) {
405 var editor = this.editor;
406 var self = this;
407 switch (opcode) {
408 case "MakeLink":
409 case "ModifyLink":
410 return (function() {
411 var obj = editor._toolbarObjects.CreateLink;
412 obj.cmd(editor, "CreateLink", link);
413 });
414 case "CheckLink":
415 return (function() {
416 window.open(link.href);
417 });
418 case "RemoveLink":
419 return (function() {
420 if (confirm(self.localize("Please confirm unlink") + "\n" +
421 self.localize("Link points to:") + " " + link.href)) {
422 var obj = editor._toolbarObjects.CreateLink;
423 obj.cmd(editor, "UnLink", link);
424 }
425 });
426 }
427 },
428
429 deleteElementHandler : function (tmp,table) {
430 var editor = this.editor;
431 var self = this;
432 return (function() {
433 if(confirm(self.localize("Please confirm remove") + " " + tmp.tagName.toLowerCase())) {
434 var el = tmp;
435 var p = el.parentNode;
436 p.removeChild(el);
437 if(HTMLArea.is_gecko) {
438 if(p.tagName.toLowerCase() == "td" && !p.hasChildNodes()) p.appendChild(editor._doc.createElement("br"));
439 editor.forceRedraw();
440 editor.focusEditor();
441 editor.updateToolbar();
442 if(table) {
443 var save_collapse = table.style.borderCollapse;
444 table.style.borderCollapse = "collapse";
445 table.style.borderCollapse = "separate";
446 table.style.borderCollapse = save_collapse;
447 }
448 }
449 }
450 });
451 },
452
453 documentClickHandler : function (ev) {
454 if (!ev) var ev = window.event;
455 if (!this.currentMenu) {
456 alert(this.localize("How did you get here? (Please report!)"));
457 return false;
458 }
459 var el = (ev.target) ? ev.target : ev.srcElement;
460 for (; el != null && el != this.currentMenu; el = el.parentNode);
461 if (el == null) {
462 this.closeMenu();
463 this.editor.updateToolbar();
464 }
465 }
466 });
467
468 ContextMenu.tableOperationsHandler = function(editor,tbo,opcode) {
469 return (function() {
470 tbo.onButtonPress(editor,opcode);
471 });
472 };
473
474 ContextMenu.imageHandler = function(editor, currentTarget) {
475 return (function() {
476 var obj = editor._toolbarObjects.InsertImage;
477 obj.cmd(editor, obj.name, currentTarget);
478 if (HTMLArea.is_opera) {
479 editor._iframe.focus();
480 }
481 if (!editor.config.btnList[obj.name][7]) {
482 editor.updateToolbar();
483 }
484 });
485 };
486
487 ContextMenu.execCommandHandler = function(editor,opcode) {
488 return (function() {
489 editor.execCommand(opcode);
490 });
491 };
492
493 ContextMenu.blockElementsHandler = function(editor, currentTarget, buttonId) {
494 return (function() {
495 var blockElements = editor.plugins.BlockElements;
496 if (blockElements) {
497 blockElements = blockElements.instance;
498 blockElements.onButtonPress(editor, buttonId, currentTarget);
499 } else {
500 var el = currentTarget;
501 var par = el.parentNode;
502 var p = editor._doc.createElement("p");
503 var after = (buttonId === "InsertParagraphAfter");
504 p.appendChild(editor._doc.createElement("br"));
505 par.insertBefore(p, after ? el.nextSibling : el);
506 var sel = editor._getSelection();
507 var range = editor._createRange(sel);
508 editor.selectNodeContents(p, true);
509 }
510 });
511 };
512
513 ContextMenu.mouseOverHandler = function(editor,item) {
514 return (function() {
515 item.className += " hover";
516 editor._statusBarTree.innerHTML = item.__msh.tooltip || '&nbsp;';
517 });
518 };
519
520 ContextMenu.mouseOutHandler = function(item) {
521 return (function() {
522 item.className = item.className.replace(/hover/,"");
523 });
524 };
525
526 ContextMenu.itemContextMenuHandler = function(item) {
527 return (function(ev) {
528 item.__msh.activate();
529 if(!HTMLArea.is_ie) HTMLArea._stopEvent(ev);
530 return false;
531 });
532 };
533
534 ContextMenu.mouseDownHandler = function(item) {
535 return (function(ev) {
536 HTMLArea._stopEvent(ev);
537 return false;
538 });
539 };
540
541 ContextMenu.mouseUpHandler = function(item,instance) {
542 return (function(ev) {
543 var timeStamp = (new Date()).getTime();
544 if (timeStamp - instance.timeStamp > 500) item.__msh.activate();
545 if (!HTMLArea.is_ie) HTMLArea._stopEvent(ev);
546 instance.editor.updateToolbar();
547 return false;
548 });
549 };
550
551 ContextMenu.activateHandler = function(item,instance) {
552 return (function() {
553 item.__msh.action();
554 instance.closeMenu();
555 });
556 };
557
558 ContextMenu.keyPressHandler = function(instance) {
559 return (function(ev) {
560 if (!ev) var ev = window.event;
561 if (ev.keyCode == 27) {
562 instance.closeMenu();
563 return false;
564 }
565 if(ev.altKey && !ev.ctrlKey) {
566 var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
567 var keys = instance.keys;
568 for (var i = keys.length; --i >= 0;) {
569 var k = keys[i];
570 if (k[0].toLowerCase() == key) k[1].__msh.activate();
571 }
572 HTMLArea._stopEvent(ev);
573 return false;
574 }
575 });
576 };
577
578 ContextMenu.getPos = function(el) {
579 var r = { x: el.offsetLeft, y: el.offsetTop };
580 if (el.offsetParent) {
581 var tmp = ContextMenu.getPos(el.offsetParent);
582 r.x += tmp.x;
583 r.y += tmp.y;
584 }
585 return r;
586 };
587