r111282 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r111281‎ | r111282 | r111283 >
Date:07:09, 12 February 2012
Author:preilly
Status:ok
Tags:
Comment:
revert r111273
Modified paths:
  • /trunk/extensions/MobileFrontend2 (added) (history)
  • /trunk/extensions/MobileSkin (deleted) (history)

Diff [purge]

Index: trunk/extensions/MobileFrontend2/MobileFrontend2.php
@@ -0,0 +1,84 @@
 2+<?php
 3+/**
 4+* Extension MobileFrontend2 — Mobile Frontend 2
 5+*
 6+* @file
 7+* @ingroup Extensions
 8+*/
 9+
 10+// Needs to be called within MediaWiki; not standalone
 11+if ( !defined( 'MEDIAWIKI' ) ) {
 12+ echo( "This is an extension to the MediaWiki package and cannot be run standalone.\n" );
 13+ die( -1 );
 14+}
 15+
 16+// Extension credits that will show up on Special:Version
 17+$wgExtensionCredits['other'][] = array(
 18+ 'path' => __FILE__,
 19+ 'name' => 'MobileFrontend2',
 20+ 'version' => 1,
 21+ 'author' => 'John Du Hart',
 22+ 'descriptionmsg' => 'mobile-frontend2-desc',
 23+ 'url' => 'https://www.mediawiki.org/wiki/Extension:MobileFrontend2',
 24+);
 25+
 26+$dir = dirname( __FILE__ ) . '/';
 27+$wgExtensionMessagesFiles['MobileFrontend2'] = $dir . 'MobileFrontend2.i18n.php';
 28+
 29+$wgAutoloadClasses['MobileFrontend2_Detection'] = $dir . 'MobileFrontend2_Detection.php';
 30+$wgAutoloadClasses['MobileFrontend2_Hooks'] = $dir . 'MobileFrontend2_Hooks.php';
 31+$wgAutoloadClasses['MobileFrontend2_Options'] = $dir . 'MobileFrontend2_Options.php';
 32+$wgAutoloadClasses['MobileFrontend2_PostParse'] = $dir . 'MobileFrontend2_PostParse.php';
 33+
 34+// Skins
 35+$wgAutoloadClasses['SkinMobile'] = $dir . 'skins/Mobile.php';
 36+
 37+// Hooks
 38+$wgHooks['RequestContextCreateSkin'][] = 'MobileFrontend2_Hooks::createSkin';
 39+$wgHooks['ParserSectionCreate'][] = 'MobileFrontend2_Hooks::parserSectionCreate';
 40+$wgHooks['ArticleViewHeader'][] = 'MobileFrontend2_Hooks::articleView';
 41+$wgHooks['ResourceLoaderGetStartupModules'][] = 'MobileFrontend2_Hooks::startupModule';
 42+$wgHooks['ResourceLoaderRegisterModules'][] = 'MobileFrontend2_Hooks::registerModules';
 43+$wgHooks['BeforeInitialize'][] = 'MobileFrontend2_Hooks::beforeInitialize';
 44+$wgExtensionFunctions[] = 'MobileFrontend2_Hooks::setup';
 45+
 46+// Modules
 47+$commonModuleInfo = array(
 48+ 'localBasePath' => dirname( __FILE__ ) . '/modules',
 49+ 'remoteExtPath' => 'MobileFrontend2/modules',
 50+);
 51+
 52+// Main style
 53+$wgResourceModules['ext.mobileFrontend2'] = array(
 54+ 'scripts' => 'ext.mobileFrontend2/ext.mobileFrontend2.js',
 55+ 'messages' => array(
 56+ 'mobile-frontend2-show-button',
 57+ 'mobile-frontend2-hide-button',
 58+ ),
 59+ 'dependencies' => array(
 60+ 'mediawiki.util.lite',
 61+ 'mediawiki.api.lite',
 62+ ),
 63+) + $commonModuleInfo;
 64+
 65+$wgResourceModules['ext.mobileFrontend2.common'] = array(
 66+ 'styles' => array(
 67+ 'ext.mobileFrontend2/ext.mobileFrontend2.css',
 68+ 'ext.mobileFrontend2/ext.mobileFrontend2.search.css',
 69+ ),
 70+) + $commonModuleInfo;
 71+
 72+$wgResourceModules['zepto'] = array(
 73+ 'scripts' => array(
 74+ 'zepto/zepto.js',
 75+ 'zepto/zepto.mw.js',
 76+ ),
 77+) + $commonModuleInfo;
 78+
 79+// Config
 80+/**
 81+ * Logo used on MobileFrontend2
 82+ *
 83+ * @var $wgMobileFrontend2Logo string
 84+ */
 85+$wgMobileFrontend2Logo = null;
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/MobileFrontend2.php
___________________________________________________________________
Added: svn:eol-style
186 + native
Index: trunk/extensions/MobileFrontend2/MobileFrontend2_PostParse.php
@@ -0,0 +1,92 @@
 2+<?php
 3+
 4+/**
 5+ * Take the bodycontent and manipulate it for mobile
 6+ */
 7+class MobileFrontend2_PostParse {
 8+
 9+ /**
 10+ * HTML to parse
 11+ *
 12+ * @var string
 13+ */
 14+ protected $html;
 15+
 16+ /**
 17+ * DOM of the bodycontent
 18+ *
 19+ * @var DOMDocument
 20+ */
 21+ protected $dom;
 22+
 23+ /**
 24+ * Private constructor, use the mange function
 25+ *
 26+ * @param $html
 27+ */
 28+ protected function __construct( $html ) {
 29+ $this->html = $html;
 30+
 31+ $this->initDom();
 32+ }
 33+
 34+ /**
 35+ * Entry point for the class
 36+ *
 37+ * @param $text string
 38+ * @return string
 39+ */
 40+ public static function mangle( $text ) {
 41+ $postParse = new self( $text );
 42+ $postParse->parse();
 43+
 44+ return $postParse->html;
 45+ }
 46+
 47+ /**
 48+ * Sets up the DOM document
 49+ */
 50+ protected function initDom() {
 51+ // LibXML is noisy apparently
 52+ libxml_use_internal_errors( true );
 53+ $dom = new DOMDocument();
 54+ $dom->loadHTML( '<?xml encoding="UTF-8">' . $this->html );
 55+ libxml_use_internal_errors( false );
 56+
 57+ $dom->strictErrorChecking = false;
 58+ $dom->encoding = 'UTF-8';
 59+
 60+ $this->dom = $dom;
 61+ }
 62+
 63+ /**
 64+ * Actually parse the DOM
 65+ */
 66+ public function parse() {
 67+ // Remove the TOC
 68+ $this->removeToc();
 69+
 70+ // Render the now manipulated HTML
 71+ $this->render();
 72+ }
 73+
 74+ /**
 75+ * Removes the TOC (#toc) from the body
 76+ */
 77+ protected function removeToc() {
 78+ $element = $this->dom->getElementById( 'toc' );
 79+
 80+ if ( $element !== null ) {
 81+ $element->parentNode->removeChild( $element );
 82+ }
 83+ }
 84+
 85+ /**
 86+ * Saves the HTML to $html
 87+ */
 88+ protected function render() {
 89+ $this->html = $this->dom->saveXML(
 90+ $this->dom->getElementsByTagName( 'body' )
 91+ ->item( 0 )->childNodes->item( 0 ) );
 92+ }
 93+}
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/MobileFrontend2_PostParse.php
___________________________________________________________________
Added: svn:eol-style
194 + native
Index: trunk/extensions/MobileFrontend2/MobileFrontend2_Hooks.php
@@ -0,0 +1,171 @@
 2+<?php
 3+
 4+/**
 5+ * Hooks for the new mobile frontend
 6+ */
 7+class MobileFrontend2_Hooks {
 8+ /**
 9+ * Loads the mobile skin if we need to
 10+ *
 11+ * @param $context ResourceContext
 12+ * @param $skin Skin
 13+ * @return bool
 14+ */
 15+ public static function createSkin( $context, &$skin ) {
 16+ // Abort if we're not using the mobile frontend
 17+ if ( !MobileFrontend2_Detection::isEnabled() ) {
 18+ return true;
 19+ }
 20+
 21+ // TODO: WML support
 22+ $skin = new SkinMobile;
 23+
 24+ // Be a dick and halt the hook
 25+ return false;
 26+ }
 27+
 28+ /**
 29+ * Adds jump back a section links to content blocks
 30+ *
 31+ * @todo broken, see mobile main page
 32+ *
 33+ * @param $parser MobileFrontend2_Parser
 34+ * @param $i int
 35+ * @param $section string
 36+ * @param $showEditLink bool
 37+ * @return bool
 38+ */
 39+ public static function parserSectionCreate( $parser, $i, &$section, $showEditLink ) {
 40+ if ( !MobileFrontend2_Detection::isEnabled() ) {
 41+ return true;
 42+ }
 43+
 44+ // We don't enclose the opening section
 45+ if ( $i == 0 ) {
 46+ return true;
 47+ }
 48+
 49+ // Separate the header from the section
 50+ preg_match( '/<H[1-6].*?' . '>.*?<\/H[1-6]>/i', $section, $match );
 51+ $headerLength = strlen( $match[0] );
 52+
 53+ $section = "<div section_id=\"$i\" id=\"section-$i\" class=\"mf2-section-container\">"
 54+ . substr( $section, 0, $headerLength )
 55+ . '<div class="mf2-content-block">'
 56+ . substr( $section, $headerLength ) . "\n\n"
 57+ . '<div class="mf2-section-anchor">'
 58+ . '<a href="#section-' . $i . '">'
 59+ . wfMessage( 'mobile-frontend2-back-to-top-of-section' )->escaped()
 60+ . '</a></div></div></div>';
 61+
 62+ return true;
 63+ }
 64+
 65+ public static function articleView( &$article, &$outputDone, &$useParserCache ) {
 66+ // This is where we want to fetch the article from squids
 67+ return true;
 68+ }
 69+
 70+ /**
 71+ * Replaces jQuery with zepto.js for mobile
 72+ *
 73+ * @param $modules
 74+ * @return bool
 75+ */
 76+ public static function startupModule( &$modules ) {
 77+ // comment about this
 78+ if ( self::isMobileSkin() ) {
 79+ $modules = array(
 80+ 'zepto',
 81+ 'mediawiki',
 82+ );
 83+ return false;
 84+ }
 85+
 86+ return true;
 87+ }
 88+
 89+ /**
 90+ * Registers lite versions of core modules for mobile
 91+ *
 92+ * @param ResourceLoader $resourceLoader
 93+ * @return bool
 94+ */
 95+ public static function registerModules( ResourceLoader &$resourceLoader ) {
 96+ if ( self::isMobileSkin() ) {
 97+ global $wgResourceModules;
 98+
 99+ // We need to remove dependencies from mw.util that will don't use and
 100+ // aren't compatible with zepto.js
 101+ // Krinkle will hate me
 102+ // TODO: This only saves about 4KB, reevaluate later
 103+ $wgResourceModules['mediawiki.util.lite'] = array(
 104+ 'scripts' => 'resources/mediawiki/mediawiki.util.js',
 105+ /*'dependencies' => array(
 106+ 'jquery.client',
 107+ 'jquery.cookie',
 108+ 'jquery.messageBox',
 109+ 'jquery.mwExtension',
 110+ ),*/
 111+ //'messages' => array( 'showtoc', 'hidetoc' ),
 112+ 'position' => 'top', // For $wgPreloadJavaScriptMwUtil
 113+ );
 114+ $wgResourceModules['mediawiki.api.lite'] = array(
 115+ 'scripts' => 'resources/mediawiki/mediawiki.api.js',
 116+ 'dependencies' => 'mediawiki.util.lite',
 117+ );
 118+ }
 119+
 120+ return true;
 121+ }
 122+
 123+ /**
 124+ * Overrides the main page with the mobile version of the main page
 125+ *
 126+ * @param Title $title
 127+ * @param $unused
 128+ * @param $output
 129+ * @param $user
 130+ * @param $request
 131+ * @param $wiki
 132+ * @return bool
 133+ */
 134+ public static function beforeInitialize( Title &$title, &$unused, &$output, &$user, $request, $wiki ) {
 135+ if ( MobileFrontend2_Detection::isEnabled() && $title->isMainPage() ) {
 136+ $title = Title::newFromText( wfMsgForContent( 'mainpage-mobile' ) );
 137+ RequestContext::getMain()->setTitle( $title );
 138+ MobileFrontend2_Options::setMainPage( true );
 139+ }
 140+
 141+ return true;
 142+ }
 143+
 144+ /**
 145+ * Checks if the skin parameter is for a mobile skin
 146+ *
 147+ * This only works for load.php
 148+ *
 149+ * @return bool
 150+ */
 151+ protected static function isMobileSkin() {
 152+ return RequestContext::getMain()->getRequest()->getVal( 'skin' ) == 'mobile';
 153+ }
 154+
 155+ /**
 156+ * Perform very early setup
 157+ *
 158+ * @return bool
 159+ */
 160+ public static function setup() {
 161+ if ( !MobileFrontend2_Detection::isEnabled() ) {
 162+ return true;
 163+ }
 164+ global $wgMobileFrontend2Logo, $wgExtensionAssetsPath;
 165+
 166+ // We need a sane default and $wgExtensionAssetsPath isn't ready until
 167+ // after LocalSettings
 168+ if ( $wgMobileFrontend2Logo === null ) {
 169+ $wgMobileFrontend2Logo = $wgExtensionAssetsPath . '/MobileFrontend2/modules/ext.mobileFrontend2/images/mw.png';
 170+ }
 171+ }
 172+}
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/MobileFrontend2_Hooks.php
___________________________________________________________________
Added: svn:eol-style
1173 + native
Index: trunk/extensions/MobileFrontend2/skins/Mobile.php
@@ -0,0 +1,228 @@
 2+<?php
 3+
 4+class SkinMobile extends SkinTemplate {
 5+ var $skinname = 'mobile',
 6+ $stylename = 'mobile',
 7+ $template = 'MobileTemplate',
 8+ $useHeadElement = false;
 9+
 10+ /**
 11+ * Overridden to make changes to resource loader
 12+ *
 13+ * @param null|OutputPage $out
 14+ */
 15+ function outputPage( OutputPage $out = null ) {
 16+ global $wgScript, $wgMobileFrontend2Logo;
 17+
 18+ $out = $this->getOutput();
 19+ $request = $this->getRequest();
 20+ $user = $this->getUser();
 21+ $title = $this->getTitle();
 22+
 23+ // We need to disable all the default RL modules, do that like this
 24+ $out->clearAllModules();
 25+
 26+ // Add the mobile js
 27+ $out->addModules( 'ext.mobileFrontend2' );
 28+
 29+ // TODO: Hook for adding modules
 30+
 31+ $bodyClass = 'mobile';
 32+
 33+ if ( MobileFrontend2_Options::getMainPage() ) {
 34+ // fixup the HTML title
 35+ $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
 36+ if ( !$msg->isDisabled() ) {
 37+ $out->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
 38+ }
 39+
 40+ $bodyClass .= ' mainPage';
 41+ }
 42+
 43+ Profiler::instance()->setTemplated( true );
 44+
 45+ $this->initPage( $out );
 46+ $tpl = $this->setupTemplate( $this->template, 'skins' );
 47+
 48+ // Give the skin (us) to the template
 49+ $tpl->setRef( 'skin', $this );
 50+
 51+ // Language stuff
 52+ $lang = $this->getLanguage();
 53+ $userlang = $lang->getHtmlCode();
 54+ $userdir = $lang->getDir();
 55+
 56+ $tpl->set( 'lang', $userlang );
 57+ $tpl->set( 'dir', $userdir );
 58+
 59+ // Title
 60+ $tpl->set( 'title', $out->getPageTitle() );
 61+ $tpl->set( 'pagetitle', $out->getHTMLTitle() );
 62+
 63+ // Scriptpath (Used for search and forms)
 64+ $tpl->setRef( 'wgScript', $wgScript );
 65+
 66+ // Mobile stuff
 67+ $tpl->setRef( 'mobilelogopath', $wgMobileFrontend2Logo );
 68+
 69+ # Add a <div class="mw-content-ltr/rtl"> around the body text
 70+ # not for special pages or file pages AND only when viewing AND if the page exists
 71+ # (or is in MW namespace, because that has default content)
 72+ if( !in_array( $title->getNamespace(), array( NS_SPECIAL, NS_FILE ) ) &&
 73+ in_array( $request->getVal( 'action', 'view' ), array( 'view', 'historysubmit' ) ) &&
 74+ ( $title->exists() || $title->getNamespace() == NS_MEDIAWIKI ) ) {
 75+ $pageLang = $title->getPageLanguage();
 76+ $realBodyAttribs = array( 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
 77+ 'class' => 'mw-content-'.$pageLang->getDir() );
 78+ $out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
 79+ }
 80+
 81+ $tpl->setRef( 'bodycontent', MobileFrontend2_PostParse::mangle( $out->mBodytext ) );
 82+
 83+ // Pass the bodyClass for CSS magic
 84+ $tpl->set( 'bodyclass', $bodyClass );
 85+
 86+ // CSS & JS
 87+ // Make these last
 88+ $tpl->set( 'headscripts', $this->getHeadScripts( $out ) );
 89+ $tpl->set( 'csslinks', $out->buildCssLinks() );
 90+ $tpl->set( 'bottomscripts', $this->bottomScripts() );
 91+
 92+ // Debug comments and stuff
 93+ $tpl->set( 'debughtml', $this->generateDebugHTML() );
 94+
 95+
 96+ // Output
 97+ $res = $tpl->execute();
 98+ // result may be an error
 99+ $this->printOrError( $res );
 100+ }
 101+
 102+ /**
 103+ * Skin CSS
 104+ *
 105+ * @param OutputPage $out
 106+ */
 107+ function setupSkinUserCss( OutputPage $out ) {
 108+ $out->addModuleStyles( 'ext.mobileFrontend2.common' );
 109+ }
 110+
 111+ /**
 112+ * We're too cool for edit links, don't output them. Instead, output the
 113+ * section toggle button.
 114+ *
 115+ * @param Title $nt
 116+ * @param $section
 117+ * @param null $tooltip
 118+ * @param bool $lang
 119+ * @return string
 120+ */
 121+ public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
 122+ return '<button class="mf2-section-toggle">' . wfMessage( 'mobile-frontend2-show-button' )->escaped() . '</button>';
 123+ }
 124+
 125+ /**
 126+ * More minimal version of getHeadScripts from OutputPage
 127+ *
 128+ * @param OutputPage $out
 129+ * @return string
 130+ */
 131+ protected function getHeadScripts( OutputPage $out ) {
 132+ $scripts = $out->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true, array( 'mobile' => true ) );
 133+
 134+ $scripts .= Html::inlineScript(
 135+ ResourceLoader::makeLoaderConditionalScript(
 136+ ResourceLoader::makeConfigSetScript( $out->getJSVars() )
 137+ )
 138+ );
 139+
 140+ return $scripts;
 141+ }
 142+}
 143+
 144+class MobileTemplate extends BaseTemplate {
 145+
 146+ /**
 147+ * Main function, used by classes that subclass QuickTemplate
 148+ * to show the actual HTML output
 149+ */
 150+ public function execute() {
 151+?>
 152+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 153+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 154+ <html lang="<?php $this->text( 'lang' ) ?>" dir="<?php $this->text( 'dir' ) ?>" xml:lang="<?php $this->text( 'lang' ) ?>" xmlns="http://www.w3.org/1999/xhtml">
 155+ <head>
 156+ <title><?php $this->text( 'pagetitle' ) ?></title>
 157+
 158+ <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
 159+ <meta name="ROBOTS" content="NOINDEX, NOFOLLOW" />
 160+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
 161+
 162+ <?php $this->html( 'csslinks' ) ?>
 163+ <?php $this->html( 'headscripts' ) ?>
 164+ </head>
 165+ <body class="<?php $this->text( 'bodyclass' ) ?>">
 166+
 167+ <?php if ( !MobileFrontend2_Options::getHideSearch() ): ?>
 168+ <!-- search/header -->
 169+ <div id="results"></div>
 170+ <div id="header">
 171+ <div id="searchbox">
 172+ <?php if ( !MobileFrontend2_Options::getHideLogo() ): ?>
 173+ <img src="<?php $this->text( 'mobilelogopath' ) ?>" alt="Logo" id="mf2-logo" width="35" height="22" />
 174+ <?php endif ?>
 175+ <form action="<?php $this->text( 'wgScript' ) ?>" class="mf2-search-bar" method="get">
 176+ <input type="hidden" name="title" value="Special:Search" />
 177+
 178+ <div id="sq" class="divclearable">
 179+ <input type="text" name="search" id="search" size="22" value="" autocorrect="off" autocomplete="off" autocapitalize="off" maxlength="1024" />
 180+ <div class="clearlink" id="clearsearch"></div>
 181+ </div>
 182+ <button id="goButton" type="submit"></button>
 183+ </form>
 184+ </div>
 185+ </div>
 186+ <?php endif ?>
 187+
 188+ <!-- content -->
 189+ <div class="show" id="content_wrapper">
 190+ <?php if ( !MobileFrontend2_Options::getMainPage() ): ?>
 191+ <!-- firstHeading -->
 192+ <h1 id="firstHeading" class="firstHeading">
 193+ <span dir="auto"><?php $this->html( 'title' ) ?></span>
 194+ </h1>
 195+ <!-- /firstHeading -->
 196+ <?php endif ?>
 197+ <!-- bodyContent -->
 198+ <div id="bodyContent">
 199+ <?php $this->html( 'bodycontent' ) ?>
 200+ <!-- debughtml -->
 201+ <?php $this->html( 'debughtml' ); ?>
 202+ <!-- /debughtml -->
 203+ </div>
 204+ <!-- /bodyContent -->
 205+ </div>
 206+
 207+ <?php if ( !MobileFrontend2_Options::getHideFooter() ): ?>
 208+ <!-- footer -->
 209+ <div id="footer">
 210+ <div id="innerFooter">
 211+ <a href="#"><?php $this->msg( 'mobile-frontend2-regular-site' ) ?></a> | <a href="#"><?php $this->msg( 'mobile-frontend2-disable-images' ) ?></a>
 212+
 213+ <div id="perm">
 214+ <a href="#"><?php $this->msg( 'mobile-frontend2-perm-stop-redirect' ) ?></a>
 215+ </div>
 216+ </div>
 217+ <div id="copyright">
 218+ <?php $this->msg( 'mobile-frontend2-copyright' ) ?>
 219+ </div>
 220+ </div>
 221+ <?php endif ?>
 222+
 223+ <?php $this->html( 'bottomscripts' ) ?>
 224+
 225+ </body>
 226+ </html>
 227+<?php
 228+ }
 229+}
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/skins/Mobile.php
___________________________________________________________________
Added: svn:eol-style
1230 + native
Index: trunk/extensions/MobileFrontend2/modules/zepto/zepto.js
@@ -0,0 +1,1420 @@
 2+// Zepto.js
 3+// (c) 2010-2012 Thomas Fuchs
 4+// Zepto.js may be freely distributed under the MIT license.
 5+
 6+(function(undefined){
 7+ if (String.prototype.trim === undefined) // fix for iOS 3.2
 8+ String.prototype.trim = function(){ return this.replace(/^\s+/, '').replace(/\s+$/, '') };
 9+
 10+ // For iOS 3.x
 11+ // from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reduce
 12+ if (Array.prototype.reduce === undefined)
 13+ Array.prototype.reduce = function(fun){
 14+ if(this === void 0 || this === null) throw new TypeError();
 15+ var t = Object(this), len = t.length >>> 0, k = 0, accumulator;
 16+ if(typeof fun != 'function') throw new TypeError();
 17+ if(len == 0 && arguments.length == 1) throw new TypeError();
 18+
 19+ if(arguments.length >= 2)
 20+ accumulator = arguments[1];
 21+ else
 22+ do{
 23+ if(k in t){
 24+ accumulator = t[k++];
 25+ break;
 26+ }
 27+ if(++k >= len) throw new TypeError();
 28+ } while (true);
 29+
 30+ while (k < len){
 31+ if(k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t);
 32+ k++;
 33+ }
 34+ return accumulator;
 35+ };
 36+
 37+})();
 38+// Zepto.js
 39+// (c) 2010-2012 Thomas Fuchs
 40+// Zepto.js may be freely distributed under the MIT license.
 41+
 42+var Zepto = (function() {
 43+ var undefined, key, $$, classList, emptyArray = [], slice = emptyArray.slice,
 44+ document = window.document,
 45+ elementDisplay = {}, classCache = {},
 46+ getComputedStyle = document.defaultView.getComputedStyle,
 47+ cssNumber = { 'column-count': 1, 'columns': 1, 'font-weight': 1, 'line-height': 1,'opacity': 1, 'z-index': 1, 'zoom': 1 },
 48+ fragmentRE = /^\s*<(\w+)[^>]*>/,
 49+ elementTypes = [1, 9, 11],
 50+ adjacencyOperators = [ 'after', 'prepend', 'before', 'append' ],
 51+ table = document.createElement('table'),
 52+ tableRow = document.createElement('tr'),
 53+ containers = {
 54+ 'tr': document.createElement('tbody'),
 55+ 'tbody': table, 'thead': table, 'tfoot': table,
 56+ 'td': tableRow, 'th': tableRow,
 57+ '*': document.createElement('div')
 58+ },
 59+ readyRE = /complete|loaded|interactive/,
 60+ classSelectorRE = /^\.([\w-]+)$/,
 61+ idSelectorRE = /^#([\w-]+)$/,
 62+ tagSelectorRE = /^[\w-]+$/;
 63+
 64+ function isF(value) { return ({}).toString.call(value) == "[object Function]" }
 65+ function isO(value) { return value instanceof Object }
 66+ function isA(value) { return value instanceof Array }
 67+ function likeArray(obj) { return typeof obj.length == 'number' }
 68+
 69+ function compact(array) { return array.filter(function(item){ return item !== undefined && item !== null }) }
 70+ function flatten(array) { return array.length > 0 ? [].concat.apply([], array) : array }
 71+ function camelize(str) { return str.replace(/-+(.)?/g, function(match, chr){ return chr ? chr.toUpperCase() : '' }) }
 72+ function dasherize(str){
 73+ return str.replace(/::/g, '/')
 74+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
 75+ .replace(/([a-z\d])([A-Z])/g, '$1_$2')
 76+ .replace(/_/g, '-')
 77+ .toLowerCase();
 78+ }
 79+ function uniq(array) { return array.filter(function(item,index,array){ return array.indexOf(item) == index }) }
 80+
 81+ function classRE(name){
 82+ return name in classCache ?
 83+ classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'));
 84+ }
 85+
 86+ function maybeAddPx(name, value) { return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value; }
 87+
 88+ function defaultDisplay(nodeName) {
 89+ var element, display;
 90+ if (!elementDisplay[nodeName]) {
 91+ element = document.createElement(nodeName);
 92+ document.body.appendChild(element);
 93+ display = getComputedStyle(element, '').getPropertyValue("display");
 94+ element.parentNode.removeChild(element);
 95+ display == "none" && (display = "block");
 96+ elementDisplay[nodeName] = display;
 97+ }
 98+ return elementDisplay[nodeName];
 99+ }
 100+
 101+ function fragment(html, name) {
 102+ if (name === undefined) name = fragmentRE.test(html) && RegExp.$1;
 103+ if (!(name in containers)) name = '*';
 104+ var container = containers[name];
 105+ container.innerHTML = '' + html;
 106+ return slice.call(container.childNodes);
 107+ }
 108+
 109+ function Z(dom, selector){
 110+ dom = dom || emptyArray;
 111+ dom.__proto__ = Z.prototype;
 112+ dom.selector = selector || '';
 113+ return dom;
 114+ }
 115+
 116+ function $(selector, context){
 117+ if (!selector) return Z();
 118+ if (context !== undefined) return $(context).find(selector);
 119+ else if (isF(selector)) return $(document).ready(selector);
 120+ else if (selector instanceof Z) return selector;
 121+ else {
 122+ var dom;
 123+ if (isA(selector)) dom = compact(selector);
 124+ else if (elementTypes.indexOf(selector.nodeType) >= 0 || selector === window)
 125+ dom = [selector], selector = null;
 126+ else if (fragmentRE.test(selector))
 127+ dom = fragment(selector.trim(), RegExp.$1), selector = null;
 128+ else if (selector.nodeType && selector.nodeType == 3) dom = [selector];
 129+ else dom = $$(document, selector);
 130+ return Z(dom, selector);
 131+ }
 132+ }
 133+
 134+ $.extend = function(target){
 135+ slice.call(arguments, 1).forEach(function(source) {
 136+ for (key in source) target[key] = source[key];
 137+ })
 138+ return target;
 139+ }
 140+
 141+ $.qsa = $$ = function(element, selector){
 142+ var found;
 143+ return (element === document && idSelectorRE.test(selector)) ?
 144+ ( (found = element.getElementById(RegExp.$1)) ? [found] : emptyArray ) :
 145+ slice.call(
 146+ classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) :
 147+ tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) :
 148+ element.querySelectorAll(selector)
 149+ );
 150+ }
 151+
 152+ function filtered(nodes, selector){
 153+ return selector === undefined ? $(nodes) : $(nodes).filter(selector);
 154+ }
 155+
 156+ function funcArg(context, arg, idx, payload){
 157+ return isF(arg) ? arg.call(context, idx, payload) : arg;
 158+ }
 159+
 160+ $.isFunction = isF;
 161+ $.isObject = isO;
 162+ $.isArray = isA;
 163+
 164+ $.inArray = function(elem, array, i) {
 165+ return emptyArray.indexOf.call(array, elem, i);
 166+ }
 167+
 168+ $.map = function(elements, callback) {
 169+ var value, values = [], i, key;
 170+ if (likeArray(elements))
 171+ for (i = 0; i < elements.length; i++) {
 172+ value = callback(elements[i], i);
 173+ if (value != null) values.push(value);
 174+ }
 175+ else
 176+ for (key in elements) {
 177+ value = callback(elements[key], key);
 178+ if (value != null) values.push(value);
 179+ }
 180+ return flatten(values);
 181+ }
 182+
 183+ $.each = function(elements, callback) {
 184+ var i, key;
 185+ if (likeArray(elements))
 186+ for(i = 0; i < elements.length; i++) {
 187+ if(callback.call(elements[i], i, elements[i]) === false) return elements;
 188+ }
 189+ else
 190+ for(key in elements) {
 191+ if(callback.call(elements[key], key, elements[key]) === false) return elements;
 192+ }
 193+ return elements;
 194+ }
 195+
 196+ $.fn = {
 197+ forEach: emptyArray.forEach,
 198+ reduce: emptyArray.reduce,
 199+ push: emptyArray.push,
 200+ indexOf: emptyArray.indexOf,
 201+ concat: emptyArray.concat,
 202+ map: function(fn){
 203+ return $.map(this, function(el, i){ return fn.call(el, i, el) });
 204+ },
 205+ slice: function(){
 206+ return $(slice.apply(this, arguments));
 207+ },
 208+ ready: function(callback){
 209+ if (readyRE.test(document.readyState)) callback($);
 210+ else document.addEventListener('DOMContentLoaded', function(){ callback($) }, false);
 211+ return this;
 212+ },
 213+ get: function(idx){ return idx === undefined ? this : this[idx] },
 214+ size: function(){ return this.length },
 215+ remove: function () {
 216+ return this.each(function () {
 217+ if (this.parentNode != null) {
 218+ this.parentNode.removeChild(this);
 219+ }
 220+ });
 221+ },
 222+ each: function(callback){
 223+ this.forEach(function(el, idx){ callback.call(el, idx, el) });
 224+ return this;
 225+ },
 226+ filter: function(selector){
 227+ return $([].filter.call(this, function(element){
 228+ return element.parentNode && $$(element.parentNode, selector).indexOf(element) >= 0;
 229+ }));
 230+ },
 231+ end: function(){
 232+ return this.prevObject || $();
 233+ },
 234+ andSelf:function(){
 235+ return this.add(this.prevObject || $())
 236+ },
 237+ add:function(selector,context){
 238+ return $(uniq(this.concat($(selector,context))));
 239+ },
 240+ is: function(selector){
 241+ return this.length > 0 && $(this[0]).filter(selector).length > 0;
 242+ },
 243+ not: function(selector){
 244+ var nodes=[];
 245+ if (isF(selector) && selector.call !== undefined)
 246+ this.each(function(idx){
 247+ if (!selector.call(this,idx)) nodes.push(this);
 248+ });
 249+ else {
 250+ var excludes = typeof selector == 'string' ? this.filter(selector) :
 251+ (likeArray(selector) && isF(selector.item)) ? slice.call(selector) : $(selector);
 252+ this.forEach(function(el){
 253+ if (excludes.indexOf(el) < 0) nodes.push(el);
 254+ });
 255+ }
 256+ return $(nodes);
 257+ },
 258+ eq: function(idx){
 259+ return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1);
 260+ },
 261+ first: function(){ var el = this[0]; return el && !isO(el) ? el : $(el) },
 262+ last: function(){ var el = this[this.length - 1]; return el && !isO(el) ? el : $(el) },
 263+ find: function(selector){
 264+ var result;
 265+ if (this.length == 1) result = $$(this[0], selector);
 266+ else result = this.map(function(){ return $$(this, selector) });
 267+ return $(result);
 268+ },
 269+ closest: function(selector, context){
 270+ var node = this[0], candidates = $$(context || document, selector);
 271+ if (!candidates.length) node = null;
 272+ while (node && candidates.indexOf(node) < 0)
 273+ node = node !== context && node !== document && node.parentNode;
 274+ return $(node);
 275+ },
 276+ parents: function(selector){
 277+ var ancestors = [], nodes = this;
 278+ while (nodes.length > 0)
 279+ nodes = $.map(nodes, function(node){
 280+ if ((node = node.parentNode) && node !== document && ancestors.indexOf(node) < 0) {
 281+ ancestors.push(node);
 282+ return node;
 283+ }
 284+ });
 285+ return filtered(ancestors, selector);
 286+ },
 287+ parent: function(selector){
 288+ return filtered(uniq(this.pluck('parentNode')), selector);
 289+ },
 290+ children: function(selector){
 291+ return filtered(this.map(function(){ return slice.call(this.children) }), selector);
 292+ },
 293+ siblings: function(selector){
 294+ return filtered(this.map(function(i, el){
 295+ return slice.call(el.parentNode.children).filter(function(child){ return child!==el });
 296+ }), selector);
 297+ },
 298+ empty: function(){ return this.each(function(){ this.innerHTML = '' }) },
 299+ pluck: function(property){ return this.map(function(){ return this[property] }) },
 300+ show: function(){
 301+ return this.each(function() {
 302+ this.style.display == "none" && (this.style.display = null);
 303+ if (getComputedStyle(this, '').getPropertyValue("display") == "none") {
 304+ this.style.display = defaultDisplay(this.nodeName)
 305+ }
 306+ })
 307+ },
 308+ replaceWith: function(newContent) {
 309+ return this.each(function() {
 310+ $(this).before(newContent).remove();
 311+ });
 312+ },
 313+ wrap: function(newContent) {
 314+ return this.each(function() {
 315+ $(this).wrapAll($(newContent)[0].cloneNode(false));
 316+ });
 317+ },
 318+ wrapAll: function(newContent) {
 319+ if (this[0]) {
 320+ $(this[0]).before(newContent = $(newContent));
 321+ newContent.append(this);
 322+ }
 323+ return this;
 324+ },
 325+ unwrap: function(){
 326+ this.parent().each(function(){
 327+ $(this).replaceWith($(this).children());
 328+ });
 329+ return this;
 330+ },
 331+ hide: function(){
 332+ return this.css("display", "none")
 333+ },
 334+ toggle: function(setting){
 335+ return (setting === undefined ? this.css("display") == "none" : setting) ? this.show() : this.hide();
 336+ },
 337+ prev: function(){ return $(this.pluck('previousElementSibling')) },
 338+ next: function(){ return $(this.pluck('nextElementSibling')) },
 339+ html: function(html){
 340+ return html === undefined ?
 341+ (this.length > 0 ? this[0].innerHTML : null) :
 342+ this.each(function (idx) {
 343+ var originHtml = this.innerHTML;
 344+ $(this).empty().append( funcArg(this, html, idx, originHtml) );
 345+ });
 346+ },
 347+ text: function(text){
 348+ return text === undefined ?
 349+ (this.length > 0 ? this[0].textContent : null) :
 350+ this.each(function(){ this.textContent = text });
 351+ },
 352+ attr: function(name, value){
 353+ var res;
 354+ return (typeof name == 'string' && value === undefined) ?
 355+ (this.length == 0 ? undefined :
 356+ (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
 357+ (!(res = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : res
 358+ ) :
 359+ this.each(function(idx){
 360+ if (isO(name)) for (key in name) this.setAttribute(key, name[key])
 361+ else this.setAttribute(name, funcArg(this, value, idx, this.getAttribute(name)));
 362+ });
 363+ },
 364+ removeAttr: function(name) {
 365+ return this.each(function() { this.removeAttribute(name); });
 366+ },
 367+ data: function(name, value){
 368+ return this.attr('data-' + name, value);
 369+ },
 370+ val: function(value){
 371+ return (value === undefined) ?
 372+ (this.length > 0 ? this[0].value : null) :
 373+ this.each(function(idx){
 374+ this.value = funcArg(this, value, idx, this.value);
 375+ });
 376+ },
 377+ offset: function(){
 378+ if(this.length==0) return null;
 379+ var obj = this[0].getBoundingClientRect();
 380+ return {
 381+ left: obj.left + window.pageXOffset,
 382+ top: obj.top + window.pageYOffset,
 383+ width: obj.width,
 384+ height: obj.height
 385+ };
 386+ },
 387+ css: function(property, value){
 388+ if (value === undefined && typeof property == 'string') {
 389+ return(
 390+ this.length == 0
 391+ ? undefined
 392+ : this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property)
 393+ );
 394+ }
 395+ var css = '';
 396+ for (key in property) css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';';
 397+ if (typeof property == 'string') css = dasherize(property) + ":" + maybeAddPx(property, value);
 398+ return this.each(function() { this.style.cssText += ';' + css });
 399+ },
 400+ index: function(element){
 401+ return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0]);
 402+ },
 403+ hasClass: function(name){
 404+ if (this.length < 1) return false;
 405+ else return classRE(name).test(this[0].className);
 406+ },
 407+ addClass: function(name){
 408+ return this.each(function(idx) {
 409+ classList = [];
 410+ var cls = this.className, newName = funcArg(this, name, idx, cls);
 411+ newName.split(/\s+/g).forEach(function(klass) {
 412+ if (!$(this).hasClass(klass)) {
 413+ classList.push(klass)
 414+ }
 415+ }, this);
 416+ classList.length && (this.className += (cls ? " " : "") + classList.join(" "))
 417+ });
 418+ },
 419+ removeClass: function(name){
 420+ return this.each(function(idx) {
 421+ if(name === undefined)
 422+ return this.className = '';
 423+ classList = this.className;
 424+ funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass) {
 425+ classList = classList.replace(classRE(klass), " ")
 426+ });
 427+ this.className = classList.trim()
 428+ });
 429+ },
 430+ toggleClass: function(name, when){
 431+ return this.each(function(idx){
 432+ var newName = funcArg(this, name, idx, this.className);
 433+ (when === undefined ? !$(this).hasClass(newName) : when) ?
 434+ $(this).addClass(newName) : $(this).removeClass(newName);
 435+ });
 436+ }
 437+ };
 438+
 439+ 'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property){
 440+ var fn = $.fn[property];
 441+ $.fn[property] = function() {
 442+ var ret = fn.apply(this, arguments);
 443+ ret.prevObject = this;
 444+ return ret;
 445+ }
 446+ });
 447+
 448+ ['width', 'height'].forEach(function(dimension){
 449+ $.fn[dimension] = function(value) {
 450+ var offset, Dimension = dimension.replace(/./, function(m) { return m[0].toUpperCase() });
 451+ if (value === undefined) return this[0] == window ? window['inner' + Dimension] :
 452+ this[0] == document ? document.documentElement['offset' + Dimension] :
 453+ (offset = this.offset()) && offset[dimension];
 454+ else return this.each(function(idx){
 455+ var el = $(this);
 456+ el.css(dimension, funcArg(this, value, idx, el[dimension]()));
 457+ });
 458+ }
 459+ });
 460+
 461+ function insert(operator, target, node) {
 462+ var parent = (operator % 2) ? target : target.parentNode;
 463+ parent && parent.insertBefore(node,
 464+ !operator ? target.nextSibling : // after
 465+ operator == 1 ? parent.firstChild : // prepend
 466+ operator == 2 ? target : // before
 467+ null); // append
 468+ }
 469+
 470+ function traverseNode (node, fun) {
 471+ fun(node);
 472+ for (var key in node.childNodes) {
 473+ traverseNode(node.childNodes[key], fun);
 474+ }
 475+ }
 476+
 477+ adjacencyOperators.forEach(function(key, operator) {
 478+ $.fn[key] = function(html){
 479+ var nodes = isO(html) ? html : fragment(html);
 480+ if (!('length' in nodes) || nodes.nodeType) nodes = [nodes];
 481+ if (nodes.length < 1) return this;
 482+ var size = this.length, copyByClone = size > 1, inReverse = operator < 2;
 483+
 484+ return this.each(function(index, target){
 485+ for (var i = 0; i < nodes.length; i++) {
 486+ var node = nodes[inReverse ? nodes.length-i-1 : i];
 487+ traverseNode(node, function (node) {
 488+ if (node.nodeName != null && node.nodeName.toUpperCase() === 'SCRIPT' && (!node.type || node.type === 'text/javascript')) {
 489+ window['eval'].call(window, node.innerHTML);
 490+ }
 491+ });
 492+ if (copyByClone && index < size - 1) node = node.cloneNode(true);
 493+ insert(operator, target, node);
 494+ }
 495+ });
 496+ };
 497+
 498+ var reverseKey = (operator % 2) ? key+'To' : 'insert'+(operator ? 'Before' : 'After');
 499+ $.fn[reverseKey] = function(html) {
 500+ $(html)[key](this);
 501+ return this;
 502+ };
 503+ });
 504+
 505+ Z.prototype = $.fn;
 506+
 507+ return $;
 508+})();
 509+
 510+window.Zepto = Zepto;
 511+'$' in window || (window.$ = Zepto);
 512+// Zepto.js
 513+// (c) 2010-2012 Thomas Fuchs
 514+// Zepto.js may be freely distributed under the MIT license.
 515+
 516+(function($){
 517+ var $$ = $.qsa, handlers = {}, _zid = 1, specialEvents={};
 518+
 519+ specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents';
 520+
 521+ function zid(element) {
 522+ return element._zid || (element._zid = _zid++);
 523+ }
 524+ function findHandlers(element, event, fn, selector) {
 525+ event = parse(event);
 526+ if (event.ns) var matcher = matcherFor(event.ns);
 527+ return (handlers[zid(element)] || []).filter(function(handler) {
 528+ return handler
 529+ && (!event.e || handler.e == event.e)
 530+ && (!event.ns || matcher.test(handler.ns))
 531+ && (!fn || handler.fn == fn)
 532+ && (!selector || handler.sel == selector);
 533+ });
 534+ }
 535+ function parse(event) {
 536+ var parts = ('' + event).split('.');
 537+ return {e: parts[0], ns: parts.slice(1).sort().join(' ')};
 538+ }
 539+ function matcherFor(ns) {
 540+ return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)');
 541+ }
 542+
 543+ function eachEvent(events, fn, iterator){
 544+ if ($.isObject(events)) $.each(events, iterator);
 545+ else events.split(/\s/).forEach(function(type){ iterator(type, fn) });
 546+ }
 547+
 548+ function add(element, events, fn, selector, getDelegate){
 549+ var id = zid(element), set = (handlers[id] || (handlers[id] = []));
 550+ eachEvent(events, fn, function(event, fn){
 551+ var delegate = getDelegate && getDelegate(fn, event),
 552+ callback = delegate || fn;
 553+ var proxyfn = function (event) {
 554+ var result = callback.apply(element, [event].concat(event.data));
 555+ if (result === false) event.preventDefault();
 556+ return result;
 557+ };
 558+ var handler = $.extend(parse(event), {fn: fn, proxy: proxyfn, sel: selector, del: delegate, i: set.length});
 559+ set.push(handler);
 560+ element.addEventListener(handler.e, proxyfn, false);
 561+ });
 562+ }
 563+ function remove(element, events, fn, selector){
 564+ var id = zid(element);
 565+ eachEvent(events || '', fn, function(event, fn){
 566+ findHandlers(element, event, fn, selector).forEach(function(handler){
 567+ delete handlers[id][handler.i];
 568+ element.removeEventListener(handler.e, handler.proxy, false);
 569+ });
 570+ });
 571+ }
 572+
 573+ $.event = { add: add, remove: remove }
 574+
 575+ $.fn.bind = function(event, callback){
 576+ return this.each(function(){
 577+ add(this, event, callback);
 578+ });
 579+ };
 580+ $.fn.unbind = function(event, callback){
 581+ return this.each(function(){
 582+ remove(this, event, callback);
 583+ });
 584+ };
 585+ $.fn.one = function(event, callback){
 586+ return this.each(function(i, element){
 587+ add(this, event, callback, null, function(fn, type){
 588+ return function(){
 589+ var result = fn.apply(element, arguments);
 590+ remove(element, type, fn);
 591+ return result;
 592+ }
 593+ });
 594+ });
 595+ };
 596+
 597+ var returnTrue = function(){return true},
 598+ returnFalse = function(){return false},
 599+ eventMethods = {
 600+ preventDefault: 'isDefaultPrevented',
 601+ stopImmediatePropagation: 'isImmediatePropagationStopped',
 602+ stopPropagation: 'isPropagationStopped'
 603+ };
 604+ function createProxy(event) {
 605+ var proxy = $.extend({originalEvent: event}, event);
 606+ $.each(eventMethods, function(name, predicate) {
 607+ proxy[name] = function(){
 608+ this[predicate] = returnTrue;
 609+ return event[name].apply(event, arguments);
 610+ };
 611+ proxy[predicate] = returnFalse;
 612+ })
 613+ return proxy;
 614+ }
 615+
 616+ // emulates the 'defaultPrevented' property for browsers that have none
 617+ function fix(event) {
 618+ if (!('defaultPrevented' in event)) {
 619+ event.defaultPrevented = false;
 620+ var prevent = event.preventDefault;
 621+ event.preventDefault = function() {
 622+ this.defaultPrevented = true;
 623+ prevent.call(this);
 624+ }
 625+ }
 626+ }
 627+
 628+ $.fn.delegate = function(selector, event, callback){
 629+ return this.each(function(i, element){
 630+ add(element, event, callback, selector, function(fn){
 631+ return function(e){
 632+ var evt, match = $(e.target).closest(selector, element).get(0);
 633+ if (match) {
 634+ evt = $.extend(createProxy(e), {currentTarget: match, liveFired: element});
 635+ return fn.apply(match, [evt].concat([].slice.call(arguments, 1)));
 636+ }
 637+ }
 638+ });
 639+ });
 640+ };
 641+ $.fn.undelegate = function(selector, event, callback){
 642+ return this.each(function(){
 643+ remove(this, event, callback, selector);
 644+ });
 645+ }
 646+
 647+ $.fn.live = function(event, callback){
 648+ $(document.body).delegate(this.selector, event, callback);
 649+ return this;
 650+ };
 651+ $.fn.die = function(event, callback){
 652+ $(document.body).undelegate(this.selector, event, callback);
 653+ return this;
 654+ };
 655+
 656+ $.fn.on = function(event, selector, callback){
 657+ return selector === undefined || $.isFunction(selector) ?
 658+ this.bind(event, selector) : this.delegate(selector, event, callback);
 659+ };
 660+ $.fn.off = function(event, selector, callback){
 661+ return selector === undefined || $.isFunction(selector) ?
 662+ this.unbind(event, selector) : this.undelegate(selector, event, callback);
 663+ };
 664+
 665+ $.fn.trigger = function(event, data){
 666+ if (typeof event == 'string') event = $.Event(event);
 667+ fix(event);
 668+ event.data = data;
 669+ return this.each(function(){ this.dispatchEvent(event) });
 670+ };
 671+
 672+ // triggers event handlers on current element just as if an event occurred,
 673+ // doesn't trigger an actual event, doesn't bubble
 674+ $.fn.triggerHandler = function(event, data){
 675+ var e, result;
 676+ this.each(function(i, element){
 677+ e = createProxy(typeof event == 'string' ? $.Event(event) : event);
 678+ e.data = data; e.target = element;
 679+ $.each(findHandlers(element, event.type || event), function(i, handler){
 680+ result = handler.proxy(e);
 681+ if (e.isImmediatePropagationStopped()) return false;
 682+ });
 683+ });
 684+ return result;
 685+ };
 686+
 687+ // shortcut methods for `.bind(event, fn)` for each event type
 688+ ('focusin focusout load resize scroll unload click dblclick '+
 689+ 'mousedown mouseup mousemove mouseover mouseout '+
 690+ 'change select keydown keypress keyup error').split(' ').forEach(function(event) {
 691+ $.fn[event] = function(callback){ return this.bind(event, callback) };
 692+ });
 693+
 694+ ['focus', 'blur'].forEach(function(name) {
 695+ $.fn[name] = function(callback) {
 696+ if (callback) this.bind(name, callback);
 697+ else if (this.length) try { this.get(0)[name]() } catch(e){};
 698+ return this;
 699+ };
 700+ });
 701+
 702+ $.Event = function(type, props) {
 703+ var event = document.createEvent(specialEvents[type] || 'Events'), bubbles = true;
 704+ if (props) for (var name in props) (name == 'bubbles') ? (bubbles = !!props[name]) : (event[name] = props[name]);
 705+ event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null);
 706+ return event;
 707+ };
 708+
 709+})(Zepto);
 710+// Zepto.js
 711+// (c) 2010-2012 Thomas Fuchs
 712+// Zepto.js may be freely distributed under the MIT license.
 713+
 714+(function($){
 715+ function detect(ua){
 716+ var os = (this.os = {}), browser = (this.browser = {}),
 717+ webkit = ua.match(/WebKit\/([\d.]+)/),
 718+ android = ua.match(/(Android)\s+([\d.]+)/),
 719+ ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
 720+ iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
 721+ webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
 722+ touchpad = webos && ua.match(/TouchPad/),
 723+ blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/);
 724+
 725+ if (webkit) browser.version = webkit[1];
 726+ browser.webkit = !!webkit;
 727+
 728+ if (android) os.android = true, os.version = android[2];
 729+ if (iphone) os.ios = true, os.version = iphone[2].replace(/_/g, '.'), os.iphone = true;
 730+ if (ipad) os.ios = true, os.version = ipad[2].replace(/_/g, '.'), os.ipad = true;
 731+ if (webos) os.webos = true, os.version = webos[2];
 732+ if (touchpad) os.touchpad = true;
 733+ if (blackberry) os.blackberry = true, os.version = blackberry[2];
 734+ }
 735+
 736+ // ### $.os
 737+ //
 738+ // Object containing information about browser platform
 739+ //
 740+ // *Example:*
 741+ //
 742+ // $.os.ios // => true if running on Apple iOS
 743+ // $.os.android // => true if running on Android
 744+ // $.os.webos // => true if running on HP/Palm WebOS
 745+ // $.os.touchpad // => true if running on a HP TouchPad
 746+ // $.os.version // => string with a version number, e.g.
 747+ // "4.0", "3.1.1", "2.1", etc.
 748+ // $.os.iphone // => true if running on iPhone
 749+ // $.os.ipad // => true if running on iPad
 750+ // $.os.blackberry // => true if running on BlackBerry
 751+ //
 752+ // ### $.browser
 753+ //
 754+ // *Example:*
 755+ //
 756+ // $.browser.webkit // => true if the browser is WebKit-based
 757+ // $.browser.version // => WebKit version string
 758+ detect.call($, navigator.userAgent);
 759+
 760+ // make available to unit tests
 761+ $.__detect = detect;
 762+
 763+})(Zepto);
 764+// Zepto.js
 765+// (c) 2010-2012 Thomas Fuchs
 766+// Zepto.js may be freely distributed under the MIT license.
 767+
 768+(function($, undefined){
 769+ var prefix = '', eventPrefix, endEventName, endAnimationName,
 770+ vendors = {Webkit: 'webkit', Moz: '', O: 'o', ms: 'MS'},
 771+ document = window.document, testEl = document.createElement('div'),
 772+ supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i;
 773+
 774+ function downcase(str) { return str.toLowerCase() }
 775+ function normalizeEvent(name) { return eventPrefix ? eventPrefix + name : downcase(name) };
 776+
 777+ $.each(vendors, function(vendor, event){
 778+ if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
 779+ prefix = '-' + downcase(vendor) + '-';
 780+ eventPrefix = event;
 781+ return false;
 782+ }
 783+ });
 784+
 785+ $.fx = {
 786+ off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
 787+ cssPrefix: prefix,
 788+ transitionEnd: normalizeEvent('TransitionEnd'),
 789+ animationEnd: normalizeEvent('AnimationEnd')
 790+ };
 791+
 792+ $.fn.animate = function(properties, duration, ease, callback){
 793+ if ($.isObject(duration))
 794+ ease = duration.easing, callback = duration.complete, duration = duration.duration;
 795+ if (duration) duration = duration / 1000;
 796+ return this.anim(properties, duration, ease, callback);
 797+ };
 798+
 799+ $.fn.anim = function(properties, duration, ease, callback){
 800+ var transforms, cssProperties = {}, key, that = this, wrappedCallback, endEvent = $.fx.transitionEnd;
 801+ if (duration === undefined) duration = 0.4;
 802+ if ($.fx.off) duration = 0;
 803+
 804+ if (typeof properties == 'string') {
 805+ // keyframe animation
 806+ cssProperties[prefix + 'animation-name'] = properties;
 807+ cssProperties[prefix + 'animation-duration'] = duration + 's';
 808+ endEvent = $.fx.animationEnd;
 809+ } else {
 810+ // CSS transitions
 811+ for (key in properties)
 812+ if (supportedTransforms.test(key)) {
 813+ transforms || (transforms = []);
 814+ transforms.push(key + '(' + properties[key] + ')');
 815+ }
 816+ else cssProperties[key] = properties[key];
 817+
 818+ if (transforms) cssProperties[prefix + 'transform'] = transforms.join(' ');
 819+ if (!$.fx.off) cssProperties[prefix + 'transition'] = 'all ' + duration + 's ' + (ease || '');
 820+ }
 821+
 822+ wrappedCallback = function(){
 823+ var props = {};
 824+ props[prefix + 'transition'] = props[prefix + 'animation-name'] = 'none';
 825+ $(this).css(props);
 826+ callback && callback.call(this);
 827+ }
 828+ if (duration > 0) this.one(endEvent, wrappedCallback);
 829+
 830+ setTimeout(function() {
 831+ that.css(cssProperties);
 832+ if (duration <= 0) setTimeout(function() {
 833+ that.each(function(){ wrappedCallback.call(this) });
 834+ }, 0);
 835+ }, 0);
 836+
 837+ return this;
 838+ };
 839+
 840+ testEl = null;
 841+})(Zepto);
 842+// Zepto.js
 843+// (c) 2010-2012 Thomas Fuchs
 844+// Zepto.js may be freely distributed under the MIT license.
 845+
 846+(function($){
 847+ var jsonpID = 0,
 848+ isObject = $.isObject,
 849+ document = window.document,
 850+ key,
 851+ name;
 852+
 853+ // trigger a custom event and return false if it was cancelled
 854+ function triggerAndReturn(context, eventName, data) {
 855+ var event = $.Event(eventName);
 856+ $(context).trigger(event, data);
 857+ return !event.defaultPrevented;
 858+ }
 859+
 860+ // trigger an Ajax "global" event
 861+ function triggerGlobal(settings, context, eventName, data) {
 862+ if (settings.global) return triggerAndReturn(context || document, eventName, data);
 863+ }
 864+
 865+ // Number of active Ajax requests
 866+ $.active = 0;
 867+
 868+ function ajaxStart(settings) {
 869+ if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart');
 870+ }
 871+ function ajaxStop(settings) {
 872+ if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop');
 873+ }
 874+
 875+ // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
 876+ function ajaxBeforeSend(xhr, settings) {
 877+ var context = settings.context;
 878+ if (settings.beforeSend.call(context, xhr, settings) === false ||
 879+ triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false)
 880+ return false;
 881+
 882+ triggerGlobal(settings, context, 'ajaxSend', [xhr, settings]);
 883+ }
 884+ function ajaxSuccess(data, xhr, settings) {
 885+ var context = settings.context, status = 'success';
 886+ settings.success.call(context, data, status, xhr);
 887+ triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data]);
 888+ ajaxComplete(status, xhr, settings);
 889+ }
 890+ // type: "timeout", "error", "abort", "parsererror"
 891+ function ajaxError(error, type, xhr, settings) {
 892+ var context = settings.context;
 893+ settings.error.call(context, xhr, type, error);
 894+ triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error]);
 895+ ajaxComplete(type, xhr, settings);
 896+ }
 897+ // status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
 898+ function ajaxComplete(status, xhr, settings) {
 899+ var context = settings.context;
 900+ settings.complete.call(context, xhr, status);
 901+ triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings]);
 902+ ajaxStop(settings);
 903+ }
 904+
 905+ // Empty function, used as default callback
 906+ function empty() {}
 907+
 908+ // ### $.ajaxJSONP
 909+ //
 910+ // Load JSON from a server in a different domain (JSONP)
 911+ //
 912+ // *Arguments:*
 913+ //
 914+ // options — object that configure the request,
 915+ // see avaliable options below
 916+ //
 917+ // *Avaliable options:*
 918+ //
 919+ // url — url to which the request is sent
 920+ // success — callback that is executed if the request succeeds
 921+ // error — callback that is executed if the server drops error
 922+ // context — in which context to execute the callbacks in
 923+ //
 924+ // *Example:*
 925+ //
 926+ // $.ajaxJSONP({
 927+ // url: 'http://example.com/projects?callback=?',
 928+ // success: function (data) {
 929+ // projects.push(json);
 930+ // }
 931+ // });
 932+ //
 933+ $.ajaxJSONP = function(options){
 934+ var callbackName = 'jsonp' + (++jsonpID),
 935+ script = document.createElement('script'),
 936+ abort = function(){
 937+ $(script).remove();
 938+ if (callbackName in window) window[callbackName] = empty;
 939+ ajaxComplete('abort', xhr, options);
 940+ },
 941+ xhr = { abort: abort }, abortTimeout;
 942+
 943+ window[callbackName] = function(data){
 944+ clearTimeout(abortTimeout);
 945+ $(script).remove();
 946+ delete window[callbackName];
 947+ ajaxSuccess(data, xhr, options);
 948+ };
 949+
 950+ script.src = options.url.replace(/=\?/, '=' + callbackName);
 951+ $('head').append(script);
 952+
 953+ if (options.timeout > 0) abortTimeout = setTimeout(function(){
 954+ xhr.abort();
 955+ ajaxComplete('timeout', xhr, options);
 956+ }, options.timeout);
 957+
 958+ return xhr;
 959+ };
 960+
 961+ // ### $.ajaxSettings
 962+ //
 963+ // AJAX settings
 964+ //
 965+ $.ajaxSettings = {
 966+ // Default type of request
 967+ type: 'GET',
 968+ // Callback that is executed before request
 969+ beforeSend: empty,
 970+ // Callback that is executed if the request succeeds
 971+ success: empty,
 972+ // Callback that is executed the the server drops error
 973+ error: empty,
 974+ // Callback that is executed on request complete (both: error and success)
 975+ complete: empty,
 976+ // The context for the callbacks
 977+ context: null,
 978+ // Whether to trigger "global" Ajax events
 979+ global: true,
 980+ // Transport
 981+ xhr: function () {
 982+ return new window.XMLHttpRequest();
 983+ },
 984+ // MIME types mapping
 985+ accepts: {
 986+ script: 'text/javascript, application/javascript',
 987+ json: 'application/json',
 988+ xml: 'application/xml, text/xml',
 989+ html: 'text/html',
 990+ text: 'text/plain'
 991+ },
 992+ // Whether the request is to another domain
 993+ crossDomain: false,
 994+ // Default timeout
 995+ timeout: 0
 996+ };
 997+
 998+ // ### $.ajax
 999+ //
 1000+ // Perform AJAX request
 1001+ //
 1002+ // *Arguments:*
 1003+ //
 1004+ // options — object that configure the request,
 1005+ // see avaliable options below
 1006+ //
 1007+ // *Avaliable options:*
 1008+ //
 1009+ // type ('GET') — type of request GET / POST
 1010+ // url (window.location) — url to which the request is sent
 1011+ // data — data to send to server,
 1012+ // can be string or object
 1013+ // dataType ('json') — what response type you accept from
 1014+ // the server:
 1015+ // 'json', 'xml', 'html', or 'text'
 1016+ // timeout (0) — request timeout
 1017+ // beforeSend — callback that is executed before
 1018+ // request send
 1019+ // complete — callback that is executed on request
 1020+ // complete (both: error and success)
 1021+ // success — callback that is executed if
 1022+ // the request succeeds
 1023+ // error — callback that is executed if
 1024+ // the server drops error
 1025+ // context — in which context to execute the
 1026+ // callbacks in
 1027+ //
 1028+ // *Example:*
 1029+ //
 1030+ // $.ajax({
 1031+ // type: 'POST',
 1032+ // url: '/projects',
 1033+ // data: { name: 'Zepto.js' },
 1034+ // dataType: 'html',
 1035+ // timeout: 100,
 1036+ // context: $('body'),
 1037+ // success: function (data) {
 1038+ // this.append(data);
 1039+ // },
 1040+ // error: function (xhr, type) {
 1041+ // alert('Error!');
 1042+ // }
 1043+ // });
 1044+ //
 1045+ $.ajax = function(options){
 1046+ var settings = $.extend({}, options || {});
 1047+ for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key];
 1048+
 1049+ ajaxStart(settings);
 1050+
 1051+ if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) &&
 1052+ RegExp.$2 != window.location.host;
 1053+
 1054+ if (/=\?/.test(settings.url)) return $.ajaxJSONP(settings);
 1055+
 1056+ if (!settings.url) settings.url = window.location.toString();
 1057+ if (settings.data && !settings.contentType) settings.contentType = 'application/x-www-form-urlencoded';
 1058+ if (isObject(settings.data)) settings.data = $.param(settings.data);
 1059+
 1060+ if (settings.type.match(/get/i) && settings.data) {
 1061+ var queryString = settings.data;
 1062+ if (settings.url.match(/\?.*=/)) {
 1063+ queryString = '&' + queryString;
 1064+ } else if (queryString[0] != '?') {
 1065+ queryString = '?' + queryString;
 1066+ }
 1067+ settings.url += queryString;
 1068+ }
 1069+
 1070+ var mime = settings.accepts[settings.dataType],
 1071+ baseHeaders = { },
 1072+ protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
 1073+ xhr = $.ajaxSettings.xhr(), abortTimeout;
 1074+
 1075+ if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest';
 1076+ if (mime) baseHeaders['Accept'] = mime;
 1077+ settings.headers = $.extend(baseHeaders, settings.headers || {});
 1078+
 1079+ xhr.onreadystatechange = function(){
 1080+ if (xhr.readyState == 4) {
 1081+ clearTimeout(abortTimeout);
 1082+ var result, error = false;
 1083+ if ((xhr.status >= 200 && xhr.status < 300) || (xhr.status == 0 && protocol == 'file:')) {
 1084+ if (mime == 'application/json' && !(/^\s*$/.test(xhr.responseText))) {
 1085+ try { result = JSON.parse(xhr.responseText); }
 1086+ catch (e) { error = e; }
 1087+ }
 1088+ else result = xhr.responseText;
 1089+ if (error) ajaxError(error, 'parsererror', xhr, settings);
 1090+ else ajaxSuccess(result, xhr, settings);
 1091+ } else {
 1092+ ajaxError(null, 'error', xhr, settings);
 1093+ }
 1094+ }
 1095+ };
 1096+
 1097+ var async = 'async' in settings ? settings.async : true;
 1098+ xhr.open(settings.type, settings.url, async);
 1099+
 1100+ if (settings.contentType) settings.headers['Content-Type'] = settings.contentType;
 1101+ for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name]);
 1102+
 1103+ if (ajaxBeforeSend(xhr, settings) === false) {
 1104+ xhr.abort();
 1105+ return false;
 1106+ }
 1107+
 1108+ if (settings.timeout > 0) abortTimeout = setTimeout(function(){
 1109+ xhr.onreadystatechange = empty;
 1110+ xhr.abort();
 1111+ ajaxError(null, 'timeout', xhr, settings);
 1112+ }, settings.timeout);
 1113+
 1114+ xhr.send(settings.data);
 1115+ return xhr;
 1116+ };
 1117+
 1118+ // ### $.get
 1119+ //
 1120+ // Load data from the server using a GET request
 1121+ //
 1122+ // *Arguments:*
 1123+ //
 1124+ // url — url to which the request is sent
 1125+ // success — callback that is executed if the request succeeds
 1126+ //
 1127+ // *Example:*
 1128+ //
 1129+ // $.get(
 1130+ // '/projects/42',
 1131+ // function (data) {
 1132+ // $('body').append(data);
 1133+ // }
 1134+ // );
 1135+ //
 1136+ $.get = function(url, success){ return $.ajax({ url: url, success: success }) };
 1137+
 1138+ // ### $.post
 1139+ //
 1140+ // Load data from the server using POST request
 1141+ //
 1142+ // *Arguments:*
 1143+ //
 1144+ // url — url to which the request is sent
 1145+ // [data] — data to send to server, can be string or object
 1146+ // [success] — callback that is executed if the request succeeds
 1147+ // [dataType] — type of expected response
 1148+ // 'json', 'xml', 'html', or 'text'
 1149+ //
 1150+ // *Example:*
 1151+ //
 1152+ // $.post(
 1153+ // '/projects',
 1154+ // { name: 'Zepto.js' },
 1155+ // function (data) {
 1156+ // $('body').append(data);
 1157+ // },
 1158+ // 'html'
 1159+ // );
 1160+ //
 1161+ $.post = function(url, data, success, dataType){
 1162+ if ($.isFunction(data)) dataType = dataType || success, success = data, data = null;
 1163+ return $.ajax({ type: 'POST', url: url, data: data, success: success, dataType: dataType });
 1164+ };
 1165+
 1166+ // ### $.getJSON
 1167+ //
 1168+ // Load JSON from the server using GET request
 1169+ //
 1170+ // *Arguments:*
 1171+ //
 1172+ // url — url to which the request is sent
 1173+ // success — callback that is executed if the request succeeds
 1174+ //
 1175+ // *Example:*
 1176+ //
 1177+ // $.getJSON(
 1178+ // '/projects/42',
 1179+ // function (json) {
 1180+ // projects.push(json);
 1181+ // }
 1182+ // );
 1183+ //
 1184+ $.getJSON = function(url, success){
 1185+ return $.ajax({ url: url, success: success, dataType: 'json' });
 1186+ };
 1187+
 1188+ // ### $.fn.load
 1189+ //
 1190+ // Load data from the server into an element
 1191+ //
 1192+ // *Arguments:*
 1193+ //
 1194+ // url — url to which the request is sent
 1195+ // [success] — callback that is executed if the request succeeds
 1196+ //
 1197+ // *Examples:*
 1198+ //
 1199+ // $('#project_container').get(
 1200+ // '/projects/42',
 1201+ // function () {
 1202+ // alert('Project was successfully loaded');
 1203+ // }
 1204+ // );
 1205+ //
 1206+ // $('#project_comments').get(
 1207+ // '/projects/42 #comments',
 1208+ // function () {
 1209+ // alert('Comments was successfully loaded');
 1210+ // }
 1211+ // );
 1212+ //
 1213+ $.fn.load = function(url, success){
 1214+ if (!this.length) return this;
 1215+ var self = this, parts = url.split(/\s/), selector;
 1216+ if (parts.length > 1) url = parts[0], selector = parts[1];
 1217+ $.get(url, function(response){
 1218+ self.html(selector ?
 1219+ $(document.createElement('div')).html(response).find(selector).html()
 1220+ : response);
 1221+ success && success.call(self);
 1222+ });
 1223+ return this;
 1224+ };
 1225+
 1226+ var escape = encodeURIComponent;
 1227+
 1228+ function serialize(params, obj, traditional, scope){
 1229+ var array = $.isArray(obj);
 1230+ $.each(obj, function(key, value) {
 1231+ if (scope) key = traditional ? scope : scope + '[' + (array ? '' : key) + ']';
 1232+ // handle data in serializeArray() format
 1233+ if (!scope && array) params.add(value.name, value.value);
 1234+ // recurse into nested objects
 1235+ else if (traditional ? $.isArray(value) : isObject(value))
 1236+ serialize(params, value, traditional, key);
 1237+ else params.add(key, value);
 1238+ });
 1239+ }
 1240+
 1241+ // ### $.param
 1242+ //
 1243+ // Encode object as a string of URL-encoded key-value pairs
 1244+ //
 1245+ // *Arguments:*
 1246+ //
 1247+ // obj — object to serialize
 1248+ // [traditional] — perform shallow serialization
 1249+ //
 1250+ // *Example:*
 1251+ //
 1252+ // $.param( { name: 'Zepto.js', version: '0.6' } );
 1253+ //
 1254+ $.param = function(obj, traditional){
 1255+ var params = [];
 1256+ params.add = function(k, v){ this.push(escape(k) + '=' + escape(v)) };
 1257+ serialize(params, obj, traditional);
 1258+ return params.join('&').replace('%20', '+');
 1259+ };
 1260+})(Zepto);
 1261+// Zepto.js
 1262+// (c) 2010-2012 Thomas Fuchs
 1263+// Zepto.js may be freely distributed under the MIT license.
 1264+
 1265+(function ($) {
 1266+
 1267+ // ### $.fn.serializeArray
 1268+ //
 1269+ // Encode a set of form elements as an array of names and values
 1270+ //
 1271+ // *Example:*
 1272+ //
 1273+ // $('#login_form').serializeArray();
 1274+ //
 1275+ // returns
 1276+ //
 1277+ // [
 1278+ // {
 1279+ // name: 'email',
 1280+ // value: 'koss@nocorp.me'
 1281+ // },
 1282+ // {
 1283+ // name: 'password',
 1284+ // value: '123456'
 1285+ // }
 1286+ // ]
 1287+ //
 1288+ $.fn.serializeArray = function () {
 1289+ var result = [], el;
 1290+ $( Array.prototype.slice.call(this.get(0).elements) ).each(function () {
 1291+ el = $(this);
 1292+ var type = el.attr('type');
 1293+ if (
 1294+ this.nodeName.toLowerCase() != 'fieldset' &&
 1295+ !this.disabled && type != 'submit' && type != 'reset' && type != 'button' &&
 1296+ ((type != 'radio' && type != 'checkbox') || this.checked)
 1297+ ) {
 1298+ result.push({
 1299+ name: el.attr('name'),
 1300+ value: el.val()
 1301+ });
 1302+ }
 1303+ });
 1304+ return result;
 1305+ };
 1306+
 1307+ // ### $.fn.serialize
 1308+ //
 1309+ //
 1310+ // Encode a set of form elements as a string for submission
 1311+ //
 1312+ // *Example:*
 1313+ //
 1314+ // $('#login_form').serialize();
 1315+ //
 1316+ // returns
 1317+ //
 1318+ // "email=koss%40nocorp.me&password=123456"
 1319+ //
 1320+ $.fn.serialize = function () {
 1321+ var result = [];
 1322+ this.serializeArray().forEach(function (elm) {
 1323+ result.push( encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value) );
 1324+ });
 1325+ return result.join('&');
 1326+ };
 1327+
 1328+ // ### $.fn.submit
 1329+ //
 1330+ // Bind or trigger the submit event for a form
 1331+ //
 1332+ // *Examples:*
 1333+ //
 1334+ // To bind a handler for the submit event:
 1335+ //
 1336+ // $('#login_form').submit(function (e) {
 1337+ // alert('Form was submitted!');
 1338+ // e.preventDefault();
 1339+ // });
 1340+ //
 1341+ // To trigger form submit:
 1342+ //
 1343+ // $('#login_form').submit();
 1344+ //
 1345+ $.fn.submit = function (callback) {
 1346+ if (callback) this.bind('submit', callback)
 1347+ else if (this.length) {
 1348+ var event = $.Event('submit');
 1349+ this.eq(0).trigger(event);
 1350+ if (!event.defaultPrevented) this.get(0).submit()
 1351+ }
 1352+ return this;
 1353+ }
 1354+
 1355+})(Zepto);
 1356+// Zepto.js
 1357+// (c) 2010-2012 Thomas Fuchs
 1358+// Zepto.js may be freely distributed under the MIT license.
 1359+
 1360+(function($){
 1361+ var touch = {}, touchTimeout;
 1362+
 1363+ function parentIfText(node){
 1364+ return 'tagName' in node ? node : node.parentNode;
 1365+ }
 1366+
 1367+ function swipeDirection(x1, x2, y1, y2){
 1368+ var xDelta = Math.abs(x1 - x2), yDelta = Math.abs(y1 - y2);
 1369+ if (xDelta >= yDelta) {
 1370+ return (x1 - x2 > 0 ? 'Left' : 'Right');
 1371+ } else {
 1372+ return (y1 - y2 > 0 ? 'Up' : 'Down');
 1373+ }
 1374+ }
 1375+
 1376+ var longTapDelay = 750;
 1377+ function longTap(){
 1378+ if (touch.last && (Date.now() - touch.last >= longTapDelay)) {
 1379+ touch.el.trigger('longTap');
 1380+ touch = {};
 1381+ }
 1382+ }
 1383+
 1384+ $(document).ready(function(){
 1385+ $(document.body).bind('touchstart', function(e){
 1386+ var now = Date.now(), delta = now - (touch.last || now);
 1387+ touch.el = $(parentIfText(e.touches[0].target));
 1388+ touchTimeout && clearTimeout(touchTimeout);
 1389+ touch.x1 = e.touches[0].pageX;
 1390+ touch.y1 = e.touches[0].pageY;
 1391+ if (delta > 0 && delta <= 250) touch.isDoubleTap = true;
 1392+ touch.last = now;
 1393+ setTimeout(longTap, longTapDelay);
 1394+ }).bind('touchmove', function(e){
 1395+ touch.x2 = e.touches[0].pageX;
 1396+ touch.y2 = e.touches[0].pageY;
 1397+ }).bind('touchend', function(e){
 1398+ if (touch.isDoubleTap) {
 1399+ touch.el.trigger('doubleTap');
 1400+ touch = {};
 1401+ } else if (touch.x2 > 0 || touch.y2 > 0) {
 1402+ (Math.abs(touch.x1 - touch.x2) > 30 || Math.abs(touch.y1 - touch.y2) > 30) &&
 1403+ touch.el.trigger('swipe') &&
 1404+ touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)));
 1405+ touch.x1 = touch.x2 = touch.y1 = touch.y2 = touch.last = 0;
 1406+ } else if ('last' in touch) {
 1407+ touch.el.trigger('tap');
 1408+
 1409+ touchTimeout = setTimeout(function(){
 1410+ touchTimeout = null;
 1411+ touch.el.trigger('singleTap');
 1412+ touch = {};
 1413+ }, 250);
 1414+ }
 1415+ }).bind('touchcancel', function(){ touch = {} });
 1416+ });
 1417+
 1418+ ['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown', 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(m){
 1419+ $.fn[m] = function(callback){ return this.bind(m, callback) }
 1420+ });
 1421+})(Zepto);
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/modules/zepto/zepto.js
___________________________________________________________________
Added: svn:eol-style
11422 + native
Index: trunk/extensions/MobileFrontend2/modules/zepto/zepto.mw.js
@@ -0,0 +1,93 @@
 2+// Alias jQuery to Zepto (ew)
 3+window.jQuery = window.Zepto;
 4+
 5+// Add a few things missing from jQuery
 6+(function ( $ ){
 7+ // [[Class]] -> type pairs
 8+ var class2type = {};
 9+
 10+ $.type = function( obj ) {
 11+ return obj == null ?
 12+ String( obj ) :
 13+ class2type[ toString.call(obj) ] || "object";
 14+ };
 15+
 16+ $.merge = function( first, second ) {
 17+ var i = first.length,
 18+ j = 0;
 19+
 20+ if ( typeof second.length === "number" ) {
 21+ for ( var l = second.length; j < l; j++ ) {
 22+ first[ i++ ] = second[ j ];
 23+ }
 24+
 25+ } else {
 26+ while ( second[j] !== undefined ) {
 27+ first[ i++ ] = second[ j++ ];
 28+ }
 29+ }
 30+
 31+ first.length = i;
 32+
 33+ return first;
 34+ };
 35+
 36+ $.isPlainObject = function( obj ) {
 37+ // Must be an Object.
 38+ // Because of IE, we also have to check the presence of the constructor property.
 39+ // Make sure that DOM nodes and window objects don't pass through, as well
 40+ if ( !obj || $.isObject() || obj.nodeType || jQuery.isWindow( obj ) ) {
 41+ return false;
 42+ }
 43+
 44+ var hasOwn = Object.prototype.hasOwnProperty;
 45+ try {
 46+ // Not own constructor property must be Object
 47+ if ( obj.constructor &&
 48+ !hasOwn.call(obj, "constructor") &&
 49+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
 50+ return false;
 51+ }
 52+ } catch ( e ) {
 53+ // IE8,9 Will throw exceptions on certain host objects #9897
 54+ return false;
 55+ }
 56+
 57+ // Own properties are enumerated firstly, so to speed up,
 58+ // if last one is own, then all properties are own.
 59+
 60+ var key;
 61+ for ( key in obj ) {}
 62+
 63+ return key === undefined || hasOwn.call( obj, key );
 64+ };
 65+
 66+ $.isWindow = function( obj ) {
 67+ return obj && typeof obj === "object" && "setInterval" in obj;
 68+ };
 69+
 70+ $.isEmptyObject = function( obj ) {
 71+ for ( var name in obj ) {
 72+ return false;
 73+ }
 74+ return true;
 75+ };
 76+
 77+ $.makeArray = function( array, results ) {
 78+ var ret = results || [];
 79+
 80+ if ( array != null ) {
 81+ // The window, strings (and functions) also have 'length'
 82+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
 83+ var type = jQuery.type( array );
 84+
 85+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
 86+ push.call( ret, array );
 87+ } else {
 88+ jQuery.merge( ret, array );
 89+ }
 90+ }
 91+
 92+ return ret;
 93+ };
 94+})( Zepto );
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/modules/zepto/zepto.mw.js
___________________________________________________________________
Added: svn:eol-style
195 + native
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/ext.mobileFrontend2.css
@@ -0,0 +1,113 @@
 2+/* CSS For MobileFrontend2 */
 3+
 4+/* Basic styling */
 5+html {
 6+ font-size: 0.8em;
 7+}
 8+
 9+body {
 10+ line-height: 1;
 11+ color: black;
 12+ background: white;
 13+ font-family: sans-serif;
 14+ -webkit-text-size-adjust: none;
 15+ margin: 0;
 16+}
 17+
 18+p, li, dl {
 19+ line-height: 1.65;
 20+}
 21+
 22+pre {
 23+ white-space: pre-wrap;
 24+}
 25+
 26+h2 {
 27+ border-bottom: 1px solid #aaaaaa;
 28+ padding-top: 0.3em;
 29+ font-size: 1.3em;
 30+}
 31+
 32+/* Links */
 33+a {
 34+ text-decoration: none;
 35+ color: #002bb8;
 36+}
 37+
 38+a:visited {
 39+ color: #5a3696;
 40+}
 41+
 42+a:active {
 43+ color: #faa700;
 44+}
 45+
 46+a:hover {
 47+ text-decoration: underline;
 48+}
 49+
 50+a.new, a.new:visited, a.new:hover {
 51+ color: red;
 52+}
 53+
 54+/* Page Title */
 55+#firstHeading {
 56+ font-size: 1.7em;
 57+}
 58+
 59+/* Jump back a section links */
 60+.mf2-section-anchor a,
 61+.mf2-section-anchor a:visited {
 62+ margin-top: 7px;
 63+ color:blue;
 64+}
 65+
 66+/* All the main content divs should be 8px from the side */
 67+#header {
 68+ margin: 8px 8px 0 8px;
 69+}
 70+
 71+#content_wrapper {
 72+ clear: both;
 73+ margin: 0 8px;
 74+}
 75+
 76+#footer {
 77+ margin: 0 8px;
 78+ padding: 1em 0 5px;
 79+}
 80+
 81+#footer #innerFooter {
 82+ padding: 5px;
 83+ background: #dddddd;
 84+ -webkit-border-radius: 5px;
 85+ margin-top: 5px;
 86+ text-align: center;
 87+ border: 1px solid grey;
 88+ font-size: 1.1em;
 89+}
 90+
 91+#footer #innerFooter #perm {
 92+ padding-top: 2em;
 93+ font-size: 90%;
 94+}
 95+
 96+#footer #copyright {
 97+ padding: 1em 0;
 98+ font-size: 80%;
 99+}
 100+
 101+/* Hide the content block */
 102+.mf2-content-block {
 103+ display: none;
 104+}
 105+
 106+/* Disable content folding on the main page */
 107+body.mainPage .mf2-section-anchor,
 108+body.mainPage button.mf2-section-toggle {
 109+ display: none;
 110+}
 111+
 112+body.mainPage .mf2-content-block {
 113+ display: block;
 114+}
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/ext.mobileFrontend2.css
___________________________________________________________________
Added: svn:eol-style
1115 + native
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/ext.mobileFrontend2.js
@@ -0,0 +1,155 @@
 2+(function( $, mw, undefined ) {
 3+
 4+var MobileFrontend2 = mf2 = {
 5+ /**
 6+ * Timer for updating search suggestions
 7+ */
 8+ searchTimer: undefined,
 9+
 10+ /**
 11+ * mw.Api object for accessing the API
 12+ *
 13+ * @var api {mw.Api}
 14+ */
 15+ api: undefined,
 16+
 17+ /**
 18+ * Run to set up the page
 19+ */
 20+ init: function() {
 21+ // Create our API object
 22+ mf2.api = new mw.Api();
 23+
 24+ // Hook the section toggle
 25+ $( '.mf2-section-container h2' ).click( mf2.toggleSection );
 26+
 27+ // Listen for searches
 28+ $( '#search' ).keyup( mf2.searchKeyUp );
 29+
 30+ // Setup the width for search
 31+ // TODO: Only worry about this if we have the search element
 32+ mf2.updateSearchWidth();
 33+ },
 34+
 35+ /**
 36+ * Toggles page section visibility
 37+ */
 38+ toggleSection: function() {
 39+ var $header = $( this ),
 40+ $contentDiv = $header.next(),
 41+ buttonMsg;
 42+
 43+ // Toggle the div
 44+ $contentDiv.toggle();
 45+
 46+ // Change the button text
 47+ buttonMsg = $contentDiv.css( 'display' ) === 'block' ? 'mobile-frontend2-hide-button' : 'mobile-frontend2-show-button';
 48+ $header.find( 'button' ).html( mw.msg( buttonMsg ) );
 49+ },
 50+
 51+ /**
 52+ * Schedules an update of search suggestions
 53+ *
 54+ * Fired when data is entered into the search box
 55+ */
 56+ searchKeyUp: function() {
 57+ clearTimeout( mf2.searchTimer );
 58+
 59+ if ( this.value.length < 1 ) {
 60+ $( '#results' ).html( '' );
 61+ } else {
 62+ // TODO: Config
 63+ mf2.searchTimer = setTimeout( mf2.search, 500 );
 64+ }
 65+ },
 66+
 67+ /**
 68+ * Fires off the request to the API to get search results
 69+ */
 70+ search: function() {
 71+ mf2.api.get( {
 72+ action: 'opensearch',
 73+ limit: 5,
 74+ namespace: 0,
 75+ search: $( '#search' ).val()
 76+ }, mf2.searchResults );
 77+ },
 78+
 79+ /**
 80+ * Update the search suggestions with API results
 81+ *
 82+ * @param data
 83+ */
 84+ searchResults: function( data ) {
 85+ var results = data[1], // Second element has the results, fuck standards
 86+ $results = $( '#results' );
 87+
 88+ $results.show();
 89+ if ( results.length < 1 ) {
 90+ $results.text( 'No results' ); // TRANSLATE
 91+ return;
 92+ }
 93+
 94+ $r = $( '<div class="suggestions">' );
 95+
 96+ $.each( results, function( i, title ) {
 97+ $( '<div class="suggestions-result">' )
 98+ .attr( 'title', title )
 99+ .attr( 'rel', i + 1 ) // ?
 100+ .append(
 101+ $( '<a class="suggestions-result-update">' )
 102+ .text( '+' ) // Translate?
 103+ .click( mf2.updateSearchValue )
 104+ )
 105+ .append(
 106+ $( '<a class="search-result-item">' )
 107+ .attr( 'href', mw.util.wikiGetlink( title ) )
 108+ .text( title )
 109+ )
 110+ .appendTo( $r );
 111+ } );
 112+
 113+ $results.html( $r );
 114+ },
 115+
 116+ /**
 117+ * Update the value of the search box with the selected suggestion
 118+ *
 119+ * Fire when the plus symbol is clicked
 120+ */
 121+ updateSearchValue: function () {
 122+ var title = $( this ).parent().attr( 'title' );
 123+
 124+ $( '#search' )
 125+ .val( title + ' ' )
 126+ .focus();
 127+
 128+ // Reload suggestions
 129+ mf2.search();
 130+ },
 131+
 132+ /**
 133+ * Updates the width of the header and search box
 134+ *
 135+ * Fired when the screen orientation changes
 136+ */
 137+ updateSearchWidth: function () {
 138+ var clientWidth = $( window ).width(),
 139+ $sq = $( '#sq' ),
 140+ sqOffset = $sq.offset();
 141+
 142+ // TODO: ew. This should be CSS
 143+ $( '#searchbox' ).width( clientWidth - 30 );
 144+ $sq.width( clientWidth - 110 );
 145+ $( '#search' ).width( clientWidth - 130 );
 146+ $( '#results' ).css({
 147+ width: $sq.width() - 2,
 148+ left: sqOffset.left,
 149+ top: sqOffset.top + $sq.height()
 150+ });
 151+ }
 152+};
 153+
 154+$( mf2.init );
 155+
 156+})( Zepto, mediaWiki );
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/ext.mobileFrontend2.js
___________________________________________________________________
Added: svn:eol-style
1157 + native
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/ext.mobileFrontend2.search.css
@@ -0,0 +1,138 @@
 2+/* SEARCH */
 3+#search {
 4+ -webkit-appearance: none;
 5+ border-top-width: 0px;
 6+ border-right-width: 0px;
 7+ border-bottom-width: 0px;
 8+ border-left-width: 0px;
 9+ outline-style: none;
 10+ outline-width: initial;
 11+ outline-color: initial;
 12+ height: 1.4em;
 13+}
 14+
 15+#search:focus {
 16+ outline: none;
 17+}
 18+
 19+#searchbox img,
 20+#searchbox form,
 21+#searchbox input,
 22+#searchbox button {
 23+ vertical-align: middle;
 24+ display: inline-block;
 25+}
 26+
 27+#searchbox {
 28+ width: auto;
 29+ padding: 5px;
 30+ border: 1px solid #cccccc;
 31+ -webkit-border-radius: 2px;
 32+ -moz-border-radius: 2px;
 33+}
 34+
 35+#searchbox a, #searchbox img {
 36+ border: 0px;
 37+ vertical-align: middle;
 38+}
 39+
 40+#searchbox #goButton {
 41+ border: 0;
 42+ /* @embed */
 43+ background: url(images/s.gif) no-repeat top left;
 44+ background-size: 27px 25px;
 45+ height: 25px;
 46+ width: 27px;
 47+}
 48+
 49+#searchbox #searchField {
 50+ width: auto;
 51+}
 52+
 53+/*TODO: This might need to go in the main one*/
 54+.clearlink {
 55+ /* @embed */
 56+ background: url(images/close-button.png) no-repeat scroll 0 0 transparent;
 57+ background-position: center center;
 58+ cursor: pointer;
 59+ zoom: 1;
 60+ position: absolute;
 61+ right: 0.25em;
 62+ top: 50%;
 63+ margin: 1px;
 64+ height: 12px;
 65+ width: 12px;
 66+ margin-top: -6px;
 67+ z-index: 2;
 68+ border: 0px solid;
 69+ display: none;
 70+}
 71+
 72+.divclearable {
 73+ border: 1px solid #888;
 74+ display: -moz-inline-stack;
 75+ display: inline-block;
 76+ zoom: 1;
 77+ *display: inline;
 78+ vertical-align: middle;
 79+ height: 1.5em;
 80+ position: relative;
 81+}
 82+
 83+/* Search Suggestions */
 84+#results {
 85+ display: none;
 86+ background-color: #ffffff;
 87+ border-top: none;
 88+ border-left: 1px solid #888;
 89+ border-right: 1px solid #888;
 90+ border-bottom: 1px solid #888;
 91+ z-index: 2;
 92+ position: absolute;
 93+}
 94+
 95+.suggestions {
 96+ font-size: 1.2em;
 97+ cursor: pointer;
 98+}
 99+
 100+.suggestions-result {
 101+ color: black;
 102+ color: WindowText;
 103+ margin: 0;
 104+ line-height: 1.8em;
 105+ padding: 0.01em 0.25em;
 106+ text-align: left;
 107+ postion: relative;
 108+ border-bottom: solid 1px #999999;
 109+}
 110+
 111+.suggestions-result:hover {
 112+ background-color: #ACD1E9;
 113+}
 114+
 115+.suggestions-result a,
 116+.suggestions-result a:link,
 117+.suggestions-result a:visited,
 118+.suggestions-result a:hover,
 119+.suggestions-result a:active {
 120+ text-decoration: none;
 121+ color: #000000;
 122+}
 123+
 124+a.suggestions-result-update {
 125+ font-size: 1.3em;
 126+ display: block;
 127+ font-weight: normal;
 128+ text-decoration: none;
 129+ color: #000000;
 130+ position: absolute;
 131+ right: 0;
 132+ width: 1.5em;
 133+ text-align: center;
 134+}
 135+
 136+a.search-result-item {
 137+ display: block;
 138+ margin-right: 2em;
 139+}
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/ext.mobileFrontend2.search.css
___________________________________________________________________
Added: svn:eol-style
1140 + native
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/100px-globe.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/100px-globe.png
___________________________________________________________________
Added: svn:mime-type
2141 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/clearicon.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/clearicon.png
___________________________________________________________________
Added: svn:mime-type
3142 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/logo-en.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/logo-en.png
___________________________________________________________________
Added: svn:mime-type
4143 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/search-big.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/search-big.png
___________________________________________________________________
Added: svn:mime-type
5144 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/buttonbg.gif
Cannot display: file marked as a binary type.
svn:mime-type = image/gif
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/buttonbg.gif
___________________________________________________________________
Added: svn:mime-type
6145 + image/gif
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/search.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/search.png
___________________________________________________________________
Added: svn:mime-type
7146 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/system-search.gif
Cannot display: file marked as a binary type.
svn:mime-type = image/gif
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/system-search.gif
___________________________________________________________________
Added: svn:mime-type
8147 + image/gif
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s-xhdpi.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s-xhdpi.png
___________________________________________________________________
Added: svn:mime-type
9148 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s.svg
Cannot display: file marked as a binary type.
svn:mime-type = image/svg+xml
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s.svg
___________________________________________________________________
Added: svn:mime-type
10149 + image/svg+xml
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/close-button.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/close-button.png
___________________________________________________________________
Added: svn:mime-type
11150 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/arrow-left.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/arrow-left.png
___________________________________________________________________
Added: svn:mime-type
12151 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/mw.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/mw.png
___________________________________________________________________
Added: svn:mime-type
13152 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s.gif
Cannot display: file marked as a binary type.
svn:mime-type = image/gif
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s.gif
___________________________________________________________________
Added: svn:mime-type
14153 + image/gif
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s-hdpi.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/s-hdpi.png
___________________________________________________________________
Added: svn:mime-type
15154 + image/png
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/search.gif
Cannot display: file marked as a binary type.
svn:mime-type = image/gif
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/search.gif
___________________________________________________________________
Added: svn:mime-type
16155 + image/gif
Index: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/w.gif
Cannot display: file marked as a binary type.
svn:mime-type = image/gif
Property changes on: trunk/extensions/MobileFrontend2/modules/ext.mobileFrontend2/images/w.gif
___________________________________________________________________
Added: svn:mime-type
17156 + image/gif
Index: trunk/extensions/MobileFrontend2/MobileFrontend2_Options.php
@@ -0,0 +1,82 @@
 2+<?php
 3+
 4+class MobileFrontend2_Options {
 5+
 6+ /**
 7+ * Hide the search bar
 8+ *
 9+ * @var bool
 10+ */
 11+ protected static $hideSearch = false;
 12+
 13+ /**
 14+ * Hides the logo
 15+ *
 16+ * @var bool
 17+ */
 18+ protected static $hideLogo = false;
 19+
 20+ /**
 21+ * Hides the footer
 22+ *
 23+ * @var bool
 24+ */
 25+ protected static $hideFooter = false;
 26+
 27+ /**
 28+ * Not a user-option, used to do thing such as hide the title for the main
 29+ * page
 30+ *
 31+ * @var bool
 32+ */
 33+ protected static $mainPage = false;
 34+
 35+ /**
 36+ * Detects options based on user preferences
 37+ */
 38+ public static function detect() {
 39+ $request = RequestContext::getMain()->getRequest();
 40+
 41+ self::$hideSearch = $request->getBool( 'hidesearch' );
 42+ self::$hideLogo = $request->getBool( 'hidelogo' );
 43+ // TODO: Previously this was lumped into hidelogo. Notify mobile team
 44+ self::$hideFooter = $request->getBool( 'hidefooter' );
 45+
 46+ // TODO: Hook for Wikimedia
 47+ }
 48+
 49+ /**
 50+ * @return boolean
 51+ */
 52+ public static function getHideLogo() {
 53+ return self::$hideLogo;
 54+ }
 55+
 56+ /**
 57+ * @return boolean
 58+ */
 59+ public static function getHideSearch() {
 60+ return self::$hideSearch;
 61+ }
 62+
 63+ /**
 64+ * @return boolean
 65+ */
 66+ public static function getHideFooter() {
 67+ return self::$hideFooter;
 68+ }
 69+
 70+ /**
 71+ * @param boolean $mainPage
 72+ */
 73+ public static function setMainPage( $mainPage ) {
 74+ self::$mainPage = $mainPage;
 75+ }
 76+
 77+ /**
 78+ * @return boolean
 79+ */
 80+ public static function getMainPage() {
 81+ return self::$mainPage;
 82+ }
 83+}
Property changes on: trunk/extensions/MobileFrontend2/MobileFrontend2_Options.php
___________________________________________________________________
Added: svn:eol-style
184 + native
Index: trunk/extensions/MobileFrontend2/MobileFrontend2.i18n.php
@@ -0,0 +1,27 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for the extension MobileFrontend2
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+
 10+$messages = array();
 11+
 12+$messages['en'] = array(
 13+ 'mobile-frontend2-desc' => 'Mobile Frontend',
 14+ 'mobile-frontend2-back-to-top-of-section' => '&#8593; Jump back a section',
 15+ 'mobile-frontend2-show-button' => 'Show',
 16+ 'mobile-frontend2-hide-button' => 'Hide',
 17+ 'mobile-frontend2-regular-site' => 'View this page on regular {{SITENAME}}',
 18+ 'mobile-frontend2-perm-stop-redirect' => 'Permanently disable mobile site',
 19+ 'mobile-frontend2-disable-images' => 'Disable images on mobile site',
 20+
 21+ 'mainpage-mobile' => '{{int:mainpage}}',
 22+);
 23+
 24+$messages['qqq'] = array(
 25+ 'mobile-frontend2-desc' => '{{desc}}',
 26+
 27+ 'mainpage-mobile' => 'Page name for the Main Page used in the mobile view. Defaults to {{mw-msg|mainpage}}',
 28+);
\ No newline at end of file
Property changes on: trunk/extensions/MobileFrontend2/MobileFrontend2.i18n.php
___________________________________________________________________
Added: svn:eol-style
129 + native
Index: trunk/extensions/MobileFrontend2/MobileFrontend2_Detection.php
@@ -0,0 +1,70 @@
 2+<?php
 3+
 4+/**
 5+ * Class containing all detection logic for mobile frontend
 6+ */
 7+class MobileFrontend2_Detection {
 8+
 9+ /**
 10+ * Cached detection result
 11+ *
 12+ * @var null|bool
 13+ */
 14+ protected static $enabled = null;
 15+
 16+ /**
 17+ * Main function deciding if the MobileFrontend should be enabled
 18+ *
 19+ * @return bool
 20+ */
 21+ public static function isEnabled() {
 22+ if ( self::$enabled !== null ) {
 23+ return self::$enabled;
 24+ }
 25+
 26+ self::detect();
 27+
 28+ return self::$enabled;
 29+ }
 30+
 31+ private static function detect() {
 32+ $request = RequestContext::getMain()->getRequest();
 33+ $useFormat = $request->getText( 'useformat' );
 34+
 35+ // Start with the basics, did they force the frontend?
 36+ if ( $useFormat == 'mobile' ) {
 37+ return self::enable();
 38+ }
 39+
 40+ // TODO: Other detection magic
 41+
 42+ // Nope. No mobile frontend for you.
 43+ return self::disable();
 44+ }
 45+
 46+ /**
 47+ * Enable mobile frontend
 48+ *
 49+ * @return bool
 50+ */
 51+ private static function enable() {
 52+ global $wgResourceModules;
 53+
 54+ self::$enabled = true;
 55+
 56+ // Do some initialization
 57+ MobileFrontend2_Options::detect();
 58+
 59+ return true;
 60+ }
 61+
 62+ /**
 63+ * Disable mobile frontend
 64+ *
 65+ * @return bool
 66+ */
 67+ private static function disable() {
 68+ self::$enabled = false;
 69+ return false;
 70+ }
 71+}
Property changes on: trunk/extensions/MobileFrontend2/MobileFrontend2_Detection.php
___________________________________________________________________
Added: svn:eol-style
172 + native

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r111273change the name of MobileFrontend2 extension to MobileSkinpreilly05:32, 12 February 2012

Status & tagging log