MediaWiki:Gadget-CommentWidget.js

Questa pagina definisce alcuni parametri di aspetto e comportamento generale di tutte le pagine. Per personalizzarli vedi Aiuto:Stile utente.


Nota: dopo aver salvato è necessario pulire la cache del proprio browser per vedere i cambiamenti (per le pagine globali è comunque necessario attendere qualche minuto). Per Mozilla / Firefox / Safari: fare clic su Ricarica tenendo premuto il tasto delle maiuscole, oppure premere Ctrl-F5 o Ctrl-R (Command-R su Mac); per Chrome: premere Ctrl-Shift-R (Command-Shift-R su un Mac); per Konqueror: premere il pulsante Ricarica o il tasto F5; per Opera può essere necessario svuotare completamente la cache dal menù Strumenti → Preferenze; per Internet Explorer: mantenere premuto il tasto Ctrl mentre si preme il pulsante Aggiorna o premere Ctrl-F5.

/**
 * Classe CommentWidget
 * 
 * Consente di creare un'area di testo simile a quella che si usa nelle pagine
 * di discussione quando si clicca sul pulsante 'Rispondi'.
 * Il codice è frutto di una rielaborazione delle classi ReplyWidget,
 * ReplyWidgetVisual e CommentController dell'estensione DiscussionTools
 * (consultare il repository linkato di seguito per trovare i rispettivi autori).
 * Commit: a74c00ba8c256c3aa2f416f189c4256a59e37a0b
 * https://phabricator.wikimedia.org/diffusion/EDTO/browse/master/;a74c00ba8c256c3aa2f416f189c4256a59e37a0b
 */

const STORAGE_EXPIRY = 60 * 60 * 24 * 30;

/**
 * Costruttore della classe CommentWidget
 *
 * @param {Object} [config] Opzioni di configurazione
 * @param {Object} [config.cancelButtonLabel] Etichetta del pulsante di annullamento
 * @param {Object} [config.commentIndent] Indentazione del commento, indicare '*' o '#'
 * @param {Object} [config.confirmButtonLabel] Etichetta del pulsante di conferma
 * @param {Object} [config.placeholder] Testo segnaposto
 * @param {Object} [config.previewLabel] Etichetta dell'anteprima
 * @param {Object} [config.storageId] Identificativo per la memorizzazione del commento
 */
