| 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 | |
|---|
| 11 | HTMLArea.loadStyle("menu.css", "ContextMenu"); |
|---|
| 12 | |
|---|
| 13 | function ContextMenu(editor) { |
|---|
| 14 | this.editor = editor; |
|---|
| 15 | }; |
|---|
| 16 | |
|---|
| 17 | ContextMenu._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 | |
|---|
| 28 | ContextMenu.prototype.onGenerate = function() { |
|---|
| 29 | var self = this; |
|---|
| 30 | var doc = this.editordoc = this.editor._iframe.contentWindow.document; |
|---|
| 31 | HTMLArea._addEvents(doc, ["contextmenu"], |
|---|
| 32 | function (event) { |
|---|
| 33 | return self.popupMenu(HTMLArea.is_ie ? self.editor._iframe.contentWindow.event : event); |
|---|
| 34 | }); |
|---|
| 35 | this.currentMenu = null; |
|---|
| 36 | }; |
|---|
| 37 | |
|---|
| 38 | ContextMenu.prototype.getContextMenu = function(target) { |
|---|
| 39 | var self = this; |
|---|
| 40 | var editor = this.editor; |
|---|
| 41 | var config = editor.config; |
|---|
| 42 | var menu = []; |
|---|
| 43 | var tbo = this.editor.plugins.TableOperations; |
|---|
| 44 | if (tbo) tbo = tbo.instance; |
|---|
| 45 | var i18n = ContextMenu.I18N; |
|---|
| 46 | |
|---|
| 47 | var selection = editor.hasSelectedText(); |
|---|
| 48 | if (selection) |
|---|
| 49 | menu.push([ i18n["Cut"], function() { editor.execCommand("cut"); }, null, config.btnList["cut"][1] ], |
|---|
| 50 | [ i18n["Copy"], function() { editor.execCommand("copy"); }, null, config.btnList["copy"][1] ]); |
|---|
| 51 | menu.push([ i18n["Paste"], function() { editor.execCommand("paste"); }, null, config.btnList["paste"][1] ]); |
|---|
| 52 | |
|---|
| 53 | var currentTarget = target; |
|---|
| 54 | var elmenus = []; |
|---|
| 55 | |
|---|
| 56 | var link = null; |
|---|
| 57 | var table = null; |
|---|
| 58 | var tr = null; |
|---|
| 59 | var td = null; |
|---|
| 60 | var img = null; |
|---|
| 61 | |
|---|
| 62 | function tableOperation(opcode) { |
|---|
| 63 | tbo.buttonPress(editor, opcode); |
|---|
| 64 | }; |
|---|
| 65 | |
|---|
| 66 | for (; target; target = target.parentNode) { |
|---|
| 67 | var tag = target.tagName; |
|---|
| 68 | if (!tag) |
|---|
| 69 | continue; |
|---|
| 70 | tag = tag.toLowerCase(); |
|---|
| 71 | switch (tag) { |
|---|
| 72 | case "img": |
|---|
| 73 | img = target; |
|---|
| 74 | elmenus.push(null, |
|---|
| 75 | [ i18n["Image Properties"], |
|---|
| 76 | function() { |
|---|
| 77 | editor._insertImage(img); |
|---|
| 78 | }, |
|---|
| 79 | i18n["Show the image properties dialog"], |
|---|
| 80 | config.btnList["insertimage"][1] ] |
|---|
| 81 | ); |
|---|
| 82 | break; |
|---|
| 83 | case "a": |
|---|
| 84 | link = target; |
|---|
| 85 | elmenus.push(null, |
|---|
| 86 | [ i18n["Modify Link"], |
|---|
| 87 | function() { editor.execCommand("createlink", true); }, |
|---|
| 88 | i18n["Current URL is"] + ': ' + link.href, |
|---|
| 89 | config.btnList["createlink"][1] ], |
|---|
| 90 | |
|---|
| 91 | [ i18n["Check Link"], |
|---|
| 92 | function() { window.open(link.href); }, |
|---|
| 93 | i18n["Opens this link in a new window"] ], |
|---|
| 94 | |
|---|
| 95 | [ i18n["Remove Link"], |
|---|
| 96 | function() { |
|---|
| 97 | if (confirm(i18n["Please confirm that you want to unlink this element."] + "\n" + |
|---|
| 98 | i18n["Link points to:"] + " " + link.href)) { |
|---|
| 99 | while (link.firstChild) |
|---|
| 100 | link.parentNode.insertBefore(link.firstChild, link); |
|---|
| 101 | link.parentNode.removeChild(link); |
|---|
| 102 | } |
|---|
| 103 | }, |
|---|
| 104 | i18n["Unlink the current element"] ] |
|---|
| 105 | ); |
|---|
| 106 | break; |
|---|
| 107 | case "td": |
|---|
| 108 | td = target; |
|---|
| 109 | if (!tbo) break; |
|---|
| 110 | elmenus.push(null, |
|---|
| 111 | [ i18n["Cell Properties"], |
|---|
| 112 | function() { tableOperation("TO-cell-prop"); }, |
|---|
| 113 | i18n["Show the Table Cell Properties dialog"], |
|---|
| 114 | config.btnList["TO-cell-prop"][1] ] |
|---|
| 115 | ); |
|---|
| 116 | break; |
|---|
| 117 | case "tr": |
|---|
| 118 | tr = target; |
|---|
| 119 | if (!tbo) break; |
|---|
| 120 | elmenus.push(null, |
|---|
| 121 | [ i18n["Row Properties"], |
|---|
| 122 | function() { tableOperation("TO-row-prop"); }, |
|---|
| 123 | i18n["Show the Table Row Properties dialog"], |
|---|
| 124 | config.btnList["TO-row-prop"][1] ], |
|---|
| 125 | |
|---|
| 126 | [ i18n["Insert Row Before"], |
|---|
| 127 | function() { tableOperation("TO-row-insert-above"); }, |
|---|
| 128 | i18n["Insert a new row before the current one"], |
|---|
| 129 | config.btnList["TO-row-insert-above"][1] ], |
|---|
| 130 | |
|---|
| 131 | [ i18n["Insert Row After"], |
|---|
| 132 | function() { tableOperation("TO-row-insert-under"); }, |
|---|
| 133 | i18n["Insert a new row after the current one"], |
|---|
| 134 | config.btnList["TO-row-insert-under"][1] ], |
|---|
| 135 | |
|---|
| 136 | [ i18n["Delete Row"], |
|---|
| 137 | function() { tableOperation("TO-row-delete"); }, |
|---|
| 138 | i18n["Delete the current row"], |
|---|
| 139 | config.btnList["TO-row-delete"][1] ] |
|---|
| 140 | ); |
|---|
| 141 | break; |
|---|
| 142 | case "table": |
|---|
| 143 | table = target; |
|---|
| 144 | if (!tbo) break; |
|---|
| 145 | elmenus.push(null, |
|---|
| 146 | [ i18n["Table Properties"], |
|---|
| 147 | function() { tableOperation("TO-table-prop"); }, |
|---|
| 148 | i18n["Show the Table Properties dialog"], |
|---|
| 149 | config.btnList["TO-table-prop"][1] ], |
|---|
| 150 | |
|---|
| 151 | [ i18n["Insert Column Before"], |
|---|
| 152 | function() { tableOperation("TO-col-insert-before"); }, |
|---|
| 153 | i18n["Insert a new column before the current one"], |
|---|
| 154 | config.btnList["TO-col-insert-before"][1] ], |
|---|
| 155 | |
|---|
| 156 | [ i18n["Insert Column After"], |
|---|
| 157 | function() { tableOperation("TO-col-insert-after"); }, |
|---|
| 158 | i18n["Insert a new column after the current one"], |
|---|
| 159 | config.btnList["TO-col-insert-after"][1] ], |
|---|
| 160 | |
|---|
| 161 | [ i18n["Delete Column"], |
|---|
| 162 | function() { tableOperation("TO-col-delete"); }, |
|---|
| 163 | i18n["Delete the current column"], |
|---|
| 164 | config.btnList["TO-col-delete"][1] ] |
|---|
| 165 | ); |
|---|
| 166 | break; |
|---|
| 167 | case "body": |
|---|
| 168 | elmenus.push(null, |
|---|
| 169 | [ i18n["Justify Left"], |
|---|
| 170 | function() { editor.execCommand("justifyleft"); }, null, |
|---|
| 171 | config.btnList["justifyleft"][1] ], |
|---|
| 172 | [ i18n["Justify Center"], |
|---|
| 173 | function() { editor.execCommand("justifycenter"); }, null, |
|---|
| 174 | config.btnList["justifycenter"][1] ], |
|---|
| 175 | [ i18n["Justify Right"], |
|---|
| 176 | function() { editor.execCommand("justifyright"); }, null, |
|---|
| 177 | config.btnList["justifyright"][1] ], |
|---|
| 178 | [ i18n["Justify Full"], |
|---|
| 179 | function() { editor.execCommand("justifyfull"); }, null, |
|---|
| 180 | config.btnList["justifyfull"][1] ] |
|---|
| 181 | ); |
|---|
| 182 | break; |
|---|
| 183 | } |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | if (selection && !link) |
|---|
| 187 | menu.push(null, [ i18n["Make link"], |
|---|
| 188 | function() { editor.execCommand("createlink", true); }, |
|---|
| 189 | i18n["Create a link"], |
|---|
| 190 | config.btnList["createlink"][1] ]); |
|---|
| 191 | |
|---|
| 192 | for (var i in elmenus) |
|---|
| 193 | menu.push(elmenus[i]); |
|---|
| 194 | |
|---|
| 195 | menu.push(null, |
|---|
| 196 | [ i18n["Remove the"] + " <" + currentTarget.tagName + "> " + i18n["Element"], |
|---|
| 197 | function() { |
|---|
| 198 | if (confirm(i18n["Please confirm that you want to remove this element:"] + " " + currentTarget.tagName)) { |
|---|
| 199 | var el = currentTarget; |
|---|
| 200 | var p = el.parentNode; |
|---|
| 201 | p.removeChild(el); |
|---|
| 202 | if (HTMLArea.is_gecko) { |
|---|
| 203 | if (p.tagName.toLowerCase() == "td" && !p.hasChildNodes()) |
|---|
| 204 | p.appendChild(editor._doc.createElement("br")); |
|---|
| 205 | editor.forceRedraw(); |
|---|
| 206 | editor.focusEditor(); |
|---|
| 207 | editor.updateToolbar(); |
|---|
| 208 | if (table) { |
|---|
| 209 | var save_collapse = table.style.borderCollapse; |
|---|
| 210 | table.style.borderCollapse = "collapse"; |
|---|
| 211 | table.style.borderCollapse = "separate"; |
|---|
| 212 | table.style.borderCollapse = save_collapse; |
|---|
| 213 | } |
|---|
| 214 | } |
|---|
| 215 | } |
|---|
| 216 | }, |
|---|
| 217 | i18n["Remove this node from the document"] ]); |
|---|
| 218 | return menu; |
|---|
| 219 | }; |
|---|
| 220 | |
|---|
| 221 | ContextMenu.prototype.popupMenu = function(ev) { |
|---|
| 222 | var self = this; |
|---|
| 223 | var i18n = ContextMenu.I18N; |
|---|
| 224 | if (this.currentMenu) |
|---|
| 225 | this.currentMenu.parentNode.removeChild(this.currentMenu); |
|---|
| 226 | function getPos(el) { |
|---|
| 227 | var r = { x: el.offsetLeft, y: el.offsetTop }; |
|---|
| 228 | if (el.offsetParent) { |
|---|
| 229 | var tmp = getPos(el.offsetParent); |
|---|
| 230 | r.x += tmp.x; |
|---|
| 231 | r.y += tmp.y; |
|---|
| 232 | } |
|---|
| 233 | return r; |
|---|
| 234 | }; |
|---|
| 235 | function documentClick(ev) { |
|---|
| 236 | ev || (ev = window.event); |
|---|
| 237 | if (!self.currentMenu) { |
|---|
| 238 | alert(i18n["How did you get here? (Please report!)"]); |
|---|
| 239 | return false; |
|---|
| 240 | } |
|---|
| 241 | var el = HTMLArea.is_ie ? ev.srcElement : ev.target; |
|---|
| 242 | for (; el != null && el != self.currentMenu; el = el.parentNode); |
|---|
| 243 | if (el == null) |
|---|
| 244 | self.closeMenu(); |
|---|
| 245 | //HTMLArea._stopEvent(ev); |
|---|
| 246 | //return false; |
|---|
| 247 | }; |
|---|
| 248 | var keys = []; |
|---|
| 249 | function keyPress(ev) { |
|---|
| 250 | ev || (ev = window.event); |
|---|
| 251 | HTMLArea._stopEvent(ev); |
|---|
| 252 | if (ev.keyCode == 27) { |
|---|
| 253 | self.closeMenu(); |
|---|
| 254 | return false; |
|---|
| 255 | } |
|---|
| 256 | var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); |
|---|
| 257 | for (var i = keys.length; --i >= 0;) { |
|---|
| 258 | var k = keys[i]; |
|---|
| 259 | if (k[0].toLowerCase() == key) |
|---|
| 260 | k[1].__msh.activate(); |
|---|
| 261 | } |
|---|
| 262 | }; |
|---|
| 263 | self.closeMenu = function() { |
|---|
| 264 | self.currentMenu.parentNode.removeChild(self.currentMenu); |
|---|
| 265 | self.currentMenu = null; |
|---|
| 266 | HTMLArea._removeEvent(document, "mousedown", documentClick); |
|---|
| 267 | HTMLArea._removeEvent(self.editordoc, "mousedown", documentClick); |
|---|
| 268 | if (keys.length > 0) |
|---|
| 269 | HTMLArea._removeEvent(self.editordoc, "keypress", keyPress); |
|---|
| 270 | if (HTMLArea.is_ie) |
|---|
| 271 | self.iePopup.hide(); |
|---|
| 272 | }; |
|---|
| 273 | var target = HTMLArea.is_ie ? ev.srcElement : ev.target; |
|---|
| 274 | var ifpos = getPos(self.editor._iframe); |
|---|
| 275 | var x = ev.clientX + ifpos.x; |
|---|
| 276 | var y = ev.clientY + ifpos.y; |
|---|
| 277 | |
|---|
| 278 | var div; |
|---|
| 279 | var doc; |
|---|
| 280 | if (!HTMLArea.is_ie) { |
|---|
| 281 | doc = document; |
|---|
| 282 | } else { |
|---|
| 283 | // IE stinks |
|---|
| 284 | var popup = this.iePopup = window.createPopup(); |
|---|
| 285 | doc = popup.document; |
|---|
| 286 | doc.open(); |
|---|
| 287 | 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>"); |
|---|
| 288 | doc.close(); |
|---|
| 289 | } |
|---|
| 290 | div = doc.createElement("div"); |
|---|
| 291 | if (HTMLArea.is_ie) |
|---|
| 292 | div.unselectable = "on"; |
|---|
| 293 | div.oncontextmenu = function() { return false; }; |
|---|
| 294 | div.className = "htmlarea-context-menu"; |
|---|
| 295 | if (!HTMLArea.is_ie) |
|---|
| 296 | div.style.left = div.style.top = "0px"; |
|---|
| 297 | doc.body.appendChild(div); |
|---|
| 298 | |
|---|
| 299 | var table = doc.createElement("table"); |
|---|
| 300 | div.appendChild(table); |
|---|
| 301 | table.cellSpacing = 0; |
|---|
| 302 | table.cellPadding = 0; |
|---|
| 303 | var parent = doc.createElement("tbody"); |
|---|
| 304 | table.appendChild(parent); |
|---|
| 305 | |
|---|
| 306 | var options = this.getContextMenu(target); |
|---|
| 307 | for (var i = 0; i < options.length; ++i) { |
|---|
| 308 | var option = options[i]; |
|---|
| 309 | var item = doc.createElement("tr"); |
|---|
| 310 | parent.appendChild(item); |
|---|
| 311 | if (HTMLArea.is_ie) |
|---|
| 312 | item.unselectable = "on"; |
|---|
| 313 | else item.onmousedown = function(ev) { |
|---|
| 314 | HTMLArea._stopEvent(ev); |
|---|
| 315 | return false; |
|---|
| 316 | }; |
|---|
| 317 | if (!option) { |
|---|
| 318 | item.className = "separator"; |
|---|
| 319 | var td = doc.createElement("td"); |
|---|
| 320 | td.className = "icon"; |
|---|
| 321 | var IE_IS_A_FUCKING_SHIT = '>'; |
|---|
| 322 | if (HTMLArea.is_ie) { |
|---|
| 323 | td.unselectable = "on"; |
|---|
| 324 | IE_IS_A_FUCKING_SHIT = " unselectable='on' style='height=1px'> "; |
|---|
| 325 | } |
|---|
| 326 | td.innerHTML = "<div" + IE_IS_A_FUCKING_SHIT + "</div>"; |
|---|
| 327 | var td1 = td.cloneNode(true); |
|---|
| 328 | td1.className = "label"; |
|---|
| 329 | item.appendChild(td); |
|---|
| 330 | item.appendChild(td1); |
|---|
| 331 | } else { |
|---|
| 332 | var label = option[0]; |
|---|
| 333 | item.className = "item"; |
|---|
| 334 | item.__msh = { |
|---|
| 335 | item: item, |
|---|
| 336 | label: label, |
|---|
| 337 | action: option[1], |
|---|
| 338 | tooltip: option[2] || null, |
|---|
| 339 | icon: option[3] || null, |
|---|
| 340 | activate: function() { |
|---|
| 341 | self.closeMenu(); |
|---|
| 342 | self.editor.focusEditor(); |
|---|
| 343 | this.action(); |
|---|
| 344 | } |
|---|
| 345 | }; |
|---|
| 346 | label = label.replace(/_([a-zA-Z0-9])/, "<u>$1</u>"); |
|---|
| 347 | if (label != option[0]) |
|---|
| 348 | keys.push([ RegExp.$1, item ]); |
|---|
| 349 | label = label.replace(/__/, "_"); |
|---|
| 350 | var td1 = doc.createElement("td"); |
|---|
| 351 | if (HTMLArea.is_ie) |
|---|
| 352 | td1.unselectable = "on"; |
|---|
| 353 | item.appendChild(td1); |
|---|
| 354 | td1.className = "icon"; |
|---|
| 355 | if (item.__msh.icon) |
|---|
| 356 | td1.innerHTML = "<img align='middle' src='" + item.__msh.icon + "' />"; |
|---|
| 357 | var td2 = doc.createElement("td"); |
|---|
| 358 | if (HTMLArea.is_ie) |
|---|
| 359 | td2.unselectable = "on"; |
|---|
| 360 | item.appendChild(td2); |
|---|
| 361 | td2.className = "label"; |
|---|
| 362 | td2.innerHTML = label; |
|---|
| 363 | item.onmouseover = function() { |
|---|
| 364 | this.className += " hover"; |
|---|
| 365 | self.editor._statusBarTree.innerHTML = this.__msh.tooltip || ' '; |
|---|
| 366 | }; |
|---|
| 367 | item.onmouseout = function() { this.className = "item"; }; |
|---|
| 368 | item.oncontextmenu = function(ev) { |
|---|
| 369 | this.__msh.activate(); |
|---|
| 370 | if (!HTMLArea.is_ie) |
|---|
| 371 | HTMLArea._stopEvent(ev); |
|---|
| 372 | return false; |
|---|
| 373 | }; |
|---|
| 374 | item.onmouseup = function(ev) { |
|---|
| 375 | var timeStamp = (new Date()).getTime(); |
|---|
| 376 | if (timeStamp - self.timeStamp > 500) |
|---|
| 377 | this.__msh.activate(); |
|---|
| 378 | if (!HTMLArea.is_ie) |
|---|
| 379 | HTMLArea._stopEvent(ev); |
|---|
| 380 | return false; |
|---|
| 381 | }; |
|---|
| 382 | //if (typeof option[2] == "string") |
|---|
| 383 | //item.title = option[2]; |
|---|
| 384 | } |
|---|
| 385 | } |
|---|
| 386 | |
|---|
| 387 | if (!HTMLArea.is_ie) { |
|---|
| 388 | var dx = x + div.offsetWidth - window.innerWidth + 4; |
|---|
| 389 | var dy = y + div.offsetHeight - window.innerHeight + 4; |
|---|
| 390 | if (dx > 0) x -= dx; |
|---|
| 391 | if (dy > 0) y -= dy; |
|---|
| 392 | div.style.left = x + "px"; |
|---|
| 393 | div.style.top = y + "px"; |
|---|
| 394 | } else { |
|---|
| 395 | // determine the size (did I mention that IE stinks?) |
|---|
| 396 | var foobar = document.createElement("div"); |
|---|
| 397 | foobar.className = "htmlarea-context-menu"; |
|---|
| 398 | foobar.innerHTML = div.innerHTML; |
|---|
| 399 | document.body.appendChild(foobar); |
|---|
| 400 | var w = foobar.offsetWidth; |
|---|
| 401 | var h = foobar.offsetHeight; |
|---|
| 402 | document.body.removeChild(foobar); |
|---|
| 403 | this.iePopup.show(ev.screenX, ev.screenY, w, h); |
|---|
| 404 | } |
|---|
| 405 | |
|---|
| 406 | this.currentMenu = div; |
|---|
| 407 | this.timeStamp = (new Date()).getTime(); |
|---|
| 408 | |
|---|
| 409 | HTMLArea._addEvent(document, "mousedown", documentClick); |
|---|
| 410 | HTMLArea._addEvent(this.editordoc, "mousedown", documentClick); |
|---|
| 411 | if (keys.length > 0) |
|---|
| 412 | HTMLArea._addEvent(this.editordoc, "keypress", keyPress); |
|---|
| 413 | |
|---|
| 414 | HTMLArea._stopEvent(ev); |
|---|
| 415 | return false; |
|---|
| 416 | }; |
|---|