]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - doc/todo/access_keys.mdwn
Exclude working directory from library path (CVE-2016-1238)
[git.ikiwiki.info.git] / doc / todo / access_keys.mdwn
index 52031ffc1adfba02ebf62358b825224b933ce88b..fb23cf90078002a555147cf9bb6c27acf829c00a 100644 (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]]