rst: import docutils lazily, to avoid errors during ikiwiki --setup
[ikiwiki] / doc / todo / access_keys.mdwn
1 Access keys (i.e., keyboard shortcuts) can be defined for common
2 features.  Something like the following:
3
4 * 1 - Homepage
5 * 2 - Search box
6 * E - Edit
7 * R - RecentChanges
8 * H - History
9 * P - Preferences
10 * D - Discussion
11 * S - Save the current page (when editing)
12 * C - Cancel the current edit
13 * V - Preview the current page
14
15 Then, for example, in Firefox one could press Alt+Shift+E to edit the
16 page.
17
18 For links, this is implemented as:
19
20     <a href="recentchanges/" accesskey="r">RecentChanges</a>
21
22 and for forms buttons:
23
24     <input type="submit" value="Submit" accesskey="s"/>
25
26 --[[JasonBlevins]], March 21, 2008 18:05 EDT
27
28 - - - 
29
30 There were also a few thoughts about access keys on the
31 [[main_discussion_page|index/discussion]]. Now moved to here:
32
33 > Would anyone else find this a valuable addition.  In oddmuse and instiki (the only other
34 > wiki engines I am currently using, the edit, home, and submit link tags have an
35 > accesskey attribute.  I find it nice not to have to resort to the mouse for those
36 > actions.  However, it may not be something everyone appreciates.  Any thoughts?
37 > --[Mazirian](http://mazirian.com)
38
39 > > Maybe, although it would need to take the critisism at
40 > > <http://www.cs.tut.fi/~jkorpela/forms/accesskey.html> into account.
41
42 > >> Thank you for that link. Given that the edit link is the first thing you tab to
43 > >> in the current layout, I guess it isn't all that necessary. I have had a 
44 > >> a user complaint recently that Alt-e in oddmuse was overriding his access
45 > >> to the browser menu.
46
47 ----
48
49 The main criticism there it
50 seems is that some browsers implement access keys in a way (via the Alt
51 key) that allows them to override built-in keyboard shortcuts.  I
52 believe this is not a problem any longer in Firefox (which uses the
53 Shift+Alt prefix) but I suppose it could still be a problem in other
54 browsers.
55
56 Another criticism is that most browsers do not display the access keys
57 that are defined.  The [article][] cited on the main discussion page
58 suggests underlining the relevant mnemonic.  I think it would be
59 sufficient to just list them in the basewiki documentation somewhere.
60
61   [article]: http://www.cs.tut.fi/~jkorpela/forms/accesskey.html
62
63 It's an unfortunate situation&mdash;I'd like an alternative to the
64 rodent but there are quite a few downsides to using access keys.
65 Tabbing isn't quite the same as a nice shortcut key.  There's always
66 Conkeror...
67
68 --[[JasonBlevins]], March 22, 2008 10:35 EDT
69
70 ----
71
72 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.
73
74 As I am new to ikiwiki plugin writing, feedback is greatly appreciated.
75
76 [[!toggle  id="accesskeys" text="Toggle: accesskeys.pm"]]
77
78 [[!toggleable  id="accesskeys" text="""
79
80         #!/usr/bin/perl
81         
82         package IkiWiki::Plugin::accesskeys;
83         
84         use warnings;
85         use strict;
86         use IkiWiki 3.00;
87         use CGI::FormBuilder;
88         
89         =head1 NAME
90         
91         accesskeys.pm - IkiWiki module to implement access keys (keyboard shortcuts)
92         
93         =head1 VERSION
94         
95         v.5.0 - initial version
96         
97         =head1 DESCRIPTION
98         
99         Access keys are defined on a page called B<accesskeys>, using the C<accesskey> directive.
100         Example:
101         
102             [[!accesskey command="Save Page" key="s"]]
103         
104         B<command> may contain only alphanumeric characters (and spaces), and must be a complete 
105         match to the target link or submit button's display name. 
106         
107         B<key> may only be a single alphanumeric character. 
108         
109         The access key is applied to the first matching link on a page (including header), or the 
110         first matching submit button in the @buttons array.
111         
112         The wiki must be completely rebuilt every time the B<accesskeys> page changes.
113         
114         =head2 Sample accesskeys page
115         
116             [[!if test="enabled(accesskeys)"
117                 then="This wiki has accesskeys **enabled**."
118                 else="This wiki has accesskeys **disabled**."]]
119         
120             This page controls what access keys the wiki uses.
121         
122             * [[!accesskey command="Save Page" key="s"]]
123             * [[!accesskey command="Cancel" key="c"]]
124             * [[!accesskey command="Preview" key="v"]]
125             * [[!accesskey command="Edit" key="e"]]
126             * [[!accesskey command="RecentChanges" key="c"]]
127             * [[!accesskey command="Preferences" key="p"]]
128             * [[!accesskey command="Discussion" key="d"]]
129         
130         =head1 IMPLEMENTATION
131         
132         This plugin uses the following flow:
133         
134         =over 1
135         
136         =item 1. Override default CGI::FormBuilder::submit function
137         
138         FormBuilder does not support any arbitrary modification of it's submit buttons, so
139         in order to add the necessary attributes you have to intercept the internal function
140         call which generates the formatted html for the submit buttons. Not pretty, but it 
141         works.
142         
143         =item 2. Get list of keys 
144         
145         During the B<checkconfig> stage the B<accesskeys> source file is read (default 
146         F<accesskeys.mdwn>) to generate a list of defined keys.
147         
148         =item 3. Insert keys (links)
149         
150         Keys are inserted into links during the format stage. All defined commands are checked 
151         against the page's links and if there is a match the key is inserted. Only the first 
152         match for each command is processed.
153         
154         =item 4. Insert keys (FormBuilder buttons)
155         
156         FormBuilder pages are intercepted during formatting. Keys are inserted as above. 
157         
158         =back
159         
160         =head1 TODO
161         
162         =over 1
163         
164         =item * non-existant page links ex: ?Discussion
165         
166         =item * Support non-submit array buttons (like those added after the main group for attachments)
167         
168         =item * Support form fields (search box)
169         
170         =back
171         
172         =cut
173         
174         #=head1 HISTORY
175         
176         =head1 AUTHOR
177         
178         Written by Damian Small.
179         
180         =cut
181         
182         my %accesskeys = ();
183         
184         # Initialize original function pointer to FormBuilder::submit
185         my $original_submit_function = \&{'CGI::FormBuilder::submit'};
186         # Override default submit function in FormBuilder
187         {    
188             no strict 'refs';
189             no warnings;
190             *{'CGI::FormBuilder::submit'} = \&submit_override;
191         }
192         
193         sub submit_override {
194             # Call the original function, and get the results
195             my $contents = $original_submit_function->(@_);
196         
197             # Hack the results to add accesskeys
198             foreach my $buttonName (keys %accesskeys) {
199                 $contents =~ s/(<input id="_submit[^>]+ value="$buttonName")( \/>)/$1 title="$buttonName [$accesskeys{$buttonName}]" accesskey="$accesskeys{$buttonName}"$2/;
200             }
201         
202             return $contents;
203         }
204         
205         sub import {
206             hook(type => "getsetup", id => "accesskeys", call => \&getsetup);
207             hook(type => "checkconfig", id => "accesskeys", call => \&checkconfig);
208             hook(type => "preprocess", id => "accesskey", call => \&preprocess_accesskey);
209             hook(type => "format", id => "accesskeys", call => \&format);
210         }
211         
212         sub getsetup () {
213             return
214                 plugin => {
215                     safe => 1,
216                     rebuild => 1,
217                     section => "widget",
218             },
219         }
220         
221         sub checkconfig () {
222             if (defined $config{srcdir} && length $config{srcdir}) {
223                 # Preprocess the accesskeys page to get all the access keys
224                 # defined before other pages are rendered.
225                 my $srcfile=srcfile("accesskeys.".$config{default_pageext}, 1);
226                 if (! defined $srcfile) {
227                     $srcfile=srcfile("accesskeys.mdwn", 1);
228                 }
229                 if (! defined $srcfile) {
230                     print STDERR sprintf(gettext("accesskeys plugin will not work without %s"),
231                                          "accesskeys.".$config{default_pageext})."\n";
232                 }
233                 else {
234                     IkiWiki::preprocess("accesskeys", "accesskeys", readfile($srcfile));
235                 }
236             }
237         }
238         
239         sub preprocess_accesskey (@) {
240             my %params=@_;
241             
242             if (! defined $params{command} || ! defined $params{key}) {
243                 error gettext("missing command or key parameter");
244             }
245             
246             # check the key
247             if ($params{key} !~ /^[a-zA-Z0-9]$/) {
248                 error gettext("key parameter is not a single character");
249             }
250             # check the command
251             if ($params{command} !~ /^[a-zA-Z0-9 _]+$/) {
252                 error gettext("command parameter is not an alphanumeric string");
253             }
254             # Add the access key:
255             $accesskeys{$params{command}} = $params{key};
256         
257             return sprintf(gettext("[%s] is the access key for command '<i>%s</i>'"), $params{key}, $params{command});
258         }
259         
260         sub format (@) {
261             my %params = @_;
262             my $contents = $params{content};
263          
264             # If the accesskey page changes, all pages will need to be updated
265             #debug("Adding dependency: for " . $params{page} . " to AccessKeys");
266             add_depends($params{page}, "AccessKeys");
267         
268             # insert access keys
269             foreach my $command (keys %accesskeys) {
270                 $contents =~ s/(<a href=[^>]+)(>$command<\/a>)/$1 accesskey="$accesskeys{$command}"$2/;
271             }
272             # may need special handling for non-existant discussion links (and possibly other similar cases?)
273             #$contents =~ s/(<a href=[^>]+)(>\?<\/a>Discussion)/$1 accesskey="d"$2/;
274         
275             return $contents;
276         }
277         
278         1
279
280
281 [[!toggle id="accesskeys" text="hide accesskeys.pm"]]
282 """]]
283
284 --[[DamianSmall]]
285
286 [[!tag wishlist]]