1 // This is NOT a core component of chorizo, but an optional user script.
2 // Please refer to chorizo.usage(1) for more information on user scripts.
4 // Press "f" (open link in current window) or "F" (open in new window)
5 // to activate link hints. After typing the characters for one of them,
6 // press Enter to confirm. Press Escape to abort.
8 // This is an "80% solution". It works for many web sites, but has
9 // flaws. For more background on this topic, see this blog post:
10 // https://www.uninformativ.de/blog/postings/2020-02-24/0/POSTING-en.html
12 // Based on the following, but modified for chorizo and personal taste:
14 // easy links for surf
15 // christian hahn <ch radamanthys de>, sep 2010
16 // http://surf.suckless.org/files/easy_links/
18 // link hints for surf
19 // based on chromium plugin code, adapted by Nibble<.gs@gmail.com>
20 // http://surf.suckless.org/files/link_hints/
22 // Anonymous function to get private namespace.
25 var charset = "sdfghjklertzuivbn".split("");
27 var key_follow_new_win = "F";
29 function update_highlights_or_abort()
32 var col_sel, col_unsel;
35 if (document.chorizo_hints.state === "follow_new")
37 col_unsel = "#DAFFAD";
42 col_unsel = "#A7FFF5";
46 for (var id in document.chorizo_hints.labels)
48 var label = document.chorizo_hints.labels[id];
49 var bgcol = col_unsel;
51 longest_id = Math.max(longest_id, id.length);
53 if (document.chorizo_hints.box.value !== "")
55 submatch = id.match("^" + document.chorizo_hints.box.value);
56 if (submatch !== null)
59 var box_shadow_inner = "#B00000";
60 if (id === document.chorizo_hints.box.value)
63 box_shadow_inner = "red";
64 if (label.elem.tagName.toLowerCase() === "a")
65 href_suffix = ": <span style='font-size: 75%'>" +
66 label.elem.href + "</span>";
69 var len = submatch[0].length;
70 label.span.innerHTML = "<b>" + submatch[0] + "</b>" +
71 id.substring(len, id.length) +
73 label.span.style.visibility = "visible";
75 save_parent_style(label);
76 label.elem.style.boxShadow = "0 0 5pt 2pt black, 0 0 0 2pt " +
77 box_shadow_inner + " inset";
81 label.span.style.visibility = "hidden";
82 reset_parent_style(label);
87 label.span.style.visibility = "visible";
88 label.span.innerHTML = id;
89 reset_parent_style(label);
91 label.span.style.backgroundColor = bgcol;
94 if (document.chorizo_hints.box.value.length > longest_id)
95 set_state("inactive");
100 var choice = document.chorizo_hints.box.value;
101 var was_state = document.chorizo_hints.state;
103 var elem = document.chorizo_hints.labels[choice].elem;
104 set_state("inactive"); /* Nukes labels. */
108 var tag_name = elem.tagName.toLowerCase();
109 var type = elem.type ? elem.type.toLowerCase() : "";
111 console.log("[hints] Selected elem [" + elem + "] [" + tag_name +
114 if (was_state === "follow_new" && tag_name === "a")
115 window.open(elem.href);
118 tag_name === "input" &&
121 type !== "checkbox" &&
127 tag_name === "textarea" ||
128 tag_name === "select"
136 function reset_parent_style(label)
138 if (label.parent_style !== null)
139 label.elem.style.boxShadow = label.parent_style.boxShadow;
142 function save_parent_style(label)
144 if (label.parent_style === null)
146 var style = window.getComputedStyle(label.elem);
147 label.parent_style = new Object();
148 label.parent_style.boxShadow = style.getPropertyValue("boxShadow");
152 function set_state(new_state)
154 console.log("[hints] New state: " + new_state);
156 document.chorizo_hints.state = new_state;
158 if (document.chorizo_hints.state === "inactive")
162 // Removing our box causes unwanted scrolling. Just hide it.
163 document.chorizo_hints.box.blur();
164 document.chorizo_hints.box.value = "";
165 document.chorizo_hints.box.style.visibility = "hidden";
169 if (document.chorizo_hints.labels === null)
172 // What a terrible hack.
174 // Web sites often grab key events. That interferes with our
175 // script. But of course, they tend to ignore key events if an
176 // input element is currently focused. So ... yup, we install an
177 // invisible text box (opacity 0) and focus it while follow mode
179 var box = document.chorizo_hints.box;
182 document.chorizo_hints.box = document.createElement("input");
183 box = document.chorizo_hints.box;
185 box.addEventListener("keydown", on_box_key);
186 box.addEventListener("input", on_box_input);
187 box.style.opacity = "0";
188 box.style.position = "fixed";
189 box.style.left = "0px";
190 box.style.top = "0px";
193 box.setAttribute("chorizo_input_box", "yes");
195 document.body.appendChild(box);
198 box.style.visibility = "visible";
201 update_highlights_or_abort();
205 function create_labels()
207 document.chorizo_hints.labels = new Object();
209 var selector = "a[href]:not([href=''])";
210 if (document.chorizo_hints.state !== "follow_new")
212 selector += ", input:not([type=hidden]):not([chorizo_input_box=yes])";
213 selector += ", textarea, select, button";
216 var elements = document.body.querySelectorAll(selector);
218 for (var i = 0; i < elements.length; i++)
220 var elem = elements[i];
226 // Appending the next "digit" (instead of prepending it as
227 // you would do it in a base conversion) scatters the labels
229 label_id += charset[n % charset.length];
230 n = Math.floor(n / charset.length);
233 var span = document.createElement("span");
234 span.style.border = "black 1pt solid";
235 span.style.color = "black";
236 span.style.fontFamily = "monospace";
237 span.style.fontSize = "10pt";
238 span.style.fontWeight = "normal";
239 span.style.margin = "0px 2pt";
240 span.style.position = "absolute";
241 span.style.textTransform = "lowercase";
242 span.style.visibility = "hidden";
243 span.style.zIndex = "2147483647"; // Max for WebKit according to luakit
245 document.chorizo_hints.labels[label_id] = {
248 "parent_style": null,
251 // Appending the spans as children to anchors gives better
252 // placement results, but we can *only* do this for <a> ...
253 var tag_name = elem.tagName.toLowerCase();
254 if (tag_name === "a")
256 span.style.borderTopLeftRadius = "10pt";
257 span.style.borderBottomLeftRadius = "10pt";
258 span.style.padding = "0px 2pt 0px 5pt";
259 elem.appendChild(span);
263 span.style.borderRadius = "10pt";
264 span.style.padding = "0px 5pt";
265 elem.parentNode.insertBefore(span, elem);
268 console.log("[hints] Label ID " + label_id + ", " + i +
269 " for elem [" + elem + "]");
273 function nuke_labels()
275 for (var id in document.chorizo_hints.labels)
277 var label = document.chorizo_hints.labels[id];
279 reset_parent_style(label);
281 var tag_name = label.elem.tagName.toLowerCase();
282 if (tag_name === "a")
283 label.elem.removeChild(label.span);
285 label.elem.parentNode.removeChild(label.span);
288 document.chorizo_hints.labels = null;
291 function on_box_input(e)
293 update_highlights_or_abort();
296 function on_box_key(e)
298 if (e.key === "Escape")
302 set_state("inactive");
304 else if (e.key === "Enter")
312 function on_window_key(e)
314 if (e.target.nodeName.toLowerCase() === "textarea" ||
315 e.target.nodeName.toLowerCase() === "input" ||
316 document.designMode === "on" ||
317 e.target.contentEditable === "true")
322 if (document.chorizo_hints.state === "inactive")
324 if (e.key === key_follow)
326 else if (e.key === key_follow_new_win)
327 set_state("follow_new");
331 if (document.chorizo_hints === undefined)
333 document.chorizo_hints = new Object();
334 document.chorizo_hints.box = null;
335 document.chorizo_hints.labels = null;
336 document.chorizo_hints.state = "inactive";
338 document.addEventListener("keyup", on_window_key);
340 console.log("[hints] Initialized.");
343 console.log("[hints] ALREADY INSTALLED");