ckeditor5-image-upload-base64
Version:
The development environment of CKEditor 5 – the best browser-based rich text editor.
188 lines (163 loc) • 5.66 kB
JavaScript
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/outdentcodeblockcommand
*/
import Command from '@ckeditor/ckeditor5-core/src/command';
import {
getLeadingWhiteSpaces,
getIndentOutdentPositions,
isModelSelectionInCodeBlock
} from './utils';
/**
* The code block indentation decrease command plugin.
*
* @extends module:core/command~Command
*/
export default class OutdentCodeBlockCommand extends Command {
constructor( editor ) {
super( editor );
/**
* A sequence of characters removed from the line when the command is executed.
*
* @readonly
* @private
* @member {String}
*/
this._indentSequence = editor.config.get( 'codeBlock.indentSequence' );
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._checkEnabled();
}
/**
* Executes the command. When the command {@link #isEnabled is enabled}, the indentation of the
* code lines in the selection will be decreased.
*
* @fires execute
*/
execute() {
const editor = this.editor;
const model = editor.model;
model.change( writer => {
const positions = getIndentOutdentPositions( model );
// Outdent all positions, for instance assuming the indent sequence is 4x space (" "):
//
// <codeBlock>^foo</codeBlock> -> <codeBlock>foo</codeBlock>
//
// <codeBlock> ^bar</codeBlock> -> <codeBlock>bar</codeBlock>
//
// Also, when there is more than one position:
//
// <codeBlock>
// ^foobar
// <softBreak></softBreak>
// ^bazqux
// </codeBlock>
//
// ->
//
// <codeBlock>
// foobar
// <softBreak></softBreak>
// bazqux
// </codeBlock>
for ( const position of positions ) {
const range = getLastOutdentableSequenceRange( this.editor.model, position, this._indentSequence );
if ( range ) {
writer.remove( range );
}
}
} );
}
/**
* Checks whether the command can be enabled in the current context.
*
* @private
* @returns {Boolean} Whether the command should be enabled.
*/
_checkEnabled() {
if ( !this._indentSequence ) {
return false;
}
const model = this.editor.model;
if ( !isModelSelectionInCodeBlock( model.document.selection ) ) {
return false;
}
// Outdent command can execute only when there is an indent character sequence
// in some of the lines.
return getIndentOutdentPositions( model ).some( position => {
return getLastOutdentableSequenceRange( model, position, this._indentSequence );
} );
}
}
// For a position coming from `getIndentOutdentPositions()`, it returns the range representing
// the last occurrence of the indent sequence among the leading whitespaces of the code line the
// position represents.
//
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock>foo^</codeBlock> -> null
// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock> -> null
// <codeBlock> ^foo</codeBlock> -> null
// <codeBlock> ^foo</codeBlock> -> <codeBlock> [ ]foo</codeBlock>
// <codeBlock> ^foo bar</codeBlock> -> <codeBlock>[ ]foo bar</codeBlock>
//
// @param {<module:engine/model/model~Model>} model
// @param {<module:engine/model/position~Position>} position
// @param {String} sequence
// @returns {<module:engine/model/range~Range>|null}
function getLastOutdentableSequenceRange( model, position, sequence ) {
// Positions start before each text node (code line). Get the node corresponding to the position.
const nodeAtPosition = getCodeLineTextNodeAtPosition( position );
if ( !nodeAtPosition ) {
return null;
}
const leadingWhiteSpaces = getLeadingWhiteSpaces( nodeAtPosition );
const lastIndexOfSequence = leadingWhiteSpaces.lastIndexOf( sequence );
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock> ^foo</codeBlock> -> null
//
if ( lastIndexOfSequence + sequence.length !== leadingWhiteSpaces.length ) {
return null;
}
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock> ^foo</codeBlock> -> null
//
if ( lastIndexOfSequence === -1 ) {
return null;
}
const { parent, startOffset } = nodeAtPosition;
// Create a range that contains the **last** indent sequence among the leading whitespaces
// of the line.
//
// For instance, assuming the indent sequence is 4x space (" "):
//
// <codeBlock> ^foo</codeBlock> -> <codeBlock> [ ]foo</codeBlock>
//
return model.createRange(
model.createPositionAt( parent, startOffset + lastIndexOfSequence ),
model.createPositionAt( parent, startOffset + lastIndexOfSequence + sequence.length )
);
}
function getCodeLineTextNodeAtPosition( position ) {
// Positions start before each text node (code line). Get the node corresponding to the position.
let nodeAtPosition = position.parent.getChild( position.index );
// <codeBlock>foo^</codeBlock>
// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
nodeAtPosition = position.nodeBefore;
}
// <codeBlock>^</codeBlock>
// <codeBlock>foo^<softBreak></softBreak>bar</codeBlock>
if ( !nodeAtPosition || nodeAtPosition.is( 'element', 'softBreak' ) ) {
return null;
}
return nodeAtPosition;
}