/*!
* VisualEditor UserInterface SequenceRegistry class.
*
* @copyright See AUTHORS.txt
*/
/**
* Sequence registry.
*
* @class
* @extends OO.Registry
* @constructor
*/
ve.ui.SequenceRegistry = function VeUiSequenceRegistry() {
// Parent constructor
ve.ui.SequenceRegistry.super.call( this );
};
/* Inheritance */
OO.inheritClass( ve.ui.SequenceRegistry, OO.Registry );
/**
* Register a sequence with the factory.
*
* @param {ve.ui.Sequence} sequence
* @throws {Error} If sequence is not an instance of ve.ui.Sequence
*/
ve.ui.SequenceRegistry.prototype.register = function ( sequence ) {
// Validate arguments
if ( !( sequence instanceof ve.ui.Sequence ) ) {
throw new Error(
'sequence must be an instance of ve.ui.Sequence, cannot be a ' + typeof sequence
);
}
ve.ui.SequenceRegistry.super.prototype.register.call( this, sequence.getName(), sequence );
};
/**
* Matching sequence and corresponding range
*
* @typedef {Object} Match
* @memberof ve.ui.SequenceRegistry
* @property {ve.ui.Sequence} sequence
* @property {ve.Range} range
*/
/**
* Find sequence matches a given offset in the data
*
* @param {ve.dm.ElementLinearData} data
* @param {number} offset
* @param {boolean} [isPaste] Whether this in the context of a paste
* @param {boolean} [isDelete] Whether this is after content being deleted
* @return {ve.ui.SequenceRegistry.Match[]} Array of matching sequences, and the corresponding range of the match for each.
*/
ve.ui.SequenceRegistry.prototype.findMatching = function ( data, offset, isPaste, isDelete ) {
// To avoid blowup when matching RegExp sequences, we're going to grab
// all the plaintext to the left (until the nearest node) *once* and pass
// it to each sequence matcher. We're also going to hard-limit that
// plaintext to 256 characters to ensure we don't run into O(N^2)
// slowdown when inserting N characters of plain text.
// First skip over open elements, then close elements, to ensure that
// pressing enter after a (possibly nested) list item or inside a
// paragraph works properly. Typing "foo\n" inside a paragraph creates
// "foo</p><p>" in the content model, and typing "foo\n" inside a list
// creates "foo</p></li><li><p>" -- we want to give the matcher a
// chance to match "foo\n+" in these cases.
let textStart;
let state = 0;
for ( textStart = offset - 1; textStart >= 0 && ( offset - textStart ) <= 256; textStart-- ) {
if ( state === 0 && !data.isOpenElementData( textStart ) ) {
state++;
}
if ( state === 1 && !data.isCloseElementData( textStart ) ) {
state++;
}
if ( state === 2 && data.isElementData( textStart ) ) {
break;
}
}
const sequences = [];
const plaintext = data.getText( true, new ve.Range( textStart + 1, offset ) );
// Now search through the registry.
for ( const name in this.registry ) {
const sequence = this.registry[ name ];
if ( isPaste && !sequence.checkOnPaste ) {
continue;
}
if ( isDelete && !sequence.checkOnDelete ) {
continue;
}
const range = sequence.match( data, offset, plaintext );
if ( range !== null ) {
sequences.push( {
sequence: sequence,
range: range
} );
}
}
return sequences;
};
/* Initialization */
ve.ui.sequenceRegistry = new ve.ui.SequenceRegistry();
/* Registrations */
ve.ui.sequenceRegistry.register(
new ve.ui.Sequence( 'bulletStar', 'bulletWrapOnce', [ { type: 'paragraph' }, '*', ' ' ], 2 )
);
ve.ui.sequenceRegistry.register(
new ve.ui.Sequence( 'numberDot', 'numberWrapOnce', [ { type: 'paragraph' }, '1', '.', ' ' ], 3 )
);
ve.ui.sequenceRegistry.register(
new ve.ui.Sequence( 'horizontalRule', 'insertHorizontalRule', [ { type: 'paragraph' }, '-', '-', '-', '-' ], 4 )
);