Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name VimNav
- // @author Bernd Pol <bernd.pol@online.de>
- // @licence GPL
- // @description Vim-like navigation in a webkit based browser.
- // ==/UserScript==
- /*
- * Caveats:
- * - Does not work properly with forms.
- * Use TAB (in Midori) to select input fields there.
- * NOTE: This will stop navigating by labels thus after leaving the forms in
- * question navigation must be activated again.
- */
- /*
- * This is inspired by the "vimkeybindings" greasemonkey script by
- * "arno <arenevier@fdn.fr>", the "KeyNav" greasemonkey script,
- * version 0.1.1 beta by Itamar Benzaken, and the "follow.js" uzbl link
- * following script.
- */
- /*
- * 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()
- * Link Following
- * Common variables
- * Clear all link information
- * function clearLinkInfo()
- * Label handling
- * function labelText( posNumber )
- * function labelNumber( labelString )
- * 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,
- "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,
- "^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.
- * 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 for confirmation.
- * This corresponds to the "startNavigating" and "startNavNewTab" navigation
- * modes when autoselect mode is on.
- */
- var openInThisTab = "g"; // if branching the new page will open in this tab
- var openInNewTab = "t"; // the new page will open in another tab
- /*
- * Show yet to select label digits only
- * ------------------------------------
- * If true the labels will be shortened in each step by the most recently
- * selected digit and only those with at least one digit remaining will display.
- * This keeps the display clean but the remaining labels will be more difficult
- * to spot.
- * If false, the group of labels from which the next digit will be selected is
- * shown in another color while keeping the labels display unchanged otherwise.
- * This keeps the display more consistent but tends to hide useful information.
- */
- var shortenLabels = true;
- /*
- * 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
- * ------------------
- */
- /*
- * 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 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, -wndHeight / 2 );
- }
- function gohalfdown() {
- window.scrollBy( 0, wndHeight / 2 );
- }
- /*
- * Fullpage vertical movements
- */
- function gopageup() {
- window.scrollBy( 0, -wndHeight );
- }
- function gopagedown() {
- window.scrollBy( 0, wndHeight );
- }
- /*
- * 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 );
- }
- /**********************
- *** 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;
- /*
- * 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;
- }
- /*
- * 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;
- }
- /*
- * 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) continue;
- if (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.hasAttribute( "href" ) |
- curNode.hasAttribute( "onclick" );
- if (isClickable) {
- clickableNodes.push( curNode );
- }
- }
- 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;
- /*
- * 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];
- /*
- * Create a hidden overlay for this element.
- */
- curOverlay = document.createElement( "span" );
- curOverlay.id = overlayId;
- //
- curOverlay.style.position = "absolute";
- 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.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 left over overlays.
- */
- function removeOverlays() {
- var nodes = document.getElementById(overlayId);
- if (nodes) {
- nodes.parentNode.removeChild(nodes);
- }
- }
- /*
- * 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 (curName == "input") {
- /*
- * There are several types of input elements which need be handled
- * differently.
- */
- var curType = curElement.getAttribute('curType').toLowerCase();
- if (curType == 'text' || curType == 'file' || curType == 'password') {
- /*
- * These need be explicitely selected.
- */
- curElement.focus();
- curElement.select();
- } else {
- /*
- * It is a genuine input element.
- * This allows us to use the click() method.
- */
- curElement.click();
- }
- }
- else if (name == 'textarea' || name == 'select') {
- /*
- * Handle these like the special input element types.
- */
- item.focus();
- item.select();
- }
- 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.
- */
- element.dispatchEvent(thisEvent);
- }
- 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" )
- 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.min( 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();
- /*
- * 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