You are not logged in.

#1 2021-02-16 10:50:55

Docbroke
Member
From: India
Registered: 2015-06-13
Posts: 1,433

Help with javascript

the script I am using is one borrowed from surf scripts, which I am using with weaver. Here is the script

// ==UserScript==
// @name vimkeybindings
// @namespace renevier.fdn.fr
// @author arno <arenevier@fdn.fr>
// @licence GPL/LGPL/MPL
// @description use vim keybingings (i, j, k, l, …) to navigate a web page.
// ==/UserScript==
/*
* If you're a vim addict, and you always find yourself typing j or k in a web
* page, then wondering why it just does not go up and down like any good
* software, that user script is what you have been looking for.
*/
function up() {
    if (window.scrollByLines)
	window.scrollByLines(-1); // gecko
    else
	window.scrollBy(0, -12); // webkit
}
function down() {
    if (window.scrollByLines)
	window.scrollByLines(1); // gecko
    else
	window.scrollBy(0, 12); // webkit
}
function pageup() {
    if (window.scrollByPages)
	window.scrollByPages(-1); // gecko
    else
	window.scrollBy(0, 0 - _pageScroll()); // webkit
}
function pagedown() {
    if (window.scrollByPages)
	window.scrollByPages(1); // gecko
    else
	window.scrollBy(0, _pageScroll()); // webkit
}
function right() {
    window.scrollBy(15, 0);
}
function left() {
    window.scrollBy(-15, 0);
}
function home() {
    window.scroll(0, 0);
}
function bottom() {
    window.scroll(document.width, document.height);
}
// If you don't like default key bindings, customize here. 
// if you want to use the combination 'Ctrl + b' (for example), use '^b'
var bindings = {
    'h' : left, 
    'l' : right,
    'k' : up,
    'j' : down,
    'g' : home,
    'G' : bottom,
    //'^b': pageup,
    //'^f': pagedown,
}
function isEditable(element) {
    
    if (element.nodeName.toLowerCase() == "textarea")
	return true;
    // we don't get keypress events for text input, but I don't known
    // if it's a bug, so let's test that
    if (element.nodeName.toLowerCase() == "input" && element.type == "text")
	return true;
    // element is editable
    if (document.designMode == "on" || element.contentEditable == "true") {
	return true;
    }
    
    return false;
}
function keypress(evt) {
    var target = evt.target;
	    
    // if we're on a editable element, we probably don't want to catch
    // keypress, we just want to write the typed character.
    if (isEditable(target))
	return;
    var key = String.fromCharCode(evt.charCode);
    if (evt.ctrlKey) {
	key = '^' + key;
    }
    var fun = bindings[key];
    if (fun)
	fun();
}
function _pageScroll() {
    // Gecko algorithm
    // ----------------
    // The page increment is the size of the page, minus the smaller of
    // 10% of the size or 2 lines.  
    return window.innerHeight - Math.min(window.innerHeight / 10, 24);
}
window.addEventListener("keypress", keypress, false);

Now my troubles are

1)  If I try to use key binding with ctrl key, like '^b', as suggested in the script, it doesn't work, only vim like single key bindings work, I don't know how to solve that.
2)  I would like to add a passsthrough mode, for example when '^z' is pressed, all keybindings are inactivated, and on pressing '^z' again keybindings start working again. ( so that when in some text-fields, vim like bindings create trouble, this passthrough mode can be activated)
3) I am not able to bind functionkeys either.

Offline

#2 2021-02-16 15:24:21

Trilby
Inspector Parrot
Registered: 2011-11-29
Posts: 29,523
Website

Re: Help with javascript

I suspect the problem is the use of "keypress" as noted in the implementation notes here.  Use keydown instead.

For "passthrough" just create a variable that get's toggled with that key - and check the variable like the code currently checks whether the current element is editable: if the toggle is on (passthrough) ignore any key binding other than the passthrough toggle key.

This works:

// ==UserScript==
// @name keybinds
// @description example of binding keys in javascript
// ==/UserScript==

var needle = "";
var passthrough = false;

