r113640 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r113639‎ | r113640 | r113641 >
Date:17:59, 12 March 2012
Author:jdlrobson
Status:resolved
Tags:
Comment:
separate toggle code from application, remove beta_application

this separates all toggling related code so that it is more
self contained and better for consumption by the phonegap app
which makes use of it.

during this refactoring I noticed that application.js is
actually the same as beta_application.js so I have removed
the latter to make the project easier to maintain.
Modified paths:
  • /trunk/extensions/MobileFrontend/Makefile (modified) (history)
  • /trunk/extensions/MobileFrontend/MobileFrontend.body.php (modified) (history)
  • /trunk/extensions/MobileFrontend/javascripts/application.js (modified) (history)
  • /trunk/extensions/MobileFrontend/javascripts/beta_application.js (deleted) (history)
  • /trunk/extensions/MobileFrontend/javascripts/toggle.js (added) (history)
  • /trunk/extensions/MobileFrontend/tests/js/test_application.js (modified) (history)
  • /trunk/extensions/MobileFrontend/tests/js/test_toggle.js (added) (history)

Diff [purge]

Index: trunk/extensions/MobileFrontend/tests/js/test_toggle.js
@@ -0,0 +1,58 @@
 2+module("MobileFrontend toggle.js: wm_toggle_section", {
 3+ setup: function() {
 4+ MFET.createFixtures();
 5+ MFE.toggle.init();
 6+ $("#content_1,#anchor_1,#section_1 .hide").hide();
 7+ $("#section_1 .show").show();
 8+ },
 9+ teardown: function() {
 10+ MFET.cleanFixtures();
 11+ window.location.hash = "#";
 12+ }
 13+});
 14+
 15+test("wm_toggle_section", function() {
 16+ MFE.toggle.wm_toggle_section("1");
 17+ strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
 18+ strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
 19+ strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
 20+ strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
 21+
 22+ // perform second toggle
 23+ MFE.toggle.wm_toggle_section("1");
 24+ strictEqual($("#content_1").is(":visible"), false, "check content is hidden on a toggle");
 25+ strictEqual($("#anchor_1").is(":visible"), false, "check anchor is hidden on toggle");
 26+ strictEqual($("#section_1 .hide").is(":visible"), false, "check hide button now hidden");
 27+ strictEqual($("#section_1 .show").is(":visible"), true, "check show button now visible");
 28+});
 29+
 30+test("wm_reveal_for_hash", function() {
 31+ MFE.toggle.wm_reveal_for_hash("#First_Section");
 32+ strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
 33+ strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
 34+ strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
 35+ strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
 36+});
 37+
 38+test("wm_reveal_for_hash", function() {
 39+ MFE.toggle.wm_reveal_for_hash("#First_Section_2");
 40+ strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
 41+ strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
 42+ strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
 43+ strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
 44+});
 45+
 46+test("clicking hash links", function() {
 47+ MFET.triggerEvent($("[href=#First_Section_2]")[0], "click");
 48+ strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
 49+ strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
 50+ strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
 51+ strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
 52+});
 53+
 54+test("clicking a heading toggles it", function() {
 55+ var visibilityStart = $("#content_1").is(":visible");
 56+ MFET.triggerEvent($("#section_1")[0], "click");
 57+ strictEqual(visibilityStart, false, "check content is hidden at start");
 58+ strictEqual($("#content_1").is(":visible"), true, "check content is hidden on a toggle");
 59+});
Index: trunk/extensions/MobileFrontend/tests/js/test_application.js
@@ -57,61 +57,3 @@
5858 strictEqual(visible3, false, "toggle");
5959 });
6060
61 -module("MobileFrontend application.js: wm_toggle_section", {
62 - setup: function() {
63 - MFET.createFixtures();
64 - MFE.init();
65 - $("#content_1,#anchor_1,#section_1 .hide").hide();
66 - $("#section_1 .show").show();
67 - },
68 - teardown: function() {
69 - MFET.cleanFixtures();
70 - window.location.hash = "#";
71 - }
72 -});
73 -
74 -test("wm_toggle_section", function() {
75 - MFE.wm_toggle_section("1");
76 - strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
77 - strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
78 - strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
79 - strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
80 -
81 - // perform second toggle
82 - MFE.wm_toggle_section("1");
83 - strictEqual($("#content_1").is(":visible"), false, "check content is hidden on a toggle");
84 - strictEqual($("#anchor_1").is(":visible"), false, "check anchor is hidden on toggle");
85 - strictEqual($("#section_1 .hide").is(":visible"), false, "check hide button now hidden");
86 - strictEqual($("#section_1 .show").is(":visible"), true, "check show button now visible");
87 -});
88 -
89 -test("wm_reveal_for_hash", function() {
90 - MFE.wm_reveal_for_hash("#First_Section");
91 - strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
92 - strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
93 - strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
94 - strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
95 -});
96 -
97 -test("wm_reveal_for_hash", function() {
98 - MFE.wm_reveal_for_hash("#First_Section_2");
99 - strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
100 - strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
101 - strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
102 - strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
103 -});
104 -
105 -test("clicking hash links", function() {
106 - MFET.triggerEvent($("[href=#First_Section_2]")[0], "click");
107 - strictEqual($("#content_1").is(":visible"), true, "check content is visible on a toggle");
108 - strictEqual($("#anchor_1").is(":visible"), true, "check anchor is visible on toggle");
109 - strictEqual($("#section_1 .hide").is(":visible"), true, "check hide button now visible");
110 - strictEqual($("#section_1 .show").is(":visible"), false, "check show button now hidden");
111 -});
112 -
113 -test("clicking a heading toggles it", function() {
114 - var visibilityStart = $("#content_1").is(":visible");
115 - MFET.triggerEvent($("#section_1")[0], "click");
116 - strictEqual(visibilityStart, false, "check content is hidden at start");
117 - strictEqual($("#content_1").is(":visible"), true, "check content is hidden on a toggle");
118 -});
Index: trunk/extensions/MobileFrontend/MobileFrontend.body.php
@@ -1276,6 +1276,7 @@
12771277 $testModules['qunit']['ext.mobilefrontend.tests'] = array(
12781278 'scripts' => array( 'tests/js/fixtures.js', 'javascripts/application.js',
12791279 'javascripts/opensearch.js', 'javascripts/banner.js',
 1280+ 'javascripts/toggle.js', 'tests/js/test_toggle.js',
12801281 'tests/js/test_application.js', 'tests/js/test_opensearch.js', 'tests/js/test_banner.js' ),
12811282 'dependencies' => array( ),
12821283 'localBasePath' => dirname( __FILE__ ),
Index: trunk/extensions/MobileFrontend/javascripts/beta_application.js
@@ -1,183 +0,0 @@
2 -/*global document, window */
3 -/*jslint sloppy: true, white:true, maxerr: 50, indent: 4, plusplus: true*/
4 -/*
5 -TODO: getElementsByClassName not supported by IE < 9
6 -TODO: addEventListener not supported by IE < 9
7 -*/
8 -MobileFrontend = (function() {
9 - var utilities;
10 -
11 - function init() {
12 - var i, search, clearSearch, results, languageSelection, a, heading, btns,
13 - sectionHeadings = utilities( '.section_heading' );
14 - utilities( document.body ).addClass( 'jsEnabled' );
15 -
16 - // TODO: remove in future - currently enables toggling in Wikipedia Mobile App v < 1.1
17 - window.wm_toggle_section = wm_toggle_section;
18 - var btns = utilities( '.section_heading button' );
19 - for( i = 0; i < btns.length; i++ ) {
20 - utilities( btns[i] ).remove();
21 - }
22 -
23 - function openSectionHandler() {
24 - var sectionNumber = this.id ? this.id.split( '_' )[1] : -1;
25 - if( sectionNumber > -1 ) {
26 - wm_toggle_section( sectionNumber );
27 - }
28 - }
29 - function createButton( visible ) {
30 - var btn, label;
31 - btn = document.createElement( 'button' );
32 - label = document.createTextNode( visible ? showText : hideText );
33 - btn.className = visible ? 'show' : 'hide';
34 - btn.appendChild( label );
35 - btn.style.display = visible ? 'inline-block' : 'none';
36 - return btn;
37 - }
38 - if(!sectionHeadings) {
39 - sectionHeadings = [];
40 - } else {
41 - utilities( document.body ).addClass( 'togglingEnabled' );
42 - }
43 - for( i = 0; i < sectionHeadings.length; i++ ) {
44 - heading = sectionHeadings[i];
45 - heading.removeAttribute( 'onclick' ); // TODO: remove any legacy onclick handlers
46 - heading.insertBefore( createButton( true ), heading.firstChild );
47 - heading.insertBefore( createButton( false ), heading.firstChild );
48 - utilities( heading ).bind( 'click', openSectionHandler );
49 - }
50 - results = document.getElementById( 'results' );
51 - languageSelection = document.getElementById( 'languageselection' );
52 -
53 - function navigateToLanguageSelection() {
54 - var url;
55 - if ( languageSelection ) {
56 - url = languageSelection.options[languageSelection.selectedIndex].value;
57 - if ( url ) {
58 - location.href = url;
59 - }
60 - }
61 - }
62 - utilities( languageSelection ).bind( 'change', navigateToLanguageSelection );
63 -
64 - function logoClick() {
65 - var n = document.getElementById( 'nav' ).style;
66 - n.display = n.display === 'block' ? 'none' : 'block';
67 - }
68 - utilities( document.getElementById( 'logo' ) ).bind( 'click', logoClick );
69 -
70 - function checkHash() {
71 - var hash = this.hash || document.location.hash;
72 - if ( hash.indexOf( '#' ) === 0 ) {
73 - wm_reveal_for_hash( hash );
74 - }
75 - }
76 - checkHash();
77 - for ( a = document.getElementsByTagName( 'a' ), i = 0; i < a.length; i++ ) {
78 - utilities( a[i] ).bind( 'click', checkHash );
79 - }
80 -
81 - // Try to scroll and hide URL bar
82 - window.scrollTo( 0, 1 );
83 - }
84 -
85 - function wm_reveal_for_hash( hash ) {
86 - var targetel = document.getElementById( hash.substr(1) ),
87 - p, section_idx;
88 - if ( targetel ) {
89 - p = targetel;
90 - while ( p && p.className !== 'content_block' &&
91 - p.className !== 'section_heading' ) {
92 - p = p.parentNode;
93 - }
94 - if ( p && p.style.display !== 'block' ) {
95 - section_idx = parseInt( p.id.split( '_' )[1], 10 );
96 - wm_toggle_section( section_idx );
97 - }
98 - }
99 - }
100 -
101 - function wm_toggle_section( section_id ) {
102 - var b = document.getElementById( 'section_' + section_id ),
103 - bb = b.getElementsByTagName( 'button' ), i, s, e;
104 - for ( i = 0; i <= 1; i++ ) {
105 - s = bb[i].style;
106 - s.display = s.display === 'none' || ( i && !s.display ) ? 'inline-block' : 'none';
107 - }
108 - for ( i = 0, d = ['content_','anchor_']; i<=1; i++ ) {
109 - e = document.getElementById( d[i] + section_id );
110 - if ( e ) {
111 - e.style.display = e.style.display === 'block' ? 'none' : 'block';
112 - }
113 - }
114 - }
115 -
116 - utilities = typeof jQuery !== 'undefined' ? jQuery : function( el ) {
117 - if( typeof(el) === 'string' ) {
118 - if( document.querySelectorAll ) {
119 - return [].slice.call( document.querySelectorAll( el ) );
120 - }
121 - }
122 -
123 - function addClass( name ) {
124 - var className = el.className,
125 - classNames = className.split( ' ' );
126 - classNames.push(name); // TODO: only push if unique
127 - el.className = classNames.join( ' ' );
128 - }
129 -
130 - function removeClass( name ) {
131 - var className = el.className,
132 - classNames = className.split( ' ' ),
133 - newClasses = [], i;
134 - for( i = 0; i < classNames.length; i++ ) {
135 - if( classNames[i] !== name ) {
136 - newClasses.push( classNames[i] );
137 - }
138 - }
139 - el.className = newClasses.join( ' ' );
140 - }
141 -
142 - function bind( type, handler ) {
143 - el.addEventListener( type, handler, false );
144 - }
145 -
146 - function remove() {
147 - el.parentNode.removeChild(el);
148 - }
149 -
150 - return {
151 - addClass: addClass,
152 - bind: bind,
153 - remove: remove,
154 - removeClass: removeClass
155 - };
156 - }
157 - utilities.ajax = utilities.ajax || function( options ) {
158 - var xmlHttp, url;
159 - if ( window.XMLHttpRequest ) {
160 - xmlHttp = new XMLHttpRequest();
161 - } else {
162 - xmlHttp = new ActiveXObject( 'Microsoft.XMLHTTP' );
163 - }
164 - if( xmlHttp.overrideMimeType ) { // non standard
165 - xmlHttp.overrideMimeType( 'text/xml' );
166 - }
167 - xmlHttp.onreadystatechange = function() {
168 - if ( xmlHttp.readyState === 4 && xmlHttp.status === 200 ) {
169 - options.success( xmlHttp.responseXML );
170 - }
171 - };
172 - xmlHttp.open( 'GET', options.url, true );
173 - xmlHttp.send();
174 - };
175 -
176 - init();
177 - return {
178 - wm_reveal_for_hash: wm_reveal_for_hash,
179 - wm_toggle_section: wm_toggle_section,
180 - init: init,
181 - utils: utilities
182 - };
183 -
184 -}());
Index: trunk/extensions/MobileFrontend/javascripts/toggle.js
@@ -0,0 +1,95 @@
 2+/*global document, window */
 3+/*jslint sloppy: true, white:true, maxerr: 50, indent: 4, plusplus: true*/
 4+MobileFrontend.toggle = (function() {
 5+ var u = MobileFrontend.utils;
 6+
 7+ function init() {
 8+ var i, a, heading, btns,
 9+ sectionHeadings = u( '.section_heading' );
 10+
 11+ // TODO: remove in future - currently enables toggling in Wikipedia Mobile App v < 1.1
 12+ window.wm_toggle_section = wm_toggle_section;
 13+ btns = u( '.section_heading button' );
 14+ for( i = 0; i < btns.length; i++ ) {
 15+ u( btns[i] ).remove();
 16+ }
 17+
 18+ function openSectionHandler() {
 19+ var sectionNumber = this.id ? this.id.split( '_' )[1] : -1;
 20+ if( sectionNumber > -1 ) {
 21+ wm_toggle_section( sectionNumber );
 22+ }
 23+ }
 24+ function createButton( visible ) {
 25+ var btn, label;
 26+ btn = document.createElement( 'button' );
 27+ label = document.createTextNode( visible ? showText : hideText );
 28+ btn.className = visible ? 'show' : 'hide';
 29+ btn.appendChild( label );
 30+ btn.style.display = visible ? 'inline-block' : 'none';
 31+ return btn;
 32+ }
 33+ if(!sectionHeadings) {
 34+ sectionHeadings = [];
 35+ } else {
 36+ u( document.body ).addClass( 'togglingEnabled' );
 37+ }
 38+ for( i = 0; i < sectionHeadings.length; i++ ) {
 39+ heading = sectionHeadings[i];
 40+ heading.removeAttribute( 'onclick' ); // TODO: remove any legacy onclick handlers
 41+ heading.insertBefore( createButton( true ), heading.firstChild );
 42+ heading.insertBefore( createButton( false ), heading.firstChild );
 43+ u( heading ).bind( 'click', openSectionHandler );
 44+ }
 45+
 46+ function checkHash() {
 47+ var hash = this.hash || document.location.hash;
 48+ if ( hash.indexOf( '#' ) === 0 ) {
 49+ wm_reveal_for_hash( hash );
 50+ }
 51+ }
 52+ checkHash();
 53+ for ( a = document.getElementsByTagName( 'a' ), i = 0; i < a.length; i++ ) {
 54+ u( a[i] ).bind( 'click', checkHash );
 55+ }
 56+ }
 57+
 58+ function wm_reveal_for_hash( hash ) {
 59+ var targetel = document.getElementById( hash.substr(1) ),
 60+ p, section_idx;
 61+ if ( targetel ) {
 62+ p = targetel;
 63+ while ( p && p.className !== 'content_block' &&
 64+ p.className !== 'section_heading' ) {
 65+ p = p.parentNode;
 66+ }
 67+ if ( p && p.style.display !== 'block' ) {
 68+ section_idx = parseInt( p.id.split( '_' )[1], 10 );
 69+ wm_toggle_section( section_idx );
 70+ }
 71+ }
 72+ }
 73+
 74+ function wm_toggle_section( section_id ) {
 75+ var b = document.getElementById( 'section_' + section_id ),
 76+ bb = b.getElementsByTagName( 'button' ), i, s, e;
 77+ for ( i = 0; i <= 1; i++ ) {
 78+ s = bb[i].style;
 79+ s.display = s.display === 'none' || ( i && !s.display ) ? 'inline-block' : 'none';
 80+ }
 81+ for ( i = 0, d = ['content_','anchor_']; i<=1; i++ ) {
 82+ e = document.getElementById( d[i] + section_id );
 83+ if ( e ) {
 84+ e.style.display = e.style.display === 'block' ? 'none' : 'block';
 85+ }
 86+ }
 87+ }
 88+
 89+ init();
 90+ return {
 91+ wm_reveal_for_hash: wm_reveal_for_hash,
 92+ wm_toggle_section: wm_toggle_section,
 93+ init: init
 94+ };
 95+
 96+})();
Index: trunk/extensions/MobileFrontend/javascripts/application.js
@@ -4,45 +4,9 @@
55 var utilities;
66
77 function init() {
8 - var i, results, languageSelection, a, heading, btns,
9 - sectionHeadings = utilities( '.section_heading' );
 8+ var languageSelection;
109 utilities( document.body ).addClass( 'jsEnabled' );
1110
12 - // TODO: remove in future - currently enables toggling in Wikipedia Mobile App v < 1.1
13 - window.wm_toggle_section = wm_toggle_section;
14 - var btns = utilities( '.section_heading button' );
15 - for( i = 0; i < btns.length; i++ ) {
16 - utilities( btns[i] ).remove();
17 - }
18 -
19 - function openSectionHandler() {
20 - var sectionNumber = this.id ? this.id.split( '_' )[1] : -1;
21 - if( sectionNumber > -1 ) {
22 - wm_toggle_section( sectionNumber );
23 - }
24 - }
25 - function createButton( visible ) {
26 - var btn, label;
27 - btn = document.createElement( 'button' );
28 - label = document.createTextNode( visible ? showText : hideText );
29 - btn.className = visible ? 'show' : 'hide';
30 - btn.appendChild( label );
31 - btn.style.display = visible ? 'inline-block' : 'none';
32 - return btn;
33 - }
34 - if(!sectionHeadings) {
35 - sectionHeadings = [];
36 - } else {
37 - utilities( document.body ).addClass( 'togglingEnabled' );
38 - }
39 - for( i = 0; i < sectionHeadings.length; i++ ) {
40 - heading = sectionHeadings[i];
41 - heading.removeAttribute( 'onclick' ); // TODO: remove any legacy onclick handlers
42 - heading.insertBefore( createButton( true ), heading.firstChild );
43 - heading.insertBefore( createButton( false ), heading.firstChild );
44 - utilities( heading ).bind( 'click', openSectionHandler );
45 - }
46 - results = document.getElementById( 'results' );
4711 languageSelection = document.getElementById( 'languageselection' );
4812
4913 function navigateToLanguageSelection() {
@@ -62,52 +26,10 @@
6327 }
6428 utilities( document.getElementById( 'logo' ) ).bind( 'click', logoClick );
6529
66 - function checkHash() {
67 - var hash = this.hash || document.location.hash;
68 - if ( hash.indexOf( '#' ) === 0 ) {
69 - wm_reveal_for_hash( hash );
70 - }
71 - }
72 - checkHash();
73 - for ( a = document.getElementsByTagName( 'a' ), i = 0; i < a.length; i++ ) {
74 - utilities( a[i] ).bind( 'click', checkHash );
75 - }
76 -
7730 // Try to scroll and hide URL bar
7831 window.scrollTo( 0, 1 );
7932 }
8033
81 - function wm_reveal_for_hash( hash ) {
82 - var targetel = document.getElementById( hash.substr(1) ),
83 - p, section_idx;
84 - if ( targetel ) {
85 - p = targetel;
86 - while ( p && p.className !== 'content_block' &&
87 - p.className !== 'section_heading' ) {
88 - p = p.parentNode;
89 - }
90 - if ( p && p.style.display !== 'block' ) {
91 - section_idx = parseInt( p.id.split( '_' )[1], 10 );
92 - wm_toggle_section( section_idx );
93 - }
94 - }
95 - }
96 -
97 - function wm_toggle_section( section_id ) {
98 - var b = document.getElementById( 'section_' + section_id ),
99 - bb = b.getElementsByTagName( 'button' ), i, s, e;
100 - for ( i = 0; i <= 1; i++ ) {
101 - s = bb[i].style;
102 - s.display = s.display === 'none' || ( i && !s.display ) ? 'inline-block' : 'none';
103 - }
104 - for ( i = 0, d = ['content_','anchor_']; i<=1; i++ ) {
105 - e = document.getElementById( d[i] + section_id );
106 - if ( e ) {
107 - e.style.display = e.style.display === 'block' ? 'none' : 'block';
108 - }
109 - }
110 - }
111 -
11234 utilities = typeof jQuery !== 'undefined' ? jQuery : function( el ) {
11335 if( typeof(el) === 'string' ) {
11436 if( document.querySelectorAll ) {
@@ -169,8 +91,6 @@
17092
17193 init();
17294 return {
173 - wm_reveal_for_hash: wm_reveal_for_hash,
174 - wm_toggle_section: wm_toggle_section,
17595 init: init,
17696 utils: utilities
17797 };
Index: trunk/extensions/MobileFrontend/Makefile
@@ -2,5 +2,5 @@
33 java -jar yuicompressor-2.4.6.jar javascripts/application.js -o javascripts/application.min.js
44 java -jar yuicompressor-2.4.6.jar javascripts/banner.js -o javascripts/banner.min.js
55 java -jar yuicompressor-2.4.6.jar javascripts/opensearch.js -o javascripts/opensearch.min.js
6 - java -jar yuicompressor-2.4.6.jar javascripts/beta_application.js -o javascripts/beta_application.min.js
 6+ java -jar yuicompressor-2.4.6.jar javascripts/toggle.js -o javascripts/toggle.min.js
77 java -jar yuicompressor-2.4.6.jar javascripts/beta_opensearch.js -o javascripts/beta_opensearch.min.js

Follow-up revisions

RevisionCommit summaryAuthorDate
r113642follow up to r113640...jdlrobson18:15, 12 March 2012
r114201MFT r113486, r113488, r113512, r113553, r113640, r113642, r113644, r113645, r...awjrichards22:18, 19 March 2012

Status & tagging log