DamianSmall 2011-08-24 12:46:17 -04:00 committed by admin
parent 3433b39bb9
commit 6664b620b6
1 changed files with 216 additions and 0 deletions

View File

@ -67,4 +67,220 @@ 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 id="accesskeys" text="Toggle: accesskeys.pm"]]
[[!toggleable id="accesskeys" text="""
#!/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
[[!if test="enabled(accesskeys)"
then="This wiki has accesskeys **enabled**."
else="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
[[!toggle id="accesskeys" text="hide accesskeys.pm"]]
"""]]
--[[DamianSmall]]
[[!tag wishlist]]