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