Trevor Parscal has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/65249


Change subject: image-insertion (WIP, DO NOT MERGE)
......................................................................

image-insertion (WIP, DO NOT MERGE)

Initial commit for MediaInsertButtonTool
* Get MediaInsertButtonTool to appear in the toolbar
* Create MediaInsertDialog and basic i18n support
* Adding images works (at this moment just the same static image)
* Place the cursor in the right place after image is added
* Create MediaWidget and MediaInput base files
* Add mw specific media insert dialog and button
* MW dialog, add event for setting media source and enabling
or disabling apply button
* Added API request to check if image exists and pull its URL

TODO:

1) Lazily place images vs placing all once deferred are done
( requires sane imageinfo from api )
2) Inherit SelectWidget and add image cells as MenuItemWidgets
3) MediaInsertDialog to build transaction and insert mixed media
(images / video thumbs)

Change-Id: Ia803ff3ef518782ce76802d2dab7559686a1bb0a
---
M VisualEditor.i18n.php
M VisualEditor.php
M demos/ve/index.php
M modules/ve/test/index.php
A modules/ve/ui/dialogs/ve.ui.MWMediaInsertDialog.js
A modules/ve/ui/dialogs/ve.ui.MediaInsertDialog.js
A modules/ve/ui/styles/images/ajax-loader.gif
M modules/ve/ui/styles/ve.ui.Widget.css
A modules/ve/ui/tools/buttons/ve.ui.MWMediaInsertButtonTool.js
A modules/ve/ui/tools/buttons/ve.ui.MediaInsertButtonTool.js
M modules/ve/ui/widgets/ve.ui.InputWidget.js
A modules/ve/ui/widgets/ve.ui.MediaWidget.js
12 files changed, 692 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor 
refs/changes/49/65249/1

diff --git a/VisualEditor.i18n.php b/VisualEditor.i18n.php
index 7712f08..946d2ad 100644
--- a/VisualEditor.i18n.php
+++ b/VisualEditor.i18n.php
@@ -32,6 +32,8 @@
        'visualeditor-dialog-media-title' => 'Media settings',
        'visualeditor-dialog-reference-title' => 'Reference',
        'visualeditor-dialog-template-title' => 'Template',
+       'visualeditor-dialog-media-insert-title' => 'Insert Media',
+       'visualeditor-media-input-placeholder' => 'Search for media',
        'visualeditor-dialog-action-apply' => 'Apply changes',
        'visualeditor-dialog-action-cancel' => 'Cancel',
        'visualeditor-dialog-action-close' => 'Close',
@@ -156,6 +158,10 @@
 
 See also:
 * {{msg-mw|Visualeditor-dialog-action-close}}',
+       'visualeditor-dialog-media-title' => 'Media settings dialog title text',
+       'visualeditor-dialog-media-insert-title' => 'Media insert dialog title 
text',
+       'visualeditor-media-input-placeholder' => 'Place holder text for media 
search input',
+       'visualeditor-dialog-action-apply' => 'Label text for button to apply 
changes made in dialog',
        'visualeditor-dialog-action-cancel' => 'Used as button text.
 {{Identical|Cancel}}',
        'visualeditor-dialog-action-close' => 'Used as tooltip for the "Close" 
button.
diff --git a/VisualEditor.php b/VisualEditor.php
index 85dbc87..5a3183d 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -406,6 +406,7 @@
                        've/ui/widgets/ve.ui.MenuWidget.js',
                        've/ui/widgets/ve.ui.PendingInputWidget.js',
                        've/ui/widgets/ve.ui.LookupInputWidget.js',
+                       've/ui/widgets/ve.ui.MediaWidget.js',
                        've/ui/widgets/ve.ui.TextInputMenuWidget.js',
                        've/ui/widgets/ve.ui.LinkTargetInputWidget.js',
                        've/ui/widgets/ve.ui.MWLinkTargetInputWidget.js',
@@ -421,8 +422,10 @@
 
                        've/ui/dialogs/ve.ui.ContentDialog.js',
                        've/ui/dialogs/ve.ui.MediaDialog.js',
+                       've/ui/dialogs/ve.ui.MediaInsertDialog.js',
                        've/ui/dialogs/ve.ui.PagedDialog.js',
                        've/ui/dialogs/ve.ui.MWMetaDialog.js',
+                       've/ui/dialogs/ve.ui.MWMediaInsertDialog.js',
 
                        've/ui/tools/ve.ui.ButtonTool.js',
                        've/ui/tools/ve.ui.AnnotationButtonTool.js',
@@ -436,6 +439,8 @@
                        've/ui/tools/buttons/ve.ui.ItalicButtonTool.js',
                        've/ui/tools/buttons/ve.ui.ClearButtonTool.js',
                        've/ui/tools/buttons/ve.ui.MediaButtonTool.js',
+                       've/ui/tools/buttons/ve.ui.MediaInsertButtonTool.js',
+                       've/ui/tools/buttons/ve.ui.MWMediaInsertButtonTool.js',
                        've/ui/tools/buttons/ve.ui.LinkButtonTool.js',
                        've/ui/tools/buttons/ve.ui.MWLinkButtonTool.js',
                        've/ui/tools/buttons/ve.ui.BulletButtonTool.js',
@@ -516,6 +521,8 @@
                        'visualeditor-aliennode-tooltip',
                        'visualeditor-dialog-meta-title',
                        'visualeditor-dialog-media-title',
+                       'visualeditor-dialog-media-insert-title',
+                       'visualeditor-media-input-placeholder',
                        'visualeditor-dialog-content-title',
                        'visualeditor-dialog-action-apply',
                        'visualeditor-dialog-action-cancel',
diff --git a/demos/ve/index.php b/demos/ve/index.php
index 15b15c3..fabc16e 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -257,6 +257,7 @@
                <script 
src="../../modules/ve/ui/widgets/ve.ui.MenuWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.PendingInputWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.LookupInputWidget.js"></script>
+               <script 
src="../../modules/ve/ui/widgets/ve.ui.MediaWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.LinkTargetInputWidget.js"></script>
                <script 
src="../../modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js"></script>
@@ -270,6 +271,7 @@
                <script 
src="../../modules/ve/ui/layouts/ve.ui.StackPanelLayout.js"></script>
                <script 
src="../../modules/ve/ui/dialogs/ve.ui.ContentDialog.js"></script>
                <script 
src="../../modules/ve/ui/dialogs/ve.ui.MediaDialog.js"></script>
+               <script 
src="../../modules/ve/ui/dialogs/ve.ui.MediaInsertDialog.js"></script>
                <script 
src="../../modules/ve/ui/dialogs/ve.ui.PagedDialog.js"></script>
                <script 
src="../../modules/ve/ui/dialogs/ve.ui.MWMetaDialog.js"></script>
                <script 
src="../../modules/ve/ui/tools/ve.ui.ButtonTool.js"></script>
@@ -283,6 +285,7 @@
                <script 
src="../../modules/ve/ui/tools/buttons/ve.ui.ItalicButtonTool.js"></script>
                <script 
src="../../modules/ve/ui/tools/buttons/ve.ui.ClearButtonTool.js"></script>
                <script 
src="../../modules/ve/ui/tools/buttons/ve.ui.MediaButtonTool.js"></script>
+               <script 
src="../../modules/ve/ui/tools/buttons/ve.ui.MediaInsertButtonTool.js"></script>
                <script 
src="../../modules/ve/ui/tools/buttons/ve.ui.LinkButtonTool.js"></script>
                <script 
src="../../modules/ve/ui/tools/buttons/ve.ui.MWLinkButtonTool.js"></script>
                <script 
src="../../modules/ve/ui/tools/buttons/ve.ui.BulletButtonTool.js"></script>
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 2d7af4b..fcebbaf 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -237,6 +237,7 @@
                <script 
src="../../ve/ui/tools/buttons/ve.ui.ItalicButtonTool.js"></script>
                <script 
src="../../ve/ui/tools/buttons/ve.ui.ClearButtonTool.js"></script>
                <script 
src="../../ve/ui/tools/buttons/ve.ui.MediaButtonTool.js"></script>
+               <script 
src="../../ve/ui/tools/buttons/ve.ui.MediaInsertButtonTool.js"></script>
                <script 
src="../../ve/ui/tools/buttons/ve.ui.LinkButtonTool.js"></script>
                <script 
src="../../ve/ui/tools/buttons/ve.ui.MWLinkButtonTool.js"></script>
                <script 
src="../../ve/ui/tools/buttons/ve.ui.BulletButtonTool.js"></script>
diff --git a/modules/ve/ui/dialogs/ve.ui.MWMediaInsertDialog.js 
b/modules/ve/ui/dialogs/ve.ui.MWMediaInsertDialog.js
new file mode 100644
index 0000000..2759cdf
--- /dev/null
+++ b/modules/ve/ui/dialogs/ve.ui.MWMediaInsertDialog.js
@@ -0,0 +1,62 @@
+/*!
+ * VisualEditor user interface MediaInsertDialog class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * Document dialog.
+ *
+ * @class
+ * @abstract
+ * @extends ve.ui.MediaInsertDialog
+ *
+ * @constructor
+ * @param {ve.Surface} surface
+ */
+ve.ui.MWMediaInsertDialog = function VeUiMWMediaInsertDialog( surface ) {
+       // Parent constructor
+       ve.ui.MediaInsertDialog.call( this, surface );
+       this.mediaSource = '';
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ui.MWMediaInsertDialog, ve.ui.MediaInsertDialog );
+
+/* Methods */
+
+ve.ui.MWMediaInsertDialog.prototype.onApplyButtonClick = function () {
+       this.insertImage( this.mediaSource );
+       // Close dialog
+       ve.ui.Dialog.prototype.onApplyButtonClick.call( this );
+};
+
+/**
+ * Handle frame ready events.
+ *
+ * @method
+ */
+ve.ui.MWMediaInsertDialog.prototype.initialize = function () {
+       // Call parent method
+       ve.ui.Dialog.prototype.initialize.call( this );
+
+       this.mediaWidget = new ve.ui.MediaWidget( { '$$': this.$$ } );
+       this.$body.append( this.mediaWidget.$ );
+
+       this.applyButton.setDisabled( true );
+
+       // mediaWidget Events
+
+       this.mediaWidget.connect( this, { 'setMediaSource': 'onSetMediaSource' 
} );
+};
+
+ve.ui.MWMediaInsertDialog.prototype.onSetMediaSource = function ( src ) {
+       this.mediaSource = src;
+       this.applyButton.setDisabled( src === null );
+};
+
+/* Registration */
+
+ve.ui.dialogFactory.register( 'mwMediaInsert', ve.ui.MWMediaInsertDialog );
diff --git a/modules/ve/ui/dialogs/ve.ui.MediaInsertDialog.js 
b/modules/ve/ui/dialogs/ve.ui.MediaInsertDialog.js
new file mode 100644
index 0000000..589ccbf
--- /dev/null
+++ b/modules/ve/ui/dialogs/ve.ui.MediaInsertDialog.js
@@ -0,0 +1,80 @@
+/*!
+ * VisualEditor user interface MediaInsertDialog class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * Document dialog.
+ *
+ * @class
+ * @abstract
+ * @extends ve.ui.Dialog
+ *
+ * @constructor
+ * @param {ve.Surface} surface
+ */
+ve.ui.MediaInsertDialog = function VeUiMediaInsertDialog( surface ) {
+       // Parent constructor
+       ve.ui.Dialog.call( this, surface );
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ui.MediaInsertDialog, ve.ui.Dialog );
+
+/* Static Properties */
+
+ve.ui.MediaInsertDialog.static.titleMessage = 
'visualeditor-dialog-media-insert-title';
+
+ve.ui.MediaInsertDialog.static.icon = 'picture';
+
+/* Methods */
+
+/**
+ * Handle frame ready events.
+ *
+ * @method
+ */
+ve.ui.MediaInsertDialog.prototype.initialize = function () {
+       // Call parent method
+       ve.ui.Dialog.prototype.initialize.call( this );
+
+       this.mediaInput = new ve.ui.TextInputWidget( { '$$': this.$$ } );
+       this.mediaInputLabel = new ve.ui.InputLabelWidget( {
+               '$$': this.$$, 'input': this.mediaInput, 'label': 'Media file 
path'
+       } );
+       this.$body.append( this.$$( '<div 
class="ve-ui-mediaInputWidget"></div>' )
+               .append( this.mediaInputLabel.$, this.mediaInput.$ )
+       );
+};
+
+ve.ui.MediaInsertDialog.prototype.onApplyButtonClick = function () {
+       this.insertImage( this.mediaInput.$input.val() );
+       // Close dialog
+       ve.ui.Dialog.prototype.onApplyButtonClick.call( this );
+};
+
+ve.ui.MediaInsertDialog.prototype.insertImage = function ( src ) {
+       // Built transaction
+       var tx = ve.dm.Transaction.newFromInsertion(
+               this.surface.documentModel,
+               this.surface.model.selection.start, [
+                       {
+                               'type': 'MWimage',
+                               'attributes': {
+                                       'src': src
+                               }
+                       },
+                       { 'type': '/MWimage' }
+               ]
+       );
+
+       // Process transaction
+       this.surface.model.change( tx, new ve.Range( 
this.surface.model.selection.start + 2) );
+};
+
+/* Registration */
+
+ve.ui.dialogFactory.register( 'mediaInsert', ve.ui.MediaInsertDialog );
diff --git a/modules/ve/ui/styles/images/ajax-loader.gif 
b/modules/ve/ui/styles/images/ajax-loader.gif
new file mode 100644
index 0000000..7afdde3
--- /dev/null
+++ b/modules/ve/ui/styles/images/ajax-loader.gif
Binary files differ
diff --git a/modules/ve/ui/styles/ve.ui.Widget.css 
b/modules/ve/ui/styles/ve.ui.Widget.css
index 945a68a..93e0bfd 100644
--- a/modules/ve/ui/styles/ve.ui.Widget.css
+++ b/modules/ve/ui/styles/ve.ui.Widget.css
@@ -518,6 +518,70 @@
        padding: 0 0.125em 0.5em 0.125em;
 }
 
+/* ve.ui.MediaWidget */
+
+.ve-ui-mediaWidget .ve-ui-textInputWidget {
+       margin: 1em 0 .75em 0;
+}
+
+.ve-ui-mediaWidget .ve-ui-textInputWidget input {
+       margin-top: 1em;
+       width: 100%;
+       font-size: 1.2em;
+       -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+}
+
+.ve-ui-mediaWidget-thumb {
+       /*float: left;*/
+       display: inline-block;
+       vertical-align: top;
+       overflow: hidden;
+       line-height: 1em;
+       padding: 0;
+       margin: 0;
+}
+.ve-ui-mediaWidget-thumb img {
+       margin: 0;
+       padding: 0;
+}
+
+.ve-ui-mediaWidget-loader {
+       position: fixed;
+       /*display: none;*/
+       right: 0;
+       bottom: 0;
+       left: 0;
+       padding: 1em;
+       line-height: 1em;
+       opacity: .8;
+       z-index: 200;
+}
+.ve-ui-mediaWidget-spinner {
+       background: #FFFFFF url( images/ajax-loader.gif ) center center;
+       height: 100px;
+       width: 100px;
+       border-radius: 1em;
+       background-repeat: no-repeat;
+       margin: auto;
+       position: fixed;
+       top: 0;
+       right: 0;
+       bottom: 1em;
+       left: 0;
+       min-height: 100px;
+       max-height: 100px;
+}
+
+.ve-ui-mediaWidget-scroller {
+       overflow-y: scroll;
+       overflow-x: none;
+       height: 300px; /* Needs to be generated by the widget */
+       width: 100%;
+       box-sizing: border-box;
+}
+
 /* RTL Definitions */
 
 /* @noflip */
diff --git a/modules/ve/ui/tools/buttons/ve.ui.MWMediaInsertButtonTool.js 
b/modules/ve/ui/tools/buttons/ve.ui.MWMediaInsertButtonTool.js
new file mode 100644
index 0000000..0aeb4a6
--- /dev/null
+++ b/modules/ve/ui/tools/buttons/ve.ui.MWMediaInsertButtonTool.js
@@ -0,0 +1,34 @@
+/*!
+ * VisualEditor UserInterface MWMediaButtonTool class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * UserInterface content button tool.
+ *
+ * @class
+ * @extends ve.ui.MediaInsertButtonTool
+ * @constructor
+ * @param {ve.ui.Toolbar} toolbar
+ * @param {Object} [config] Config options
+ */
+ve.ui.MWMediaInsertButtonTool = function VeUiMWMediaButtonTool( toolbar, 
config ) {
+       // Parent constructor
+       ve.ui.MediaInsertButtonTool.call( this, toolbar, config );
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ui.MWMediaInsertButtonTool, ve.ui.MediaInsertButtonTool );
+
+/* Static Properties */
+
+ve.ui.MWMediaInsertButtonTool.static.name = 'mwMediaInsert';
+
+ve.ui.MWMediaInsertButtonTool.static.dialog = 'mwMediaInsert';
+
+/* Registration */
+
+ve.ui.toolFactory.register( 'mwMediaInsert', ve.ui.MWMediaInsertButtonTool );
diff --git a/modules/ve/ui/tools/buttons/ve.ui.MediaInsertButtonTool.js 
b/modules/ve/ui/tools/buttons/ve.ui.MediaInsertButtonTool.js
new file mode 100644
index 0000000..6ded468
--- /dev/null
+++ b/modules/ve/ui/tools/buttons/ve.ui.MediaInsertButtonTool.js
@@ -0,0 +1,37 @@
+/*!
+ * VisualEditor UserInterface MediaButtonTool class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/**
+ * @class
+ * @extends ve.ui.DialogButtonTool
+ * @constructor
+ * @param {ve.ui.Toolbar} toolbar
+ * @param {Object} [config] Config options
+ */
+ve.ui.MediaInsertButtonTool = function VeUiMediaInsertButtonTool( toolbar, 
config ) {
+       // Parent constructor
+       ve.ui.DialogButtonTool.call( this, toolbar, config );
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ui.MediaInsertButtonTool, ve.ui.DialogButtonTool );
+
+/* Static Properties */
+
+ve.ui.MediaInsertButtonTool.static.name = 'mediaInsert';
+
+ve.ui.MediaInsertButtonTool.static.icon = 'picture';
+
+ve.ui.MediaInsertButtonTool.static.titleMessage =
+       'visualeditor-dialogbutton-media-tooltip';
+
+ve.ui.MediaInsertButtonTool.static.dialog = 'mediaInsert';
+
+/* Registration */
+
+ve.ui.toolFactory.register( 'mediaInsert', ve.ui.MediaInsertButtonTool );
diff --git a/modules/ve/ui/widgets/ve.ui.InputWidget.js 
b/modules/ve/ui/widgets/ve.ui.InputWidget.js
index 17743c5..c108c3e 100644
--- a/modules/ve/ui/widgets/ve.ui.InputWidget.js
+++ b/modules/ve/ui/widgets/ve.ui.InputWidget.js
@@ -20,7 +20,7 @@
  */
 ve.ui.InputWidget = function VeUiInputWidget( config ) {
        // Config intialization
-       config = ve.extendObject( { 'readOnly': false }, config );
+       config = ve.extendObject( { 'readOnly': false, 'placeholder': '' }, 
config );
 
        // Parent constructor
        ve.ui.Widget.call( this, config );
diff --git a/modules/ve/ui/widgets/ve.ui.MediaWidget.js 
b/modules/ve/ui/widgets/ve.ui.MediaWidget.js
new file mode 100644
index 0000000..fe423bc
--- /dev/null
+++ b/modules/ve/ui/widgets/ve.ui.MediaWidget.js
@@ -0,0 +1,397 @@
+/*!
+ * VisualEditor UserInterface MediaWidget class.
+ *
+ * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+
+/*global mw*/
+
+/**
+ * Creates an ve.ui.MediaWidget object.
+ *
+ * @class
+ * @extends ve.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Config options
+ */
+ve.ui.MediaWidget = function VeUiMediaWidget( config ) {
+       // Config intialization
+       this.config = ve.extendObject( {
+               'sources': {
+                       'local': {
+                               'url': mw.util.wikiScript( 'api' ),
+                               'request': null,
+                               'gsroffset': 0
+                       },
+                       'commons': {
+                               'url': '//commons.wikimedia.org/w/api.php',
+                               'request': null,
+                               'gsroffset': 0
+                       },
+               },
+               'imagesPerPage': 20,
+               'imageHeight': 150,
+               'imageMargin': 5,
+               'direction': 'left',
+               'initialSearch': mw.config.get( 'wgTitle' ),
+               // Bound to be window of iframe.
+               '$window': $( $( config.$$.frame.$ )[0].contentWindow ),
+               '$document': $( config.$$.frame.$content.context ),
+               'animate': false,
+               'timeout': 50
+       }, config );
+
+       // Parent constructor
+       ve.ui.Widget.call( this, config );
+
+       this.mediaInput = new ve.ui.TextInputWidget( {
+               '$$': this.$$, 'placeholder': ve.msg( 
'visualeditor-media-input-placeholder' )
+       } );
+
+       this.mediaInput.$input.on( 'keyup', ve.bind( this.onMediaInputKeyUp, 
this ) );
+       this.req = null;
+
+       // Properties
+       this.blocks = [];
+       this.imageObjects = [];
+
+       // Row property assists with building rows
+       this.row = { // this.currentRow ?
+               widthCumulative: 0,
+               currentBlocks: []
+       };
+
+       // Debouce timeouts
+       this.resizeTimeout = null;
+       this.scrollTimeout = null;
+       this.inputTimeout = null;
+       
+       this.state = {
+               iDuringAjax: false,
+               resultSet: 0
+       };
+
+       this.bufferPx = 40;
+
+       // Elements
+       this.mediaInput = new ve.ui.TextInputWidget( {
+               '$$': this.$$,
+               'placeholder': ve.msg( 'visualeditor-media-input-placeholder' ),
+               'value': this.config.initialSearch
+       } );
+
+       this.$suggestions = this.$$( '<div 
class="ve-ui-mediaWidget-suggestions"></div>' );
+       this.$scroller = this.$$( '<div 
class="ve-ui-mediaWidget-scroller"></div>' )
+               .append( this.$suggestions );
+
+       this.$loader = this.$$(
+               '<div class="ve-ui-mediaWidget-loader">' +
+                       '<div class="ve-ui-mediaWidget-spinner"></div>' +
+               '</div>'
+       );
+
+       this.$.append(
+               this.mediaInput.$.addClass( 've-ui-mediaWidget' ),
+               this.$loader,
+               this.$scroller
+       ).addClass( 've-ui-mediaWidget' );
+       this.mediaInput.$input.on( 'keyup', ve.bind( this.onInputKeyup, this ) 
);
+
+       // Events
+
+       this.config.$window.on( 'resize', ve.bind( function () {
+               clearTimeout( this.resizeTimeout );
+               this.resizeTimeout = setTimeout( ve.bind( this.onResize, this 
), this.config.timeout );
+       }, this ) );
+
+       this.$scroller.on( 'scroll', ve.bind( function () {
+               clearTimeout( this.scrollTimeout );
+               this.scrollTimeout = setTimeout( ve.bind( this.onScroll, this 
), this.config.timeout );
+       }, this ) );
+
+       // Init
+       this.fetchMedia();
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ui.MediaWidget, ve.ui.Widget );
+
+/* Methods */
+
+ve.ui.MediaWidget.prototype.onMediaInputKeyUp = function () {
+       this.emit( 'setMediaSource', null );
+       if ( this.req ) {
+               this.req.abort();
+               this.req = null;
+       }
+       this.req = $.ajax( {
+               'url': mw.util.wikiScript( 'api' ),
+               'data': {
+                       'format': 'json',
+                       'action': 'query',
+                       'indexpageids': '1',
+                       'titles': 'File:' + this.mediaInput.$input.val(),
+                       'prop': 'imageinfo',
+                       'iiprop': 'url'
+               },
+               'dataType': 'json',
+               'success': ve.bind( function ( data ) {
+                       var pageid = data.query.pageids[0];
+                       if ( pageid > 0 ) {
+                               this.emit( 'setMediaSource', 
data.query.pages[pageid].imageinfo[0].url );
+                       }
+               }, this )
+       } );
+};
+
+ve.ui.MediaWidget.prototype.onInputKeyup = function () {
+       if ( this.state.isDuringAjax || this.mediaInput.$input.val() === '' ) {
+               return;
+       }
+       clearTimeout( this.inputTimeout );
+       this.inputTimeout = setTimeout( ve.bind( function () {
+               this.resetImages();
+               this.mwApiRequest();
+       },  this ), this.config.timeout );
+       this.emit( 'setMediaSource', null );
+};
+
+ve.ui.MediaWidget.prototype.onResize = function () {
+       if ( this.state.isDuringAjax ) {
+               return;
+       }
+       // Init blocks
+       this.row.widthCumulative = 0;
+       this.row.currentBlocks = [];
+       this.initImages();
+};
+
+ve.ui.MediaWidget.prototype.onScroll = function () {
+       if ( this.state.isDuringAjax || !this.isNearBottom() || 
this.mediaInput.$input.val() === '' ) {
+               ve.log( 'aborting from scroll' );
+               return;
+       }
+       ve.log( 'fetching media...' );
+       this.fetchMedia();
+};
+
+ve.ui.MediaWidget.prototype.loaded = function () {
+       this.containerWidth = this.$scroller.width();
+       this.row.widthCumulative = 0;
+       this.row.currentBlocks = [];
+       this.initImages();
+       this.$loader.hide();
+       this.easeIn();
+};
+
+
+ve.ui.MediaWidget.prototype.easeIn = function () {
+       var scrollTo = this.$scroller.scrollTop() + this.config.imageHeight;
+       // Ease in new content by one row
+       if ( this.config.animate ) {
+               this.$scroller.animate( { scrollTop: scrollTo }, 800, ve.bind( 
function () {
+                       this.state.isDuringAjax = false;
+               }, this ) );
+       } else {
+               this.state.isDuringAjax = false;
+       }
+};
+
+ve.ui.MediaWidget.prototype.initImages = function () {
+       var image, i = 0;
+       ve.log( 'placing: ' + this.imageObjects.length );
+       // Set container width
+       for( ; i < this.imageObjects.length; i++ ) {
+               image = this.imageObjects[i];
+               // check for scaledWidth
+               if ( !( 'scaledWidth' in image ) ) {
+                       image.scaledWidth = image.$block.find( 'img' ).width();
+               }
+               this.place( this.imageObjects[i] );
+       }
+};
+
+ve.ui.MediaWidget.prototype.place = function ( image ) {
+       var adjustmentEach, width, newWidth, marginAdjust, i = 0,
+               cellWidth = image.scaledWidth + ( this.config.imageMargin * 2 ),
+               testRowWidth = this.row.widthCumulative + cellWidth;
+
+       // Determine if this block will put the row greater than the container 
width
+       
+       if ( testRowWidth > this.containerWidth ) {
+
+               // Test the pending row width for a massive difference.
+               if ( ( testRowWidth - this.containerWidth ) >= ( 
image.scaledWidth / 2 ) ) {
+                       // Build row with current blocks, positive adjustment
+                       adjustmentEach = Math.abs( Math.floor(
+                               ( this.containerWidth - 
this.row.widthCumulative ) / this.row.currentBlocks.length
+                               ) - 1
+                       );
+                       // Block will be added to currentBlocks after row is 
built
+               } else {
+                       // Since it's not insane, add up the row and make 
negative adjustment
+                       this.row.widthCumulative += cellWidth;
+                       this.row.currentBlocks.push( image );
+                       // Negative Adjustment
+                       adjustmentEach = -Math.abs( Math.ceil(
+                               ( this.row.widthCumulative - 
this.containerWidth ) / this.row.currentBlocks.length
+                               ) + 1
+                       );
+               }
+               // Adjust the row, for one or more blocks
+               for( ; i < this.row.currentBlocks.length; i++ )  {
+                       width = this.row.currentBlocks[i].scaledWidth; // again 
go forward with scaledWidth
+                       newWidth = width + adjustmentEach;
+
+                       if ( adjustmentEach > 0 ) {
+                               // Center image in container
+                               // Grow the image inside the block, important 
to set height auto so it scales
+                               // accordingly
+                               this.row.currentBlocks[i].$block.find( 'img' 
).css( {
+                                       'height': 'auto', 'width': newWidth
+                               } );
+                               marginAdjust = 0;
+                       } else {
+                               marginAdjust = -Math.abs( ( newWidth - width ) 
/ 2 );
+                       }
+                       // Size image container, and show
+                       this.row.currentBlocks[i].$block.css( { 'width': 
newWidth } );
+                       this.row.currentBlocks[i].$block.find( 'img' ).css(
+                               'margin-' + this.config.direction, marginAdjust 
+ 'px'
+                       );
+               }
+
+               // Row is now complete, reset helpers.
+               this.row.widthCumulative = 0;
+               this.row.currentBlocks = [];
+
+               // Since adjustment is positive, add to next row
+               if ( adjustmentEach > 0 ) {
+                       this.row.widthCumulative += cellWidth;
+                       this.row.currentBlocks.push( image );
+               }
+       } else {
+               // Cumulate
+               this.row.widthCumulative += cellWidth;
+               this.row.currentBlocks.push( image );
+       }
+};
+
+ve.ui.MediaWidget.prototype.resetImages = function () {
+       // Reset blocks
+       this.imageObjects = [];
+
+       // Reset row
+       this.row.widthCumulative = 0;
+       this.row.currentBlocks = [];
+
+       // Reset state.
+       this.state.loadedImages = 0;
+       this.state.resultSet = 0;
+       this.resetSourceCounts();
+
+       // Reset suggestions
+       this.$suggestions.empty();
+};
+
+ve.ui.MediaWidget.prototype.resetSourceCounts = function () {
+       var source;
+       for ( source in this.config.sources ) {
+               this.config.sources[source].gsroffset = 0;
+       }
+};
+
+ve.ui.MediaWidget.prototype.isNearBottom = function () {
+       return (
+               this.$scroller.scrollTop() + this.$scroller.height() + 
this.bufferPx >=
+                       this.$suggestions.height()
+       );
+};
+
+ve.ui.MediaWidget.prototype.fetchMedia = function () {
+       this.state.isDuringAjax = true;
+       // make ajax request
+       this.$loader.show();
+       this.mwApiRequest();
+};
+
+// Make mw api request
+ve.ui.MediaWidget.prototype.mwApiRequest = function () {
+       this.emit( 'setMediaSource', null );
+       var searchString = this.mediaInput.$input.val(),
+               source;
+
+       // Make requests from each source
+       for ( source in this.config.sources ) {
+               if ( this.config.sources[source].request ) {
+                       this.config.sources[source].request.abort();
+                       this.config.sources[source].request = null;
+               }
+
+               this.config.sources[source].request = $.getJSON(
+                       this.config.sources[source].url + '?callback=?', {
+                               'action': 'query',
+                               'generator': 'search',
+                               'gsrsearch': searchString,
+                               'gsrnamespace': 6,
+                               'gsroffset': 
this.config.sources[source].gsroffset,
+                               'prop': 'imageinfo',
+                               'iiprop': 'size|url',
+                               'iiurlwidth': 300,
+                               'iiurlheight': 400,
+                               //'iiurlheight': this.config.imageHeight,
+                               'format': 'json'
+                       } ).done( ve.bind( function ( data ) {  // BAD: 
function in a loop. Tis a WIP
+                               data.source = source;
+                               this.loadDataFromApi( data, source );
+               }, this ) );
+       }
+};
+
+// Handles data returned from mwApiRequest request
+ve.ui.MediaWidget.prototype.loadDataFromApi = function ( data ) {
+       var     height = this.config.imageHeight,
+               loading = $.Deferred(),
+               source = data.source,
+               promises = [],
+               image = {},
+               $block,
+               page,
+               p;
+
+       // gsroffset
+       if ( !( 'query' in data ) || !( 'pages' in data.query ) ) {
+               this.isDuringAjax = false;
+               this.currentApiRequest = null;
+               this.$loader.hide();
+               return;
+       }
+
+       if ( 'query-continue' in data && 'search' in data['query-continue'] ) {
+               this.config.sources[source].gsroffset = 
data['query-continue'].search.gsroffset;
+       }
+
+       for ( page in data.query.pages ) {
+               p = $.Deferred();
+               image = data.query.pages[page].imageinfo[0];
+
+               $block = this.$$( '<div>' ).addClass( 've-ui-mediaWidget-thumb' 
).css(
+                       { 'height': height, 'margin': this.config.imageMargin }
+               ).append( this.$$( '<img>' ).attr( 'src', image.thumburl ).css( 
{ 'height': height } )
+                               .on( { 'load': p.resolve, 'error': p.resolve } )
+               );
+
+               promises.push( p );
+               this.$suggestions.append( $block );
+
+               image = ve.extendObject( { '$block': $block },  image );
+
+               this.imageObjects.push( image );
+       }
+
+       $.when.apply( this, promises ).done( loading.resolve );
+       $.when( loading ).done( ve.bind( function () { this.loaded(); }, this ) 
);
+};

-- 
To view, visit https://gerrit.wikimedia.org/r/65249
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia803ff3ef518782ce76802d2dab7559686a1bb0a
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Trevor Parscal <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to