source: trunk/plugins/ContextMenu/context-menu.js @ 988

Last change on this file since 988 was 988, checked in by ray, 11 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
File size: 15.9 KB
Line 
1// Context Menu Plugin for HTMLArea-3.0
2// Sponsored by www.americanbible.org
3// Implementation by Mihai Bazon, http://dynarch.com/mishoo/
4//
5// (c) dynarch.com 2003.
6// Distributed under the same terms as HTMLArea itself.
7// This notice MUST stay intact for use (see license.txt).
8//
9// $Id$
10
11Xinha.loadStyle("menu.css", "ContextMenu");
12
13function ContextMenu(editor) {
14        this.editor = editor;
15}
16
17ContextMenu._pluginInfo = {
18        name          : "ContextMenu",
19        version       : "1.0",
20        developer     : "Mihai Bazon",
21        developer_url : "http://dynarch.com/mishoo/",
22        c_owner       : "dynarch.com",
23        sponsor       : "American Bible Society",
24        sponsor_url   : "http://www.americanbible.org",
25        license       : "htmlArea"
26};
27
28Xinha.Config.prototype.ContextMenu = {
29        disableMozillaSpellCheck : false
30}
31
32ContextMenu.prototype.onGenerate = function() {
33        var self = this;
34        var doc = this.editordoc = this.editor._iframe.contentWindow.document;
35        Xinha._addEvents(doc, ["contextmenu"],
36                            function (event) {
37                                    return self.popupMenu(Xinha.is_ie ? self.editor._iframe.contentWindow.event : event);
38                            });
39        this.currentMenu = null;
40       
41        if (this.editor.config.ContextMenu.disableMozillaSpellCheck) {
42                this.editordoc.body.spellcheck = false; // Firefox spellchecking is quite confusing for the user when they don't get the browser context menu
43        }
44};
45
46ContextMenu.prototype.getContextMenu = function(target) {
47        var self = this;
48        var editor = this.editor;
49        var config = editor.config;
50        var menu = [];
51        var tbo = this.editor.plugins.TableOperations;
52        if (tbo) tbo = tbo.instance;
53
54        var selection = editor.hasSelectedText();
55        if (!Xinha.is_gecko) {
56                if (selection) {
57                        menu.push([ Xinha._lc("Cut", "ContextMenu"), function() { editor.execCommand("cut"); }, null, config.btnList["cut"][1] ],
58                                  [ Xinha._lc("Copy", "ContextMenu"), function() { editor.execCommand("copy"); }, null, config.btnList["copy"][1] ]);
59                        menu.push([ Xinha._lc("Paste", "ContextMenu"), function() { editor.execCommand("paste"); }, null, config.btnList["paste"][1] ]);
60                }
61        }
62        var currentTarget = target;
63        var elmenus = [];
64
65        var link = null;
66        var table = null;
67        var tr = null;
68        var td = null;
69        var img = null;
70
71        function tableOperation(opcode) {
72                tbo.buttonPress(editor, opcode);
73        }
74
75        function insertPara(after) {
76                var el = currentTarget;
77                var par = el.parentNode;
78                var p = editor._doc.createElement("p");
79                p.appendChild(editor._doc.createElement("br"));
80                par.insertBefore(p, after ? el.nextSibling : el);
81                var sel = editor._getSelection();
82                var range = editor._createRange(sel);
83                if (!Xinha.is_ie) {
84                        sel.removeAllRanges();
85                        range.selectNodeContents(p);
86                        range.collapse(true);
87                        sel.addRange(range);
88                } else {
89                        range.moveToElementText(p);
90                        range.collapse(true);
91                        range.select();
92                }
93        }
94
95        for (; target; target = target.parentNode) {
96                var tag = target.tagName;
97                if (!tag)
98                        continue;
99                tag = tag.toLowerCase();
100                switch (tag) {
101                    case "img":
102                        img = target;
103                        elmenus.push(null,
104                                     [ Xinha._lc("_Image Properties...", "ContextMenu"),
105                                       function() {
106                                               editor._insertImage(img);
107                                       },
108                                       Xinha._lc("Show the image properties dialog", "ContextMenu"),
109                                       config.btnList["insertimage"][1] ]
110                                );
111                        break;
112                    case "a":
113                        link = target;
114                        elmenus.push(null,
115                                     [ Xinha._lc("_Modify Link...", "ContextMenu"),
116               function() { editor.config.btnList['createlink'][3](editor); },
117                                       Xinha._lc("Current URL is", "ContextMenu") + ': ' + link.href,
118                                       config.btnList["createlink"][1] ],
119
120                                     [ Xinha._lc("Chec_k Link...", "ContextMenu"),
121                                       function() { window.open(link.href); },
122                                       Xinha._lc("Opens this link in a new window", "ContextMenu") ],
123
124                                     [ Xinha._lc("_Remove Link...", "ContextMenu"),
125                                       function() {
126                                               if (confirm(Xinha._lc("Please confirm that you want to unlink this element.", "ContextMenu") + "\n" +
127                                                           Xinha._lc("Link points to:", "ContextMenu") + " " + link.href)) {
128                                                       while (link.firstChild)
129                                                               link.parentNode.insertBefore(link.firstChild, link);
130                                                       link.parentNode.removeChild(link);
131                                               }
132                                       },
133                                       Xinha._lc("Unlink the current element", "ContextMenu") ]
134                                );
135                        break;
136                    case "td":
137                        td = target;
138                        if (!tbo) break;
139                        elmenus.push(null,
140                                     [ Xinha._lc("C_ell Properties...", "ContextMenu"),
141                                       function() { tableOperation("TO-cell-prop"); },
142                                       Xinha._lc("Show the Table Cell Properties dialog", "ContextMenu"),
143                                       config.btnList["TO-cell-prop"][1] ],
144
145             [ Xinha._lc("Insert Cell After", "ContextMenu"),
146                                       function() { tableOperation("TO-cell-insert-after"); },
147                                       Xinha._lc("Insert Cell After", "ContextMenu"),
148                                       config.btnList["TO-cell-insert-after"][1] ],
149
150             [ Xinha._lc("Insert Cell Before", "ContextMenu"),
151                                       function() { tableOperation("TO-cell-insert-before"); },
152                                       Xinha._lc("Insert Cell After", "ContextMenu"),
153                                       config.btnList["TO-cell-insert-before"][1] ],
154
155             [ Xinha._lc("Delete Cell", "ContextMenu"),
156                                       function() { tableOperation("TO-cell-delete"); },
157                                       Xinha._lc("Delete Cell", "ContextMenu"),
158                                       config.btnList["TO-cell-delete"][1] ],
159
160             [ Xinha._lc("Merge Cells", "ContextMenu"),
161                                       function() { tableOperation("TO-cell-merge"); },
162                                       Xinha._lc("Merge Cells", "ContextMenu"),
163                                       config.btnList["TO-cell-merge"][1] ]
164                                );
165                        break;
166                    case "tr":
167                        tr = target;
168                        if (!tbo) break;
169                        elmenus.push(null,
170                                     [ Xinha._lc("Ro_w Properties...", "ContextMenu"),
171                                       function() { tableOperation("TO-row-prop"); },
172                                       Xinha._lc("Show the Table Row Properties dialog", "ContextMenu"),
173                                       config.btnList["TO-row-prop"][1] ],
174
175                                     [ Xinha._lc("I_nsert Row Before", "ContextMenu"),
176                                       function() { tableOperation("TO-row-insert-above"); },
177                                       Xinha._lc("Insert a new row before the current one", "ContextMenu"),
178                                       config.btnList["TO-row-insert-above"][1] ],
179
180                                     [ Xinha._lc("In_sert Row After", "ContextMenu"),
181                                       function() { tableOperation("TO-row-insert-under"); },
182                                       Xinha._lc("Insert a new row after the current one", "ContextMenu"),
183                                       config.btnList["TO-row-insert-under"][1] ],
184
185                                     [ Xinha._lc("_Delete Row", "ContextMenu"),
186                                       function() { tableOperation("TO-row-delete"); },
187                                       Xinha._lc("Delete the current row", "ContextMenu"),
188                                       config.btnList["TO-row-delete"][1] ]
189                                );
190                        break;
191                    case "table":
192                        table = target;
193                        if (!tbo) break;
194                        elmenus.push(null,
195                                     [ Xinha._lc("_Table Properties...", "ContextMenu"),
196                                       function() { tableOperation("TO-table-prop"); },
197                                       Xinha._lc("Show the Table Properties dialog", "ContextMenu"),
198                                       config.btnList["TO-table-prop"][1] ],
199
200                                     [ Xinha._lc("Insert _Column Before", "ContextMenu"),
201                                       function() { tableOperation("TO-col-insert-before"); },
202                                       Xinha._lc("Insert a new column before the current one", "ContextMenu"),
203                                       config.btnList["TO-col-insert-before"][1] ],
204
205                                     [ Xinha._lc("Insert C_olumn After", "ContextMenu"),
206                                       function() { tableOperation("TO-col-insert-after"); },
207                                       Xinha._lc("Insert a new column after the current one", "ContextMenu"),
208                                       config.btnList["TO-col-insert-after"][1] ],
209
210                                     [ Xinha._lc("De_lete Column", "ContextMenu"),
211                                       function() { tableOperation("TO-col-delete"); },
212                                       Xinha._lc("Delete the current column", "ContextMenu"),
213                                       config.btnList["TO-col-delete"][1] ]
214                                );
215                        break;
216                    case "body":
217                        elmenus.push(null,
218                                     [ Xinha._lc("Justify Left", "ContextMenu"),
219                                       function() { editor.execCommand("justifyleft"); }, null,
220                                       config.btnList["justifyleft"][1] ],
221                                     [ Xinha._lc("Justify Center", "ContextMenu"),
222                                       function() { editor.execCommand("justifycenter"); }, null,
223                                       config.btnList["justifycenter"][1] ],
224                                     [ Xinha._lc("Justify Right", "ContextMenu"),
225                                       function() { editor.execCommand("justifyright"); }, null,
226                                       config.btnList["justifyright"][1] ],
227                                     [ Xinha._lc("Justify Full", "ContextMenu"),
228                                       function() { editor.execCommand("justifyfull"); }, null,
229                                       config.btnList["justifyfull"][1] ]
230                                );
231                        break;
232                }
233        }
234
235        if (selection && !link)
236                menu.push(null, [ Xinha._lc("Make lin_k...", "ContextMenu"),
237           function() { editor.config.btnList['createlink'][3](editor); },
238                                  Xinha._lc("Create a link", "ContextMenu"),
239                                  config.btnList["createlink"][1] ]);
240
241        for (var i = 0; i < elmenus.length; ++i)
242                menu.push(elmenus[i]);
243
244        if (!/html|body/i.test(currentTarget.tagName))
245                menu.push(null,
246                          [ Xinha._lc({string: "Remove the $elem Element...", replace: {elem: "&lt;" + currentTarget.tagName + "&gt;"}}, "ContextMenu"),
247                            function() {
248                                    if (confirm(Xinha._lc("Please confirm that you want to remove this element:", "ContextMenu") + " " +
249                                                currentTarget.tagName)) {
250                                            var el = currentTarget;
251                                            var p = el.parentNode;
252                                            p.removeChild(el);
253                                            if (Xinha.is_gecko) {
254                                                    if (p.tagName.toLowerCase() == "td" && !p.hasChildNodes())
255                                                            p.appendChild(editor._doc.createElement("br"));
256                                                    editor.forceRedraw();
257                                                    editor.focusEditor();
258                                                    editor.updateToolbar();
259                                                    if (table) {
260                                                            var save_collapse = table.style.borderCollapse;
261                                                            table.style.borderCollapse = "collapse";
262                                                            table.style.borderCollapse = "separate";
263                                                            table.style.borderCollapse = save_collapse;
264                                                    }
265                                            }
266                                    }
267                            },
268                            Xinha._lc("Remove this node from the document", "ContextMenu") ],
269                          [ Xinha._lc("Insert paragraph before", "ContextMenu"),
270                            function() { insertPara(false); },
271                            Xinha._lc("Insert a paragraph before the current node", "ContextMenu") ],
272                          [ Xinha._lc("Insert paragraph after", "ContextMenu"),
273                            function() { insertPara(true); },
274                            Xinha._lc("Insert a paragraph after the current node", "ContextMenu") ]
275                          );
276        if (!menu[0]) menu.shift(); //If the menu begins with a separator, remove it for cosmetical reasons
277        return menu;
278};
279
280ContextMenu.prototype.popupMenu = function(ev) {
281        var self = this;
282        if (this.currentMenu)
283        {
284                this.closeMenu();
285        }
286        function getPos(el) {
287                var r = { x: el.offsetLeft, y: el.offsetTop };
288                if (el.offsetParent) {
289                        var tmp = getPos(el.offsetParent);
290                        r.x += tmp.x;
291                        r.y += tmp.y;
292                }
293                return r;
294        }
295        function documentClick(ev) {
296                ev || (ev = window.event);
297                if (!self.currentMenu) {
298                        alert(Xinha._lc("How did you get here? (Please report!)", "ContextMenu"));
299                        return false;
300                }
301                var el = Xinha.is_ie ? ev.srcElement : ev.target;
302                for (; el != null && el != self.currentMenu; el = el.parentNode);
303                if (el == null)
304                        self.closeMenu();
305                //Xinha._stopEvent(ev);
306                //return false;
307        }
308        var keys = [];
309        function keyPress(ev) {
310                ev || (ev = window.event);
311                Xinha._stopEvent(ev);
312                if (ev.keyCode == 27) {
313                        self.closeMenu();
314                        return false;
315                }
316                var key = String.fromCharCode(Xinha.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
317                for (var i = keys.length; --i >= 0;) {
318                        var k = keys[i];
319                        if (k[0].toLowerCase() == key)
320                                k[1].__msh.activate();
321                }
322        }
323        self.closeMenu = function() {
324                self.currentMenu.parentNode.removeChild(self.currentMenu);
325                self.currentMenu = null;
326                Xinha._removeEvent(document, "mousedown", documentClick);
327                Xinha._removeEvent(self.editordoc, "mousedown", documentClick);
328                if (keys.length > 0)
329                        Xinha._removeEvent(self.editordoc, "keypress", keyPress);
330                if (Xinha.is_ie)
331                        self.iePopup.hide();
332        }
333        var target = Xinha.is_ie ? ev.srcElement : ev.target;
334     var ifpos = getPos(self.editor._htmlArea);//_iframe);
335        var x = ev.clientX + ifpos.x;
336        var y = ev.clientY + ifpos.y;
337
338        var div;
339        var doc;
340        if (!Xinha.is_ie) {
341                doc = document;
342        } else {
343                // IE stinks
344                var popup = this.iePopup = window.createPopup();
345                doc = popup.document;
346                doc.open();
347                doc.write("<html><head><style type='text/css'>@import url(" + _editor_url + "plugins/ContextMenu/menu.css); html, body { padding: 0px; margin: 0px; overflow: hidden; border: 0px; }</style></head><body unselectable='yes'></body></html>");
348                doc.close();
349        }
350        div = doc.createElement("div");
351        if (Xinha.is_ie)
352                div.unselectable = "on";
353        div.oncontextmenu = function() { return false; };
354        div.className = "htmlarea-context-menu";
355        if (!Xinha.is_ie)
356                div.style.left = div.style.top = "0px";
357        doc.body.appendChild(div);
358
359        var table = doc.createElement("table");
360        div.appendChild(table);
361        table.cellSpacing = 0;
362        table.cellPadding = 0;
363        var parent = doc.createElement("tbody");
364        table.appendChild(parent);
365
366        var options = this.getContextMenu(target);
367        for (var i = 0; i < options.length; ++i) {
368                var option = options[i];
369                var item = doc.createElement("tr");
370                parent.appendChild(item);
371                if (Xinha.is_ie)
372                        item.unselectable = "on";
373                else item.onmousedown = function(ev) {
374                        Xinha._stopEvent(ev);
375                        return false;
376                };
377                if (!option) {
378                        item.className = "separator";
379                        var td = doc.createElement("td");
380                        td.className = "icon";
381                        var IE_IS_A_FUCKING_SHIT = '>';
382                        if (Xinha.is_ie) {
383                                td.unselectable = "on";
384                                IE_IS_A_FUCKING_SHIT = " unselectable='on' style='height=1px'>&nbsp;";
385                        }
386                        td.innerHTML = "<div" + IE_IS_A_FUCKING_SHIT + "</div>";
387                        var td1 = td.cloneNode(true);
388                        td1.className = "label";
389                        item.appendChild(td);
390                        item.appendChild(td1);
391                } else {
392                        var label = option[0];
393                        item.className = "item";
394                        item.__msh = {
395                                item: item,
396                                label: label,
397                                action: option[1],
398                                tooltip: option[2] || null,
399                                icon: option[3] || null,
400                                activate: function() {
401                                        self.closeMenu();
402                                        self.editor.focusEditor();
403                                        this.action();
404                                }
405                        };
406                        label = label.replace(/_([a-zA-Z0-9])/, "<u>$1</u>");
407                        if (label != option[0])
408                                keys.push([ RegExp.$1, item ]);
409                        label = label.replace(/__/, "_");
410                        var td1 = doc.createElement("td");
411                        if (Xinha.is_ie)
412                                td1.unselectable = "on";
413                        item.appendChild(td1);
414                        td1.className = "icon";
415                        if (item.__msh.icon)
416      {
417        var t = Xinha.makeBtnImg(item.__msh.icon, doc);
418        td1.appendChild(t);
419      }
420      var td2 = doc.createElement("td");
421                        if (Xinha.is_ie)
422                                td2.unselectable = "on";
423                        item.appendChild(td2);
424                        td2.className = "label";
425                        td2.innerHTML = label;
426                        item.onmouseover = function() {
427                                this.className += " hover";
428                                self.editor._statusBarTree.innerHTML = this.__msh.tooltip || '&nbsp;';
429                        };
430                        item.onmouseout = function() { this.className = "item"; };
431                        item.oncontextmenu = function(ev) {
432                                this.__msh.activate();
433                                if (!Xinha.is_ie)
434                                        Xinha._stopEvent(ev);
435                                return false;
436                        };
437                        item.onmouseup = function(ev) {
438                                var timeStamp = (new Date()).getTime();
439                                if (timeStamp - self.timeStamp > 500)
440                                        this.__msh.activate();
441                                if (!Xinha.is_ie)
442                                        Xinha._stopEvent(ev);
443                                return false;
444                        };
445                        //if (typeof option[2] == "string")
446                        //item.title = option[2];
447                }
448        }
449
450        if (!Xinha.is_ie) {
451    /* FIXME: I think this is to stop the popup from running off the bottom of the screen?
452                var dx = x + div.offsetWidth - window.innerWidth + 4;
453                var dy = y + div.offsetHeight - window.innerHeight + 4;
454    // alert('dy= (' + y + '+' + div.offsetHeight + '-' + window.innerHeight + ' + 4 ) = ' + dy);
455                if (dx > 0) x -= dx;
456                if (dy > 0) y -= dy;
457    */
458                div.style.left = x + "px";
459                div.style.top = y + "px";
460        } else {
461    // To get the size we need to display the popup with some width/height
462    // then we can get the actual size of the div and redisplay the popup at the
463    // correct dimensions.
464    this.iePopup.show(ev.screenX, ev.screenY, 300,50);
465                var w = div.offsetWidth;
466                var h = div.offsetHeight;
467                this.iePopup.show(ev.screenX, ev.screenY, w, h);
468        }
469
470        this.currentMenu = div;
471        this.timeStamp = (new Date()).getTime();
472
473        Xinha._addEvent(document, "mousedown", documentClick);
474        Xinha._addEvent(this.editordoc, "mousedown", documentClick);
475        if (keys.length > 0)
476                Xinha._addEvent(this.editordoc, "keypress", keyPress);
477
478        Xinha._stopEvent(ev);
479        return false;
480};
Note: See TracBrowser for help on using the repository browser.