Access keys (i.e., keyboard shortcuts) can be defined for common features. Something like the following:

  • 1 - Homepage
  • 2 - Search box
  • E - Edit
  • R - RecentChanges
  • H - History
  • P - Preferences
  • D - Discussion
  • S - Save the current page (when editing)
  • C - Cancel the current edit
  • V - Preview the current page

Then, for example, in Firefox one could press Alt+Shift+E to edit the page.

For links, this is implemented as:

<a href="recentchanges/" accesskey="r">RecentChanges</a>

and for forms buttons:

<input type="submit" value="Submit" accesskey="s"/>

--JasonBlevins, March 21, 2008 18:05 EDT


There were also a few thoughts about access keys on the main discussion page. Now moved to here:

Would anyone else find this a valuable addition. In oddmuse and instiki (the only other wiki engines I am currently using, the edit, home, and submit link tags have an accesskey attribute. I find it nice not to have to resort to the mouse for those actions. However, it may not be something everyone appreciates. Any thoughts? --Mazirian

Maybe, although it would need to take the critisism at http://www.cs.tut.fi/~jkorpela/forms/accesskey.html into account.

Thank you for that link. Given that the edit link is the first thing you tab to in the current layout, I guess it isn't all that necessary. I have had a a user complaint recently that Alt-e in oddmuse was overriding his access to the browser menu.


The main criticism there it seems is that some browsers implement access keys in a way (via the Alt key) that allows them to override built-in keyboard shortcuts. I believe this is not a problem any longer in Firefox (which uses the Shift+Alt prefix) but I suppose it could still be a problem in other browsers.

Another criticism is that most browsers do not display the access keys that are defined. The article cited on the main discussion page suggests underlining the relevant mnemonic. I think it would be sufficient to just list them in the basewiki documentation somewhere.

It's an unfortunate situation—I'd like an alternative to the rodent but there are quite a few downsides to using access keys. Tabbing isn't quite the same as a nice shortcut key. There's always Conkeror...

--JasonBlevins, March 22, 2008 10:35 EDT


I've written a plugin to implement access keys, configured using a wiki page similar to shortcuts. It works for links and most form submit buttons.

As I am new to ikiwiki plugin writing, feedback is greatly appreciated.

Toggle: accesskeys.pm

#!/usr/bin/perl

package IkiWiki::Plugin::accesskeys;

use warnings;
use strict;
use IkiWiki 3.00;
use CGI::FormBuilder;

=head1 NAME

accesskeys.pm - IkiWiki module to implement access keys (keyboard shortcuts)

=head1 VERSION

v.5.0 - initial version

=head1 DESCRIPTION

Access keys are defined on a page called B<accesskeys>, using the C<accesskey> directive.
Example:

    [[!accesskey  command="Save Page" key="s"]]

B<command> may contain only alphanumeric characters (and spaces), and must be a complete 
match to the target link or submit button's display name. 

B<key> may only be a single alphanumeric character. 

The access key is applied to the first matching link on a page (including header), or the 
first matching submit button in the @buttons array.

The wiki must be completely rebuilt every time the B<accesskeys> page changes.

=head2 Sample accesskeys page

    This wiki has accesskeys **disabled**.

    This page controls what access keys the wiki uses.

    * [[!accesskey  command="Save Page" key="s"]]
    * [[!accesskey  command="Cancel" key="c"]]
    * [[!accesskey  command="Preview" key="v"]]
    * [[!accesskey  command="Edit" key="e"]]
    * [[!accesskey  command="RecentChanges" key="c"]]
    * [[!accesskey  command="Preferences" key="p"]]
    * [[!accesskey  command="Discussion" key="d"]]

=head1 IMPLEMENTATION

This plugin uses the following flow:

=over 1

=item 1. Override default CGI::FormBuilder::submit function

FormBuilder does not support any arbitrary modification of it's submit buttons, so
in order to add the necessary attributes you have to intercept the internal function
call which generates the formatted html for the submit buttons. Not pretty, but it 
works.

=item 2. Get list of keys 

During the B<checkconfig> stage the B<accesskeys> source file is read (default 
F<accesskeys.mdwn>) to generate a list of defined keys.

=item 3. Insert keys (links)

Keys are inserted into links during the format stage. All defined commands are checked 
against the page's links and if there is a match the key is inserted. Only the first 
match for each command is processed.

=item 4. Insert keys (FormBuilder buttons)

FormBuilder pages are intercepted during formatting. Keys are inserted as above. 

=back

=head1 TODO

=over 1

=item * non-existant page links ex: ?Discussion

=item * Support non-submit array buttons (like those added after the main group for attachments)

=item * Support form fields (search box)

=back

=cut

#=head1 HISTORY

=head1 AUTHOR

Written by Damian Small.

=cut

my %accesskeys = ();

# Initialize original function pointer to FormBuilder::submit
my $original_submit_function = \&{'CGI::FormBuilder::submit'};
# Override default submit function in FormBuilder
{    
    no strict 'refs';
    no warnings;
    *{'CGI::FormBuilder::submit'} = \&submit_override;
}

sub submit_override {
    # Call the original function, and get the results
    my $contents = $original_submit_function->(@_);

    # Hack the results to add accesskeys
    foreach my $buttonName (keys %accesskeys) {
    $contents =~ s/(<input id="_submit[^>]+ value="$buttonName")( \/>)/$1 title="$buttonName [$accesskeys{$buttonName}]" accesskey="$accesskeys{$buttonName}"$2/;
    }

    return $contents;
}

sub import {
    hook(type => "getsetup", id => "accesskeys", call => \&getsetup);
    hook(type => "checkconfig", id => "accesskeys", call => \&checkconfig);
    hook(type => "preprocess", id => "accesskey", call => \&preprocess_accesskey);
    hook(type => "format", id => "accesskeys", call => \&format);
}

sub getsetup () {
    return
    plugin => {
        safe => 1,
        rebuild => 1,
        section => "widget",
    },
}

sub checkconfig () {
    if (defined $config{srcdir} && length $config{srcdir}) {
    # Preprocess the accesskeys page to get all the access keys
    # defined before other pages are rendered.
    my $srcfile=srcfile("accesskeys.".$config{default_pageext}, 1);
    if (! defined $srcfile) {
        $srcfile=srcfile("accesskeys.mdwn", 1);
    }
    if (! defined $srcfile) {
        print STDERR sprintf(gettext("accesskeys plugin will not work without %s"),
                 "accesskeys.".$config{default_pageext})."\n";
    }
    else {
        IkiWiki::preprocess("accesskeys", "accesskeys", readfile($srcfile));
    }
    }
}

sub preprocess_accesskey (@) {
    my %params=@_;

    if (! defined $params{command} || ! defined $params{key}) {
    error gettext("missing command or key parameter");
    }

    # check the key
    if ($params{key} !~ /^[a-zA-Z0-9]$/) {
    error gettext("key parameter is not a single character");
    }
    # check the command
    if ($params{command} !~ /^[a-zA-Z0-9 _]+$/) {
    error gettext("command parameter is not an alphanumeric string");
    }
    # Add the access key:
    $accesskeys{$params{command}} = $params{key};

    return sprintf(gettext("[%s] is the access key for command '<i>%s</i>'"), $params{key}, $params{command});
}

sub format (@) {
    my %params = @_;
    my $contents = $params{content};

    # If the accesskey page changes, all pages will need to be updated
    #debug("Adding dependency: for " . $params{page} . " to AccessKeys");
    add_depends($params{page}, "AccessKeys");

    # insert access keys
    foreach my $command (keys %accesskeys) {
    $contents =~ s/(<a href=[^>]+)(>$command<\/a>)/$1 accesskey="$accesskeys{$command}"$2/;
    }
    # may need special handling for non-existant discussion links (and possibly other similar cases?)
    #$contents =~ s/(<a href=[^>]+)(>\?<\/a>Discussion)/$1 accesskey="d"$2/;

    return $contents;
}

1

hide accesskeys.pm

--DamianSmall