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