jenkins-bot has submitted this change and it was merged.

Change subject: Refactor Citoid extension as an inspector
......................................................................


Refactor Citoid extension as an inspector

Transform the dialog into an inspector and refactor it to work with
promises, as well as stage an initial empty reference and apply or
erase it when the user chooses what to do.

Depends on ooui fix I9d0c6c12c19043

* Make the Citoid extension an inspector.
* Change the workflow to depend on promises. Abort all promises
  when the user clicks away.
* Create a 'lookup' button that sends the API request and creates
  a list of rendered citation previews for the user to choose from.
* Display a preview of the citation the way it will appear in the
  reference list, including its type and a matching icon.
* The citation is added only if the user actively picks the
  preview option.

Bug: T88152
Change-Id: Ib2e06015529c239c972093f8d285d9f814c16961
---
M Citoid.php
M i18n/en.json
M i18n/qqq.json
D modules/ve.ui.CiteFromIDDialog.js
D modules/ve.ui.CiteFromIDDialogTool.js
A modules/ve.ui.CiteFromIdInspector.css
A modules/ve.ui.CiteFromIdInspector.js
A modules/ve.ui.CiteFromIdInspectorTool.js
A modules/ve.ui.CiteFromIdOptionWidget.js
9 files changed, 588 insertions(+), 335 deletions(-)

Approvals:
  Trevor Parscal: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/Citoid.php b/Citoid.php
index 0fa6ed8..07fccb8 100644
--- a/Citoid.php
+++ b/Citoid.php
@@ -35,10 +35,12 @@
 // Register modules
 $wgResourceModules['ext.citoid.visualEditor'] = array(
        'scripts' => array(
-               'modules/ve.ui.CiteFromIDDialogTool.js',
-               'modules/ve.ui.CiteFromIDDialog.js'
+               'modules/ve.ui.CiteFromIdInspectorTool.js',
+               'modules/ve.ui.CiteFromIdOptionWidget.js',
+               'modules/ve.ui.CiteFromIdInspector.js'
        ),
        'styles' => array(
+               'modules/ve.ui.CiteFromIdInspector.css'
        ),
        'dependencies ' => array(
                'ext.visualEditor.mwreference',
@@ -46,14 +48,16 @@
        ),
        'messages' => array(
                'citoid-520-error',
+               'citoid-citeFromIDDialog-lookup-button',
                'citoid-citeFromIDDialog-search',
                'citoid-citeFromIDDialog-search-label',
                'citoid-citeFromIDDialog-search-placeholder',
                'citoid-citeFromIDDialog-search-progress',
                'citoid-citeFromIDDialog-title',
                'citoid-citeFromIDTool-title',
+               'citoid-template-type-map.json',
                'citoid-typeMap-config-error',
-               'citoid-template-type-map.json'
+               'citoid-unknown-error'
        ),
        'targets' => array( 'desktop', 'mobile' ),
        'localBasePath' => __DIR__,
diff --git a/i18n/en.json b/i18n/en.json
index b2ab928..0bddd6b 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -3,12 +3,14 @@
                "authors": []
        },
        "citoid-520-error": "Unable to retrieve information from the provided 
identifier.",
-       "citoid-desc": "Provides access points between the citoid service and 
MediaWiki",
+       "citoid-citeFromIDDialog-lookup-button": "Lookup",
        "citoid-citeFromIDDialog-search": "Search",
        "citoid-citeFromIDDialog-search-label": "URL or DOI",
        "citoid-citeFromIDDialog-search-placeholder": "e.g. 
http://www.example.com";,
        "citoid-citeFromIDDialog-search-progress": "Searching, please wait...",
        "citoid-citeFromIDDialog-title": "Autofill citations by URL or DOI",
        "citoid-citeFromIDTool-title": "Autofill from URL",
-       "citoid-typeMap-config-error": "Mediawiki:citoid-template-type-map.json 
is improperly configured."
+       "citoid-desc": "Provides access points between the citoid service and 
MediaWiki",
+       "citoid-typeMap-config-error": "Mediawiki:citoid-template-type-map.json 
is improperly configured.",
+       "citoid-unknown-error": "An unknown error has occured that prevented us 
from creating a citation. Please try again later."
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 61c7ab4..c575718 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -5,12 +5,14 @@
                ]
        },
        "citoid-520-error": "Error message for when citoid service returns a 
520 error",
-       "citoid-desc": 
"{{desc|name=Citoid|url=http://www.mediawiki.org/wiki/Citoid}}";,
+       "citoid-citeFromIDDialog-lookup-button": "Label for the lookup button 
for citation inputs.",
        "citoid-citeFromIDDialog-search": "Text for the search 
button.\n{{Identical|Search}}",
        "citoid-citeFromIDDialog-search-label": "Label for the URL/DOI search 
field.",
        "citoid-citeFromIDDialog-search-placeholder": "Placeholder for the 
URL/DOI search field.",
        "citoid-citeFromIDDialog-search-progress": "Message for when the search 
is in progress",
        "citoid-citeFromIDDialog-title": "The title displayed on the dialog",
        "citoid-citeFromIDTool-title": "What the dialog is called in the menu",
