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 <[email protected]>
Gerrit-Reviewer: Inez <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits