modules/ve-mw/ui/actions/ve.ui.MWWikitextAction.js
/*!
* VisualEditor UserInterface MWWikitextAction class.
*
* @copyright See AUTHORS.txt
*/
/**
* Content action.
*
* @class
* @extends ve.ui.Action
*
* @constructor
* @param {ve.ui.Surface} surface Surface to act on
*/
ve.ui.MWWikitextAction = function VeUiMWWikitextAction() {
// Parent constructor
ve.ui.MWWikitextAction.super.apply( this, arguments );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWWikitextAction, ve.ui.Action );
/* Static Properties */
ve.ui.MWWikitextAction.static.name = 'mwWikitext';
ve.ui.MWWikitextAction.static.methods = [ 'toggleWrapSelection', 'wrapSelection', 'wrapLine' ];
/* Methods */
/**
* Wrap an selection inline
*
* @param {string} before Text to go before selection
* @param {string} after Text to go after selection
* @param {Function|string} placeholder Placeholder text to insert at an empty selection
* @param {Function} [expandOffsetsCallback] Function that returns a tuple of offsets to expand to selection to in order to get relevant text for unwrapping
* @param {Function} [unwrapOffsetsCallback] Function that returns a tuple of offsets to unwrap from the selected text,
* e.g. "''Foo'''" -> [2,3] to unwrap 2 from the left and 3 from the right
* @return {boolean} Action was executed
*/
ve.ui.MWWikitextAction.prototype.toggleWrapSelection = function ( before, after, placeholder, expandOffsetsCallback, unwrapOffsetsCallback ) {
const originalFragment = this.surface.getModel().getFragment( null, false, true /* excludeInsertions */ );
let fragment = originalFragment;
let textBefore, textAfter;
if ( expandOffsetsCallback ) {
const contextRange = fragment.expandLinearSelection( 'siblings' ).getSelection().getCoveringRange();
const data = fragment.getDocument().data;
const range = fragment.getSelection().getCoveringRange();
textBefore = data.getText( true, new ve.Range( contextRange.start, range.start ) );
textAfter = data.getText( true, new ve.Range( range.end, contextRange.end ) );
const expandOffsets = expandOffsetsCallback( textBefore, textAfter );
if ( expandOffsets ) {
fragment = originalFragment.adjustLinearSelection( expandOffsets[ 0 ], expandOffsets[ 1 ] );
}
}
if ( unwrapOffsetsCallback ) {
const unwrapOffsets = unwrapOffsetsCallback( fragment.getText(), textBefore, textAfter );
if ( unwrapOffsets ) {
fragment.unwrapText( unwrapOffsets[ 0 ], unwrapOffsets[ 1 ] );
} else {
fragment.wrapText( before, after, placeholder, true );
}
originalFragment.select();
return true;
}
fragment.wrapText( before, after, placeholder ).select();
originalFragment.select();
return true;
};
/**
* Wrap an selection inline
*
* @param {string} before Text to go before selection
* @param {string} after Text to go after selection
* @param {Function|string} placeholder Placeholder text to insert at an empty selection
* @return {boolean} Action was executed
*/
ve.ui.MWWikitextAction.prototype.wrapSelection = function ( before, after, placeholder ) {
const fragment = this.surface.getModel().getFragment( null, false, true /* excludeInsertions */ );
fragment.wrapText( before, after, placeholder ).select();
return true;
};
/**
* Wrap an selection as a block element on its own line
*
* If the selection is collapsed, it expands to take the whole line, otherwise it splits
* the paragraph to make sure it is one line
*
* @param {string} before Text to go before each line
* @param {string} after Text to go after each line
* @param {Function|string} placeholder Placeholder text to insert at an empty selection
* @param {Function} [unwrapOffsetsCallback] Function that returns a tuple of offsets to unwrap from the selected text,
* e.g. '== Foo ===' -> [2,3] to unwrap 2 from the left and 3 from the right
* @return {boolean} Action was executed
*/
ve.ui.MWWikitextAction.prototype.wrapLine = function ( before, after, placeholder, unwrapOffsetsCallback ) {
let originalFragment = this.surface.getModel().getFragment( null, false, true /* excludeInsertions */ );
const selectedNodes = originalFragment.getLeafNodes();
let unwrapped = false;
for ( let i = selectedNodes.length - 1; i >= 0; i-- ) {
if ( selectedNodes.length > 1 && selectedNodes[ i ].nodeRange.isCollapsed() ) {
continue;
}
const fragment = this.surface.getModel().getLinearFragment( selectedNodes[ i ].nodeRange, true );
const unwrapOffsets = unwrapOffsetsCallback && unwrapOffsetsCallback( fragment.getText() );
if ( selectedNodes.length === 1 && originalFragment.getSelection().isCollapsed() ) {
originalFragment = fragment;
}
if ( unwrapOffsets ) {
fragment.unwrapText( unwrapOffsets[ 0 ], unwrapOffsets[ 1 ] );
unwrapped = true;
}
const wrappedFragment = fragment.wrapText( before, after, placeholder );
if ( !unwrapped && wrappedFragment !== fragment ) {
if ( !ve.dm.LinearData.static.isElementData(
wrappedFragment.collapseToStart().adjustLinearSelection( -1, 0 ).getData()[ 0 ]
) ) {
wrappedFragment.collapseToStart().insertContent( [ { type: '/paragraph' }, { type: 'paragraph' } ] );
}
if ( !ve.dm.LinearData.static.isElementData(
wrappedFragment.collapseToEnd().adjustLinearSelection( 0, 1 ).getData()[ 0 ]
) ) {
wrappedFragment.collapseToEnd().insertContent( [ { type: '/paragraph' }, { type: 'paragraph' } ] );
}
}
}
originalFragment.select();
return true;
};
/* Registration */
ve.ui.actionFactory.register( ve.ui.MWWikitextAction );