-       "citoid-typeMap-config-error": "Error message indicating 
Mediawiki:citoid-template-type-map.json either doesn't exist or contains 
errors."
+       "citoid-desc": 
"{{desc|name=Citoid|url=http://www.mediawiki.org/wiki/Citoid}}";,
+       "citoid-typeMap-config-error": "Error message indicating 
Mediawiki:citoid-template-type-map.json either doesn't exist or contains 
errors.",
+       "citoid-unknown-error": "Error message indicating that the service to 
create citations has returned an error or is temporarily malfunctioning, asking 
the user to try again later."
 }
diff --git a/modules/ve.ui.CiteFromIDDialog.js 
b/modules/ve.ui.CiteFromIDDialog.js
deleted file mode 100644
index 92d5f7c..0000000
--- a/modules/ve.ui.CiteFromIDDialog.js
+++ /dev/null
@@ -1,285 +0,0 @@
-mw.loader.using( 'ext.visualEditor.mwreference' ).done( function () {
-
-       /**
-        * Dialog to insert filled references using citoid service
-        *
-        * @class
-        * @extends ve.ui.MWCitationDialog
-        * @constructor
-        * @param {Object} [config] Configuration options
-        */
-
-       ve.ui.CiteFromIDDialog = function VeUiCiteFromIDDialog( config ) {
-               // Parent constructor
-               ve.ui.CiteFromIDDialog.super.call( this, config );
-       };
-
-       /* Inheritance */
-       OO.inheritClass( ve.ui.CiteFromIDDialog, ve.ui.MWCitationDialog );
-
-       /* Static Properties */
-       ve.ui.CiteFromIDDialog.static.name = 'citefromid';
-       ve.ui.CiteFromIDDialog.static.title = mw.msg( 
'citoid-citeFromIDDialog-title' );
-       // The string used in TemplateData to identify the correct Map object
-       ve.ui.CiteFromIDDialog.static.templateDataName = 
'extension/Citoid/ve.ui.CiteFromIDDialog';
-       // The requested format from the citoid client, passed as a GET 
parameter
-       ve.ui.CiteFromIDDialog.static.citoidFormat = 'mediawiki';
-
-       /**
-        * @inheritdoc
-        */
-       ve.ui.CiteFromIDDialog.prototype.initialize = function () {
-
-               // Skip ve.ui.MWCitationDialog and ve.ui.MWTemplateDialog 
initialize methods
-               
ve.ui.CiteFromIDDialog.super.super.super.prototype.initialize.call( this );
-
-               // Booklet layout required due to inheriting from 
MWTemplateDialog
-               this.bookletLayout = new OO.ui.BookletLayout(
-                       ve.extendObject(
-                               this.constructor.static.bookletLayoutConfig
-                       )
-               );
-
-               this.searchInput = new OO.ui.TextInputWidget( {
-                       multiline: false,
-                       placeholder: mw.msg( 
'citoid-citeFromIDDialog-search-placeholder' )
-               } );
-
-               var panel = new OO.ui.PanelLayout( {
-                               padded: true,
-                               scrollable: true,
-                               expanded: false
-                       } ),
-                       inputsFieldset = new OO.ui.FieldsetLayout(),
-                       searchField = new OO.ui.FieldLayout( this.searchInput, {
-                               align: 'top',
-                               label: mw.msg( 
'citoid-citeFromIDDialog-search-label' )
-                       } );
-
-               inputsFieldset.$element.append(
-                       searchField.$element
-               );
-
-               panel.$element.append( inputsFieldset.$element );
-               this.$body.append( panel.$element );
-
-               this.modules = [ 'ext.visualEditor.data' ];
-
-               // Events
-               this.searchInput.connect( this, { enter: 'onSearchInputEnter' } 
);
-       };
-
-       /**
-        * Respond to the user hitting enter in the link/doi input
-        */
-       ve.ui.CiteFromIDDialog.prototype.onSearchInputEnter = function () {
-               this.executeAction( 'insert' );
-       };
-
-       /**
-        * Insert filled template based on search results from citoid service
-        * @param {Object[]} searchResults Array of citation objects from 
citoid service
-        */
-       ve.ui.CiteFromIDDialog.prototype.insertTemplate = function ( 
searchResults ) {
-
-               var transclusion, template, templateName, templateTypeMap, 
partPromise, item, fragment,
-                       citation = searchResults[ 0 ], // Uses the first 
citation result for the time being
-                       dialog = this,
-                       surfaceModel = this.getFragment().getSurface(),
-                       doc = surfaceModel.getDocument(),
-                       internalList = doc.getInternalList();
-
-               // Try to parse Mediawiki namespace templateTypeMap definition
-               try {
-                       templateTypeMap = JSON.parse( mw.message( 
'citoid-template-type-map.json' ).plain() );
-               } catch ( e ) {
-                       mw.notify( mw.msg( 'citoid-typeMap-config-error' ) );
-                       return;
-               }
-
-               // Set up blank referenceModel
-               if ( !this.referenceModel ) {
-                       // Collapse returns a new fragment, so update 
this.fragment
-                       this.fragment = this.getFragment().collapseToEnd();
-                       this.referenceModel = new ve.dm.MWReferenceModel();
-                       this.referenceModel.insertInternalItem( surfaceModel );
-                       this.referenceModel.insertReferenceNode( 
this.getFragment() );
-               }
-
-               // Gets back contents of <ref> tag
-               item = this.referenceModel.findInternalItem( surfaceModel );
-               if ( item ) {
-                       fragment = this.getFragment().clone(
-                               new ve.dm.LinearSelection( doc, 
item.getChildren()[0].getRange() )
-                       );
-
-                       transclusion = new ve.dm.MWTransclusionModel();
-                       templateName = templateTypeMap[citation.itemType];
-
-                       // if TemplateName is undefined, this means that items 
of this citoid
-                       // type does not have a Template defined within the 
message.
-                       if ( !templateName ) {
-                               mw.notify( mw.msg( 
'citoid-typeMap-config-error' ) );
-                               return;
-                       }
-
-                       template = ve.dm.MWTemplateModel.newFromName( 
transclusion, templateName );
-                       this.template = template;
-
-                       // Promise for template being added to the transclusion
-                       partPromise = transclusion.addPart( template );
-
-                       partPromise.done( function () {
-                               dialog.fillTemplate( citation );
-
-                               transclusion.insertTransclusionNode( fragment );
-                               // HACK: Scorch the earth - this is only needed 
because without it, the reference list won't
-                               // re-render properly, and can be removed once 
someone fixes that
-                               dialog.referenceModel.setDocument(
-                                       doc.cloneFromRange(
-                                               internalList.getItemNode( 
dialog.referenceModel.getListIndex() ).getRange()
-                                       )
-                               );
-
-                               dialog.referenceModel.updateInternalItem( 
surfaceModel );
-                               dialog.close();
-                       } );
-               }
-       };
-
-       /**
-        * Fills template object with parameters from with values in citation 
object
-        * @param {Object} citation Contains values to insert into template
-        */
-       ve.ui.CiteFromIDDialog.prototype.fillTemplate = function ( citation ) {
-               var citoidField, templateField, i, j,
-                       template = this.template,
-                       spec = template.getSpec(),
-                       maps = spec.getMaps(),
-                       map = 
maps[ve.ui.CiteFromIDDialog.static.templateDataName];
-
-               for ( citoidField in map ) {
-                       templateField = map[citoidField];
-                       // Construct parameters
-                       if ( typeof templateField === 'string' && 
citation[citoidField] !== undefined ) {
-                               // Case: Citoid parameter directly equivalent 
to TemplateData parameter
-                               template.addParameter( new 
ve.dm.MWParameterModel( template, templateField, citation[citoidField ] ) );
-                       } else if ( Array.isArray( citation[citoidField] ) ) {
-                               // Case: Citoid parameter equivalent to 1 or 2D 
Array of TD parameters
-                               for ( i = 0; i < citation[ citoidField 
].length; i++ ) {
-                                       // Iterate through first dimension of 
array
-                                       if ( typeof citation[ citoidField ][ i 
] === 'string' && templateField[i] !== undefined ) {
-                                               // Case: Citoid parameter 
equivalent to 1D Array of TD parameters
-                                               template.addParameter( new 
ve.dm.MWParameterModel( template, templateField[i], citation[ citoidField ][ i 
] ) );
-                                       } else if ( Array.isArray( citation[ 
citoidField ][ i ] ) ) {
-                                               // Case: Citoid parameter 
equivalent to 2D Array of TD parameters
-                                               for ( j = 0; j < citation[ 
citoidField ][ i ].length; j++ ) {
-                                                       // Iterate through 2nd 
dimension of Array
-                                                       if ( typeof citation[ 
citoidField ][ i ][ j ] === 'string' && templateField[ i ][ j ] !== undefined ) 
{
-                                                               
template.addParameter( new ve.dm.MWParameterModel( template, templateField[ i 
][ j ], citation[ citoidField ][ i ][ j ] ) );
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-       };
-
-       /**
-        * @inheritdoc
-        */
-       ve.ui.CiteFromIDDialog.prototype.getActionProcess = function ( action ) 
{
-               if ( action === 'apply' || action === 'insert' ) {
-                       return new OO.ui.Process( function () {
-                               var dialog = this;
-
-                               dialog.pushPending();
-
-                               $.ajax( {
-                                       beforeSend: function ( request ) {
-                                               request.setRequestHeader( 
'Content-Type', 'application/json' );
-                                       },
-                                       url: mw.config.get( 'wgCitoidConfig' 
).citoidServiceUrl,
-                                       type: 'GET',
-                                       data: {
-                                               search: encodeURI( 
dialog.searchInput.getValue() ),
-                                               format: 
ve.ui.CiteFromIDDialog.static.citoidFormat
-                                       },
-                                       dataType: 'json'
-                               } )
-                                       .done( function ( result ) {
-                                               dialog.insertTemplate( result );
-                                       } )
-                                       .fail( function ( response, textStatus, 
errorThrown ) {
-                                               // 520 status from citoid means 
there was no response at the
-                                               // URL provided, but it returns 
a citation regardless. We're
-                                               // choosing to insert that 
citation here but to notify the user.
-                                               if ( response.status === 520 ) {
-                                                       dialog.insertTemplate( 
response.responseJSON );
-                                                       mw.notify( mw.message( 
'citoid-520-error' ) );
-                                               } else {
-                                                       mw.notify( 'Status: '  
+ textStatus +  ' Error: ' + errorThrown );
-                                               }
-                                       } )
-                                       .always( function () {
-                                               dialog.popPending();
-                                       } );
-                       }, this );
-               }
-
-               // Parent method
-               return 
ve.ui.CiteFromIDDialog.super.prototype.getActionProcess.call( this, action );
-       };
-
-       /**
-        * @inheritdoc
-        */
-       ve.ui.CiteFromIDDialog.prototype.getReadyProcess = function ( data ) {
-               return 
ve.ui.CiteFromIDDialog.super.prototype.getReadyProcess.call( this, data )
-                       .next( function () {
-                               this.searchInput.focus();
-                       }, this );
-       };
-
-       /**
-        * Handle the transclusion being ready to use.
-        * Enables apply/insert buttons
-        */
-       ve.ui.CiteFromIDDialog.prototype.onTransclusionReady = function () {
-               // Parent method
-               
ve.ui.CiteFromIDDialog.super.prototype.onTransclusionReady.call( this );
-               // TODO- disable when no input
-               this.actions.setAbilities( { apply: true, insert: true } );
-       };
-
-       /**
-        * Overrides Template Dialog method which expects this.template to
-        * exist on initialization and sets the template as the dialog title.
-        *
-        * @return {string} Title of dialog
-        */
-       ve.ui.CiteFromIDDialog.prototype.getTemplatePartLabel = function () {
-               return ve.msg( 'citoid-citeFromIDDialog-title' );
-       };
-
-       /**
-        * Overrides Template Dialog method which has a fixed height
-        * @inheritdoc
-        */
-       ve.ui.CiteFromIDDialog.prototype.getBodyHeight =
-               
ve.ui.CiteFromIDDialog.super.super.super.prototype.getBodyHeight;
-
-       /**
-        * @inheritdoc
-        */
-       ve.ui.CiteFromIDDialog.prototype.getTeardownProcess = function ( data ) 
{
-               return 
ve.ui.CiteFromIDDialog.super.prototype.getTeardownProcess.call( this, data )
-                       .first( function () {
-                               // Clear search input box
-                               this.searchInput.setValue( '' );
-                       }, this );
-       };
-
-       ve.ui.windowFactory.register( ve.ui.CiteFromIDDialog );
-
-} );
diff --git a/modules/ve.ui.CiteFromIDDialogTool.js 
b/modules/ve.ui.CiteFromIDDialogTool.js
deleted file mode 100644
index 0186129..0000000
--- a/modules/ve.ui.CiteFromIDDialogTool.js
+++ /dev/null
@@ -1,43 +0,0 @@
-mw.loader.using( 'ext.visualEditor.mwreference' ).done( function () {
-
-       /**
-        * MediaWiki UserInterface cite from ID dialog tool.
-        *
-        * @class
-        * @abstract
-        * @extends ve.ui.Tool
-        * @constructor
-        * @param {OO.ui.Toolbar} toolbar
-        * @param {Object} [config] Configuration options
-        */
-
-       // Don't create tool unless the configuration message is present
-       try {
-               JSON.parse( mw.message( 'citoid-template-type-map.json' 
).plain() );
-       } catch ( e ) {
-               return;
-       }
-
-       ve.ui.CiteFromIDDialogTool = function VeUiCiteFromIDDialogTool( 
toolGroup, config ) {
-               OO.ui.Tool.call( this, toolGroup, config );
-       };
-
-       OO.inheritClass( ve.ui.CiteFromIDDialogTool, ve.ui.Tool );
-
-       ve.ui.CiteFromIDDialogTool.static.name = 'citefromid';
-       ve.ui.CiteFromIDDialogTool.static.icon = 'ref-cite-web';
-       ve.ui.CiteFromIDDialogTool.static.title = mw.msg( 
'citoid-citeFromIDTool-title' );
-       ve.ui.CiteFromIDDialogTool.static.group = 'cite';
-       ve.ui.CiteFromIDDialogTool.static.commandName = 'citefromid';
-       ve.ui.CiteFromIDDialogTool.static.autoAddToCatchall = false;
-
-       ve.ui.commandRegistry.register(
-               new ve.ui.Command(
-                       'citefromid', 'window', 'open',
-                       { args: [ 'citefromid' ], supportedSelections: [ 
'linear' ] }
-               )
-       );
-
-       ve.ui.toolFactory.register( ve.ui.CiteFromIDDialogTool );
-
-} );
diff --git a/modules/ve.ui.CiteFromIdInspector.css 
b/modules/ve.ui.CiteFromIdInspector.css
new file mode 100644
index 0000000..971a5c7
--- /dev/null
+++ b/modules/ve.ui.CiteFromIdInspector.css
@@ -0,0 +1,28 @@
+.ve-ui-citeFromIdInspector-preview {
+       max-height: 15em;
+       overflow-y: auto;
+}
+
+.ve-ui-citeFromIdOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
+       white-space: normal;
+}
+
+.ve-ui-citeFromIdOptionWidget-wrapper {
+       margin-left: -2em;
+       margin-top: 0.6em;
+       line-height: 1.3em;
+}
+
+.ve-ui-citeFromIdOptionWidget-wrapper a:hover {
+       text-decoration: none;
+}
+
+.ve-ui-citeFromIdOptionWidget .oo-ui-labelElement-label {
+       font-size: 1em;
+       font-weight: bold;
+}
+
+.ve-ui-citeFromIdOptionWidget .oo-ui-iconElement-icon {
+       background-size: 24px 24px;
+       height: 2.5em;
+}
diff --git a/modules/ve.ui.CiteFromIdInspector.js 
b/modules/ve.ui.CiteFromIdInspector.js
new file mode 100644
index 0000000..23d7d58
--- /dev/null
+++ b/modules/ve.ui.CiteFromIdInspector.js
@@ -0,0 +1,431 @@
+/**
+ * Inspector to insert filled references using citoid service
+ *
+ * @class
+ * @extends ve.ui.FragmentInspector
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+
+ve.ui.CiteFromIdInspector = function VeUiCiteFromIdInspector( config ) {
+       // Parent constructor
+       ve.ui.CiteFromIdInspector.super.call( this, config );
+
+       this.referenceModel = null;
+       this.transclusionModel = null;
+       this.doneStaging = false;
+       this.results = [];
+       this.citeTools = [];
+       this.templateTypeMap = null;
+       this.lookupPromise = null;
+
+       this.$element.addClass( 've-ui-citeFromIdInspector' );
+};
+
+/* Inheritance */
+OO.inheritClass( ve.ui.CiteFromIdInspector, ve.ui.FragmentInspector );
+
+/* Static properties */
+
+ve.ui.CiteFromIdInspector.static.name = 'citefromid';
+
+ve.ui.CiteFromIdInspector.static.title = OO.ui.deferMsg( 
'citoid-citeFromIDDialog-title' );
+
+ve.ui.CiteFromIdInspector.static.size = 'large';
+
+// The string used in TemplateData to identify the correct Map object
+
+// TODO: Replace this to a more general string "Citoid"
+ve.ui.CiteFromIdInspector.static.templateDataName = 
'extension/Citoid/ve.ui.CiteFromIDDialog';
+
+// The requested format from the citoid client, passed as a GET parameter
+ve.ui.CiteFromIdInspector.static.citoidFormat = 'mediawiki';
+
+ve.ui.CiteFromIdInspector.static.actions = [];
+
+/* Methods */
+
+/**
+ * @inheritDoc
+ */
+ve.ui.CiteFromIdInspector.prototype.initialize = function () {
+       var lookupActionFieldLayout,
+               lookupFieldset = new OO.ui.FieldsetLayout(),
+               limit = ve.init.target.constructor.static.citationToolsLimit;
+
+       // Parent method
+       ve.ui.CiteFromIdInspector.super.prototype.initialize.call( this );
+
+       this.templateTypeMap = JSON.parse( mw.message( 
'citoid-template-type-map.json' ).plain() );
+       // Get the available tools for their titles and icons
+       try {
+               // Must use mw.message to avoid JSON being parsed as Wikitext
+               this.citeTools = JSON.parse( mw.message( 
'visualeditor-cite-tool-definition.json' ).plain() );
+               // Limit the number of tools
+               this.citeTools.splice( limit );
+       } catch ( e ) { }
+
+       // Lookup fieldset
+       this.lookupInput = new OO.ui.TextInputWidget( {
+               multiline: false,
+               placeholder: mw.msg( 
'citoid-citeFromIDDialog-search-placeholder' )
+       } );
+
+       this.lookupButton = new OO.ui.ButtonWidget( {
+               label: mw.msg( 'citoid-citeFromIDDialog-lookup-button' )
+       } );
+       lookupActionFieldLayout = new OO.ui.ActionFieldLayout( 
this.lookupInput, this.lookupButton, {
+               align: 'top',
+               label: mw.msg( 'citoid-citeFromIDDialog-search-label' )
+       } );
+
+       lookupFieldset.$element.append(
+               lookupActionFieldLayout.$element
+       );
+
+       // Preview fieldset
+       this.previewSelectWidget = new OO.ui.SelectWidget( {
+               classes: [ 've-ui-citeFromIdInspector-preview' ]
+       } );
+       this.previewSelectWidget.aggregate( { update: 'itemUpdate' } );
+       this.previewSelectWidget.toggle( false );
+
+       // Events
+       this.lookupInput.connect( this, { change: 'onLookupInputChange' } );
+       this.lookupButton.connect( this, { click: 'onLookupButtonClick' } );
+       this.previewSelectWidget.connect( this, {
+               choose: 'onPreviewSelectWidgetChoose',
+               itemUpdate: 'onPreviewSelectWidgetItemUpdate'
+       } );
+
+       // Attach
+       this.form.$element.append( lookupFieldset.$element, 
this.previewSelectWidget.$element );
+};
+
+/**
+ * Respond to form submit.
+ */
+ve.ui.CiteFromIdInspector.prototype.onFormSubmit = function () {
+       this.executeAction( 'lookup' );
+       return false;
+};
+
+/**
+ * Respond to item update event in the preview select widget
+ */
+ve.ui.CiteFromIdInspector.prototype.onPreviewSelectWidgetItemUpdate = function 
() {
+       this.updateSize();
+};
+
+/**
+ * Respond to preview select widget choose event
+ */
+ve.ui.CiteFromIdInspector.prototype.onPreviewSelectWidgetChoose = function ( 
item ) {
+       var fragment,
+               surfaceModel = this.getFragment().getSurface(),
+               doc = surfaceModel.getDocument(),
+               internalList = doc.getInternalList(),
+               index = item.getData();
+
+       if ( this.results[ index ] ) {
+               // Apply staging
+               this.getFragment().getSurface().applyStaging();
+
+               // Gets back contents of <ref> tag
+               item = this.referenceModel.findInternalItem( surfaceModel );
+               fragment = this.getFragment().clone(
+                       new ve.dm.LinearSelection( doc, item.getChildren()[ 0 
].getRange() )
+               );
+
+               this.results[ index ].transclusionModel.insertTransclusionNode( 
fragment );
+               // HACK: Scorch the earth - this is only needed because without 
it,
+               // the reference list won't re-render properly, and can be 
removed
+               // once someone fixes that
+               this.referenceModel.setDocument(
+                       doc.cloneFromRange(
+                               internalList.getItemNode( 
this.referenceModel.getListIndex() ).getRange()
+                       )
+               );
+               this.referenceModel.updateInternalItem( surfaceModel );
+               this.doneStaging = true;
+
+               // Close the inspector
+               this.close();
+       }
+};
+
+/**
+ * Respond to change value of the search input.
+ * @param {string} value Current value
+ */
+ve.ui.CiteFromIdInspector.prototype.onLookupInputChange = function ( value ) {
+       if ( this.lookupPromise ) {
+               // Abort existing promises
+               this.lookupPromise.abort();
+               this.lookupInput.popPending();
+               this.lookupPromise = null;
+       }
+       this.lookupButton.setDisabled( value === '' );
+};
+
+/**
+ * Respond to lookup button click, perform lookup
+ */
+ve.ui.CiteFromIdInspector.prototype.onLookupButtonClick = function () {
+       this.executeAction( 'lookup' );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.CiteFromIdInspector.prototype.getSetupProcess = function ( data ) {
+       return ve.ui.CiteFromIdInspector.super.prototype.getSetupProcess.call( 
this, data )
+               .next( function () {
+                       // Reset
+                       this.lookupPromise = null;
+                       this.doneStaging = false;
+                       this.results = [];
+                       this.lookupButton.setDisabled( true );
+                       this.previewSelectWidget.toggle( false );
+                       // Stage an empty reference
+                       this.getFragment().getSurface().pushStaging();
+
+                       // Collapse returns a new fragment, so update 
this.fragment
+                       this.fragment = this.getFragment().collapseToEnd();
+
+                       // Create model
+                       this.referenceModel = new ve.dm.MWReferenceModel();
+
+                       // Insert an empty reference
+                       this.referenceModel.insertInternalItem( 
this.getFragment().getSurface() );
+                       this.referenceModel.insertReferenceNode( 
this.getFragment() );
+               }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.CiteFromIdInspector.prototype.getReadyProcess = function ( data ) {
+       return ve.ui.LinkInspector.super.prototype.getReadyProcess.call( this, 
data )
+               .next( function () {
+                       // Focus on the input
+                       this.lookupInput.setDisabled( false ).focus().select();
+               }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+ve.ui.CiteFromIdInspector.prototype.getTeardownProcess = function ( data ) {
+       return 
ve.ui.CiteFromIdInspector.super.prototype.getTeardownProcess.call( this, data )
+               .first( function () {
+                       if ( !this.doneStaging ) {
+                               this.fragment.getSurface().popStaging();
+                       }
+
+                       // Empty the input
+                       this.lookupInput.setValue( null );
+
+                       // Reset
+                       if ( this.lookupPromise ) {
+                               this.lookupPromise.abort();
+                       }
+                       this.lookupPromise = null;
+                       this.clearResults();
+                       this.referenceModel = null;
+               }, this );
+};
+
+/**
+ * Clear the search results
+ */
+ve.ui.CiteFromIdInspector.prototype.clearResults = function () {
+       this.results = [];
+       this.previewSelectWidget.clearItems();
+};
+
+/**
+ * @inheritDoc
+ */
+ve.ui.CiteFromIdInspector.prototype.getActionProcess = function ( action ) {
+       if ( action === 'lookup' ) {
+               return new OO.ui.Process( function () {
+                       // Clear the results
+                       this.clearResults();
+                       // Look up
+                       return this.performLookup();
+               }, this );
+       }
+       // Fallback to parent handler
+       return ve.ui.CiteFromIdInspector.super.prototype.getActionProcess.call( 
this, action );
+};
+
+/**
+ * Send a request to the citoid service
+ * @return {[type]} [description]
+ */
+ve.ui.CiteFromIdInspector.prototype.performLookup = function () {
+       var xhr,
+               inspector = this;
+
+       // TODO: Add caching for requested urls
+       if ( this.lookupPromise ) {
+               // Abort existing lookup
+               this.lookupPromise.abort();
+               this.lookupInput.popPending();
+       }
+       // Set as pending
+       this.lookupButton.setDisabled( true );
+       this.lookupInput.pushPending();
+       xhr = new mw.Api().get(
+                               // Data
+                               {
+                                       search: encodeURI( 
inspector.lookupInput.getValue() ),
+                                       format: 
ve.ui.CiteFromIdInspector.static.citoidFormat
+                               },
+                               // Settings
+                               {
+                                       url: mw.config.get( 'wgCitoidConfig' 
).citoidServiceUrl,
+                                       dataType: 'json',
+                                       type: 'GET'
+                               }
+                       );
+       this.lookupPromise = xhr
+               .then(
+                       // Success
+                       function ( searchResults ) {
+                               // Apply staging
+                               inspector.lookupInput.popPending();
+                               inspector.lookupButton.setDisabled( false );
+                               return inspector.buildTemplateResults( 
searchResults );
+                       },
+                       // Fail
+                       function ( type, response ) {
+                               var textStatus = response.textStatus;
+                               // 520 status from citoid means there was no 
response at the
+                               // URL provided, but it returns a citation 
regardless. We're
+                               // choosing to insert that citation here but to 
notify the user.
+                               if ( response.xhr.status === 520 ) {
+                                       // Enable the input and lookup button
+                                       inspector.lookupInput.popPending();
+                                       inspector.lookupButton.setDisabled( 
false );
+
+                                       // Notify the user that something went 
wrong
+                                       mw.notify( mw.message( 
'citoid-520-error' ) );
+
+                                       // Add as a regular web citation
+                                       return inspector.buildTemplateResults( 
response.xhr.responseJSON );
+                               } else {
+                                       inspector.lookupInput.popPending();
+                                       inspector.lookupButton.setDisabled( 
false );
+                                       if ( textStatus !== 'abort' ) {
+                                               mw.notify( mw.msg( 
'citoid-unknown-error' ) );
+                                       }
+                                       return new OO.ui.Error( mw.msg( 
'citoid-unknown-error' ) );
+                               }
+                       } )
+               .promise( { abort: xhr.abort } );
+       return this.lookupPromise;
+};
+
+/**
+ * Insert filled template based on search results from citoid service
+ *
+ * @param {Object[]} searchResults Array of citation objects from citoid 
service
+ * @returns {jQuery.Promise} Promise that is resolved when the template part 
is added
+ *  or is rejected if there are any problems with the template name or the 
internal item.
+ */
+ve.ui.CiteFromIdInspector.prototype.buildTemplateResults = function ( 
searchResults ) {
+       var i, templateName, citation, result,
+               partPromises = [],
+               inspector = this;
+
+       for ( i = 0; i < searchResults.length; i++ ) {
+               citation = searchResults[i];
+               templateName = this.templateTypeMap[ citation.itemType ];
+
+               // if TemplateName is undefined, this means that items of this 
citoid
+               // type does not have a Template defined within the message.
+               if ( !templateName ) {
+                       continue;
+               }
+
+               // Create models for this result
+               this.results.push( {
+                       templateName: templateName,
+                       template: null,
+                       transclusionModel: new ve.dm.MWTransclusionModel()
+               } );
+               result = this.results[ this.results.length - 1 ];
+
+               result.template = ve.dm.MWTemplateModel.newFromName( 
result.transclusionModel, templateName );
+
+               partPromises.push(
+                       result.transclusionModel.addPart( result.template )
+                               // Fill in the details for the individual 
template
+                               .then( this.populateTemplate.bind( this, 
result.template, citation ) )
+               );
+       }
+
+       return $.when.apply( $, partPromises )
+               .then( function () {
+                       var optionWidgets = [];
+                       // Create option widgets
+                       for ( i = 0; i < inspector.results.length; i++ ) {
+                               optionWidgets.push( new 
ve.ui.CiteFromIdOptionWidget(
+                                       
inspector.getFragment().getSurface().getDocument(),
+                                       {
+                                               data: i,
+                                               transclusionModel: 
inspector.results[i].transclusionModel,
+                                               templateName: 
inspector.results[i].templateName,
+                                               citeTools: inspector.citeTools
+                                       } ) );
+                       }
+                       // Add to the select widget
+                       inspector.previewSelectWidget.addItems( optionWidgets );
+                       inspector.previewSelectWidget.toggle( true );
+               } );
+};
+
+/**
+ * Fills template object parameters with values from the citation object
+ *
+ * @param {ve.dm.MNTemplateModel} template A template model to fill
+ * @param {Object} citation An object that contains values to insert into 
template
+ */
+ve.ui.CiteFromIdInspector.prototype.populateTemplate = function ( template, 
citation ) {
+       var citoidField, templateField, i, j,
+               spec = template.getSpec(),
+               maps = spec.getMaps(),
+               map = maps[ ve.ui.CiteFromIdInspector.static.templateDataName ];
+
+       for ( citoidField in map ) {
+               templateField = map[ citoidField ];
+               // Construct parameters
+               if ( typeof templateField === 'string' && citation[ citoidField 
] !== undefined ) {
+                       // Case: Citoid parameter directly equivalent to 
TemplateData parameter
+                       template.addParameter( new ve.dm.MWParameterModel( 
template, templateField, citation[citoidField ] ) );
+               } else if ( Array.isArray( citation[ citoidField ] ) ) {
+                       // Case: Citoid parameter equivalent to 1 or 2D Array 
of TD parameters
+                       for ( i = 0; i < citation[ citoidField ].length; i++ ) {
+                               // Iterate through first dimension of array
+                               if ( typeof citation[ citoidField ][ i ] === 
'string' && templateField[ i ] !== undefined ) {
+                                       // Case: Citoid parameter equivalent to 
1D Array of TD parameters
+                                       template.addParameter( new 
ve.dm.MWParameterModel( template, templateField[ i ], citation[ citoidField ][ 
i ] ) );
+                               } else if ( Array.isArray( citation[ 
citoidField ][ i ] ) ) {
+                                       // Case: Citoid parameter equivalent to 
2D Array of TD parameters
+                                       for ( j = 0; j < citation[ citoidField 
][ i ].length; j++ ) {
+                                               // Iterate through 2nd 
dimension of Array
+                                               if ( typeof citation[ 
citoidField ][ i ][ j ] === 'string' && templateField[ i ] !== undefined && 
templateField[ i ][ j ] !== undefined ) {
+                                                       template.addParameter( 
new ve.dm.MWParameterModel( template, templateField[ i ][ j ], citation[ 
citoidField ][ i ][ j ] ) );
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+};
+
+/* Registration */
+
+ve.ui.windowFactory.register( ve.ui.CiteFromIdInspector );
diff --git a/modules/ve.ui.CiteFromIdInspectorTool.js 
b/modules/ve.ui.CiteFromIdInspectorTool.js
new file mode 100644
index 0000000..2acb07f
--- /dev/null
+++ b/modules/ve.ui.CiteFromIdInspectorTool.js
@@ -0,0 +1,42 @@
+( function () {
+
+       /**
+        * MediaWiki UserInterface cite from ID inspector tool.
+        *
+        * @class
+        * @abstract
+        * @extends ve.ui.Tool
+        * @constructor
+        * @param {OO.ui.Toolbar} toolbar
+        * @param {Object} [config] Configuration options
+        */
+
+       // Don't create tool unless the configuration message is present
+       try {
+               JSON.parse( mw.message( 'citoid-template-type-map.json' 
).plain() );
+       } catch ( e ) {
+               return;
+       }
+
+       ve.ui.CiteFromIdInspectorTool = function VeUiCiteFromIdInspectorTool( 
toolGroup, config ) {
+               ve.ui.InspectorTool.call( this, toolGroup, config );
+       };
+
+       OO.inheritClass( ve.ui.CiteFromIdInspectorTool, ve.ui.InspectorTool );
+
+       ve.ui.CiteFromIdInspectorTool.static.name = 'citefromid';
+       ve.ui.CiteFromIdInspectorTool.static.icon = 'ref-cite-web';
+       ve.ui.CiteFromIdInspectorTool.static.title = OO.ui.deferMsg( 
'citoid-citeFromIDTool-title' );
+       ve.ui.CiteFromIdInspectorTool.static.group = 'cite';
+       ve.ui.CiteFromIdInspectorTool.static.commandName = 'citefromid';
+
+       ve.ui.commandRegistry.register(
+               new ve.ui.Command(
+                       'citefromid', 'window', 'open',
+                       { args: [ 'citefromid' ], supportedSelections: [ 
'linear' ] }
+               )
+       );
+
+       ve.ui.toolFactory.register( ve.ui.CiteFromIdInspectorTool );
+
+}() );
diff --git a/modules/ve.ui.CiteFromIdOptionWidget.js 
b/modules/ve.ui.CiteFromIdOptionWidget.js
new file mode 100644
index 0000000..0aa8f03
--- /dev/null
+++ b/modules/ve.ui.CiteFromIdOptionWidget.js
@@ -0,0 +1,72 @@
+/**
+ * Citoid extension citation option widget
+ *
+ * @extends {OO.ui.DecoratedOptionWidget}
+ * @param {Object} config Dialog configuration object
+ */
+ve.ui.CiteFromIdOptionWidget = function VeUiCiteFromIdOptionWidget( 
documentModel, config ) {
+       var i, len, icon, uiSurface, item,
+               widget = this;
+
+       config = config || {};
+
+       this.allLinks = {};
+       this.templateName = config.templateName || 'Cite web';
+       this.template = config.template;
+       this.transclusionModel = config.transclusionModel;
+       this.title = this.templateName;
+
+       if ( Array.isArray( config.citeTools ) ) {
+               for ( i = 0, len = config.citeTools.length; i < len; i++ ) {
+                       item = config.citeTools[i];
+                       if ( item.template === this.templateName ) {
+                               this.title = item.title;
+                               icon = item.icon;
+                       }
+               }
+       }
+       // Parent constructor
+       ve.ui.CiteFromIdOptionWidget.super.call( this, $.extend( config, {
+               icon: icon || 'ref-' + this.templateName.toLowerCase().replace( 
' ', '-' )
+       } ) );
+
+       // Create a node
+       uiSurface = new ve.ui.DesktopSurface(
+               new ve.dm.ElementLinearData(
+                       documentModel.getStore(),
+                       [
+                               {
+                                       type: 'mwTransclusionBlock',
+                                       attributes: {
+                                               mw: 
this.transclusionModel.getPlainObject()
+                                       }
+                               },
+                               { type: '/mwTransclusionBlock' },
+                               { type: 'internalList' },
+                               { type: '/internalList' }
+                       ]
+               ) );
+       this.$referenceWrapper = $( '<div>' )
+               .addClass( 've-ui-citeFromIdOptionWidget-wrapper' )
+               // HACK: We want to update the size of the inspector according 
to the
+               // size of this widget, and for that we need to know when the 
generated
+               // node is done generating. This should be fixed at some point 
to not
+               // require listening DOMSubtreeModified event.
+               .on( 'DOMSubtreeModified', ve.debounce( function () {
+                       widget.emit( 'update' );
+               }, 100 ) )
+               .on( 'click mousedown', function ( e ) {
+                       e.preventDefault();
+               } )
+               .append( 
uiSurface.view.documentView.documentNode.children[0].$element );
+
+       // Display the preview
+       this.setLabel( this.title );
+
+       // Initialize
+       this.$element
+               .addClass( 've-ui-citeFromIdOptionWidget' )
+               .append( this.$referenceWrapper );
+};
+
+OO.inheritClass( ve.ui.CiteFromIdOptionWidget, OO.ui.DecoratedOptionWidget );

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ib2e06015529c239c972093f8d285d9f814c16961
Gerrit-PatchSet: 16
Gerrit-Project: mediawiki/extensions/Citoid
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Mooeypoo <[email protected]>
Gerrit-Reviewer: Mvolz <[email protected]>
Gerrit-Reviewer: Trevor Parscal <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to