2013-07-17 19:09:30 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor UserInterface MediaWiki MWReferenceDialog class.
|
|
|
|
*
|
2014-01-05 12:05:05 +00:00
|
|
|
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
|
2013-07-17 19:09:30 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Dialog for editing MediaWiki references.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @extends ve.ui.MWDialog
|
|
|
|
*
|
|
|
|
* @constructor
|
2013-11-05 00:29:50 +00:00
|
|
|
* @param {ve.ui.WindowSet} windowSet Window set this dialog is part of
|
2013-09-25 10:21:09 +00:00
|
|
|
* @param {Object} [config] Configuration options
|
2013-07-17 19:09:30 +00:00
|
|
|
*/
|
2013-10-09 19:59:03 +00:00
|
|
|
ve.ui.MWReferenceDialog = function VeUiMWReferenceDialog( windowSet, config ) {
|
2013-07-17 19:09:30 +00:00
|
|
|
// Parent constructor
|
2013-10-09 19:59:03 +00:00
|
|
|
ve.ui.MWDialog.call( this, windowSet, config );
|
2013-07-17 19:09:30 +00:00
|
|
|
|
|
|
|
// Properties
|
|
|
|
this.ref = null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
2013-10-11 21:44:09 +00:00
|
|
|
OO.inheritClass( ve.ui.MWReferenceDialog, ve.ui.MWDialog );
|
2013-07-17 19:09:30 +00:00
|
|
|
|
|
|
|
/* Static Properties */
|
|
|
|
|
2013-08-27 23:28:29 +00:00
|
|
|
ve.ui.MWReferenceDialog.static.name = 'reference';
|
|
|
|
|
2014-02-12 21:45:37 +00:00
|
|
|
ve.ui.MWReferenceDialog.static.title =
|
|
|
|
OO.ui.deferMsg( 'visualeditor-dialog-reference-title' );
|
2013-07-17 19:09:30 +00:00
|
|
|
|
|
|
|
ve.ui.MWReferenceDialog.static.icon = 'reference';
|
|
|
|
|
2013-08-09 18:29:09 +00:00
|
|
|
ve.ui.MWReferenceDialog.static.toolbarGroups = [
|
2013-12-12 19:04:15 +00:00
|
|
|
// History
|
2013-08-27 23:28:29 +00:00
|
|
|
{ 'include': [ 'undo', 'redo' ] },
|
2013-12-12 19:04:15 +00:00
|
|
|
// No formatting
|
|
|
|
/* {
|
|
|
|
'type': 'menu',
|
2014-01-13 22:46:15 +00:00
|
|
|
'indicator': 'down',
|
2013-12-12 19:04:15 +00:00
|
|
|
'include': [ { 'group': 'format' } ],
|
|
|
|
'promote': [ 'paragraph' ],
|
|
|
|
'demote': [ 'preformatted', 'heading1' ]
|
|
|
|
},*/
|
|
|
|
// Style
|
|
|
|
{
|
|
|
|
'type': 'list',
|
|
|
|
'icon': 'text-style',
|
2014-01-13 22:46:15 +00:00
|
|
|
'indicator': 'down',
|
2013-12-12 19:04:15 +00:00
|
|
|
'include': [ { 'group': 'textStyle' }, 'clear' ],
|
|
|
|
'promote': [ 'bold', 'italic' ],
|
|
|
|
'demote': [ 'strikethrough', 'code', 'underline', 'clear' ]
|
|
|
|
},
|
|
|
|
// Link
|
|
|
|
{ 'include': [ 'link' ] },
|
2014-02-05 19:39:39 +00:00
|
|
|
// No structure
|
|
|
|
/* {
|
2013-12-12 19:04:15 +00:00
|
|
|
'type': 'bar',
|
|
|
|
'include': [ 'number', 'bullet', 'outdent', 'indent' ]
|
2014-02-05 19:39:39 +00:00
|
|
|
},*/
|
2013-12-12 19:04:15 +00:00
|
|
|
// Insert
|
|
|
|
{
|
2014-02-12 23:04:28 +00:00
|
|
|
'label': OO.ui.deferMsg( 'visualeditor-toolbar-insert' ),
|
2014-01-13 22:46:15 +00:00
|
|
|
'indicator': 'down',
|
2013-12-12 19:04:15 +00:00
|
|
|
'include': '*',
|
2014-02-05 19:45:56 +00:00
|
|
|
'exclude': [
|
|
|
|
{ 'group': 'format' }, { 'group': 'structure' },
|
|
|
|
'reference', 'referenceList',
|
|
|
|
'gallery'
|
|
|
|
],
|
2013-12-12 19:04:15 +00:00
|
|
|
'demote': [ 'specialcharacter' ]
|
|
|
|
}
|
2013-07-17 19:09:30 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
ve.ui.MWReferenceDialog.static.surfaceCommands = [
|
2013-12-12 19:04:15 +00:00
|
|
|
'undo',
|
|
|
|
'redo',
|
|
|
|
'bold',
|
|
|
|
'italic',
|
|
|
|
'link',
|
|
|
|
'clear',
|
|
|
|
'underline',
|
|
|
|
'subscript',
|
|
|
|
'superscript',
|
|
|
|
'pasteSpecial'
|
2013-07-17 19:09:30 +00:00
|
|
|
];
|
|
|
|
|
2014-02-03 15:08:52 +00:00
|
|
|
ve.ui.MWReferenceDialog.static.pasteRules = ve.extendObject(
|
2014-02-06 22:30:07 +00:00
|
|
|
ve.copy( ve.init.mw.Target.static.pasteRules ),
|
2014-02-03 15:08:52 +00:00
|
|
|
{
|
|
|
|
'all': {
|
|
|
|
'blacklist': OO.simpleArrayUnion(
|
2014-02-06 22:30:07 +00:00
|
|
|
ve.getProp( ve.init.mw.Target.static.pasteRules, 'all', 'blacklist' ) || [],
|
2014-02-03 15:08:52 +00:00
|
|
|
[
|
|
|
|
// Nested references are impossible
|
|
|
|
'mwReference', 'mwReferenceList',
|
|
|
|
// Lists are tables are actually possible in wikitext with a leading
|
|
|
|
// line break but we prevent creating these with the UI
|
|
|
|
'list', 'listItem', 'definitionList', 'definitionListItem',
|
|
|
|
'table', 'tableCaption', 'tableSection', 'tableRow', 'tableCell'
|
|
|
|
]
|
|
|
|
),
|
|
|
|
// Headings are not possible in wikitext without HTML
|
|
|
|
'conversions': {
|
|
|
|
'mwHeading': 'paragraph'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2013-07-17 19:09:30 +00:00
|
|
|
/* Methods */
|
|
|
|
|
2013-11-05 00:29:50 +00:00
|
|
|
/**
|
|
|
|
* Handle reference surface change events
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceDialog.prototype.onDocumentTransact = function () {
|
|
|
|
var data = this.referenceSurface.getContent(),
|
|
|
|
// TODO: Check for other types of empty, e.g. only whitespace?
|
|
|
|
disabled = data.length <= 4;
|
|
|
|
|
|
|
|
this.insertButton.setDisabled( disabled );
|
|
|
|
this.applyButton.setDisabled( disabled );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle search select events.
|
|
|
|
*
|
|
|
|
* @param {Object|null} item Reference attributes or null if no item is selected
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceDialog.prototype.onSearchSelect = function ( item ) {
|
|
|
|
if ( item ) {
|
|
|
|
this.useReference( item );
|
|
|
|
this.close( { 'action': 'insert' } );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Work on a specific reference.
|
|
|
|
*
|
|
|
|
* @param {Object} [ref] Reference attributes, omit to work on a new reference
|
|
|
|
* @chainable
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceDialog.prototype.useReference = function ( ref ) {
|
|
|
|
var newDoc, refGroup,
|
|
|
|
doc = this.surface.getModel().getDocument();
|
|
|
|
|
|
|
|
if ( ref ) {
|
|
|
|
// Use an existing reference
|
|
|
|
this.ref = {
|
|
|
|
'listKey': ref.listKey,
|
|
|
|
'listGroup': ref.listGroup,
|
|
|
|
'refGroup': ref.refGroup,
|
|
|
|
'listIndex': ref.listIndex
|
|
|
|
};
|
|
|
|
newDoc = doc.cloneFromRange( doc.getInternalList().getItemNode( ref.listIndex ).getRange() );
|
|
|
|
refGroup = ref.refGroup;
|
|
|
|
} else {
|
|
|
|
// Create a new reference
|
|
|
|
this.ref = null;
|
|
|
|
newDoc = new ve.dm.Document( [
|
|
|
|
{ 'type': 'paragraph', 'internal': { 'generated': 'wrapper' } },
|
|
|
|
{ 'type': '/paragraph' },
|
|
|
|
{ 'type': 'internalList' },
|
|
|
|
{ 'type': '/internalList' }
|
|
|
|
] );
|
|
|
|
refGroup = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
if ( this.referenceSurface ) {
|
|
|
|
this.referenceSurface.destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Properties
|
|
|
|
this.referenceSurface = new ve.ui.SurfaceWidget(
|
|
|
|
newDoc,
|
|
|
|
{
|
|
|
|
'$': this.$,
|
|
|
|
'tools': this.constructor.static.toolbarGroups,
|
2014-02-03 15:08:52 +00:00
|
|
|
'commands': this.constructor.static.surfaceCommands,
|
|
|
|
'pasteRules': this.constructor.static.pasteRules
|
2013-11-05 00:29:50 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Event handlers
|
|
|
|
this.referenceSurface.getSurface().getModel().getDocument()
|
|
|
|
.connect( this, { 'transact': 'onDocumentTransact' } );
|
|
|
|
|
|
|
|
// Initialization
|
|
|
|
this.referenceGroupInput.setValue( refGroup );
|
|
|
|
this.contentFieldset.$element.append( this.referenceSurface.$element );
|
|
|
|
this.referenceSurface.initialize();
|
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
2013-07-17 19:09:30 +00:00
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceDialog.prototype.initialize = function () {
|
|
|
|
// Parent method
|
|
|
|
ve.ui.MWDialog.prototype.initialize.call( this );
|
|
|
|
|
|
|
|
// Properties
|
2013-12-02 20:10:55 +00:00
|
|
|
this.panels = new OO.ui.StackLayout( { '$': this.$ } );
|
2013-10-09 20:09:59 +00:00
|
|
|
this.editPanel = new OO.ui.PanelLayout( {
|
2013-11-01 19:45:59 +00:00
|
|
|
'$': this.$, 'scrollable': true, 'padded': true
|
2013-07-17 19:09:30 +00:00
|
|
|
} );
|
2013-11-01 19:45:59 +00:00
|
|
|
this.searchPanel = new OO.ui.PanelLayout( { '$': this.$ } );
|
2014-01-17 14:24:12 +00:00
|
|
|
this.applyButton = new OO.ui.ButtonWidget( {
|
2013-11-01 19:45:59 +00:00
|
|
|
'$': this.$,
|
2013-07-17 19:09:30 +00:00
|
|
|
'label': ve.msg( 'visualeditor-dialog-action-apply' ),
|
|
|
|
'flags': ['primary']
|
|
|
|
} );
|
2014-01-17 14:24:12 +00:00
|
|
|
this.insertButton = new OO.ui.ButtonWidget( {
|
2013-11-01 19:45:59 +00:00
|
|
|
'$': this.$,
|
2013-07-17 19:09:30 +00:00
|
|
|
'label': ve.msg( 'visualeditor-dialog-reference-insert-button' ),
|
|
|
|
'flags': ['constructive']
|
|
|
|
} );
|
2014-01-17 14:24:12 +00:00
|
|
|
this.selectButton = new OO.ui.ButtonWidget( {
|
2013-11-01 19:45:59 +00:00
|
|
|
'$': this.$,
|
2013-07-17 19:09:30 +00:00
|
|
|
'label': ve.msg ( 'visualeditor-dialog-reference-useexisting-label' )
|
|
|
|
} );
|
2014-01-17 14:24:12 +00:00
|
|
|
this.backButton = new OO.ui.ButtonWidget( {
|
2013-11-01 19:45:59 +00:00
|
|
|
'$': this.$,
|
2013-07-17 19:09:30 +00:00
|
|
|
'label': ve.msg( 'visualeditor-dialog-action-goback' )
|
|
|
|
} );
|
2013-11-01 19:45:59 +00:00
|
|
|
this.contentFieldset = new OO.ui.FieldsetLayout( { '$': this.$ } );
|
2013-10-09 20:09:59 +00:00
|
|
|
this.optionsFieldset = new OO.ui.FieldsetLayout( {
|
2013-11-01 19:45:59 +00:00
|
|
|
'$': this.$,
|
2013-07-17 19:09:30 +00:00
|
|
|
'label': ve.msg( 'visualeditor-dialog-reference-options-section' ),
|
|
|
|
'icon': 'settings'
|
|
|
|
} );
|
|
|
|
// TODO: Use a drop-down or something, and populate with existing groups instead of free-text
|
2014-02-20 01:33:35 +00:00
|
|
|
this.referenceGroupInput = new OO.ui.TextInputWidget( {
|
|
|
|
'$': this.$,
|
|
|
|
'placeholder': ve.msg( 'visualeditor-dialog-reference-options-group-placeholder' )
|
|
|
|
} );
|
2014-01-22 20:13:59 +00:00
|
|
|
this.referenceGroupField = new OO.ui.FieldLayout( this.referenceGroupInput, {
|
2013-11-01 19:45:59 +00:00
|
|
|
'$': this.$,
|
2014-01-22 20:13:59 +00:00
|
|
|
'align': 'top',
|
2013-07-17 19:09:30 +00:00
|
|
|
'label': ve.msg( 'visualeditor-dialog-reference-options-group-label' )
|
|
|
|
} );
|
2013-10-09 20:09:59 +00:00
|
|
|
this.search = new ve.ui.MWReferenceSearchWidget(
|
2013-11-01 19:45:59 +00:00
|
|
|
this.surface, { '$': this.$ }
|
2013-10-09 20:09:59 +00:00
|
|
|
);
|
2013-07-17 19:09:30 +00:00
|
|
|
|
|
|
|
// Events
|
2013-11-05 00:29:50 +00:00
|
|
|
this.applyButton.connect( this, { 'click': [ 'close', { 'action': 'apply' } ] } );
|
|
|
|
this.insertButton.connect( this, { 'click': [ 'close', { 'action': 'insert' } ] } );
|
2013-07-17 19:09:30 +00:00
|
|
|
this.selectButton.connect( this, { 'click': function () {
|
2013-11-01 19:45:59 +00:00
|
|
|
this.backButton.$element.show();
|
|
|
|
this.insertButton.$element.hide();
|
|
|
|
this.selectButton.$element.hide();
|
2013-12-02 20:10:55 +00:00
|
|
|
this.panels.setItem( this.searchPanel );
|
2013-07-17 19:09:30 +00:00
|
|
|
this.search.getQuery().$input.focus().select();
|
|
|
|
} } );
|
|
|
|
this.backButton.connect( this, { 'click': function () {
|
2013-11-01 19:45:59 +00:00
|
|
|
this.backButton.$element.hide();
|
|
|
|
this.insertButton.$element.show();
|
|
|
|
this.selectButton.$element.show();
|
2013-12-02 20:10:55 +00:00
|
|
|
this.panels.setItem( this.editPanel );
|
2013-11-01 19:45:59 +00:00
|
|
|
this.editPanel.$element.find( '.ve-ce-documentNode' ).focus();
|
2013-07-17 19:09:30 +00:00
|
|
|
} } );
|
|
|
|
this.search.connect( this, { 'select': 'onSearchSelect' } );
|
|
|
|
|
|
|
|
// Initialization
|
|
|
|
this.panels.addItems( [ this.editPanel, this.searchPanel ] );
|
2013-11-01 19:45:59 +00:00
|
|
|
this.editPanel.$element.append( this.contentFieldset.$element, this.optionsFieldset.$element );
|
2014-01-22 20:13:59 +00:00
|
|
|
this.optionsFieldset.addItems( [ this.referenceGroupField ] );
|
2013-11-01 19:45:59 +00:00
|
|
|
this.searchPanel.$element.append( this.search.$element );
|
|
|
|
this.$body.append( this.panels.$element );
|
2013-07-17 19:09:30 +00:00
|
|
|
this.$foot.append(
|
2013-11-01 19:45:59 +00:00
|
|
|
this.applyButton.$element,
|
|
|
|
this.insertButton.$element,
|
|
|
|
this.selectButton.$element,
|
|
|
|
this.backButton.$element
|
2013-07-17 19:09:30 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2013-11-05 00:29:50 +00:00
|
|
|
ve.ui.MWReferenceDialog.prototype.setup = function ( data ) {
|
|
|
|
// Parent method
|
|
|
|
ve.ui.MWDialog.prototype.setup.call( this, data );
|
|
|
|
|
2013-07-17 19:09:30 +00:00
|
|
|
var ref,
|
|
|
|
focusedNode = this.surface.getView().getFocusedNode();
|
|
|
|
|
|
|
|
if ( focusedNode instanceof ve.ce.MWReferenceNode ) {
|
|
|
|
ref = focusedNode.getModel().getAttributes();
|
2013-11-01 19:45:59 +00:00
|
|
|
this.applyButton.$element.show();
|
|
|
|
this.insertButton.$element.hide();
|
|
|
|
this.selectButton.$element.hide();
|
2013-07-17 19:09:30 +00:00
|
|
|
} else {
|
2013-11-01 19:45:59 +00:00
|
|
|
this.applyButton.$element.hide();
|
|
|
|
this.insertButton.$element.show();
|
|
|
|
this.selectButton.$element.show();
|
2013-07-17 19:09:30 +00:00
|
|
|
}
|
2013-11-01 19:45:59 +00:00
|
|
|
this.backButton.$element.hide();
|
2013-12-02 20:10:55 +00:00
|
|
|
this.panels.setItem( this.editPanel );
|
2013-07-17 19:09:30 +00:00
|
|
|
this.useReference( ref );
|
2013-08-30 17:55:58 +00:00
|
|
|
this.search.buildIndex();
|
|
|
|
this.selectButton.setDisabled( !this.search.getResults().getItems().length );
|
2013-07-17 19:09:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
2013-11-05 00:29:50 +00:00
|
|
|
ve.ui.MWReferenceDialog.prototype.teardown = function ( data ) {
|
2013-12-02 19:30:54 +00:00
|
|
|
var i, len, txs, item, newDoc, group, refGroup, listGroup, keyIndex, refNodes, itemNodeRange,
|
2013-07-17 19:09:30 +00:00
|
|
|
surfaceModel = this.surface.getModel(),
|
2013-08-20 17:18:37 +00:00
|
|
|
// Store the original selection browsers may reset it after
|
|
|
|
// the first model change.
|
|
|
|
selection = surfaceModel.getSelection().clone(),
|
2013-07-17 19:09:30 +00:00
|
|
|
doc = surfaceModel.getDocument(),
|
|
|
|
internalList = doc.getInternalList();
|
|
|
|
|
2013-11-05 00:29:50 +00:00
|
|
|
// Data initialization
|
|
|
|
data = data || {};
|
2013-07-17 19:09:30 +00:00
|
|
|
|
2013-11-05 00:29:50 +00:00
|
|
|
if ( data.action === 'insert' || data.action === 'apply' ) {
|
Introduce newFromDocumentReplace() transaction builder
Replaces newFromNodeReplacement(). newFromNodeReplacement was very
simplistic and didn't support metadata or internal list items, so
if you had comments or references inside of the data you were editing
(reference contents or an image caption), they'd get mangled.
With this, you can do:
newDoc = doc.getDocumentSlice( node );
// Edit newDoc
tx = ve.dm.Transaction.newFromDocumentReplace( doc, node, newDoc );
surface.change( newDoc );
and that takes care of metadata, internal list items, and things like
references that reference internal list items.
ve.dm.Document.js:
* In getDocumentSlice(), store a reference to the original document
and the number of items in its InternalList at the time of slicing
in the created slice. This is used for reconciliation when the
modified slice is injected back into the parent document with
newFromDocumentReplace().
ve.dm.InternalList.js:
* Add a method for merging in another InternalList. This provides a
mapping from old to new InternalList indexes so the linear model data
being injected by newFromDocumentReplace() can have its InternalList
indexes remapped.
ve.dm.Transaction.js:
* Replace newFromNodeReplacement() with newFromDocumentReplace()
ve.ui.MWMediaEditDialog.js, ve.ui.MWReferenceDialog.js:
* Use getDocumentSlice/newFromDocumentReplace for editing captions/refs
* Change insertion code path to insert an empty internalItem/caption, then
newFromDocumentReplace into that
* Add empty internalList to new mini-documents
ve/test/dm/ve.dm.Transaction.test.js:
* Replace newFromNodeReplacement tests with newFromDocumentReplace tests
ve-mw/test/dm/ve.dm.Transaction.test.js (new):
* Add tests for newFromDocumentReplace with mwReference nodes
ve.dm.mwExample.js:
* Add data for newFromDocumentReplace with mwReference tests
VisualEditor.hooks.php:
* Add new test file
Bug: 52102
Change-Id: I4aa980780114b391924f04df588e81c990c32983
2013-09-05 01:05:07 +00:00
|
|
|
newDoc = this.referenceSurface.getSurface().getModel().getDocument();
|
2013-08-20 17:06:28 +00:00
|
|
|
refGroup = this.referenceGroupInput.getValue();
|
2013-07-17 19:09:30 +00:00
|
|
|
listGroup = 'mwReference/' + refGroup;
|
|
|
|
|
|
|
|
// Internal item changes
|
|
|
|
if ( this.ref ) {
|
|
|
|
// Group/key has changed
|
|
|
|
if ( this.ref.listGroup !== listGroup ) {
|
|
|
|
// Get all reference nodes with the same group and key
|
|
|
|
group = internalList.getNodeGroup( this.ref.listGroup );
|
|
|
|
refNodes = group.keyedNodes[this.ref.listKey] ?
|
|
|
|
group.keyedNodes[this.ref.listKey].slice() :
|
|
|
|
[ group.firstNodes[this.ref.listIndex] ];
|
|
|
|
// Check for name collision when moving items between groups
|
|
|
|
keyIndex = internalList.getKeyIndex( this.ref.listGroup, this.ref.listKey );
|
|
|
|
if ( keyIndex !== undefined ) {
|
|
|
|
// Resolve name collision by generating a new list key
|
2013-09-19 22:57:08 +00:00
|
|
|
this.ref.listKey = 'auto/' + internalList.getNextUniqueNumber();
|
2013-07-17 19:09:30 +00:00
|
|
|
}
|
|
|
|
// Update the group name of all references nodes with the same group and key
|
|
|
|
txs = [];
|
|
|
|
for ( i = 0, len = refNodes.length; i < len; i++ ) {
|
|
|
|
// HACK: Removing and re-inserting nodes to/from the internal list is done
|
|
|
|
// because internal list doesn't yet support attribute changes
|
|
|
|
refNodes[i].removeFromInternalList();
|
|
|
|
txs.push( ve.dm.Transaction.newFromAttributeChanges(
|
|
|
|
doc,
|
|
|
|
refNodes[i].getOuterRange().start,
|
|
|
|
{ 'refGroup': refGroup, 'listGroup': listGroup }
|
|
|
|
) );
|
|
|
|
}
|
|
|
|
surfaceModel.change( txs );
|
|
|
|
// HACK: Same as above, internal list issues
|
|
|
|
for ( i = 0, len = refNodes.length; i < len; i++ ) {
|
|
|
|
refNodes[i].addToInternalList();
|
|
|
|
}
|
|
|
|
this.ref.listGroup = listGroup;
|
|
|
|
this.ref.refGroup = refGroup;
|
|
|
|
}
|
|
|
|
// Update internal node content
|
2013-12-02 19:30:54 +00:00
|
|
|
itemNodeRange = internalList.getItemNode( this.ref.listIndex ).getRange();
|
|
|
|
surfaceModel.change( ve.dm.Transaction.newFromRemoval( doc, itemNodeRange, true ) );
|
2013-07-17 19:09:30 +00:00
|
|
|
surfaceModel.change(
|
2013-12-02 19:30:54 +00:00
|
|
|
ve.dm.Transaction.newFromDocumentInsertion( doc, itemNodeRange.start, newDoc )
|
2013-07-17 19:09:30 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Content changes
|
2013-11-05 00:29:50 +00:00
|
|
|
if ( data.action === 'insert' ) {
|
2013-08-27 20:51:48 +00:00
|
|
|
if ( !this.ref ) {
|
|
|
|
listGroup = 'mwReference/' + refGroup;
|
2013-07-17 19:09:30 +00:00
|
|
|
// Create new internal item
|
|
|
|
this.ref = {
|
2013-09-19 22:57:08 +00:00
|
|
|
'listKey': 'auto/' + internalList.getNextUniqueNumber(),
|
2013-08-27 20:51:48 +00:00
|
|
|
'listGroup': listGroup,
|
2013-07-17 19:09:30 +00:00
|
|
|
'refGroup': refGroup
|
|
|
|
};
|
Introduce newFromDocumentReplace() transaction builder
Replaces newFromNodeReplacement(). newFromNodeReplacement was very
simplistic and didn't support metadata or internal list items, so
if you had comments or references inside of the data you were editing
(reference contents or an image caption), they'd get mangled.
With this, you can do:
newDoc = doc.getDocumentSlice( node );
// Edit newDoc
tx = ve.dm.Transaction.newFromDocumentReplace( doc, node, newDoc );
surface.change( newDoc );
and that takes care of metadata, internal list items, and things like
references that reference internal list items.
ve.dm.Document.js:
* In getDocumentSlice(), store a reference to the original document
and the number of items in its InternalList at the time of slicing
in the created slice. This is used for reconciliation when the
modified slice is injected back into the parent document with
newFromDocumentReplace().
ve.dm.InternalList.js:
* Add a method for merging in another InternalList. This provides a
mapping from old to new InternalList indexes so the linear model data
being injected by newFromDocumentReplace() can have its InternalList
indexes remapped.
ve.dm.Transaction.js:
* Replace newFromNodeReplacement() with newFromDocumentReplace()
ve.ui.MWMediaEditDialog.js, ve.ui.MWReferenceDialog.js:
* Use getDocumentSlice/newFromDocumentReplace for editing captions/refs
* Change insertion code path to insert an empty internalItem/caption, then
newFromDocumentReplace into that
* Add empty internalList to new mini-documents
ve/test/dm/ve.dm.Transaction.test.js:
* Replace newFromNodeReplacement tests with newFromDocumentReplace tests
ve-mw/test/dm/ve.dm.Transaction.test.js (new):
* Add tests for newFromDocumentReplace with mwReference nodes
ve.dm.mwExample.js:
* Add data for newFromDocumentReplace with mwReference tests
VisualEditor.hooks.php:
* Add new test file
Bug: 52102
Change-Id: I4aa980780114b391924f04df588e81c990c32983
2013-09-05 01:05:07 +00:00
|
|
|
// Insert an internal item, then inject the subdocument into it
|
|
|
|
item = internalList.getItemInsertion( this.ref.listGroup, this.ref.listKey, [] );
|
2013-07-17 19:09:30 +00:00
|
|
|
surfaceModel.change( item.transaction );
|
|
|
|
this.ref.listIndex = item.index;
|
2013-12-02 19:30:54 +00:00
|
|
|
itemNodeRange = internalList.getItemNode( this.ref.listIndex ).getRange();
|
Introduce newFromDocumentReplace() transaction builder
Replaces newFromNodeReplacement(). newFromNodeReplacement was very
simplistic and didn't support metadata or internal list items, so
if you had comments or references inside of the data you were editing
(reference contents or an image caption), they'd get mangled.
With this, you can do:
newDoc = doc.getDocumentSlice( node );
// Edit newDoc
tx = ve.dm.Transaction.newFromDocumentReplace( doc, node, newDoc );
surface.change( newDoc );
and that takes care of metadata, internal list items, and things like
references that reference internal list items.
ve.dm.Document.js:
* In getDocumentSlice(), store a reference to the original document
and the number of items in its InternalList at the time of slicing
in the created slice. This is used for reconciliation when the
modified slice is injected back into the parent document with
newFromDocumentReplace().
ve.dm.InternalList.js:
* Add a method for merging in another InternalList. This provides a
mapping from old to new InternalList indexes so the linear model data
being injected by newFromDocumentReplace() can have its InternalList
indexes remapped.
ve.dm.Transaction.js:
* Replace newFromNodeReplacement() with newFromDocumentReplace()
ve.ui.MWMediaEditDialog.js, ve.ui.MWReferenceDialog.js:
* Use getDocumentSlice/newFromDocumentReplace for editing captions/refs
* Change insertion code path to insert an empty internalItem/caption, then
newFromDocumentReplace into that
* Add empty internalList to new mini-documents
ve/test/dm/ve.dm.Transaction.test.js:
* Replace newFromNodeReplacement tests with newFromDocumentReplace tests
ve-mw/test/dm/ve.dm.Transaction.test.js (new):
* Add tests for newFromDocumentReplace with mwReference nodes
ve.dm.mwExample.js:
* Add data for newFromDocumentReplace with mwReference tests
VisualEditor.hooks.php:
* Add new test file
Bug: 52102
Change-Id: I4aa980780114b391924f04df588e81c990c32983
2013-09-05 01:05:07 +00:00
|
|
|
surfaceModel.change(
|
2013-12-02 19:30:54 +00:00
|
|
|
ve.dm.Transaction.newFromDocumentInsertion( doc, itemNodeRange.start, newDoc )
|
Introduce newFromDocumentReplace() transaction builder
Replaces newFromNodeReplacement(). newFromNodeReplacement was very
simplistic and didn't support metadata or internal list items, so
if you had comments or references inside of the data you were editing
(reference contents or an image caption), they'd get mangled.
With this, you can do:
newDoc = doc.getDocumentSlice( node );
// Edit newDoc
tx = ve.dm.Transaction.newFromDocumentReplace( doc, node, newDoc );
surface.change( newDoc );
and that takes care of metadata, internal list items, and things like
references that reference internal list items.
ve.dm.Document.js:
* In getDocumentSlice(), store a reference to the original document
and the number of items in its InternalList at the time of slicing
in the created slice. This is used for reconciliation when the
modified slice is injected back into the parent document with
newFromDocumentReplace().
ve.dm.InternalList.js:
* Add a method for merging in another InternalList. This provides a
mapping from old to new InternalList indexes so the linear model data
being injected by newFromDocumentReplace() can have its InternalList
indexes remapped.
ve.dm.Transaction.js:
* Replace newFromNodeReplacement() with newFromDocumentReplace()
ve.ui.MWMediaEditDialog.js, ve.ui.MWReferenceDialog.js:
* Use getDocumentSlice/newFromDocumentReplace for editing captions/refs
* Change insertion code path to insert an empty internalItem/caption, then
newFromDocumentReplace into that
* Add empty internalList to new mini-documents
ve/test/dm/ve.dm.Transaction.test.js:
* Replace newFromNodeReplacement tests with newFromDocumentReplace tests
ve-mw/test/dm/ve.dm.Transaction.test.js (new):
* Add tests for newFromDocumentReplace with mwReference nodes
ve.dm.mwExample.js:
* Add data for newFromDocumentReplace with mwReference tests
VisualEditor.hooks.php:
* Add new test file
Bug: 52102
Change-Id: I4aa980780114b391924f04df588e81c990c32983
2013-09-05 01:05:07 +00:00
|
|
|
);
|
2013-07-17 19:09:30 +00:00
|
|
|
}
|
|
|
|
// Add reference at cursor
|
2013-08-20 17:18:37 +00:00
|
|
|
surfaceModel.getFragment( selection ).collapseRangeToEnd().insertContent( [
|
2013-07-17 19:09:30 +00:00
|
|
|
{ 'type': 'mwReference', 'attributes': this.ref }, { 'type': '/mwReference' }
|
2013-12-10 02:02:18 +00:00
|
|
|
] ).collapseRangeToEnd().select();
|
2013-07-17 19:09:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.referenceSurface.destroy();
|
|
|
|
this.referenceSurface = null;
|
|
|
|
this.ref = null;
|
2013-08-27 23:29:55 +00:00
|
|
|
|
2013-11-05 00:29:50 +00:00
|
|
|
// Parent method
|
|
|
|
ve.ui.MWDialog.prototype.teardown.call( this, data );
|
2013-08-27 23:29:55 +00:00
|
|
|
};
|
|
|
|
|
2013-07-17 19:09:30 +00:00
|
|
|
/* Registration */
|
|
|
|
|
2013-08-27 23:28:29 +00:00
|
|
|
ve.ui.dialogFactory.register( ve.ui.MWReferenceDialog );
|