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