function CommentWidget( config ) {
	config = config || {};

	CommentWidget.super.call( this, {} );

	this.defaultMode = mw.user.options.get( 'discussiontools-editmode' ) || 'visual';
	this.pending = false;
	this.isTornDown = false;
	this.pageName = mw.config.get( 'wgPageName' );
	this.storagePrefix = config.storageId ? 'userjs-gadget-comment-widget' + config.storageId : null;
	this.storage = ve.init.platform.localStorage;
	this.contentDir = $( '#mw-content-text' ).css( 'direction' );

	if ( [ '*', '#' ].includes( config.commentIndent ) ) {
		this.commentIndent = config.commentIndent;
	}

	// hack: sfruttiamo la funzione della classe ReplyWidgetVisual per creare un
	// CommentTargetWidget senza duplicare il sorgente salvato in cache;
	// non essendo previsto potrebbe essere instabile
	this.commentBodyWidget = this.createReplyBodyWidget( {
		placeholder: config.placeholder,
		authors: [],
		defaultMode: this.defaultMode
	} );
	this.confirmButtonLabel = config.confirmButtonLabel || 'Conferma';
	this.confirmButton = new OO.ui.ButtonWidget( {
		flags: [ 'primary', 'progressive' ],
		label: this.confirmButtonLabel,
		title: this.confirmButtonLabel + ' [' +
			new ve.ui.Trigger( ve.ui.commandHelpRegistry.lookup( 'dialogConfirm' ).shortcuts[ 0 ] ).getMessage() +
			']',
		accessKey: mw.msg( 'discussiontools-replywidget-publish-accesskey' )
	} );
	this.cancelButtonLabel = config.cancelButtonLabel || 'Annulla';
	this.cancelButton = new OO.ui.ButtonWidget( {
		flags: [ 'destructive' ],
		label: this.cancelButtonLabel,
		framed: false,
		title: this.cancelButtonLabel + ' [' +
			new ve.ui.Trigger( ve.ui.commandHelpRegistry.lookup( 'dialogCancel' ).shortcuts[ 0 ] ).getMessage() +
			']'
	} );
	this.$headerWrapper = $( '<div>' ).addClass( 'ext-discussiontools-ui-replyWidget-headerWrapper' );

	if ( !OO.ui.isMobile() ) {
		this.modeTabSelect = new ModeTabSelectWidget( {
			classes: [ 'ext-discussiontools-ui-replyWidget-modeTabs' ],
			items: [
				new ModeTabOptionWidget( {
					label: mw.msg( 'discussiontools-replywidget-mode-visual' ),
					data: 'visual'
				} ),
				new ModeTabOptionWidget( {
					label: mw.msg( 'discussiontools-replywidget-mode-source' ),
					data: 'source'
				} )
			],
			framed: false
		} );
		this.modeTabSelect.$element.attr( 'aria-label', mw.msg( 'visualeditor-mweditmode-tooltip' ) );
		this.modeTabSelect.selectItemByData( this.getMode() );
		this.modeTabSelect.connect( this, {
			choose: 'onModeTabSelectChoose'
		} );
		this.$headerWrapper.append(
			this.modeTabSelect.$element
		);
	}

	this.$bodyWrapper = $( '<div>' ).addClass( 'ext-discussiontools-ui-replyWidget-bodyWrapper' ).append(
		this.commentBodyWidget.$element
	);
	this.$preview = $( '<div>' )
		.addClass( 'ext-discussiontools-ui-replyWidget-preview' )
		.attr( 'data-label', config.previewLabel || mw.msg( 'discussiontools-replywidget-preview' ) )
		.attr( 'dir', this.contentDir );
	this.$actionsWrapper = $( '<div>' ).addClass( 'ext-discussiontools-ui-replyWidget-actionsWrapper' );
	this.$actions = $( '<div>' ).addClass( 'ext-discussiontools-ui-replyWidget-actions' ).append(
		this.cancelButton.$element,
		this.confirmButton.$element
	);

	this.$footer = $( '<div>' ).addClass( 'ext-discussiontools-ui-replyWidget-footer' );
	new mw.Api().loadMessagesIfMissing( [
		'wikimedia-discussiontools-replywidget-terms-click',
		'wikimedia-license-links'
	] ).then( () => {
		this.$footer.append(
			$( '<p>' ).addClass( 'plainlinks' ).html(
					mw.message( 'wikimedia-discussiontools-replywidget-terms-click',
					this.confirmButtonLabel,
					mw.message( 'wikimedia-license-links' ).parseDom()
				).parse()
			)
		);
	} );

	this.$actionsWrapper.append( this.$footer, this.$actions );

	this.connect( this, { confirm: 'teardown' } );
	this.confirmButton.connect( this, { click: 'onConfirmClick' } );
	this.cancelButton.connect( this, { click: 'tryTeardown' } );
	this.beforeUnloadHandler = this.onBeforeUnload.bind( this );
	this.onInputChangeThrottled = OO.ui.throttle( this.onInputChange.bind( this ), 1000 );
	this.commentBodyWidget.connect( this, { change: this.onInputChangeThrottled } );

	this.$element.addClass( 'ext-discussiontools-ui-replyWidget ext-discussiontools-ui-replyWidget-ve' ).append(
		this.$headerWrapper,
		this.$bodyWrapper,
		this.$preview,
		this.$actionsWrapper
	);
	this.setup();
}

OO.inheritClass( CommentWidget, OO.ui.Widget );
OO.mixinClass( CommentWidget, require( 'ext.discussionTools.ReplyWidget' ).ReplyWidgetVisual );

CommentWidget.prototype.detach = function () {
	this.$element.detach();
	this.emit( 'detach' );
};

CommentWidget.prototype.focus = function () {
	const target = this.commentBodyWidget;

	setTimeout( () => {
		if ( target.getSurface() ) {
			target.getSurface().getView().selectLastSelectableContentOffset();
			target.focus();
		}
	} );

	return this;
};

CommentWidget.prototype.getMode = function () {
	return this.commentBodyWidget.target.getSurface() ?
		this.commentBodyWidget.target.getSurface().getMode() :
		this.defaultMode;
};

