| 1 |
lars |
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: context-menu.js 42 2007-04-18 10:07:34Z tiefland $
|
|
|
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 |
};
|