Merge "Title::getTalkPage(): Restore behavior of interwiki-prefixed & fragment-only...
[lhc/web/wiklou.git] / resources / src / mediawiki.util / jquery.accessKeyLabel.js
1 /**
2 * jQuery plugin to update the tooltip to show the correct access key
3 *
4 * @class jQuery.plugin.accessKeyLabel
5 */
6
7 // Cached access key modifiers for used browser
8 var cachedAccessKeyModifiers,
9
10 // Whether to use 'test-' instead of correct prefix (used for testing)
11 useTestPrefix = false,
12
13 // tag names which can have a label tag
14 // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Form-associated_content
15 labelable = 'button, input, textarea, keygen, meter, output, progress, select';
16
17 /**
18 * Find the modifier keys that need to be pressed together with the accesskey to trigger the input.
19 *
20 * The result is dependant on the ua paramater or the current platform.
21 * For browsers that support accessKeyLabel, #getAccessKeyLabel never calls here.
22 * Valid key values that are returned can be: ctrl, alt, option, shift, esc
23 *
24 * @private
25 * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
26 * @return {Array} Array with 1 or more of the string values, in this order: ctrl, option, alt, shift, esc
27 */
28 function getAccessKeyModifiers( ua ) {
29 var profile, accessKeyModifiers;
30
31 // use cached prefix if possible
32 if ( !ua && cachedAccessKeyModifiers ) {
33 return cachedAccessKeyModifiers;
34 }
35
36 profile = $.client.profile( ua );
37
38 switch ( profile.name ) {
39 case 'chrome':
40 case 'opera':
41 if ( profile.name === 'opera' && profile.versionNumber < 15 ) {
42 accessKeyModifiers = [ 'shift', 'esc' ];
43 } else if ( profile.platform === 'mac' ) {
44 accessKeyModifiers = [ 'ctrl', 'option' ];
45 } else {
46 // Chrome/Opera on Windows or Linux
47 // (both alt- and alt-shift work, but alt with E, D, F etc does not
48 // work since they are browser shortcuts)
49 accessKeyModifiers = [ 'alt', 'shift' ];
50 }
51 break;
52 case 'firefox':
53 case 'iceweasel':
54 if ( profile.versionBase < 2 ) {
55 // Before v2, Firefox used alt, though it was rebindable in about:config
56 accessKeyModifiers = [ 'alt' ];
57 } else {
58 if ( profile.platform === 'mac' ) {
59 if ( profile.versionNumber < 14 ) {
60 accessKeyModifiers = [ 'ctrl' ];
61 } else {
62 accessKeyModifiers = [ 'ctrl', 'option' ];
63 }
64 } else {
65 accessKeyModifiers = [ 'alt', 'shift' ];
66 }
67 }
68 break;
69 case 'safari':
70 case 'konqueror':
71 if ( profile.platform === 'win' ) {
72 accessKeyModifiers = [ 'alt' ];
73 } else {
74 if ( profile.layoutVersion > 526 ) {
75 // Non-Windows Safari with webkit_version > 526
76 accessKeyModifiers = [ 'ctrl', profile.platform === 'mac' ? 'option' : 'alt' ];
77 } else {
78 accessKeyModifiers = [ 'ctrl' ];
79 }
80 }
81 break;
82 case 'msie':
83 case 'edge':
84 accessKeyModifiers = [ 'alt' ];
85 break;
86 default:
87 accessKeyModifiers = profile.platform === 'mac' ? [ 'ctrl' ] : [ 'alt' ];
88 break;
89 }
90
91 // cache modifiers
92 if ( !ua ) {
93 cachedAccessKeyModifiers = accessKeyModifiers;
94 }
95 return accessKeyModifiers;
96 }
97
98 /**
99 * Get the access key label for an element.
100 *
101 * Will use native accessKeyLabel if available (currently only in Firefox 8+),
102 * falls back to #getAccessKeyModifiers.
103 *
104 * @private
105 * @param {HTMLElement} element Element to get the label for
106 * @return {string} Access key label
107 */
108 function getAccessKeyLabel( element ) {
109 // abort early if no access key
110 if ( !element.accessKey ) {
111 return '';
112 }
113 // use accessKeyLabel if possible
114 // https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel
115 if ( !useTestPrefix && element.accessKeyLabel ) {
116 return element.accessKeyLabel;
117 }
118 return ( useTestPrefix ? 'test' : getAccessKeyModifiers().join( '-' ) ) + '-' + element.accessKey;
119 }
120
121 /**
122 * Update the title for an element (on the element with the access key or it's label) to show
123 * the correct access key label.
124 *
125 * @private
126 * @param {HTMLElement} element Element with the accesskey
127 * @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
128 */
129 function updateTooltipOnElement( element, titleElement ) {
130 var oldTitle, parts, regexp, newTitle, accessKeyLabel,
131 separatorMsg = mw.message( 'word-separator' ).plain();
132
133 oldTitle = titleElement.title;
134 if ( !oldTitle ) {
135 // don't add a title if the element didn't have one before
136 return;
137 }
138
139 parts = ( separatorMsg + mw.message( 'brackets' ).plain() ).split( '$1' );
140 regexp = new RegExp( parts.map( mw.util.escapeRegExp ).join( '.*?' ) + '$' );
141 newTitle = oldTitle.replace( regexp, '' );
142 accessKeyLabel = getAccessKeyLabel( element );
143
144 if ( accessKeyLabel ) {
145 // Should be build the same as in Linker::titleAttrib
146 newTitle += separatorMsg + mw.message( 'brackets', accessKeyLabel ).plain();
147 }
148 if ( oldTitle !== newTitle ) {
149 titleElement.title = newTitle;
150 }
151 }
152
153 /**
154 * Update the title for an element to show the correct access key label.
155 *
156 * @private
157 * @param {HTMLElement} element Element with the accesskey
158 */
159 function updateTooltip( element ) {
160 var id, $element, $label, $labelParent;
161 updateTooltipOnElement( element, element );
162
163 // update associated label if there is one
164 $element = $( element );
165 if ( $element.is( labelable ) ) {
166 // Search it using 'for' attribute
167 id = element.id.replace( /"/g, '\\"' );
168 if ( id ) {
169 $label = $( 'label[for="' + id + '"]' );
170 if ( $label.length === 1 ) {
171 updateTooltipOnElement( element, $label[ 0 ] );
172 }
173 }
174
175 // Search it as parent, because the form control can also be inside the label element itself
176 $labelParent = $element.parents( 'label' );
177 if ( $labelParent.length === 1 ) {
178 updateTooltipOnElement( element, $labelParent[ 0 ] );
179 }
180 }
181 }
182
183 /**
184 * Update the titles for all elements in a jQuery selection.
185 *
186 * @return {jQuery}
187 * @chainable
188 */
189 $.fn.updateTooltipAccessKeys = function () {
190 return this.each( function () {
191 updateTooltip( this );
192 } );
193 };
194
195 /**
196 * getAccessKeyModifiers
197 *
198 * @method updateTooltipAccessKeys_getAccessKeyModifiers
199 * @inheritdoc #getAccessKeyModifiers
200 */
201 $.fn.updateTooltipAccessKeys.getAccessKeyModifiers = getAccessKeyModifiers;
202
203 /**
204 * getAccessKeyLabel
205 *
206 * @method updateTooltipAccessKeys_getAccessKeyLabel
207 * @inheritdoc #getAccessKeyLabel
208 */
209 $.fn.updateTooltipAccessKeys.getAccessKeyLabel = getAccessKeyLabel;
210
211 /**
212 * getAccessKeyPrefix
213 *
214 * @method updateTooltipAccessKeys_getAccessKeyPrefix
215 * @deprecated since 1.27 Use #getAccessKeyModifiers
216 * @param {Object} [ua] An object with a 'userAgent' and 'platform' property.
217 * @return {string}
218 */
219 $.fn.updateTooltipAccessKeys.getAccessKeyPrefix = function ( ua ) {
220 return getAccessKeyModifiers( ua ).join( '-' ) + '-';
221 };
222
223 /**
224 * Switch test mode on and off.
225 *
226 * @method updateTooltipAccessKeys_setTestMode
227 * @param {boolean} mode New mode
228 */
229 $.fn.updateTooltipAccessKeys.setTestMode = function ( mode ) {
230 useTestPrefix = mode;
231 };
232
233 /**
234 * @class jQuery
235 * @mixins jQuery.plugin.accessKeyLabel
236 */