CommentWidget.prototype.clear = function ( preserveStorage ) {
	if ( !preserveStorage ) {
		this.clearStorage();
	}

	this.commentBodyWidget.clear();

	if ( this.previewRequest ) {
		this.previewRequest.abort();
		this.previewRequest = null;
	}

	this.$preview.empty();
	this.previewWikitext = null;

	if ( preserveStorage && this.storagePrefix ) {
		this.storage.set(
			this.storagePrefix + '/saveable',
			this.isEmpty() ? '' : '1',
			STORAGE_EXPIRY
		);
	}

	this.emit( 'clear' );
};

CommentWidget.prototype.clearStorage = function () {
	if ( !this.storagePrefix ) {
		return;
	}

	this.commentBodyWidget.target.clearDocState();
	this.storage.remove( this.storagePrefix + '/saveable' );
	this.emit( 'clearStorage' );
};

CommentWidget.prototype.getValue = function () {
	if ( this.getMode() === 'source' ) {
		const wikitext = this.commentBodyWidget.target.getSurface().getModel().getDom();
		return $.Deferred().resolve( this.sanitizeWikitextLinebreaks( wikitext ) ).promise();
	} else {
		const target = this.commentBodyWidget.target;
		return target.getWikitextFragment( target.getSurface().getModel().getDocument() )
			.then( wikitext => {
				if ( this.commentIndent && wikitext.startsWith( this.commentIndent ) ) {
					wikitext = wikitext.replace( this.commentIndent, '' ).trimStart();
				}
				return this.htmlTrim( wikitext );
			} );
	}
};

CommentWidget.prototype.isEmpty = function () {
	const surface = this.commentBodyWidget.target.getSurface();

	if ( !( surface && surface.getModel().getDocument().data.hasContent() ) ) {
		return true;
	} else if ( !this.commentIndent || this.getMode() === 'source' ) {
		return false;
	} else {
		const firstChild = surface.getView().getDocument().getBranchNodeFromOffset( 1 ),
			nodeData = surface.getModel().getDocument().getDataFromNode( firstChild );
		return firstChild.type === 'commentindent' &&
			( typeof nodeData[ 2 ] === 'object' || nodeData[ 2 ].type === '/paragraph' );
	}
};

CommentWidget.prototype.isDisabled = function () {
	return this.disabled;
};

CommentWidget.prototype.setDisabled = function ( disabled ) {
	CommentWidget.super.prototype.setDisabled.call( this, disabled );

	if ( this.commentBodyWidget ) {
		this.confirmButton.setDisabled( disabled );
		this.cancelButton.setDisabled( disabled );
		this.setReadOnly( disabled );
	}

	return this;
};

CommentWidget.prototype.isPending = function () {
	return this.pending;
};

CommentWidget.prototype.setPending = function ( pending ) {
	this.pending = pending;
	this.setDisabled( pending );

	if ( pending ) {
		this.commentBodyWidget.pushPending();
	} else {
		this.commentBodyWidget.popPending();
	}

	return this;
};

CommentWidget.prototype.isReadOnly = function () {
	this.commentBodyWidget.isReadOnly();
};

CommentWidget.prototype.setReadOnly = function ( readonly ) {
	this.commentBodyWidget.setReadOnly( readonly );
	this.modeTabSelect.$element.css( 'pointer-events', readonly ? 'none' : '' );
	return this;
};

CommentWidget.prototype.onBeforeUnload = function ( e ) {
	if ( !this.isEmpty() ) {
		e.preventDefault();
		return '';
	}
};

CommentWidget.prototype.onConfirmClick = function () {
	if ( this.pending ) {
		return;
	}

	this.setPending( true );

	this.getValue().always( () => {
		this.setPending( false );
	} ).then( value => {
		$( window ).off( 'beforeunload', this.beforeUnloadHandler );

		if ( value && !value.includes( '~~' + '~~' ) ) {
			value += ' --~~' + '~~';
		}

		this.emit( 'confirm', {
			value: value
		} );
	} );
};

CommentWidget.prototype.tryTeardown = function () {
	let promise;

	if ( !this.isEmpty() ) {
		const messageDialog = new OO.ui.MessageDialog();
		const windowManager = new OO.ui.WindowManager();
		$( 'body' ).append( windowManager.$element );
		windowManager.addWindows( [ messageDialog ] );
		promise = windowManager.openWindow( messageDialog, {
			title: 'Annullare il commento?',
			message: 'Confermi di voler annullare il commento che stavi scrivendo?',
			actions: [
				{
					action: 'discard',
					label: 'Annulla commento',
					flags: 'destructive'
				},
				{
					action: 'keep',
					label: 'Continua a scrivere',
					flags: [ 'primary', 'safe' ]
				}
			]
		} ).closed.then( ( data ) => {
			if ( !( data && data.action === 'discard' ) ) {
				return $.Deferred().reject().promise();
			}
		} );
	} else {
		promise = $.Deferred().resolve().promise();
	}

	promise = promise.then( () => {
		this.teardown( 'abandoned' );
	} );
	return promise;
};