document.addEventListener('keydown', function(e) {
	/* Process escape early, to unfocus an input element */
	if (e.key == 'Escape') e.target.blur();
	/* Don't process keypresses on editable elements */
	if (  (e.target.nodeName.toLowerCase() == "textarea")
		|| (e.target.nodeName.toLowerCase() == "input" && e.target.type == "text")
		|| (document.designMode == "on" || e.target.contentEditable == "true") )
		return;
	/* check modifiers: currently just control */
	var key = e.key;
	if (e.ctrlKey) key = '^' + key;
	/* Check passthrough */
	if (key == '^z') passthrough = !passthrough;
	if (passthrough) return;
	/* Key bindings */
	switch (key) {
		case 'h': window.history.back(); break;
		case 'j': window.scrollBy({top: 200, behavior: 'smooth' }); break;
		case 'k': window.scrollBy({top: -200, behavior: 'smooth' }); break;
		case 'l': window.history.forward(); break;
		case 'f': needle = prompt("Search String"); window.find(needle, false, false, true); break;
		case 'n': window.find(needle, false, false, true); break;
		case 'r': location.reload(); break;
		case '^g': alert("control + g"); break;

		case 'J': window.scrollTo(0, document.body.scrollHeight); break;
		case 'K': window.scrollTo(0, 0); break;
		case 'R': location.reload(true); break;
	}
});

Note the '^g' binding just as an example of using a modified key.  Look up the javascript docs for various key names for non alphanumeric keys, e.g., 'Enter' and 'Escape'.

EDIT: I added this (with Alt and Mod4/Super) to the weaver examples - I also create an additional example merging modal keys and hinting.  If you use the modalkeys_with_hints.js, you should not use either modalkeys.js or hints.js as it replaces them.

Last edited by Trilby (2021-02-16 16:23:29)


"UNIX is simple and coherent..." - Dennis Ritchie, "GNU's Not UNIX" -  Richard Stallman

Offline

#3 2021-02-16 18:31:48

Docbroke
Member
From: India
Registered: 2015-06-13
Posts: 1,433

Re: Help with javascript

