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