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 | }; |
---|