Remove target _blank attributes from links.
[vimprobable/e.git] / hinting.js
blob6dd89aa56a76fc7be84aa2353441da8038bef2e4
1 /*
2     (c) 2009 by Leon Winter
3     (c) 2009, 2010 by Hannes Schueller
4     (c) 2010 by Hans-Peter Deifel
5     see LICENSE file
6 */
7 function Hints() {
8     const maxAllowedHints = 500;
10     var hintContainer;
11     var currentFocusNum = 1;
12     var hints = [];
13     var mode;
15     this.createHints = function(inputText, hintMode)
16     {
17         if (hintMode) {
18             mode = hintMode;
19         }
21         var topwin = window;
22         var top_height = topwin.innerHeight;
23         var top_width = topwin.innerWidth;
24         var xpath_expr;
26         var hintCount = 0;
27         this.clearHints();
29         function helper (win, offsetX, offsetY) {
30             var doc = win.document;
32             var win_height = win.height;
33             var win_width = win.width;
35             /* Bounds */
36             var minX = offsetX < 0 ? -offsetX : 0;
37             var minY = offsetY < 0 ? -offsetY : 0;
38             var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
39             var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
41             var scrollX = win.scrollX;
42             var scrollY = win.scrollY;
44             hintContainer = doc.createElement("div");
45             hintContainer.id = "hint_container";
47             if (typeof(inputText) == "undefined" || inputText == "") {
48                 xpath_expr = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //textarea | //button | //select";
49             } else {
50                 xpath_expr = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + inputText + "')] | //input[not(@type='hidden') and contains(., '" + inputText + "')] | //a[contains(., '" + inputText + "')] | //area[contains(., '" + inputText + "')] |  //textarea[contains(., '" + inputText + "')] | //button[contains(@value, '" + inputText + "')] | //select[contains(., '" + inputText + "')]";
51             }
53             var res = doc.evaluate(xpath_expr, doc,
54                 function (p) {
55                     return "http://www.w3.org/1999/xhtml";
56                 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
58             /* generate basic hint element which will be cloned and updated later */
59             var hintSpan = doc.createElement("span");
60             hintSpan.setAttribute("class", "hinting_mode_hint");
61             hintSpan.style.position = "absolute";
62             hintSpan.style.background = "red";
63             hintSpan.style.color = "#fff";
64             hintSpan.style.font = "bold 10px monospace";
65             hintSpan.style.zIndex = "10000000";
67             /* due to the different XPath result type, we will need two counter variables */
68             var rect, elem, text, node, show_text;
69             for (var i = 0; i < res.snapshotLength; i++)
70             {
71                 if (hintCount >= maxAllowedHints)
72                     break;
74                 elem = res.snapshotItem(i);
75                 rect = elem.getBoundingClientRect();
76                 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
77                     continue;
79                 var style = topwin.getComputedStyle(elem, "");
80                 if (style.display == "none" || style.visibility != "visible")
81                     continue;
83                 var leftpos = Math.max((rect.left + scrollX), scrollX);
84                 var toppos = Math.max((rect.top + scrollY), scrollY);
86                 /* process elements text */
87                 text = _getTextFromElement(elem);
89                 /* making this block DOM compliant */
90                 var hint = hintSpan.cloneNode(false);
91                 hint.setAttribute("id", "vimprobablehint" + hintCount);
92                 hint.style.left = leftpos + "px";
93                 hint.style.top =  toppos + "px";
94                 text = doc.createTextNode(hintCount + 1);
95                 hint.appendChild(text);
97                 hintContainer.appendChild(hint);
98                 hintCount++;
99                 hints.push({
100                     elem:       elem,
101                     number:     hintCount,
102                     text:       text,
103                     span:       hint,
104                     background: elem.style.background,
105                     foreground: elem.style.color}
106                 );
108                 /* make the link black to ensure it's readable */
109                 elem.style.color = "#000";
110                 elem.style.background = "#ff0";
111             }
113             doc.documentElement.appendChild(hintContainer);
115             /* recurse into any iframe or frame element */
116             var frameTags = ["frame","iframe"];
117             for (var f = 0; f < frameTags.length; ++f) {
118                 var frames = doc.getElementsByTagName(frameTags[f]);
119                 for (var i = 0, nframes = frames.length; i < nframes; ++i) {
120                     elem = frames[i];
121                     rect = elem.getBoundingClientRect();
122                     if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
123                         continue;
124                     helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
125                 }
126             }
127         }
129         helper(topwin, 0, 0);
131         this.clearFocus();
132         this.focusHint(1);
133         if (hintCount == 1) {
134             /* just one hinted element - might as well follow it */
135             return this.fire(1);
136         }
137     };
139     /* set focus on hint with given number */
140     this.focusHint = function(n)
141     {
142         /* reset previous focused hint */
143         var hint = _getHintByNumber(currentFocusNum);
144         if (hint !== null) {
145             hint.elem.className = hint.elem.className.replace("hinting_mode_hint_focus", "hinting_mode_hint");
146             hint.elem.style.background = "#ff0";
147         }
149         currentFocusNum = n;
151         /* mark new hint as focused */
152         var hint = _getHintByNumber(currentFocusNum);
153         if (hint !== null) {
154             hint.elem.className = hint.elem.className.replace("hinting_mode_hint", "hinting_mode_hint_focus");
155             hint.elem.style.background = "#8f0";
156         }
157     };
159     /* set focus to next avaiable hint */
160     this.focusNextHint = function()
161     {
162         var index = _getHintIdByNumber(currentFocusNum);
164         if (typeof(hints[index + 1]) != "undefined") {
165             this.focusHint(hints[index + 1].number);
166         } else {
167             this.focusHint(hints[0].number);
168         }
169     };
171     /* set focus to previous avaiable hint */
172     this.focusPreviousHint = function()
173     {
174         var index = _getHintIdByNumber(currentFocusNum);
175         if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
176             this.focusHint(hints[index - 1].number);
177         } else {
178             this.focusHint(hints[hints.length - 1].number);
179         }
180     };
182     /* filters hints matching given number */
183     this.updateHints = function(n)
184     {
185         if (n == 0) {
186             return this.createHints();
187         }
188         /* remove none matching hints */
189         var remove = [];
190         for (var i = 0; i < hints.length; ++i) {
191             var hint = hints[i];
192             if (0 != hint.number.toString().indexOf(n.toString())) {
193                 remove.push(hint.number);
194             }
195         }
197         for (var i = 0; i < remove.length; ++i) {
198             _removeHint(remove[i]);
199         }
201         if (hints.length === 1) {
202             return this.fire(hints[0].number);
203         } else {
204             return this.focusHint(n);
205         }
206     };
208     this.clearFocus = function()
209     {
210         if (document.activeElement && document.activeElement.blur) {
211             document.activeElement.blur();
212         }
213     };
215     /* remove all hints and set previous style to them */
216     this.clearHints = function()
217     {
218         if (hints.length == 0) {
219             return;
220         }
221         for (var i = 0; i < hints.length; ++i) {
222             var hint = hints[i];
223             if (typeof(hint.elem) != "undefined") {
224                 hint.elem.style.background = hint.background;
225                 hint.elem.style.color = hint.foreground;
226                 hint.span.parentNode.removeChild(hint.span);
227             }
228         }
229         hints = [];
230         hintContainer.parentNode.removeChild(hintContainer);
231         window.onkeyup = null;
232     };
234     /* fires the modeevent on hint with given number */
235     this.fire = function(n)
236     {
237         var doc, result;
238         if (!n) {
239             var n = currentFocusNum;
240         }
241         var hint = _getHintByNumber(n);
242         if (typeof(hint.elem) == "undefined")
243             return "done;";
245         var el = hint.elem;
246         var tag = el.nodeName.toLowerCase();
248         this.clearHints();
250         if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
251             el.focus();
252             if (tag == "input" || tag == "textarea") {
253                 return "insert;"
254             }
255             return "done;";
256         }
258         switch (mode)
259         {
260             case "f": result = _open(el); break;
261             case "F": result = _openNewWindow(el); break;
262             default:  result = _getElemtSource(el);
263         }
265         return result;
266     };
268     this.focusInput = function()
269     {
270         if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
271             return;
273         /* prefixing html: will result in namespace error */
274         var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
275         var r = document.evaluate(hinttags, document,
276             function(p) {
277                 return "http://www.w3.org/1999/xhtml";
278             }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
279         var i;
280         var j = 0;
281         var k = 0;
282         var first = null;
283         for (i = 0; i < r.snapshotLength; i++) {
284             var elem = r.snapshotItem(i);
285             if (k == 0) {
286                 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
287                     first = elem;
288                 } else {
289                     k--;
290                 }
291             }
292             if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
293                 elem.focus();
294                 var tag = elem.nodeName.toLowerCase();
295                 if (tag == "textarea" || tag == "input") {
296                     return "insert;";
297                 }
298                 break;
299             }
300             if (elem == document.activeElement) {
301                 j = 1;
302             }
303             k++;
304         }
305         /* no appropriate field found focused - focus the first one */
306         if (j == 0 && first !== null) {
307             first.focus();
308             var tag = elem.nodeName.toLowerCase();
309             if (tag == "textarea" || tag == "input") {
310                 return "insert;";
311             }
312         }
313     };
315     /* retrieves text content fro given element */
316     function _getTextFromElement(el)
317     {
318         if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
319             text = el.value;
320         } else if (el instanceof HTMLSelectElement) {
321             if (el.selectedIndex >= 0) {
322                 text = el.item(el.selectedIndex).text;
323             } else{
324                 text = "";
325             }
326         } else {
327             text = el.textContent;
328         }
329         return text.toLowerCase();;
330     }
332     /* retrieves the hint for given hint number */
333     function _getHintByNumber(n)
334     {
335         var index = _getHintIdByNumber(n);
336         if (index !== null) {
337             return hints[index];
338         }
339         return null;
340     }
342     /* retrieves the id of hint with given number */
343     function _getHintIdByNumber(n)
344     {
345         for (var i = 0; i < hints.length; ++i) {
346             var hint = hints[i];
347             if (hint.number === n) {
348                 return i;
349             }
350         }
351         return null;
352     }
354     /* removes hint with given number from hints array */
355     function _removeHint(n)
356     {
357         var index = _getHintIdByNumber(n);
358         if (index === null) {
359             return;
360         }
361         var hint = hints[index];
362         if (hint.number === n) {
363             hint.elem.style.background = hint.background;
364             hint.elem.style.color = hint.foreground;
365             hint.span.parentNode.removeChild(hint.span);
367             /* remove hints from all hints */
368             hints.splice(index, 1);
369         }
370     }
372     /* opens given element */
373     function _open(elem)
374     {
375         if (elem.target == "_blank") {
376             elem.removeAttribute("target");
377         }
378         _clickElement(elem);
379         return "done;";
380     }
382     /* opens given element into new window */
383     function _openNewWindow(elem)
384     {
385         var oldTarget = elem.target;
387         /* set target to open in new window */
388         elem.target = "_blank";
389         _clickElement(elem);
390         elem.target = oldTarget;
392         return "done;";
393     }
395     /* fire moudedown and click event on given element */
396     function _clickElement(elem)
397     {
398         doc = elem.ownerDocument;
399         view = elem.contentWindow;
401         var evObj = doc.createEvent("MouseEvents");
402         evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
403         elem.dispatchEvent(evObj);
405         var evObj = doc.createEvent("MouseEvents");
406         evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
407         elem.dispatchEvent(evObj);
408     }
410     /* retrieves the url of given element */
411     function _getElemtSource(elem)
412     {
413         var url = elem.href || elem.src;
414         return url;
415     }
417 hints = new Hints();