| // Context Menu Plugin for HTMLArea-3.0 |
| // Sponsored by www.americanbible.org |
| // Implementation by Mihai Bazon, http://dynarch.com/mishoo/ |
| // |
| // (c) dynarch.com 2003. |
| // Distributed under the same terms as HTMLArea itself. |
| // This notice MUST stay intact for use (see license.txt). |
| // |
| // $Id: ContextMenu.js 1402 2018-02-19 07:35:49Z gogo $ |
| |
| Xinha.loadStyle("menu.css", "ContextMenu"); |
| |
| function ContextMenu(editor) { |
| this.editor = editor; |
| } |
| |
| ContextMenu._pluginInfo = { |
| name : "ContextMenu", |
| version : "1.0", |
| developer : "Mihai Bazon", |
| developer_url : "http://dynarch.com/mishoo/", |
| c_owner : "dynarch.com", |
| sponsor : "American Bible Society", |
| sponsor_url : "http://www.americanbible.org", |
| license : "htmlArea" |
| }; |
| |
| Xinha.Config.prototype.ContextMenu = { |
| disableMozillaSpellCheck : false, |
| customHooks : { } // 'a': [ ['Label', function() { alert('Action'); }, 'Tooltip', '/icon.jpg' ] ] |
| } |
| |
| ContextMenu.prototype.onGenerate = function() { |
| var self = this; |
| var doc = this.editordoc = this.editor._iframe.contentWindow.document; |
| Xinha._addEvents(doc, ["contextmenu"], |
| function (event) { |
| return self.popupMenu(Xinha.is_ie ? self.editor._iframe.contentWindow.event : event); |
| }); |
| this.currentMenu = null; |
| |
| if (this.editor.config.ContextMenu.disableMozillaSpellCheck) { |
| this.editordoc.body.spellcheck = false; // Firefox spellchecking is quite confusing for the user when they don't get the browser context menu |
| } |
| }; |
| |
| ContextMenu.prototype.getContextMenu = function(target) { |
| var self = this; |
| var editor = this.editor; |
| var config = editor.config; |
| var menu = []; |
| var tbo = this.editor.plugins.TableOperations; |
| if (tbo) tbo = tbo.instance; |
| var selection = editor.hasSelectedText(); |
| if (!Xinha.is_gecko) { |
| if (selection) { |
| menu.push([ Xinha._lc("Cut", "ContextMenu"), function() { editor.execCommand("cut"); }, null, config.btnList["cut"][1] ], |
| [ Xinha._lc("Copy", "ContextMenu"), function() { editor.execCommand("copy"); }, null, config.btnList["copy"][1] ]); |
| menu.push([ Xinha._lc("Paste", "ContextMenu"), function() { editor.execCommand("paste"); }, null, config.btnList["paste"][1] ]); |
| } |
| } |
| var currentTarget = target; |
| var elmenus = []; |
| |
| var link = null; |
| var table = null; |
| var tr = null; |
| var td = null; |
| var img = null; |
| |
| function tableOperation(opcode) { |
| tbo.buttonPress(editor, opcode); |
| } |
| |
| function insertPara(after) { |
| var el = currentTarget; |
| var par = el.parentNode; |
| var p = editor._doc.createElement("p"); |
| p.appendChild(editor._doc.createElement("br")); |
| par.insertBefore(p, after ? el.nextSibling : el); |
| var sel = editor._getSelection(); |
| var range = editor._createRange(sel); |
| if (!Xinha.is_ie) { |
| sel.removeAllRanges(); |
| range.selectNodeContents(p); |
| range.collapse(true); |
| sel.addRange(range); |
| } else { |
| range.moveToElementText(p); |
| range.collapse(true); |
| range.select(); |
| } |
| } |
| |
| for (; target; target = target.parentNode) { |
| var tag = target.tagName; |
| if (!tag) |
| continue; |
| tag = tag.toLowerCase(); |
| switch (tag) { |
| case "img": |
| img = target; |
| elmenus.push(null, |
| [ Xinha._lc("_Image Properties...", "ContextMenu"), |
| function() { |
| editor._insertImage(img); |
| }, |
| Xinha._lc("Show the image properties dialog", "ContextMenu"), |
| config.btnList["insertimage"][1] ] |
| ); |
| break; |
| case "a": |
| link = target; |
| elmenus.push(null, |
| [ Xinha._lc("_Modify Link...", "ContextMenu"), |
| function() { editor.config.btnList['createlink'][3](editor); }, |
| Xinha._lc("Current URL is", "ContextMenu") + ': ' + link.href, |
| config.btnList["createlink"][1] ], |
| |
| [ Xinha._lc("Chec_k Link...", "ContextMenu"), |
| function() { window.open(link.href); }, |
| Xinha._lc("Opens this link in a new window", "ContextMenu") ], |
| |
| [ Xinha._lc("_Remove Link...", "ContextMenu"), |
| function() { |
| if (confirm(Xinha._lc("Please confirm that you want to unlink this element.", "ContextMenu") + "\n" + |
| Xinha._lc("Link points to:", "ContextMenu") + " " + link.href)) { |
| while (link.firstChild) |
| link.parentNode.insertBefore(link.firstChild, link); |
| link.parentNode.removeChild(link); |
| } |
| }, |
| Xinha._lc("Unlink the current element", "ContextMenu") ] |
| ); |
| break; |
| case "th": |
| case "td": |
| td = target; |
| if (!tbo) break; |
| elmenus.push(null); |
| if(typeof config.btnList["TO-cell-prop"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("C_ell Properties...", "ContextMenu"), |
| function() { tableOperation("TO-cell-prop"); }, |
| Xinha._lc("Show the Table Cell Properties dialog", "ContextMenu"), |
| config.btnList["TO-cell-prop"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-cell-insert-after"] != 'undefined') |
| { |
| elmenus.push( |
| [ Xinha._lc("Insert Cell After", "ContextMenu"), |
| function() { tableOperation("TO-cell-insert-after"); }, |
| Xinha._lc("Insert Cell After", "ContextMenu"), |
| config.btnList["TO-cell-insert-after"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-cell-insert-before"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("Insert Cell Before", "ContextMenu"), |
| function() { tableOperation("TO-cell-insert-before"); }, |
| Xinha._lc("Insert Cell After", "ContextMenu"), |
| config.btnList["TO-cell-insert-before"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-cell-delete"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("Delete Cell", "ContextMenu"), |
| function() { tableOperation("TO-cell-delete"); }, |
| Xinha._lc("Delete Cell", "ContextMenu"), |
| config.btnList["TO-cell-delete"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-cell-merge"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("Merge Cells", "ContextMenu"), |
| function() { tableOperation("TO-cell-merge"); }, |
| Xinha._lc("Merge Cells", "ContextMenu"), |
| config.btnList["TO-cell-merge"][1] ] |
| ); |
| } |
| break; |
| case "tr": |
| tr = target; |
| if (!tbo) break; |
| elmenus.push(null); |
| |
| if(typeof config.btnList["TO-row-prop"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("Ro_w Properties...", "ContextMenu"), |
| function() { tableOperation("TO-row-prop"); }, |
| Xinha._lc("Show the Table Row Properties dialog", "ContextMenu"), |
| config.btnList["TO-row-prop"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-row-insert-above"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("I_nsert Row Before", "ContextMenu"), |
| function() { tableOperation("TO-row-insert-above"); }, |
| Xinha._lc("Insert a new row before the current one", "ContextMenu"), |
| config.btnList["TO-row-insert-above"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-row-insert-under"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("In_sert Row After", "ContextMenu"), |
| function() { tableOperation("TO-row-insert-under"); }, |
| Xinha._lc("Insert a new row after the current one", "ContextMenu"), |
| config.btnList["TO-row-insert-under"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-row-delete"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("_Delete Row", "ContextMenu"), |
| function() { tableOperation("TO-row-delete"); }, |
| Xinha._lc("Delete the current row", "ContextMenu"), |
| config.btnList["TO-row-delete"][1] ] |
| ); |
| } |
| break; |
| case "table": |
| table = target; |
| if (!tbo) break; |
| elmenus.push(null); |
| |
| if(typeof config.btnList["TO-table-prop"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("_Table Properties...", "ContextMenu"), |
| function() { tableOperation("TO-table-prop"); }, |
| Xinha._lc("Show the Table Properties dialog", "ContextMenu"), |
| config.btnList["TO-table-prop"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-col-insert-before"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("Insert _Column Before", "ContextMenu"), |
| function() { tableOperation("TO-col-insert-before"); }, |
| Xinha._lc("Insert a new column before the current one", "ContextMenu"), |
| config.btnList["TO-col-insert-before"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-col-insert-after"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("Insert C_olumn After", "ContextMenu"), |
| function() { tableOperation("TO-col-insert-after"); }, |
| Xinha._lc("Insert a new column after the current one", "ContextMenu"), |
| config.btnList["TO-col-insert-after"][1] ]); |
| } |
| |
| if(typeof config.btnList["TO-col-delete"] != 'undefined') |
| { |
| elmenus.push([ Xinha._lc("De_lete Column", "ContextMenu"), |
| function() { tableOperation("TO-col-delete"); }, |
| Xinha._lc("Delete the current column", "ContextMenu"), |
| config.btnList["TO-col-delete"][1] ] |
| ); |
| } |
| break; |
| case "body": |
| elmenus.push(null, |
| [ Xinha._lc("Justify Left", "ContextMenu"), |
| function() { editor.execCommand("justifyleft"); }, null, |
| config.btnList["justifyleft"][1] ], |
| [ Xinha._lc("Justify Center", "ContextMenu"), |
| function() { editor.execCommand("justifycenter"); }, null, |
| config.btnList["justifycenter"][1] ], |
| [ Xinha._lc("Justify Right", "ContextMenu"), |
| function() { editor.execCommand("justifyright"); }, null, |
| config.btnList["justifyright"][1] ], |
| [ Xinha._lc("Justify Full", "ContextMenu"), |
| function() { editor.execCommand("justifyfull"); }, null, |
| config.btnList["justifyfull"][1] ] |
| ); |
| break; |
| } |
| } |
| |
| // If there is a selection, and not a link, image, or multiple words |
| // then this may be a misspelled word, cancel the context menu so that |
| // the browser's default will appear and offer suggestions |
| if (selection && !link && !img && !editor.getSelectedHTML().replace(/<[^>]+>/, '').match(/\s/)) |
| { |
| return false; |
| } |
| |
| if (selection && !link) |
| menu.push(null, [ Xinha._lc("Make lin_k...", "ContextMenu"), |
| function() { editor.config.btnList['createlink'][3](editor); }, |
| Xinha._lc("Create a link", "ContextMenu"), |
| config.btnList["createlink"][1] ]); |
| |
| if(editor.config.ContextMenu.customHooks[currentTarget.tagName.toLowerCase()]) |
| { |
| var items = editor.config.ContextMenu.customHooks[currentTarget.tagName.toLowerCase()]; |
| |
| for (var i = 0; i < items.length; ++i) |
| { |
| menu.push(items[i]); |
| } |
| } |
| |
| for (var i = 0; i < elmenus.length; ++i) |
| menu.push(elmenus[i]); |
| |
| if (!/html|body/i.test(currentTarget.tagName)) |
| menu.push(null, |
| [ Xinha._lc("Remove the $elem Element...", 'ContextMenu', {elem: "<" + currentTarget.tagName + ">"}), |
| function() { |
| if (confirm(Xinha._lc("Please confirm that you want to remove this element:", "ContextMenu") + " " + |
| currentTarget.tagName)) { |
| var el = currentTarget; |
| var p = el.parentNode; |
| p.removeChild(el); |
| if (Xinha.is_gecko) { |
| if (p.tagName.toLowerCase() == "td" && !p.hasChildNodes()) |
| p.appendChild(editor._doc.createElement("br")); |
| editor.forceRedraw(); |
| editor.focusEditor(); |
| editor.updateToolbar(); |
| if (table) { |
| var save_collapse = table.style.borderCollapse; |
| table.style.borderCollapse = "collapse"; |
| table.style.borderCollapse = "separate"; |
| table.style.borderCollapse = save_collapse; |
| } |
| } |
| } |
| }, |
| Xinha._lc("Remove this node from the document", "ContextMenu") ], |
| [ Xinha._lc("Insert paragraph before", "ContextMenu"), |
| function() { insertPara(false); }, |
| Xinha._lc("Insert a paragraph before the current node", "ContextMenu") ], |
| [ Xinha._lc("Insert paragraph after", "ContextMenu"), |
| function() { insertPara(true); }, |
| Xinha._lc("Insert a paragraph after the current node", "ContextMenu") ] |
| ); |
| if (!menu[0]) menu.shift(); //If the menu begins with a separator, remove it for cosmetical reasons |
| return menu; |
| }; |
| |
| ContextMenu.prototype.popupMenu = function(ev) { |
| var self = this; |
| if (this.currentMenu) |
| { |
| this.closeMenu(); |
| } |
| function getPos(el) { |
| var r = { x: el.offsetLeft, y: el.offsetTop }; |
| if (el.offsetParent) { |
| var tmp = getPos(el.offsetParent); |
| r.x += tmp.x; |
| r.y += tmp.y; |
| } |
| return r; |
| } |
| function documentClick(ev) { |
| ev || (ev = window.event); |
| if (!self.currentMenu) { |
| alert(Xinha._lc("How did you get here? (Please report!)", "ContextMenu")); |
| return false; |
| } |
| var el = Xinha.is_ie ? ev.srcElement : ev.target; |
| for (; el != null && el != self.currentMenu; el = el.parentNode); |
| if (el == null) |
| self.closeMenu(); |
| //Xinha._stopEvent(ev); |
| //return false; |
| } |
| var keys = []; |
| function keyPress(ev) { |
| ev || (ev = window.event); |
| Xinha._stopEvent(ev); |
| if (ev.keyCode == 27) { |
| self.closeMenu(); |
| return false; |
| } |
| var key = String.fromCharCode(Xinha.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); |
| for (var i = keys.length; --i >= 0;) { |
| var k = keys[i]; |
| if (k[0].toLowerCase() == key) |
| k[1].__msh.activate(); |
| } |
| } |
| self.closeMenu = function() { |
| self.currentMenu.parentNode.removeChild(self.currentMenu); |
| self.currentMenu = null; |
| Xinha._removeEvent(document, "mousedown", documentClick); |
| Xinha._removeEvent(self.editordoc, "mousedown", documentClick); |
| if (keys.length > 0) |
| Xinha._removeEvent(self.editordoc, "keypress", keyPress); |
| if (Xinha.is_ie) |
| self.iePopup.hide(); |
| } |
| var target = Xinha.is_ie ? ev.srcElement : ev.target; |
| var ifpos = getPos(self.editor._htmlArea);//_iframe); |
| var x = ev.clientX + ifpos.x; |
| var y = ev.clientY + ifpos.y; |
| |
| var div; |
| var doc; |
| if (!Xinha.is_ie) { |
| doc = document; |
| } else { |
| // IE stinks |
| var popup = this.iePopup = window.createPopup(); |
| doc = popup.document; |
| doc.open(); |
| doc.write("<html><head><style type='text/css'>@import url(" + Xinha.getPluginDir('ContextMenu') + "/menu.css); html, body { padding: 0px; margin: 0px; overflow: hidden; border: 0px; }</style></head><body unselectable='yes'></body></html>"); |
| doc.close(); |
| } |
| div = doc.createElement("div"); |
| if (Xinha.is_ie) |
| div.unselectable = "on"; |
| div.oncontextmenu = function() { return false; }; |
| div.className = "htmlarea-context-menu"; |
| if (!Xinha.is_ie) { |
| div.style.visibility = "hidden"; |
| div.style.left = div.style.top = "-200px"; |
| } |
| doc.body.appendChild(div); |
| |
| var table = doc.createElement("table"); |
| div.appendChild(table); |
| table.cellSpacing = 0; |
| table.cellPadding = 0; |
| var parent = doc.createElement("tbody"); |
| table.appendChild(parent); |
| |
| var options = this.getContextMenu(target); |
| if(options === false) return false; // No context menu |
| |
| for (var i = 0; i < options.length; ++i) { |
| var option = options[i]; |
| var item = doc.createElement("tr"); |
| parent.appendChild(item); |
| if (Xinha.is_ie) |
| item.unselectable = "on"; |
| else item.onmousedown = function(ev) { |
| Xinha._stopEvent(ev); |
| return false; |
| }; |
| if (!option) { |
| item.className = "separator"; |
| var td = doc.createElement("td"); |
| td.className = "icon"; |
| var IE_IS_A_FUCKING_SHIT = '>'; |
| if (Xinha.is_ie) { |
| td.unselectable = "on"; |
| IE_IS_A_FUCKING_SHIT = " unselectable='on' style='height=1px'> "; |
| } |
| td.innerHTML = "<div" + IE_IS_A_FUCKING_SHIT + "</div>"; |
| var td1 = td.cloneNode(true); |
| td1.className = "label"; |
| item.appendChild(td); |
| item.appendChild(td1); |
| } else { |
| var label = option[0]; |
| item.className = "item"; |
| item.__msh = { |
| item: item, |
| label: label, |
| action: option[1], |
| tooltip: option[2] || null, |
| icon: option[3] || null, |
| activate: function() { |
| self.closeMenu(); |
| self.editor.focusEditor(); |
| this.action(); |
| } |
| }; |
| label = label.replace(/_([a-zA-Z0-9])/, "<u>$1</u>"); |
| if (label != option[0]) |
| keys.push([ RegExp.$1, item ]); |
| label = label.replace(/__/, "_"); |
| var td1 = doc.createElement("td"); |
| if (Xinha.is_ie) |
| td1.unselectable = "on"; |
| item.appendChild(td1); |
| td1.className = "icon"; |
| if (item.__msh.icon) |
| { |
| var t = Xinha.makeBtnImg(item.__msh.icon, doc); |
| td1.appendChild(t); |
| } |
| var td2 = doc.createElement("td"); |
| if (Xinha.is_ie) |
| td2.unselectable = "on"; |
| item.appendChild(td2); |
| td2.className = "label"; |
| td2.innerHTML = label; |
| item.onmouseover = function() { |
| this.className += " hover"; |
| self.editor._statusBarTree.innerHTML = this.__msh.tooltip || ' '; |
| }; |
| item.onmouseout = function() { this.className = "item"; }; |
| item.oncontextmenu = function(ev) { |
| this.__msh.activate(); |
| if (!Xinha.is_ie) |
| Xinha._stopEvent(ev); |
| return false; |
| }; |
| item.onmouseup = function(ev) { |
| var timeStamp = (new Date()).getTime(); |
| if (timeStamp - self.timeStamp > 500) |
| this.__msh.activate(); |
| if (!Xinha.is_ie) |
| Xinha._stopEvent(ev); |
| return false; |
| }; |
| //if (typeof option[2] == "string") |
| //item.title = option[2]; |
| } |
| } |
| |
| if (!Xinha.is_ie) { |
| /* keep then menu from overflowing the client window boundaries */ |
| |
| /* provide a virtual margin to leave a swoosh of air between the |
| meny and the window edge. This should probably go into the menu |
| container css as margin 10px instead... |
| */ |
| var margin = 10; |
| |
| if (y + div.offsetHeight + margin > window.innerHeight) |
| y = window.innerHeight - div.offsetHeight - margin; |
| if (x + div.offsetWidth + margin > window.innerWidth) |
| x = window.innerWidth - div.offsetWidth - margin; |
| |
| div.style.left = x + "px"; |
| div.style.top = y + "px"; |
| div.style.visibility = "visible"; |
| |
| } else { |
| // To get the size we need to display the popup with some width/height |
| // then we can get the actual size of the div and redisplay the popup at the |
| // correct dimensions. |
| this.iePopup.show(ev.screenX, ev.screenY, 300,50); |
| var w = div.offsetWidth; |
| var h = div.offsetHeight; |
| this.iePopup.show(ev.screenX, ev.screenY, w, h); |
| } |
| |
| this.currentMenu = div; |
| this.timeStamp = (new Date()).getTime(); |
| |
| Xinha._addEvent(document, "mousedown", documentClick); |
| Xinha._addEvent(this.editordoc, "mousedown", documentClick); |
| if (keys.length > 0) |
| Xinha._addEvent(this.editordoc, "keypress", keyPress); |
| |
| Xinha._stopEvent(ev); |
| return false; |
| }; |