Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // VimNav.user.js
- // Version 1.1
- //
- // ==UserScript==
- // @name VimNav
- // @author Bernd Pol <bernd.pol@online.de>
- // @licence GPL
- // @description Vim-like navigation in a webkit based browser.
- // ==/UserScript==
- /*
- * VERSIONS
- *
- * 1.1 Bugfix
- * - proper handling of password fields
- * (no more VarNav functions called from inside)
- * - input fields now properly selected
- * - labels display unaltered
- * (could have been changed to uppercase if node parent did so)
- * New Features
- * - scroll height adjustable by factors 1, 2, 3, and 4
- * (by default bound to "^1", "^2", "^3", and "^4")
- *
- * 1.0 Initial Release
- */
- /*
- * TODO
- * - second level commands (like "gu", etc.)
- * - repeat counts
- */
- /*
- * KNOWN ISSUES
- *
- * On more complicated structured pages the browser (Midori only?) will call up
- * this script several times in a row. This will cause commands to be repeatedly
- * called up, once per running instance.
- * There is no way to detect such a multiple instatiating on the Javascript
- * level however which means:
- * - vertical movements will occur over multiples of the programmed distance
- * (to cope for this situation a window height adjust factor has been
- * introduced which will effectively reduce the height by which each movement
- * call in a row will scroll the window, by default bound to ^1, ^2, ^3, ^4)
- * - hint labels will take significant more time to show up
- * (hint-based navigation will be unaffected in most cases, however, as only
- * one instance will effectively control the behaviour)
- */
- /*
- * CREDITS
- *
- * This is inspired by (and was partially copied from):
- * - the "vimkeybindings" greasemonkey script by "arno <arenevier@fdn.fr>"
- * <http://userscripts.org/scripts/review/32369>
- * - the "KeyNav" greasemonkey script, version 0.1.1 beta by Itamar Benzaken
- * <http://userscripts.org/scripts/review/33808>
- * - the "follow.js" uzbl link following script
- * <http://www.uzbl.org/wiki/follow.sh>
- * - and the "goup", javascript version, uzbl page/domain switching script
- * <http://www.uzbl.org/wiki/go-up>
- */
- /*
- * Note: This is a Midori browser specific script.
- * Using other browsers may require some rewrite.
- */
- /****************
- *** Contents ***
- ****************/
- /*
- * Configuration
- * Key Bindings Configuration
- * var keyBindings
- * var navKeyBindings
- * Basic Label Setup
- * var autoselectLink
- * var collSequence
- * var overlayId
- * var shortenLabels
- * var nodeLabelSize
- * var nodeLabelColor
- * var nodeLabelBckground
- * var partialLabelColor
- * var partialLabelBackground
- * var foundLabelColor
- * var foundLabelBackground
- * var nodeOpacity
- * Simple Navigation
- * Small vertical movements
- * function goUp()
- * function goDown()
- * Halfpage vertical movements
- * function goHalfUp()
- * function goHalfDown()
- * Fullpage vertical movements
- * function goPageUp()
- * function goPageDown()
- * Horizontal movements
- * function goRight()
- * function goLeft()
- * Document wide vertical movements
- * function goTop()
- * function goBottom()
- * URL dependent stuff
- * function goUrlPageUp()
- * function goUrlDomainUp()
- * Adjust Movements
- * function adjustHeight1()
- * function adjustHeight2()
- * function adjustHeight3()
- * function adjustHeight4()
- * Link Following
- * Common variables
- * Clear all link information
- * function clearLinkInfo()
- * Label handling
- * function labelText( posNumber )
- * function labelNumber( labelString )
- * function positionOf( thisElement )
- * Maintaining navigation information
- * function isVisible( thisElement )
- * function isDisplayable( thisElement )
- * function findClickableNodes()
- * function createOverlays()
- * function showOverlays( labelHead )
- * function hideOverlays()
- * function redisplayOverlays()
- * function removeOverlays()
- * Navigating
- * function isValidLabel( thisHead )
- * function navigateByLabel()
- * function clickLabel( labelPos )
- * function startNavigating()
- * function startNavNewTab()
- * function stopNavigating()
- * Keyboard Interface
- * function isEditable( element )
- * function evalKey( keyEvent )
- * function hasValidNavKey()
- * function keyHandler( keyEvent )
- * function keyRepeatHandler( keyEvent )
- * Script Body
- * Initialization
- * Register keyboard event handlers
- */
- // -----------------------------------------------------------------------------
- // start of configuration section
- // -----------------------------------------------------------------------------
- /*********************
- *** Configuration ***
- *********************/
- /*
- * Key Bindings Configuration
- * ==========================
- *
- * To use a Ctrl-key combination prepend "^" before the character
- * (e.g. "^b" denotes the Ctrl-b control).
- * Character case is implicit, e.g. "B" denotes Shift-b.
- *
- * The Esc key has been set up as universal stop action key which is treated
- * separately in the keydown event handler.
- *
- * NOTE:
- * Javascript apparently has problems to properly process language specific
- * keyboards (like umlauts on a german layout), thus best use the ASCII
- * character set only.
- *
- * NOTE:
- * Midori processes its own shortcuts before they reach this script. So make
- * sure there are no conflicts.
- * --> If necessary redefine conflicting Midori specific shortcuts there in
- * Tools->Customize Shortcuts...
- */
- /*
- * Standard key bindings
- * ---------------------
- */
- var keyBindings = {
- "h" : goLeft,
- "l" : goRight,
- "k" : goHalfUp,
- "j" : goHalfDown,
- "K" : goUp,
- "J" : goDown,
- "u" : goPageUp,
- "d" : goPageDown,
- "t" : goTop,
- "b" : goBottom,
- "U" : goUrlPageUp,
- "D" : goUrlDomainUp,
- "^1" : adjustHeight1, // factors to decrease the effective window
- "^2" : adjustHeight2, // height in scrolling (sometimes useful
- "^3" : adjustHeight3, // when this script was called up multiple
- "^4" : adjustHeight4, // times in a row)
- "f" : startNavigating, // if autoselecting, open match in current tab
- "F" : startNavNewTab, // if autoselecting, open match in a new tab
- }
- /*
- * Navigation key bindings
- * -----------------------
- * These provide some page movement actions when navigating by labels where the
- * usual navigation keys are not available.
- *
- * NOTE: These bindings are only valid when navigating. Otherwise the standard
- * bindings defined above apply.
- */
- var navKeyBindings = {
- "^h" : goLeft,
- "^l" : goRight,
- "^k" : goHalfUp,
- "^j" : goHalfDown,
- "^t" : goTop,
- "^b" : goBottom,
- "^s" : redisplayOverlays,
- "^d" : hideOverlays,
- "^r" : repositionLabels, // sometimes useful if labels overlap
- "^f" : stopNavigating,
- }
- /*
- * Basic Label Setup
- * =================
- */
- /*
- * Link selection behaviour
- * ------------------------
- */
- /*
- * How to select a link
- * --------------------
- * If true this will select link as soon as there is a match.
- * Otherwise the user must confirm the selection:
- * Return: open link in this tab
- * Space: open link in new tab
- * Although this requires an additional keypress it allows for selecting another
- * link (via backspace correction).
- */
- var autoselectLink = true;
- /*
- * If not autoselecting we need some special keys to trigger the selection.
- */
- var openInThisTab = "g";
- var openInNewTab = "t";
- /*
- * Collateral Sequences
- * -----------------------
- *
- * There are several label number representations possible. Just uncomment the
- * one you want.
- *
- * Note that the first symbol in sequence will be treated as zero equivalent
- * and the labels will be get those zero equivalents prepended if necessary,
- * e.g. the number 1 in a three-digit "alpha" sequence will show as "aab".
- */
- var collSequence = "optimal"; // automatic: find shortest to type sequence
- // var collSequence = "numeric"; // decimal numbers
- // var collSequence = "alpha"; // lower case letter sequences
- // var collSequence = "longalpha"; // lower followed by upper case letters
- // This can be any unique sequence of symbols, e.g.:
- // var collSequence = "asdfghjkl"; // home row keys (for touch typers)
- // var collSequence = "uiophjklnm"; // right hand only
- /*
- * The overlay identification
- * --------------------------
- * This will be prepended to every label overlay element. Redefine if there are
- * name conflicts.
- */
- var overlayId = "VimNavLabel";
- /*
- * Node label display
- * ------------------
- */
- var shortenLabels = true; // show matching labels and selectable digits only
- /*
- * This defines the font size shown in the labels. It may be an absolute number
- * with a trailing "px" giving the font height in pixels, a number with trailing
- * "%" giving the height relative to the parents font size, or one of the
- * predefined font size property values (ranging from smallest to largest):
- * "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"
- * or defining a relative value to the parent font size:
- * "smaller", "larger"
- * ( see also: http://www.w3schools.com/jsref/prop_style_fontsize.asp )
- */
- var nodeLabelSize = "12px";
- //var nodeLabelSize = "85%";
- //var nodeLabelSize = "small";
- var nodeLabelColor = "red";
- var nodeLabelBackground = "lightyellow";
- var partialLabelColor = "blue";
- var partialLabelBackground = "lightgreen";
- var foundLabelColor = "yellow";
- var foundLabelBackground = "red";
- var nodeOpacity = 0.6;
- // -----------------------------------------------------------------------------
- // end of configuration section
- // -----------------------------------------------------------------------------
- /*************************
- *** Simple Navigation ***
- ************************/
- var wndHeight; // height of the currently focused window
- var wndHeightAdjust = 1; // factor to decrease the height movement
- var keyLabel; // label value of current key
- var keyCode; // code value of current key
- var keyAction = 0; // the action to perform on the current key
- /*
- * Small vertical movements
- */
- function goUp() {
- window.scrollBy( 0, -5 );
- }
- function goDown() {
- window.scrollBy( 0, 5 );
- }
- /*
- * Halfpage vertical movements
- */
- function goHalfUp() {
- window.scrollBy( 0, -1 * ((wndHeight / wndHeightAdjust) / 2) );
- }
- var sc = 0;
- function goHalfDown() {
- window.scrollBy( 0, (wndHeight / wndHeightAdjust) / 2 );
- }
- /*
- * Fullpage vertical movements
- */
- function goPageUp() {
- window.scrollBy( 0, -1 * (wndHeight / wndHeightAdjust) );
- }
- function goPageDown() {
- window.scrollBy( 0, wndHeight / wndHeightAdjust );
- }
- /*
- * Horizontal movements
- */
- function goRight() {
- window.scrollBy( 15, 0 );
- }
- function goLeft() {
- window.scrollBy( -15, 0 );
- }
- /*
- * Document wide vertical movements
- */
- function goTop() {
- window.scroll( 0, 0 );
- }
- function goBottom() {
- window.scroll( document.width, document.height );
- }
- /*
- * URL dependent stuff
- * ===================
- */
- /*
- * Go up one page in the URL
- * -------------------------
- */
- function goUrlPageUp() {
- /*
- * Most of this could be inline below. We compute these here to keep the
- * switching stuff better readable.
- * TODO
- * There is a recursion problem if the shortened URL was implicitely
- * expanded to point to the current page again. This could be caught if
- * there was a possibilitiy to keep the current URL somehow globally when
- * the document reloads.
- */
- var oldLocation = window.location;
- var newLocation = null;
- var newLocArray =
- window.location.href.match(/(\w+:\/\/.+?\/)([\w\?\=\+\%\&\-\.]+\/?)$/);
- if (newLocArray) {
- newLocation = newLocArray[1];
- }
- /*
- * Now go up one level if possible.
- */
- if (newLocation && newLocation != oldLocation) {
- window.location = newLocation;
- }
- /*
- * We are at the top page already. Let the user know this.
- */
- else
- alert( "Already at top page. Cannot go further up." );
- }
- /*
- * Go up one domain in the URL
- * ---------------------------
- */
- function goUrlDomainUp() {
- var oldDomain = document.domain;
- var newDomain = null;
- /*
- * Even if we do not often need subdomain switching let's keep this stuff
- * more readable, too.
- */
- var subDomArray =
- oldDomain.match(/^(?!www\.)\w+\.(.+?)\.([a-z]{2,4})(?:\.([a-z]{2}))?$/);
- if (subDomArray) {
- /*
- * The URL is now broken up into array elements.
- * We take the subdomain out and join everything together to th new URL.
- */
- var subDomL = subDomArray.length;
- newDomain =
- window.location.protocol + "//" +
- subDomArray.slice(1, subDomArray[subDomL] ? subDomL
- : subDomL-1).join(".");
- }
- /*
- * Go up one sub domain level if possible.
- */
- if (newDomain) {
- window.location = newDomain;
- }
- /*
- * We are at the top domain already. Let the user know this.
- */
- else
- alert( "Already at top domain. Cannot go further up." );
- }
- /*
- * Adjust Movements
- * ================
- *
- * These functions only set the wndHeightAdjust factor in order to cope whith
- * situations where the browser (Midori only?) initializes the script multiple
- * times (which currently can happen on complicated structured pages).
- * Their only purpose is to bind these adjustments to some keys.
- */
- function adjustHeight1() {
- wndHeightAdjust = 1;
- }
- function adjustHeight2() {
- wndHeightAdjust = 2;
- }
- function adjustHeight3() {
- wndHeightAdjust = 3;
- }
- function adjustHeight4() {
- wndHeightAdjust = 4;
- }
- /**********************
- *** Link Following ***
- **********************/
- /*
- * Common variables
- */
- var navigating = false;
- var openNewTab = false;
- var waitForConfirmation = false;
- var clickableNodes;
- var labelsOverlays;
- var nodeLabels;
- var hasLinkNodes;
- var curLabelHead;
- var curLabelNum;
- var matchingLabelNum;
- var labelDigits;
- var useSequence;
- var useBase;
- var relPosLabels = false;
- /*
- * Clear All Link Information
- * --------------------------
- */
- function clearLinkInfo() {
- removeOverlays();
- labelsOverlays = null;
- nodeLabels = null;
- clickableNodes = null;
- hasLinkNodes = false;
- curLabelHead = "";
- curLabelNum = -1; // marks number as invalid
- matchingLabelNum = -1;
- labelDigits = 0;
- waitForConfirmation = false;
- relPosLabels = false;
- }
- /*
- * Label Handling
- * ==============
- */
- /*
- * Construct the Label Text For a Given Position Number
- * ----------------------------------------------------
- * @param posNumber decimal position number
- * (must be >= 0)
- * @return string representation of this number according to the
- * predefined collateral sequence.
- * Leading filled with the zero equivalence of the predefined
- * collateral sequence up to labelDigits length.
- */
- function labelText( posNumber ) {
- var head = posNumber;
- var remainder = 0;
- var labelString = "";
- /*
- * Numeric sequences should count from 1 instead from 0.
- */
- if (collSequence == "numeric" ||
- (collSequence == "optimal" && useSequence.charAt(0) == "0"))
- head++;
- /*
- * Compute the symbolic digits.
- */
- if (head == 0) {
- labelString = useSequence.charAt(0);
- }
- while (head > 0) {
- remainder = head % useBase;
- labelString = useSequence.charAt(remainder) + labelString;
- head = (head - remainder) / useBase;
- }
- // Fill with the zero equivalent of this collateral sequence.
- while (labelString.length < labelDigits) {
- labelString = useSequence.charAt(0) + labelString;
- }
- return labelString;
- }
- /*
- * Construct the Label Lumber For a Given Label String
- * ---------------------------------------------------
- * @param labelString string representation of the label
- * @return decimal equivalent according to the prdefined
- * collataration sequence.
- */
- function labelNumber( labelString ) {
- var posNumber = 0;
- var curBase = useBase;
- var curDigit;
- for (var i=labelString.length-1; i >= 0; i--) {
- curDigit = labelString.charAt(i);
- posNumber += useBase * useSequence.indexOf(curDigit);
- curBase *= useBase;
- }
- /*
- * Adjust for numeric counting from 1 instead of 0.
- */
- if (collSequence == "numeric" ||
- (collSequence == "optimal" && useSequence.charAt(0) == "0"))
- posNumber--;
- return posNumber;
- }
- /*
- * Evaluate the position of an element
- * -----------------------------------
- *
- * @param thisElement element to inspect
- * @return array [up, left, width, height] of position and size
- */
- function positionOf( thisElement ) {
- var up = thisElement.offsetTop;
- var left = thisElement.offsetLeft;
- var width = thisElement.offsetWidth;
- var height = thisElement.offsetHeight;
- while (thisElement.offsetParent) {
- thisElement = thisElement.offsetParent;
- up += thisElement.offsetTop;
- left += thisElement.offsetLeft;
- }
- return [up, left, width, height];
- }
- /*
- * Switch the relative positions mode of the labels display.
- * ---------------------------------------------------------
- * In some elements (i.e. some tables containing links) some labels might be put
- * one over the other which makes the overwritten ones unaccessible. In these
- * cases the labels often can be separated if the relative positions of the
- * nodes they belong to will be used. This switch accomplishes that task.
- *
- * We should not make this a standard behaviour, however, because it often will
- * result in the browser trying to display the labels outside the current
- * viewport.
- *
- * NOTE: This feature is meant to be used during labels navigation only. Thus
- * bind it to navKeyBindings instead of plain keyBindings.
- */
- function repositionLabels() {
- var newPosMode = ! relPosLabels;
- hideOverlays();
- removeOverlays();
- relPosLabels = newPosMode;
- createOverlays()
- showOverlays( curLabelHead );
- }
- /*
- * Maintaining Navigation Information
- * ==================================
- */
- /*
- * Check visibility of an element.
- * -------------------------------
- * Recursively checks the given Element wether it is not hidden.
- *
- * @param thisElement node to be checked.
- * @return true if this Element is not hidden and not behind a hidden parent in
- * the DOM tree.
- */
- function isVisible( thisElement ) {
- if ( thisElement == document ) {
- return true;
- }
- if ( ! thisElement ) {
- return false;
- }
- if ( ! thisElement.parentNode ) {
- return false;
- }
- if ( thisElement.style ) {
- if ( thisElement.style.display == 'none' ) {
- return false;
- }
- if ( thisElement.style.visibility == 'hidden' ) {
- return false;
- }
- }
- return isVisible( thisElement.parentNode );
- }
- /*
- * Check if the element is displayable at all.
- * -------------------------------------------
- *
- * @param thisElement element to check
- * @return false if the element width or height are equal or below zero
- */
- function isDisplayable( thisElement ) {
- var width = thisElement.offsetWidth;
- var height = thisElement.offsetHeight;
- if (width <= 0 || height <= 0)
- return false;
- else
- return true;
- }
- /*
- * Find Clickable Elements in the Document
- * ---------------------------------------
- * Scans the child nodes of the current ducument for those being clickable.
- *
- * @return clickableNodes: array of clickable child nodes collected
- * labelDigits: number of label digits required by this
- * collateral sequence
- */
- function findClickableNodes() {
- /*
- * Make sure to always start in a clear state.
- */
- clearLinkInfo();
- clickableNodes = new Array();
- /*
- * Recursively scan the DOM-provided document links array.
- */
- function addClickableNodesIn( thisParent ) {
- for (var i = 0; i < thisParent.childNodes.length; i++) {
- var curNode = thisParent.childNodes[i];
- /*
- * Look at available and visible type 1 nodes only.
- */
- if (curNode.nodeType == 1 &&
- isDisplayable( curNode ) &&
- isVisible( curNode )) {
- /*
- * Check if this is a clickable element and
- * add it to the clickableNodes array if so.
- */
- var isClickable =
- curNode.nodeName.toLowerCase()=="input" |
- curNode.nodeName.toLowerCase()=="select" |
- curNode.nodeName.toLowerCase()=="textarea" |
- curNode.hasAttribute( "tabindex" ) |
- curNode.hasAttribute( "href" ) |
- curNode.hasAttribute( "onclick" );
- if (isClickable) {
- clickableNodes.push( curNode );
- }
- }
- /*
- * Recursively check for clickable nodes in the childs of this one.
- */
- addClickableNodesIn( curNode );
- }
- }
- /*
- * Now start this scan at the document root.
- */
- addClickableNodesIn( document );
- /*
- * If wanted now find an optimal collateral sequence for labels display.
- */
- var curLength = clickableNodes.length;
- /*
- * Do so only if there are any clickable nodes at all.
- */
- if (curLength > 0) {
- hasLinkNodes = true;
- } else {
- hasLinkNodes = false;
- return;
- }
- if (collSequence == "optimal") {
- if (curLength < 10) {
- // Labels need one number digit only.
- useSequence = "0123456789";
- useBase = 10;
- }
- else if (curLength < 50) {
- // Labels displayable with one longalpha digit.
- useSequence = "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ";
- useBase = 50;
- }
- else if (curLength > 99) {
- // Labels would need more than three number digits.
- // Note: This could as well be a lower + upper case sequence but
- // using lower case only appears to be more practical.
- useSequence = "abcdefghijklmnopqrstuvxyz";
- useBase = 25;
- }
- else {
- // Labels displayable with two number digits.
- useSequence = "0123456789";
- useBase = 10;
- }
- }
- /*
- * Finally compute the number of digits the labels need to show.
- */
- while (curLength > 1) {
- labelDigits++;
- curLength /= useBase;
- }
- }
- /*
- * Create Labels Overlays
- * ----------------------
- * Requires the clickableNodes being evaluated already and no overlays being
- * created yet.
- *
- * @return labelsOverlays: array of label elements
- * nodeLabels: array of label texts
- */
- function createOverlays() {
- var curElement;
- var curLabel;
- var curOverlay;
- var curPosition;
- /*
- * Do nothing if there are no clickable nodes at all.
- */
- if (! hasLinkNodes)
- return;
- /*
- * Scan the clickableNodes and construct a labels overlay for each.
- */
- labelsOverlays = new Array();
- nodeLabels = new Array();
- for (var i = 0; i < clickableNodes.length; i++) {
- curLabel = labelText( i );
- curElement = clickableNodes[i];
- curPosition = positionOf( curElement );
- /*
- * Create a hidden overlay for this element.
- */
- curOverlay = document.createElement( "span" );
- curOverlay.id = overlayId;
- //
- curOverlay.style.position = "absolute";
- if (relPosLabels) {
- curOverlay.style.left = curPosition[1] + "px";
- curOverlay.style.top = curPosition[0] + "px";
- }
- curOverlay.style.width = "auto";
- curOverlay.style.padding = "1px";
- curOverlay.style.background = nodeLabelBackground;
- curOverlay.style.fontSize = nodeLabelSize;
- curOverlay.style.fontWeight = 'bold';
- curOverlay.style.fontColor = "black";
- curOverlay.style.textTransform = "none";
- //
- curOverlay.style.zorder = 1000; // always on top
- curOverlay.style.opacity = nodeOpacity;
- //
- curOverlay.style.border = "1px dashed darkgray";
- curOverlay.style.fontColor = "black";
- //
- curOverlay.style.visibility = "hidden";
- // This will be displayed:
- curOverlay.innerHTML =
- "<font color=\"" +
- nodeLabelColor + "\">" +
- curLabel +
- "</font>";
- //
- labelsOverlays.push( curOverlay );
- nodeLabels.push( curLabel );
- /*
- * Insert this into the document as sibling of the current element.
- */
- // curElement.setAttribute( overlayId, curLabel );
- curElement.parentNode.insertBefore( curOverlay, curElement );
- }
- }
- /*
- * Show Labels Overlays
- * --------------------
- * Shows overlays starting with labelHead, hides all others.
- * If no direct match yet show the label tails only. Else show the complete
- * label.
- *
- * @param labelHead initial character sequence of the labels to be shown
- * where only the remaining tail will be displayed
- * if "*": show all labels without change
- * if "": reset and show all labels
- *
- */
- function showOverlays( labelHead ) {
- var curLabel;
- var curOverlay;
- var headLength = labelHead.length;
- /*
- * Do nothing if there are no clickable nodes at all.
- */
- if (! hasLinkNodes)
- return;
- /*
- * Scan the labels overlays array.
- */
- for (var i = 0; i < labelsOverlays.length; i++) {
- labelsOverlays[i].style.visibility = "hidden";
- curLabel = nodeLabels[i];
- if (labelHead == "") {
- // Restore the label text to all digits and show the label.
- labelsOverlays[i].innerHTML =
- "<font color=\"" +
- nodeLabelColor + "\">" +
- curLabel +
- "</font>";
- labelsOverlays[i].style.visibility = "visible";
- }
- else if (labelHead == "*") {
- if (matchingLabelNum >= 0)
- labelsOverlays[matchingLabelNum].style.visibility = "visible";
- else
- labelsOverlays[i].style.visibility = "visible";
- }
- else {
- if (curLabel.substring( 0, headLength) == labelHead) {
- if (headLength != labelDigits) {
- /*
- * This is a partial label.
- */
- if (shortenLabels) {
- // Show relevant digits only.
- labelsOverlays[i].innerHTML =
- "<font color=\"" +
- nodeLabelColor + "\">" +
- curLabel.substring( headLength, labelDigits ) +
- "</font>";
- } else {
- // Mark matching labels differently.
- labelsOverlays[i].innerHTML =
- "<font style=\"background: " +
- partialLabelBackground + "\" color=\"" +
- partialLabelColor + "\">" +
- curLabel +
- "</font>";
- }
- }
- else {
- // This is a full match, remember and show it.
- matchingLabelNum = i;
- labelsOverlays[i].innerHTML =
- "<font color=\"" +
- foundLabelColor +
- "\" style=\"background: " +
- foundLabelBackground + "\">" +
- curLabel +
- "</font>";
- }
- /*
- * Show this label
- */
- labelsOverlays[i].style.visibility = "visible";
- }
- /*
- * Treat nonmatching labels here.
- */
- else {
- // Restore to full label representation.
- labelsOverlays[i].innerHTML =
- "<font color=\"" +
- nodeLabelColor + "\">" +
- curLabel +
- "</font>";
- if (shortenLabels)
- labelsOverlays[i].style.visibility = "hidden";
- else
- labelsOverlays[i].style.visibility = "visible";
- }
- }
- }
- }
- /*
- * Hide overlays
- * -------------
- * Hides every label overlay.
- */
- function hideOverlays() {
- /*
- * Do nothing if there are no clickable nodes at all.
- */
- if (! hasLinkNodes)
- return;
- /*
- * Scan the labels overlays array and hide the nodes displays.
- */
- for (var i = 0; i < labelsOverlays.length; i++) {
- labelsOverlays[i].style.visibility = "hidden";
- }
- }
- /*
- * Display overlays again
- * ----------------------
- */
- function redisplayOverlays() {
- showOverlays( curLabelHead );
- }
- /*
- * Remove label overlays
- * ---------------------
- * Removes all labels overlays.
- *
- * NOTE: This invalidates the overlays and should not be called out of context.
- */
- function removeOverlays() {
- /*
- * Do nothing if there are no overlays at all.
- */
- if (! hasLinkNodes) {
- return;
- }
- /*
- * Track the labels overlays array and remove the node elements kept from
- * their parents.
- */
- var curNode;
- for (var i = 0; i < labelsOverlays.length; i++) {
- curNode = labelsOverlays[i];
- curNode.parentNode.removeChild(curNode);
- }
- }
- /*
- * Navigating
- * ==========
- */
- /*
- * Check if the label is valid.
- * ----------------------------
- * Checks if a label starting with the given digits sequence is known in the
- * nodeLabels array.
- *
- * @param thisHead head sequence of the label to check
- * @return true there are labels starting with this sequence
- * curLabelNum: number of first occurence found
- * false there are no such labels known
- * curLabelNum: -1
- */
- function isValidLabel( thisHead ) {
- if (thisHead == "") {
- curLabelNum = -1;
- return false;
- }
- var headLength = thisHead.length;
- for( var i = 0; i < nodeLabels.length; i++ ) {
- if (thisHead == nodeLabels[i].substring( 0, headLength )) {
- curLabelNum = i;
- return true;
- }
- }
- curLabelNum = -1;
- return false;
- }
- /*
- * Navigate by label
- * -----------------
- * Process the current key to find an according link label and perform the
- * proper action there.
- */
- function navigateByLabel() {
- if (waitForConfirmation) {
- /*
- * We found a match but the user must tell what to do with it.
- */
- if (keyLabel == openInThisTab) {
- openNewTab = false;
- waitForConfirmation = false;
- clickLabel( matchingLabelNum );
- }
- else if (keyLabel == openInNewTab) {
- openNewTab = true;
- waitForConfirmation = false;
- clickLabel( matchingLabelNum );
- }
- else if (keyCode == 0x08) { // backspace
- waitForConfirmation = false;
- matchingLabelNum = -1; // removes the match in any case
- /*
- * Remove trailing character from the selection.
- */
- curLabelHead =
- curLabelHead.substring( 0,
- curLabelHead.length - 1 );
- showOverlays( curLabelHead );
- }
- }
- /*
- * We assume only numbers or ASCII characters will be used in labels.
- */
- else if (hasValidNavKey()) {
- if (isValidLabel( curLabelHead + keyLabel )) {
- /*
- * The key belongs to a valid label. Show the resulting
- * selection.
- */
- curLabelHead = curLabelHead + keyLabel;
- showOverlays( curLabelHead );
- if (matchingLabelNum != -1) {
- /*
- * We found a match.
- */
- if (autoselectLink) {
- waitForConfirmation = false;
- clickLabel( matchingLabelNum );
- } else {
- waitForConfirmation = true;
- //clickLabel( matchingLabelNum );
- }
- keyAction = null;
- return;
- }
- } else {
- /*
- * Simply skip the invalid entry.
- */
- keyAction = null;
- return;
- }
- }
- /*
- * If neither character or number, some other action, e.g. simple extra
- * navigation to properly show the labels in the viewport, may be
- * wanted.
- */
- else {
- if (keyCode == 0x08) { // backspace
- /*
- * Remove trailing character from the selection.
- */
- if (curLabelHead != "") {
- matchingLabelNum = -1; // removes the match in any case
- curLabelHead =
- curLabelHead.substring( 0,
- curLabelHead.length - 1 );
- waitForConfirmation = false;
- showOverlays( curLabelHead );
- }
- }
- else {
- /*
- * Look up if there is some special action to perform.
- */
- keyAction = navKeyBindings[keyLabel];
- }
- }
- }
- /*
- * Simulate a Mouseclick
- * ---------------------
- *
- * @param labelPos number of the label to be clicked on
- */
- function clickLabel( labelPos ) {
- var curElement = clickableNodes[ labelPos ];
- var curLabel = nodeLabels[ labelPos ];
- var curName = curElement.nodeName.toLowerCase();
- stopNavigating();
- if (curName == "a") {
- /*
- * It is a link. Just go there.
- */
- if (openNewTab) {
- openNewTab = false;
- window.open( curElement.getAttribute( "href" ), "", "" );
- } else {
- window.location = curElement.getAttribute("href");
- }
- }
- else if (curElement.hasAttribute("onclick")) {
- /*
- * This requires some more effort in order to trigger the attached
- * actions.
- * At first we need a special event to track mouse clicks.
- */
- var thisEvent = document.createEvent("MouseEvents");
- /*
- * Then the mouse click action needs to be defined.
- */
- thisEvent.initMouseEvent(
- "click", // the event type
- true, true, // allow bubbles and default action cancels
- window, // this view's base
- 0, // mouse click count
- 0, 0, 0, 0, // screen and client coordinates
- false, false, // no control or alt key depressed simultaneously
- false, false, // ditto, shift or meta key
- 0, // mouse button
- null); // no other related target
- /*
- * Finally get this known to the system.
- */
- curElement.dispatchEvent(thisEvent);
- }
- else if (curName == "input") {
- /*
- * There are several types of input elements which need be handled
- * differently.
- */
- var curType = curElement.getAttribute('type').toLowerCase();
- if (curType == 'text' || curType == 'file' || curType == 'password') {
- /*
- * These need be explicitely selected.
- */
- curElement.focus();
- curElement.select();
- curElement.click();
- } else {
- /*
- * It is a genuine input element.
- * This allows us to use the click() method.
- */
- curElement.click();
- }
- }
- else if (curName == 'textarea' || curName == 'select') {
- /*
- * Handle these like the special input element types.
- */
- curElement.focus();
- curElement.select();
- }
- else if (curElement.hasAttribute( "href" )) {
- /*
- * Handle a possible not detected link.
- */
- if (openNewTab) {
- openNewTab = false;
- window.open( curElement.getAttribute( "href" ), "", "" );
- } else {
- window.location = curElement.getAttribute("href");
- }
- }
- else {
- alert ("Could not click element " + curLabel +
- ": " + curName +
- "\nNo idea what to do with it." );
- }
- }
- /*
- * Start Navigating
- * ----------------
- */
- // If autoselecting, open in new tab.
- function startNavNewTab() {
- openNewTab = true;
- startNavigating();
- }
- // If autoselecting, open in current tab.
- function startNavigating() {
- navigating = true;
- waitForConfirmation = false;
- if (hasLinkNodes) {
- clearLinkInfo();
- }
- findClickableNodes();
- if (hasLinkNodes) {
- createOverlays();
- showOverlays("");
- } else {
- navigating = false;
- alert( "No clickable node found on this page." );
- }
- }
- /*
- * Stop Navigating
- * ---------------
- */
- function stopNavigating() {
- hideOverlays();
- navigating = false;
- clearLinkInfo();
- }
- /**************************
- *** Keyboard Interface ***
- **************************/
- /*
- * Check for an editable element
- * -----------------------------
- * @param element DOM element to be checked
- * @return true if element is editable
- * (i.e. shall receive all keys)
- * false otherwise.
- */
- function isEditable( element ) {
- if ( element.nodeName.toLowerCase() == "textarea" )
- return true;
- if ( element.nodeName.toLowerCase() == "input" &&
- ( element.type == "text" || element.type == "password" ) )
- return true;
- if ( document.designMode == "on" || element.contentEditable == "true" )
- return true;
- return false;
- }
- /*
- * The Keyboard Event Handlers
- * ===========================
- */
- /*
- * Evaluate the key in the given keyEvent
- * --------------------------------------
- * @param keyEvent event to evalute
- * @return function pointer in the keyAction variable.
- */
- function evalKey( keyEvent ) {
- wndHeight = window.innerHeight - Math.max( window.innerHeight / 10, 2 );
- // Handle specific key codes.
- // NOTE: Might be device specific, not thoroughly tested.
- keyCode = keyEvent.keyCode;
- // Account for keypad (NumLock on).
- if ( keyCode >= 96 && keyCode <= 105 )
- // numbers
- keyCode -= 48;
- else if ( keyCode >= 106 && keyCode <= 110 )
- // operators and punctuation
- keyCode -= 64;
- // Convert to character representation.
- keyLabel = String.fromCharCode( keyCode );
- /*
- * We must explicitely process the shift key because the the keyCode
- * conversion will always return upper case.
- */
- if ( ! keyEvent.shiftKey && keyCode >= 32 )
- keyLabel = String.fromCharCode( keyCode ).toLowerCase();
- if ( keyEvent.ctrlKey )
- keyLabel = "^" + keyLabel;
- /*
- * Evaluate the action to be performed.
- */
- if (! navigating) {
- keyAction = keyBindings[keyLabel];
- }
- else {
- keyAction = navKeyBindings[keyLabel];
- /*
- * Any invalid input stops navigating.
- */
- // if (! keyAction && ! hasValidNavKey() ) {
- // stopNavigating();
- // }
- }
- }
- /*
- * Check for a valid navigation key
- * --------------------------------
- * Checks the current keyLabel if it is a number or an ASCII character.
- *
- * @return true if this is a valid navigation key.
- */
- function hasValidNavKey() {
- if (keyLabel >= "0" && keyLabel <= "9" ||
- keyLabel >= "a" && keyLabel <= "z" ||
- keyLabel >= "A" && keyLabel <= "Z") {
- return true;
- } else {
- return false;
- }
- }
- /*
- * Handle most keypresses here
- * ---------------------------
- * @param keyEvent event to evalute
- */
- function keyHandler( keyEvent ) {
- keyAction = null;
- if (navigating) {
- evalKey( keyEvent );
- navigateByLabel();
- }
- else {
- /*
- * Skip targets which need keypresses by their own.
- */
- if (isEditable( keyEvent.target )) {
- return;
- }
- /*
- * The Esc key has been handled on key down already so skip it here.
- */
- if (keyEvent.keyCode == 0x1b) {
- return;
- }
- /*
- * Evaluate the function the key is bound to and execute it.
- */
- evalKey( keyEvent );
- }
- /*
- * Now perform the pending command.
- */
- if (keyAction) {
- // Prevent double execution of repeatable commands.
- if ( keyAction != goLeft &&
- keyAction != goRight &&
- keyAction != goUp &&
- keyAction != goDown ) {
- keyAction();
- }
- }
- }
- /*
- * Handle repeatable keys
- * ----------------------
- * There are a few commands which may be repeatably executed.
- * They need be handled separately as auto-repeat works on downheld keys only.
- *
- * @param keyEvent event to evalute
- *
- * NOTE:
- * There is some (possible Midori related) bug where javascript appears to send
- * keys twice if NumLock is on. Hence we restrict this to small movements only.
- */
- function keyRepeatHandler( keyEvent ) {
- keyAction = null;
- /*
- * There is only one keydown action to perform if we are navigating
- * by labels.
- */
- if (navigating) {
- /*
- * The Esc key is a stop all feature.
- * Hence check for this one first.
- */
- if (keyEvent.keyCode == 0x1b) {
- stopNavigating();
- return;
- }
- }
- /*
- * Otherwise handle some special cases.
- */
- else {
- // Skip targets which need keypresses by their own.
- if (isEditable( keyEvent.target )) {
- return;
- }
- }
- /*
- * Evaluate the function the key is bound to and execute it.
- */
- evalKey( keyEvent );
- if (keyAction) {
- // Execute repeatable commands only.
- if (keyAction == goLeft ||
- keyAction == goRight ||
- keyAction == goUp ||
- keyAction == goDown ) {
- keyAction();
- }
- }
- }
- /*******************
- *** Script Body ***
- *******************/
- /*
- * Initialization
- * ==============
- */
- /*
- * Make sure to start in a clean state.
- */
- clearLinkInfo();
- wndHeightAdjust = 1;
- /*
- * Set up a static labels collation sequence according to the configuration.
- */
- if (collSequence == "numeric") {
- useSequence = "0123456789";
- useBase = 10;
- }
- else if (collSequence == "alpha") {
- useSequence = "abcdefghijklmnopqrstuvxyz";
- useBase = 25;
- }
- else if (collSequence == "longalpha") {
- // We use lower key characters first, then upper key ones
- // to ease the typing.
- useSequence = "abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ";
- useBase = 50;
- }
- else if (collSequence != "optimal") {
- useSequence = collSequence;
- useBase = collSequence.length;
- }
- /*
- * Register keyboard event handlers.
- */
- window.addEventListener( "keyup", keyHandler, false );
- window.addEventListener( "keydown", keyRepeatHandler, false );
- // ----------------------------------------------------------------------------
- // vim:shiftwidth=4:softtabstop=4:expandtab:textwidth=80
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement