Robmoen has uploaded a new change for review.

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


Change subject: Do not merge.  WIP for image suggestions
......................................................................

Do not merge.  WIP for image suggestions

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: Id49b9effb0dce833c2e8312f134a9f62e7df1dc0
---
M VisualEditor.i18n.php
M VisualEditor.php
M demos/ve/index.php
M modules/ve/init/mw/targets/ve.init.mw.ViewPageTarget.js
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
13 files changed, 649 insertions(+), 3 deletions(-)


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

diff --git a/VisualEditor.i18n.php b/VisualEditor.i18n.php
index 9e30618..e00486b 100644
--- a/VisualEditor.i18n.php
+++ b/VisualEditor.i18n.php
@@ -28,6 +28,8 @@
        'visualeditor-dialog-meta-title' => 'Page settings',
        'visualeditor-dialog-content-title' => 'Content settings',
        'visualeditor-dialog-media-title' => 'Media settings',
+       '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',
@@ -136,6 +138,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 78119e7..d5f919f 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -384,6 +384,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',
@@ -401,6 +402,8 @@
                        've/ui/dialogs/ve.ui.MediaDialog.js',
                        've/ui/dialogs/ve.ui.PagedDialog.js',
                        've/ui/dialogs/ve.ui.MWMetaDialog.js',
+                       've/ui/dialogs/ve.ui.MediaInsertDialog.js',
+                       've/ui/dialogs/ve.ui.MWMediaInsertDialog.js',
 
                        've/ui/tools/ve.ui.ButtonTool.js',
                        've/ui/tools/ve.ui.AnnotationButtonTool.js',
@@ -414,6 +417,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',
@@ -495,6 +500,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 a374e54..3792d53 100644
--- a/demos/ve/index.php
+++ b/demos/ve/index.php
@@ -254,6 +254,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>
@@ -264,6 +265,7 @@
                <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.PagedDialog.js"></script>
+               <script 
src="../../modules/ve/ui/dialogs/ve.ui.MediaInsertDialog.js"></script>
                <script 
src="../../modules/ve/ui/tools/ve.ui.ButtonTool.js"></script>
                <script 
src="../../modules/ve/ui/tools/ve.ui.AnnotationButtonTool.js"></script>
                <script 
src="../../modules/ve/ui/tools/ve.ui.DialogButtonTool.js"></script>
@@ -275,6 +277,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/init/mw/targets/ve.init.mw.ViewPageTarget.js 
b/modules/ve/init/mw/targets/ve.init.mw.ViewPageTarget.js
index f3e2f7a..70694e1 100644
--- a/modules/ve/init/mw/targets/ve.init.mw.ViewPageTarget.js
+++ b/modules/ve/init/mw/targets/ve.init.mw.ViewPageTarget.js
@@ -62,6 +62,7 @@
                                        { 'name': 'textStyle', 'items' : 
['mwFormat'] },
                                        { 'name': 'textStyle', 'items' : 
['bold', 'italic', 'mwLink', 'clear'] },
                                        { 'name': 'list', 'items' : ['number', 
'bullet', 'outdent', 'indent'] }
+
                                ]
                        }
                },
diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php
index 6b47593..f3e7f4e 100644
--- a/modules/ve/test/index.php
+++ b/modules/ve/test/index.php
@@ -230,6 +230,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..706453a
--- /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.addListenerMethods( 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..95f1770
--- /dev/null
+++ b/modules/ve/ui/dialogs/ve.ui.MediaInsertDialog.js
@@ -0,0 +1,82 @@
+/*!
+ * 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 7eeef79..51e19ef 100644
--- a/modules/ve/ui/styles/ve.ui.Widget.css
+++ b/modules/ve/ui/styles/ve.ui.Widget.css
@@ -491,4 +491,66 @@
 /* @noflip */
 .ve-ui-ltr {
        direction: ltr;
-}
\ No newline at end of file
+}
+
+/* MediaInsert Widget */
+
+.ve-ui-mediaInsertWidget .ve-ui-textInputWidget {
+       margin: 1em 0 .75em 0;
+}
+
+.ve-ui-mediaInsertWidget .ve-ui-textInputWidget input {
+       width: 100%;
+       font-size: 1.2em;
+       -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+}
+
+.ve-ui-mediaThumb {
+       /*float: left;*/
+       display: inline-block;
+       vertical-align: top;
+       overflow: hidden;
+       line-height: 1em;
+       padding: 0;
+       margin: 0;
+}
+.ve-ui-mediaThumb img {
+       margin: 0;
+       padding: 0;
+}
+.ve-ui-loader {
+       position: fixed;
+       /*display: none;*/
+       right: 0;
+       bottom: 0;
+       left: 0;
+       padding: 1em;
+       line-height: 1em;
+       opacity: .8;
+       z-index: 200;
+}
+.ve-ui-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-media-scroller {
+       overflow-y: scroll;
+       overflow-x: none;
+       height: 300px; /* Needs to be generated by the widget */
+       width: 100%;
+       box-sizing: border-box;
+}
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 35aabf2..a19a604 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 );
@@ -37,8 +37,10 @@
        this.$input.attr( {
                'type': this.constructor.static.inputType,
                'name': config.name,
-               'value': config.value
+               'value': config.value,
+               'placeholder': config.placeholder
        } );
+
        this.setReadOnly( config.readOnly );
        this.$.addClass( 've-ui-inputWidget' ).append( this.$input );
 };
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..7046e73
--- /dev/null
+++ b/modules/ve/ui/widgets/ve.ui.MediaWidget.js
@@ -0,0 +1,349 @@
+/*!
+ * 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 ) {
+
+       // Parent constructor
+       ve.ui.Widget.call( this, config );
+
+       // Options
+       var defaultOptions = {
+                       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
+       };
+
+       // Options intialization
+       this.options = ve.extendObject( defaultOptions, config );
+
+       // 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.options.initialSearch
+       } );
+
+       this.$suggestions = this.$$( '<div 
class="ve-ui-media-suggestions"></div>' );
+       this.$scroller = this.$$( '<div class="ve-ui-media-scroller"></div>' )
+               .append( this.$suggestions );
+
+       this.$loader = this.$$( '<div class="ve-ui-loader"><div 
class="ve-ui-spinner"></div></div>' );
+
+       this.$.append(
+               this.mediaInput.$.addClass( 've-ui-mediaInsertInputWidget' ),
+               this.$loader,
+               this.$scroller
+       ).addClass( 've-ui-mediaInsertWidget' );
+       this.mediaInput.$input.on( 'keyup', ve.bind( this.onInputKeyup, this ) 
);
+
+       // Events
+
+       this.options.$window.on( 'resize', ve.bind( function () {
+               clearTimeout( this.resizeTimeout );
+               this.resizeTimeout = setTimeout( ve.bind( this.onResize, this 
), this.options.timeout );
+       }, this ) );
+
+       this.$scroller.on( 'scroll', ve.bind( function () {
+               clearTimeout( this.scrollTimeout );
+               this.scrollTimeout = setTimeout( ve.bind( this.onScroll, this 
), this.options.timeout );
+       }, this ) );
+
+       // Init
+       this.fetchMedia();
+
+};
+
+/* Inheritance */
+
+ve.inheritClass( ve.ui.MediaWidget, ve.ui.Widget );
+
+/* Methods */
+
+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.options.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.options.imageHeight;
+       // Ease in new content by one row
+       if ( this.options.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.options.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.options.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.options.sources ) {
+               this.options.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.options.sources ) {
+               if ( this.options.sources[source].request ) {
+                       this.options.sources[source].request.abort();
+                       this.options.sources[source].request = null;
+               }
+
+               this.options.sources[source].request = $.getJSON(
+                       this.options.sources[source].url + '?callback=?', {
+                               'action': 'query',
+                               'generator': 'search',
+                               'gsrsearch': searchString,
+                               'gsrnamespace': 6,
+                               'gsroffset': 
this.options.sources[source].gsroffset,
+                               'prop': 'imageinfo',
+                               'iiprop': 'size|url',
+                               'iiurlwidth': 300,
+                               'iiurlheight': 400,
+                               //'iiurlheight': this.options.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.options.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.options.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-mediaThumb' ).css(
+                       { 'height': height, 'margin': this.options.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/62730
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Id49b9effb0dce833c2e8312f134a9f62e7df1dc0
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Robmoen <rm...@wikimedia.org>
Gerrit-Reviewer: Inez <i...@wikia-inc.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to