CommentWidget.prototype.teardown = function ( mode ) {
	this.commentBodyWidget.disconnect( this );
	this.onInputChange();

	if ( this.modeTabSelect ) {
		this.modeTabSelect.blur();
	}

	$( window ).off( 'beforeunload', this.beforeUnloadHandler );
	this.isTornDown = true;
	this.clear( mode === 'refresh' );
	this.$element.remove();
	this.emit( 'teardown', mode );
	return this;
};

CommentWidget.prototype.onInputChange = function () {
	if ( this.isTornDown ) {
		return;
	}

	if ( this.storagePrefix ) {
		this.storage.set(
			this.storagePrefix + '/saveable',
			this.isEmpty() ? '' : '1',
			STORAGE_EXPIRY
		);
	}

	if ( this.getMode() === 'source' ) {
		this.getValue().then( wikitext => this.preparePreview( wikitext ) );
	}
};

CommentWidget.prototype.onModeTabSelectChoose = function ( option ) {
	const mode = option.getData();

	if ( mode === this.getMode() ) {
		return;
	}

	this.modeTabSelect.setDisabled( true );
	this.switch( mode ).then(
		null,
		() => {
			this.modeTabSelect.selectItemByData( this.getMode() );
		}
	).always( () => {
		this.modeTabSelect.setDisabled( false );
		this.modeTabSelect.findSelectedItem().$element.mouseleave();
	} );
};

CommentWidget.prototype.htmlTrim = function ( str ) {
	return str.replace( /^[\t\n\f\r ]+/, '' ).replace( /[\t\n\f\r ]+$/, '' );
};