Thanks a ton. You solved the issue.
I did few updates to your script, which adds, zoom+/- keys and readingmode. I did small update to the detection of editable elements, which now works better, ( use text input of https://invidious.tube for testing).
Here is the script

// ==UserScript==
// @name keybinds
// @description example of binding keys in javascript
// ==/UserScript==

var needle = "";
var passthrough = false;

if(window.content && window.content.document && window.content.document.simplyread_original === undefined) window.content.document.simplyread_original = false;
function simplyread(nostyle, nolinks)
{
	/* count the number of <p> tags that are direct children of parenttag */
    document.body.style.zoom = "150%";
	function count_p(parenttag)
	{
		var n = 0;
		var c = parenttag.childNodes;
		for (var i = 0; i < c.length; i++) {
			if (c[i].tagName == "p" || c[i].tagName == "P")
				n++;
		}
		return n;
	}
	
	var doc;
	doc = (document.body === undefined)
	      ? window.content.document : document;
	
	/* if simplyread_original is set, then the simplyread version is currently active,
	 * so switch to the simplyread_original html */
	if (doc.simplyread_original) {
		doc.body.innerHTML = doc.simplyread_original;
		for (var i = 0; i < doc.styleSheets.length; i++)
			doc.styleSheets[i].disabled = false;
		doc.simplyread_original = false;
		return 0;
	}
	
	doc.simplyread_original = doc.body.innerHTML;
	doc.body.innerHTML = doc.body.innerHTML.replace(/<br[^>]*>\s*<br[^>]*>/g, "<p>");
	
	var biggest_num = 0;
	var biggest_tag;
	
	/* search for tag with most direct children <p> tags */
	var t = doc.getElementsByTagName("*");
	for (var i = 0; i < t.length; i++) {
		var p_num = count_p(t[i]);
		if (p_num > biggest_num) {
			biggest_num = p_num;
			biggest_tag = t[i];
		}
	}
	
	if (biggest_num == 0) return 1;
	
	/* save and sanitise content of chosen tag */
	var fresh = doc.createElement("div");
	fresh.innerHTML = biggest_tag.innerHTML;
	fresh.innerHTML = fresh.innerHTML.replace(/<\/?font[^>]*>/g, "");
	fresh.innerHTML = fresh.innerHTML.replace(/style="[^"]*"/g, "");
	if(nolinks)
		fresh.innerHTML = fresh.innerHTML.replace(/<\/?a[^>]*>/g, "");
	fresh.innerHTML = fresh.innerHTML.replace(/<\/?span[^>]*>/g, "");
	fresh.innerHTML = fresh.innerHTML.replace(/<style[^>]*>/g, "<style media=\"aural\">"); /* ensures contents of style tag are ignored */
	
	for (var i = 0; i < doc.styleSheets.length; i++)
		doc.styleSheets[i].disabled = true;
	
	srstyle =

        "p{margin:0ex auto;} h1,h2,h3,h4{font-weight:normal}" +
		"p+p{text-indent:2em;} body{background:#cccccc none}" +
		"img{display:block; max-width: 32em; padding:1em; margin: auto}" +
		"h1{text-align:center;text-transform:uppercase}" +
		"div#sr{width:42em; padding:4em; padding-top:2em;" +
		"background-color:#F4ECD8; margin:auto; line-height:1.4;" +
		"text-align:justify; hyphens:auto; font-family:sans-serif; }"; +
        "div,pre,textarea,body,input,td,tr,p{background-color: #000000 !important; background-image: none !important; font-size:26 !important; }" +
        "a {color: #70e070 !important;} a:hover,a:focus { color: #7070e0 !important;} a:visited { color: #e07070 !important;}"

		/* text-rendering:optimizeLegibility; - someday this will work,
		 *   but at present it just ruins justify, so is disabled */
	
	doc.body.innerHTML =
		"<style type=\"text/css\">" + (nostyle ? "" : srstyle) + "</style>" +
		"<div id=\"sr\">" + "<h1>"+doc.title+"</h1>" + fresh.innerHTML + "</div>";
    
    return 0;
}

document.addEventListener('keydown', function(e) {
	/* Process escape early, to unfocus an input element */
	if (e.key == 'Escape') e.target.blur();
	/* Don't process keypresses on editable elements */
//	if (  (e.target.nodeName.toLowerCase() == "textarea")
//		|| (e.target.nodeName.toLowerCase() == "input" && e.target.type == "text")
//		|| (document.designMode == "on" || e.target.contentEditable == "true") )
    if (e.target.nodeName.toLowerCase() === "textarea" ||
        e.target.nodeName.toLowerCase() === "input" ||
        document.designMode === "on" ||
        e.target.contentEditable === "true")
		return;
	/* check modifiers: currently just control */
	var key = e.key;
	if (e.ctrlKey) key = '^' + key;
    if (e.altKey) key = 'A-' + key;
	if (e.metaKey) key = 'M-' + key; /* meta = mod4 aka "Super" */
	/* Check passthrough */
	if (key == '^z') passthrough = !passthrough;
	if (passthrough) return;
	/* Key bindings */
	switch (key) {
        /* modifiers must be in order of M-A-^ */
		/* e.g., Ctrl+c = ^c, Mod+Ctrl+c = M-^c, Alt+Mod+r = M-A-r */
		case 'h': window.history.back(); break;
		case '[': window.history.back(); break;
		case 'j': window.scrollBy({top: 200, behavior: 'smooth' }); break;
		case 'k': window.scrollBy({top: -200, behavior: 'smooth' }); break;
		case 'l': window.history.forward(); break;
		case ']': window.history.forward(); break;
		case '/': needle = prompt("Search String"); window.find(needle, false, false, true); break;
		case 'n': window.find(needle, false, false, true); break;
		case 'F5': location.reload(); break;
		case 'G': window.scrollTo(0, document.body.scrollHeight); break;
		case 'g': window.scrollTo(0, 0); break;
		case 'R': location.reload(true); break;

                case '+': document.body.style.zoom = "150%"; break;
                case '=': document.body.style.zoom = (window.innerWidth * 150 / document.body.clientWidth) + '%'; break;
                case '-': document.body.style.zoom = (window.innerWidth * 75 / document.body.clientWidth) + '%'; break;

                case 'r': simplyread(); break;
	}
});

For hinting this script from lariza (modified to include passthrough), works better.

// This is NOT a core component of lariza, but an optional user script.
// Please refer to lariza.usage(1) for more information on user scripts.

// Press "f" (open link in current window) or "F" (open in new window)
// to activate link hints. After typing the characters for one of them,
// press Enter to confirm. Press Escape to abort.
//
// This is an "80% solution". It works for many web sites, but has
// flaws. For more background on this topic, see this blog post:
// https://www.uninformativ.de/blog/postings/2020-02-24/0/POSTING-en.html

// Based on the following, but modified for lariza and personal taste:
//
// easy links for surf
// christian hahn <ch radamanthys de>, sep 2010
// http://surf.suckless.org/files/easy_links/
//
// link hints for surf
// based on chromium plugin code, adapted by Nibble<.gs@gmail.com>
// http://surf.suckless.org/files/link_hints/

// Anonymous function to get private namespace.
(function() {

var charset = "sdfghjklertzuivbn".split("");
var key_follow = "f";
var key_follow_new_win = "F";

function update_highlights_or_abort()
{
    var submatch;
    var col_sel, col_unsel;
    var longest_id = 0;

    if (document.lariza_hints.state === "follow_new")
    {
        col_unsel = "#DAFFAD";
        col_sel = "#FF5D00";
    }
    else
    {
        col_unsel = "#A7FFF5";
        col_sel = "#33FF00";
    }

    for (var id in document.lariza_hints.labels)
    {
        var label = document.lariza_hints.labels[id];
        var bgcol = col_unsel;

        longest_id = Math.max(longest_id, id.length);

        if (document.lariza_hints.box.value !== "")
        {
            submatch = id.match("^" + document.lariza_hints.box.value);
            if (submatch !== null)
            {
                var href_suffix = "";
                var box_shadow_inner = "#B00000";
                if (id === document.lariza_hints.box.value)
                {
                    bgcol = col_sel;
                    box_shadow_inner = "red";
                    if (label.elem.tagName.toLowerCase() === "a")
                        href_suffix = ": <span style='font-size: 75%'>" +
                                      label.elem.href + "</span>";
                }

                var len = submatch[0].length;
                label.span.innerHTML = "<b>" + submatch[0] + "</b>" +
                                       id.substring(len, id.length) +
                                       href_suffix;
                label.span.style.visibility = "visible";

                save_parent_style(label);
                label.elem.style.boxShadow = "0 0 5pt 2pt black, 0 0 0 2pt " +
                                             box_shadow_inner + " inset";
            }
            else
            {
                label.span.style.visibility = "hidden";
                reset_parent_style(label);
            }
        }
        else
        {
            label.span.style.visibility = "visible";
            label.span.innerHTML = id;
            reset_parent_style(label);
        }
        label.span.style.backgroundColor = bgcol;
    }

    if (document.lariza_hints.box.value.length > longest_id)
        set_state("inactive");
}

function open_match()
{
    var choice = document.lariza_hints.box.value;
    var was_state = document.lariza_hints.state;

    var elem = document.lariza_hints.labels[choice].elem;
    set_state("inactive");  /* Nukes labels. */

    if (elem)
    {
        var tag_name = elem.tagName.toLowerCase();
        var type = elem.type ? elem.type.toLowerCase() : "";

        console.log("[hints] Selected elem [" + elem + "] [" + tag_name +
                    "] [" + type + "]");

        if (was_state === "follow_new" && tag_name === "a")
            window.open(elem.href);
        else if (
            (
                tag_name === "input" &&
                type !== "button" &&
                type !== "color" &&
                type !== "checkbox" &&
                type !== "file" &&
                type !== "radio" &&
                type !== "reset" &&
                type !== "submit"
            ) ||
            tag_name === "textarea" ||
            tag_name === "select"
        )
            elem.focus();
        else
            elem.click();
    }
}

function reset_parent_style(label)
{
    if (label.parent_style !== null)
        label.elem.style.boxShadow = label.parent_style.boxShadow;
}

function save_parent_style(label)
{
    if (label.parent_style === null)
    {
        var style = window.getComputedStyle(label.elem);
        label.parent_style = new Object();
        label.parent_style.boxShadow = style.getPropertyValue("boxShadow");
    }
}

function set_state(new_state)
{
    console.log("[hints] New state: " + new_state);

    document.lariza_hints.state = new_state;

    if (document.lariza_hints.state === "inactive")
    {
        nuke_labels();

        // Removing our box causes unwanted scrolling. Just hide it.
        document.lariza_hints.box.blur();
        document.lariza_hints.box.value = "";
        document.lariza_hints.box.style.visibility = "hidden";
    }
    else
    {
        if (document.lariza_hints.labels === null)
            create_labels();

        // What a terrible hack.
        //
        // Web sites often grab key events. That interferes with our
        // script. But of course, they tend to ignore key events if an
        // input element is currently focused. So ... yup, we install an
        // invisible text box (opacity 0) and focus it while follow mode
        // is active.
        var box = document.lariza_hints.box;
        if (box === null)
        {
            document.lariza_hints.box = document.createElement("input");
            box = document.lariza_hints.box;

            box.addEventListener("keydown", on_box_key);
            box.addEventListener("input", on_box_input);
            box.style.opacity = "0";
            box.style.position = "fixed";
            box.style.left = "0px";
            box.style.top = "0px";
            box.type = "text";

            box.setAttribute("lariza_input_box", "yes");

            document.body.appendChild(box);
        }

        box.style.visibility = "visible";
        box.focus();

        update_highlights_or_abort();
    }
}

function create_labels()
{
    document.lariza_hints.labels = new Object();

    var selector = "a[href]:not([href=''])";
    if (document.lariza_hints.state !== "follow_new")
    {
        selector += ", input:not([type=hidden]):not([lariza_input_box=yes])";
        selector += ", textarea, select, button";
    }

    var elements = document.body.querySelectorAll(selector);

    for (var i = 0; i < elements.length; i++)
    {
        var elem = elements[i];

        var label_id = "";
        var n = i;
        do
        {
            // Appending the next "digit" (instead of prepending it as
            // you would do it in a base conversion) scatters the labels
            // better.
            label_id += charset[n % charset.length];
            n = Math.floor(n / charset.length);
        } while (n !== 0);

        var span = document.createElement("span");
        span.style.border = "black 1pt solid";
        span.style.color = "black";
        span.style.fontFamily = "monospace";
        span.style.fontSize = "10pt";
        span.style.fontWeight = "normal";
        span.style.margin = "0px 2pt";
        span.style.position = "absolute";
        span.style.textTransform = "lowercase";
        span.style.visibility = "hidden";
        span.style.zIndex = "2147483647";  // Max for WebKit according to luakit

        document.lariza_hints.labels[label_id] = {
            "elem": elem,
            "span": span,
            "parent_style": null,
        };

        // Appending the spans as children to anchors gives better
        // placement results, but we can *only* do this for <a> ...
        var tag_name = elem.tagName.toLowerCase();
        if (tag_name === "a")
        {
            span.style.borderTopLeftRadius = "10pt";
            span.style.borderBottomLeftRadius = "10pt";
            span.style.padding = "0px 2pt 0px 5pt";
            elem.appendChild(span);
        }
        else
        {
            span.style.borderRadius = "10pt";
            span.style.padding = "0px 5pt";
            elem.parentNode.insertBefore(span, elem);
        }

        console.log("[hints] Label ID " + label_id + ", " + i +
                    " for elem [" + elem + "]");
    }
}

function nuke_labels()
{
    for (var id in document.lariza_hints.labels)
    {
        var label = document.lariza_hints.labels[id];

        reset_parent_style(label);

        var tag_name = label.elem.tagName.toLowerCase();
        if (tag_name === "a")
            label.elem.removeChild(label.span);
        else
            label.elem.parentNode.removeChild(label.span);
    }

    document.lariza_hints.labels = null;
}

function on_box_input(e)
{
    update_highlights_or_abort();
}

function on_box_key(e)
{
    if (e.key === "Escape")
    {
        e.preventDefault();
        e.stopPropagation();
        set_state("inactive");
    }
    else if (e.key === "Enter")
    {
        e.preventDefault();
        e.stopPropagation();
        open_match();
    }
}

function on_window_key(e)
{
    if (e.target.nodeName.toLowerCase() === "textarea" ||
        e.target.nodeName.toLowerCase() === "input" ||
        document.designMode === "on" ||
        e.target.contentEditable === "true")
    {
        return;
    }
	if (e.key == '^z') passthrough = !passthrough;
	if (passthrough) return;

    if (document.lariza_hints.state === "inactive")
    {
        if (e.key === key_follow)
            set_state("follow");
        else if (e.key === key_follow_new_win)
            set_state("follow_new");
    }
}

if (document.lariza_hints === undefined)
{
    document.lariza_hints = new Object();
    document.lariza_hints.box = null;
    document.lariza_hints.labels = null;
    document.lariza_hints.state = "inactive";

    document.addEventListener("keyup", on_window_key);

    console.log("[hints] Initialized.");
}
else
    console.log("[hints] ALREADY INSTALLED");

}());

Last edited by Docbroke (2021-02-19 18:00:53)

Offline

#4 2021-02-16 18:54:59

Trilby
Inspector Parrot
Registered: 2011-11-29
Posts: 29,523
Website

Re: Help with javascript

Thanks for the revision on input detection - that is better (I hadn't tested that much and just kept the original checks from the source in post #1).

That hinting code is a bit involved.  What do you feel it does better (than the code in modalkeys_with_hinting.js)?

The original hinting.js hasn't been updated to include any of the improvements in the modalkeys_with_hinting.js (yet), so that one is pretty limited.


"UNIX is simple and coherent..." - Dennis Ritchie, "GNU's Not UNIX" -  Richard Stallman

Offline

#5 2021-02-17 02:38:04

Docbroke
Member
From: India
Registered: 2015-06-13
Posts: 1,433

Re: Help with javascript

Original hints.js only works when viewing top of the page, in other cases hint labels are misaligned. Additionaly labels overlap the link text. This issues are solved in the script I posted and that script also add one more thing, when you type the label text it highlights and displays the url so you can confirm that you are following the right link before you click enter.

Offline

Board footer

Powered by FluxBB