All files / src/ui/actions ve.ui.ListAction.js

90.32% Statements 56/62
74.35% Branches 29/39
100% Functions 6/6
90.16% Lines 55/61

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201                              1x   6x         1x       1x   1x                     1x 4x 4x 4x                     1x 2x     2x                       1x   2x 2x                             1x 5x 5x   5x       5x   5x 3x     5x 5x       5x                                   5x 5x 5x   5x 2x 2x     2x       3x       3x 3x 3x   3x 3x               5x 3x   5x                       1x 1x   1x       1x 1x     1x 1x   1x     1x 3x     1x 1x     1x         1x  
/*!
 * VisualEditor UserInterface ListAction class.
 *
 * @copyright See AUTHORS.txt
 */
 
/**
 * List action.
 *
 * @class
 * @extends ve.ui.Action
 * @constructor
 * @param {ve.ui.Surface} surface Surface to act on
 * @param {string} [source]
 */
ve.ui.ListAction = function VeUiListAction() {
	// Parent constructor
	ve.ui.ListAction.super.apply( this, arguments );
};
 
/* Inheritance */
 
OO.inheritClass( ve.ui.ListAction, ve.ui.Action );
 
/* Static Properties */
 
ve.ui.ListAction.static.name = 'list';
 
ve.ui.ListAction.static.methods = [ 'wrap', 'unwrap', 'toggle', 'wrapOnce' ];
 
/* Methods */
 
/**
 * Check if the current selection is wrapped in a list of a given style
 *
 * @param {string|null} style List style, e.g. 'number' or 'bullet', or null for any style
 * @param {string} [listType='list'] List type
 * @return {boolean} Current selection is all wrapped in a list
 */
ve.ui.ListAction.prototype.allWrapped = function ( style, listType ) {
	listType = listType || 'list';
	var attributes = style ? { style: style } : undefined;
	return this.surface.getModel().getFragment().hasMatchingAncestor( listType, attributes, true );
};
 
/**
 * Toggle a list around content.
 *
 * @param {string} style List style, e.g. 'number' or 'bullet'
 * @param {boolean} noBreakpoints Don't create breakpoints
 * @param {string} [listType='list'] List type
 * @return {boolean} Action was executed
 */
ve.ui.ListAction.prototype.toggle = function ( style, noBreakpoints, listType ) {
	Iif ( this.allWrapped( style, listType ) ) {
		return this.unwrap( noBreakpoints, listType );
	} else {
		return this.wrap( style, noBreakpoints, listType );
	}
};
 
/**
 * Add a list around content only if it has no list already.
 *
 * @param {string} style List style, e.g. 'number' or 'bullet'
 * @param {boolean} noBreakpoints Don't create breakpoints
 * @param {string} [listType='list'] List type
 * @return {boolean} Action was executed
 */
ve.ui.ListAction.prototype.wrapOnce = function ( style, noBreakpoints, listType ) {
	// Check for a list of any style
	Eif ( !this.allWrapped( null, listType ) ) {
		return this.wrap( style, noBreakpoints, listType );
	}
	return false;
};
 
/**
 * Add a list around content.
 *
 * TODO: Refactor functionality into {ve.dm.SurfaceFragment}.
 *
 * @param {string} style List style, e.g. 'number' or 'bullet'
 * @param {boolean} noBreakpoints Don't create breakpoints
 * @param {string} [listType='list'] List type
 * @return {boolean} Action was executed
 */
ve.ui.ListAction.prototype.wrap = function ( style, noBreakpoints, listType ) {
	var surfaceModel = this.surface.getModel(),
		selection = surfaceModel.getSelection();
 
	Iif ( !( selection instanceof ve.dm.LinearSelection ) ) {
		return false;
	}
 
	listType = listType || 'list';
 
	if ( !noBreakpoints ) {
		surfaceModel.breakpoint();
	}
 
	var documentModel = surfaceModel.getDocument();
	var range = selection.getRange();
 
	// TODO: Would be good to refactor at some point and avoid/abstract path split for block slug
	// and not block slug.
	Iif (
		range.isCollapsed() &&
		!documentModel.data.isContentOffset( range.to ) &&
		documentModel.hasSlugAtOffset( range.to )
	) {
		// Inside block level slug
		var fragment = surfaceModel.getFragment( null, true )
			.insertContent( [
				{ type: 'paragraph' },
				{ type: '/paragraph' }
			] )
			.collapseToStart()
			.adjustLinearSelection( 1, 1 )
			.select();
		range = fragment.getSelection().getRange();
	}
 
	var previousList;
	var groups = documentModel.getCoveredSiblingGroups( range );
	for ( var i = 0; i < groups.length; i++ ) {
		var group = groups[ i ];
		// TODO: Allow conversion between different list types
		if ( group.grandparent && group.grandparent.getType() === listType ) {
			Eif ( group.grandparent !== previousList ) {
				surfaceModel.getLinearFragment( group.grandparent.getOuterRange(), true )
					// Change the list style
					.changeAttributes( { style: style } );
				previousList = group.grandparent;
			}
		} else {
			// Get a range that covers the whole group
			var groupRange = new ve.Range(
				group.nodes[ 0 ].getOuterRange().start,
				group.nodes[ group.nodes.length - 1 ].getOuterRange().end
			);
			var element = { type: listType };
			Eif ( style ) {
				element.attributes = { style: style };
			}
			var itemElement = ve.dm.modelRegistry.lookup( listType ).static.createItem();
			surfaceModel.getLinearFragment( groupRange, true )
				// Convert everything to paragraphs first
				.convertNodes( 'paragraph', null, { generated: 'wrapper' } )
				// Wrap everything in a list and each content branch in a listItem
				.wrapAllNodes( element, itemElement );
		}
	}
 
	if ( !noBreakpoints ) {
		surfaceModel.breakpoint();
	}
	return true;
};
 
/**
 * Remove list around content.
 *
 * TODO: Refactor functionality into {ve.dm.SurfaceFragment}.
 *
 * @param {boolean} noBreakpoints Don't create breakpoints
 * @param {string} [listType='list'] List type
 * @return {boolean} Action was executed
 */
ve.ui.ListAction.prototype.unwrap = function ( noBreakpoints, listType ) {
	var surfaceModel = this.surface.getModel();
 
	Iif ( !( surfaceModel.getSelection() instanceof ve.dm.LinearSelection ) ) {
		return false;
	}
 
	Eif ( !noBreakpoints ) {
		surfaceModel.breakpoint();
	}
 
	var indentationAction = ve.ui.actionFactory.create( 'indentation', this.surface );
	var documentModel = surfaceModel.getDocument();
 
	listType = listType || 'list';
 
	var node;
	do {
		node = documentModel.getBranchNodeFromOffset( surfaceModel.getSelection().getRange().start );
	} while ( node.hasMatchingAncestor( listType ) && indentationAction.decrease() );
 
	Eif ( !noBreakpoints ) {
		surfaceModel.breakpoint();
	}
 
	return true;
};
 
/* Registration */
 
ve.ui.actionFactory.register( ve.ui.ListAction );