CommentWidget.prototype.sanitizeWikitextLinebreaks = function ( wikitext ) {
	return this.htmlTrim( wikitext )
		.replace( /\r/g, '\n' )
		.replace( /\n\n\n+/g, '\n\n' )
		.replace( /([^\n])\n([^\n:#\*;])/, '$1\n\n$2' );
};

CommentWidget.prototype.switch = function ( mode ) {
	if ( mode === this.getMode() ) {
		return $.Deferred().reject().promise();
	}

	let promise;

	this.setPending( true );

	switch ( mode ) {
		case 'source':
			promise = this.switchToWikitext();
			break;
		case 'visual':
			promise = this.switchToVisual();
			break;
	}

	return promise.always( () => {
		this.setPending( false );
	} );
};

CommentWidget.prototype.switchToWikitext = function () {
	const target = this.commentBodyWidget.target,
		previewDeferred = $.Deferred();

	return this.getValue().then( wikitext => {
		this.preparePreview( wikitext ).then( previewDeferred.resolve );
		setTimeout( previewDeferred.resolve, 500 );

		return previewDeferred.then( () => {
			this.setup( {
				value: wikitext,
				mode: 'source'
			} ).focus();
		} );
	} );
};

CommentWidget.prototype.switchToVisual = function () {
	return this.getValue().then( wikitext => {
		wikitext = wikitext.replace(
			/([^~]|^)~[~]~~([^~]|$)/g,
			'$1<span data-dtsignatureforswitching="1"></span>$2'
		);

		if ( wikitext ) {
			wikitext = this.sanitizeWikitextLinebreaks( wikitext );

			return new mw.Api( {
				parameters: {
					formatversion: 2,
					uselang: mw.config.get( 'wgUserLanguage' )
				}
			} ).post( {
				action: 'visualeditor',
				paction: 'parsefragment',
				page: this.pageName,
				wikitext: ( this.commentIndent || '' ) + wikitext,
				pst: true
			} ).then( ( response ) => {
				return response && response.visualeditor.content;
			} );
		} else {
			parsePromise = $.Deferred().resolve( '' ).promise();
		}
	} ).then( html => {
		const unsupportedSelectors = {
			table: 'table',
			template: '[typeof*="mw:Transclusion"]',
			extension: '[typeof*="mw:Extension"]'
		};

		let doc = '<p></p>';
		if ( html ) {
			doc = this.commentBodyWidget.target.parseDocument( html );
			mw.libs.ve.stripRestbaseIds( doc );
			for ( let type in unsupportedSelectors ) {
				if ( doc.querySelector( unsupportedSelectors[ type ] ) ) {
					const $msg = $( '<div>' ).html(
						mw.message(
							'discussiontools-error-noswitchtove',
							// The following messages are used here:
							// * discussiontools-error-noswitchtove-extension
							// * discussiontools-error-noswitchtove-table
							// * discussiontools-error-noswitchtove-template
							mw.msg( 'discussiontools-error-noswitchtove-' + type )
						).parse()
					);
					$msg.find( 'a' ).attr( {
						target: '_blank',
						rel: 'noopener'
					} );
					OO.ui.alert(
						$msg.contents(),
						{
							title: mw.msg( 'discussiontools-error-noswitchtove-title' ),
							size: 'medium'
						}
					);

					return $.Deferred().reject().promise();
				}
			}
		}

		this.preparePreview( '' );
		this.setup( {
			value: doc,
			mode: 'visual'
		} ).focus();
	} );
};

CommentWidget.prototype.isCommentIndentNode = function ( node ) {
	return node.type === 'commentindent' && node.getRoot() && node.getOffset() === 0;
};

CommentWidget.prototype.setup = function ( data ) {
	const target = this.commentBodyWidget.target;

	data = data || {};

	let htmlOrDoc;
	if ( data.value === undefined && this.storagePrefix && this.storage.get( this.storagePrefix + '/saveable' ) ) {
		const docstate = this.storage.getObject( this.storagePrefix + '/ve-docstate' );

		if ( typeof docstate === 'object' && docstate.mode ) {
			htmlOrDoc = this.storage.get( this.storagePrefix + '/ve-dochtml' );
			this.modeTabSelect.selectItemByData( docstate.mode );
			target.setDefaultMode( docstate.mode );
			target.recovered = true;
		}
	} else {
		htmlOrDoc = data.value;

		if ( data.mode ) {
			target.setDefaultMode( data.mode );
			target.getSurface().getModel().updateDocState( data.mode );
		}

		target.recovered = false;
	}

	htmlOrDoc = htmlOrDoc || ( target.getDefaultMode() === 'visual' ? '<p></p>' : '' );

	target.originalHtml = htmlOrDoc instanceof HTMLDocument ? ve.properInnerHtml( htmlOrDoc.body ) : htmlOrDoc;
	target.fromEditedState = !!data.value;

	this.commentBodyWidget.setDocument( htmlOrDoc );

	target.once( 'surfaceReady', () => {
		const surface = target.getSurface(),
			documentNode = surface.getView().getDocument().getDocumentNode();

		surface.getView().connect( this, {
			focus: [ 'emit', 'bodyFocus' ]
		} );

		if ( this.storagePrefix ) {
			target.initAutosave( {
				docId: this.storagePrefix,
				storage: this.storage,
				storageExpiry: STORAGE_EXPIRY
			} );
		}

		if ( this.commentIndent ) {
			let firstChild = documentNode.getChildren()[ 0 ],
				isDocumentUpdating = false;

			surface.getModel().on( 'documentUpdate', () => {
				if (
					isDocumentUpdating ||
					this.getMode() === 'source' ||
					this.isCommentIndentNode( firstChild )
				) {
					return;
				}

				isDocumentUpdating = true;
				surface.getModel().getLinearFragment( new ve.Range( 1 ) ).insertContent( [
					{
						type: 'commentindent',
						attributes: {
							style: this.commentIndent === '*' ? 'bullet' : 'number'
						}
					},
					{ type: 'listItem' },
					{ type: 'paragraph' }, { type: '/paragraph' },
					{ type: '/listItem' },
					{ type: '/commentindent' }
				] );

				firstChild = documentNode.getChildren()[ 0 ];
				this.focus();
				isDocumentUpdating = false;
			} );
	
			surface.getModel().emit( 'documentUpdate' );
		}

		if ( data.value === undefined ) {
			$( window ).on( 'beforeunload', this.beforeUnloadHandler );
		}

		this.afterSetup();
	} );

	this.commentBodyWidget.connect( this, {
		change: 'onInputChangeThrottled',
		cancel: 'teardown',
		submit: 'onConfirmClick'
	} );

	return this;
};

CommentWidget.prototype.afterSetup = function () {
	this.onInputChange();
};

CommentWidget.prototype.preparePreview = function ( wikitext ) {
	if ( this.getMode() !== 'source' ) {
		return $.Deferred().resolve().promise();
	}

	wikitext = wikitext !== undefined ? wikitext : '';
	wikitext = this.sanitizeWikitextLinebreaks( wikitext );

	if ( this.previewWikitext === wikitext ) {
		return $.Deferred().resolve().promise();
	}

	this.previewWikitext = wikitext;

	if ( this.previewRequest ) {
		this.previewRequest.abort();
		this.previewRequest = null;
	}

	if ( wikitext && !wikitext.includes( '~~' + '~~' ) ) {
		wikitext += ' <span style="opacity:.6;">--~~' + '~~</span>';
	}

	let parsePromise;

	if ( !wikitext ) {
		parsePromise = $.Deferred().resolve( null ).promise();
	} else {
		parsePromise = mw.user.acquireTempUserName().then( () => {
				this.previewRequest = new mw.Api().get( {
				action: 'parse',
				text: ( this.commentIndent || '' ) + wikitext,
				prop: [ 'text', 'modules', 'jsconfigvars' ],
				pst: true,
				disableeditsection: true,
				preview: true,
				useskin: mw.config.get( 'skin' ),
				contentmodel: 'wikitext',
				mobileformat: OO.ui.isMobile()
			} );

			return this.previewRequest;
		} );
	}

	return parsePromise.then( result => {
		this.$preview.html( result ? result.parse.text[ '*' ] : '' );
		this.$preview.find( '.mw-parser-output' ).css( 'margin-left', '0' );

		if ( result ) {
			mw.config.set( result.parse.jsconfigvars );
			mw.loader.load( result.parse.modulestyles );
			mw.loader.load( result.parse.modules );
		}
	} );
};

/**
 * Classe ModeTabSelectWidget
 * 
 * Copiata dall'estensione DiscussionTools.
 * Commit: 58c078437d183cd6f51d7e90ae88033ae659bf2f
 * https://gerrit.wikimedia.org/r/c/mediawiki/extensions/DiscussionTools/+/667029
 */

function ModeTabSelectWidget() {
	ModeTabSelectWidget.super.apply( this, arguments );
}

OO.inheritClass( ModeTabSelectWidget, OO.ui.TabSelectWidget );

ModeTabSelectWidget.prototype.onDocumentKeyDown = function ( e ) {
	if ( e.keyCode === OO.ui.Keys.SPACE ) {
		e = $.Event( e, { keyCode: OO.ui.Keys.ENTER } );
	}

	ModeTabSelectWidget.super.prototype.onDocumentKeyDown.call( this, e );
};

/**
 * Classe ModeTabOptionWidget
 * 
 * Copiata dall'estensione DiscussionTools.
 * Commit 58c078437d183cd6f51d7e90ae88033ae659bf2f
 * https://gerrit.wikimedia.org/r/c/mediawiki/extensions/DiscussionTools/+/667029
 */

function ModeTabOptionWidget() {
	ModeTabOptionWidget.super.apply( this, arguments );

	this.$element.addClass( 'ext-discussiontools-ui-modeTab' );
}

OO.inheritClass( ModeTabOptionWidget, OO.ui.TabOptionWidget );

ModeTabOptionWidget.static.highlightable = true;

/**
 * Classe dmCommentIndentNode
 */
dmCommentIndentNode = function VeDmCommentIndentNode() {
	dmCommentIndentNode.super.apply( this, arguments );
};

OO.inheritClass( dmCommentIndentNode, ve.dm.ListNode );

dmCommentIndentNode.static.name = 'commentindent';

dmCommentIndentNode.prototype.canHaveSlugBefore = function () {
	return false;
};

ve.dm.modelRegistry.register( dmCommentIndentNode );

/**
 * Classe ceCommentIndentNode
 */
ceCommentIndentNode = function VeCeCommentIndentNode() {
	ceCommentIndentNode.super.apply( this, arguments );
};

OO.inheritClass( ceCommentIndentNode, ve.ce.ListNode );

ceCommentIndentNode.static.name = 'commentindent';

ve.ce.nodeFactory.register( ceCommentIndentNode );

ve.dm.ListItemNode.static.parentNodeTypes = [ 'list', 'commentindent' ];

module.exports = CommentWidget;