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