File: //home/arjun/projects/buyercall/node_modules/@ckeditor/ckeditor5-code-block/src/utils.js
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module code-block/utils
*/
import { first } from 'ckeditor5/src/utils';
/**
* Returns code block languages as defined in `config.codeBlock.languages` but processed:
*
* * To consider the editor localization, i.e. to display {@link module:code-block/codeblock~CodeBlockLanguageDefinition}
* in the correct language. There is no way to use {@link module:utils/locale~Locale#t} when the user
* configuration is defined because the editor does not exist yet.
* * To make sure each definition has a CSS class associated with it even if not specified
* in the original configuration.
*
* @param {module:core/editor/editor~Editor} editor
* @returns {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>}.
*/
export function getNormalizedAndLocalizedLanguageDefinitions( editor ) {
const t = editor.t;
const languageDefs = editor.config.get( 'codeBlock.languages' );
for ( const def of languageDefs ) {
if ( def.label === 'Plain text' ) {
def.label = t( 'Plain text' );
}
if ( def.class === undefined ) {
def.class = `language-${ def.language }`;
}
}
return languageDefs;
}
/**
* Returns an object associating certain language definition properties with others. For instance:
*
* For:
*
* const definitions = {
* { language: 'php', class: 'language-php', label: 'PHP' },
* { language: 'javascript', class: 'js', label: 'JavaScript' },
* };
*
* getPropertyAssociation( definitions, 'class', 'language' );
*
* returns:
*
* {
* 'language-php': 'php'
* 'js': 'javascript'
* }
*
* and
*
* getPropertyAssociation( definitions, 'language', 'label' );
*
* returns:
*
* {
* 'php': 'PHP'
* 'javascript': 'JavaScript'
* }
*
* @param {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>}
* @param {String} key
* @param {String} value
* @param {Object.<String,String>}
*/
export function getPropertyAssociation( languageDefs, key, value ) {
const association = {};
for ( const def of languageDefs ) {
if ( key === 'class' ) {
// Only the first class is considered.
association[ def[ key ].split( ' ' ).shift() ] = def[ value ];
} else {
association[ def[ key ] ] = def[ value ];
}
}
return association;
}
/**
* For a given model text node, it returns white spaces that precede other characters in that node.
* This corresponds to the indentation part of the code block line.
*
* @param {module:engine/model/text~Text} codeLineNodes
* @returns {String}
*/
export function getLeadingWhiteSpaces( textNode ) {
return textNode.data.match( /^(\s*)/ )[ 0 ];
}
/**
* For plain text containing the code (a snippet), it returns a document fragment containing
* view text nodes separated by `<br>` elements (in place of new line characters "\n"), for instance:
*
* Input:
*
* "foo()\n
* bar()"
*
* Output:
*
* <DocumentFragment>
* "foo()"
* <br/>
* "bar()"
* </DocumentFragment>
*
* @param {module:engine/view/upcastwriter~UpcastWriter} writer
* @param {String} text The raw code text to be converted.
* @returns {module:engine/view/documentfragment~DocumentFragment}
*/
export function rawSnippetTextToViewDocumentFragment( writer, text ) {
const fragment = writer.createDocumentFragment();
const textLines = text.split( '\n' );
const nodes = textLines.reduce( ( nodes, line, lineIndex ) => {
nodes.push( line );
if ( lineIndex < textLines.length - 1 ) {
nodes.push( writer.createElement( 'br' ) );
}
return nodes;
}, [] );
writer.appendChild( nodes, fragment );
return fragment;
}
/**
* Returns an array of all model positions within the selection that represent code block lines.
*
* If the selection is collapsed, it returns the exact selection anchor position:
*
* <codeBlock>[]foo</codeBlock> -> <codeBlock>^foo</codeBlock>
* <codeBlock>foo[]bar</codeBlock> -> <codeBlock>foo^bar</codeBlock>
*
* Otherwise, it returns positions **before** each text node belonging to all code blocks contained by the selection:
*
* <codeBlock> <codeBlock>
* foo[bar ^foobar
* <softBreak></softBreak> -> <softBreak></softBreak>
* baz]qux ^bazqux
* </codeBlock> </codeBlock>
*
* It also works across other non–code blocks:
*
* <codeBlock> <codeBlock>
* foo[bar ^foobar
* </codeBlock> </codeBlock>
* <paragraph>text</paragraph> -> <paragraph>text</paragraph>
* <codeBlock> <codeBlock>
* baz]qux ^bazqux
* </codeBlock> </codeBlock>
*
* **Note:** The positions are in reverse order so they do not get outdated when iterating over them and
* the writer inserts or removes elements at the same time.
*
* **Note:** The position is located after the leading white spaces in the text node.
*
* @param {module:engine/model/model~Model} model
* @returns {Array.<module:engine/model/position~Position>}
*/
export function getIndentOutdentPositions( model ) {
const selection = model.document.selection;
const positions = [];
// When the selection is collapsed, there's only one position we can indent or outdent.
if ( selection.isCollapsed ) {
positions.push( selection.anchor );
}
// When the selection is NOT collapsed, collect all positions starting before text nodes
// (code lines) in any <codeBlock> within the selection.
else {
// Walk backward so positions we are about to collect here do not get outdated when
// inserting or deleting using the writer.
const walker = selection.getFirstRange().getWalker( {
ignoreElementEnd: true,
direction: 'backward'
} );
for ( const { item } of walker ) {
if ( item.is( '$textProxy' ) && item.parent.is( 'element', 'codeBlock' ) ) {
const leadingWhiteSpaces = getLeadingWhiteSpaces( item.textNode );
const { parent, startOffset } = item.textNode;
// Make sure the position is after all leading whitespaces in the text node.
const position = model.createPositionAt( parent, startOffset + leadingWhiteSpaces.length );
positions.push( position );
}
}
}
return positions;
}
/**
* Checks if any of the blocks within the model selection is a code block.
*
* @param {module:engine/model/selection~Selection} selection
* @returns {Boolean}
*/
export function isModelSelectionInCodeBlock( selection ) {
const firstBlock = first( selection.getSelectedBlocks() );
return firstBlock && firstBlock.is( 'element', 'codeBlock' );
}