| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- /**
- * @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/converters
- */
- import {
- rawSnippetTextToModelDocumentFragment,
- getPropertyAssociation
- } from './utils';
- /**
- * A model-to-view (both editing and data) converter for the `codeBlock` element.
- *
- * Sample input:
- *
- * <codeBlock language="javascript">foo();<softBreak></softBreak>bar();</codeBlock>
- *
- * Sample output (editing):
- *
- * <pre data-language="JavaScript"><code class="language-javascript">foo();<br />bar();</code></pre>
- *
- * Sample output (data, see {@link module:code-block/converters~modelToDataViewSoftBreakInsertion}):
- *
- * <pre><code class="language-javascript">foo();\nbar();</code></pre>
- *
- * @param {module:engine/model/model~Model} model
- * @param {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>} languageDefs The normalized language
- * configuration passed to the feature.
- * @param {Boolean} [useLabels=false] When `true`, the `<pre>` element will get a `data-language` attribute with a
- * human–readable label of the language. Used only in the editing.
- * @returns {Function} Returns a conversion callback.
- */
- export function modelToViewCodeBlockInsertion( model, languageDefs, useLabels = false ) {
- // Language CSS classes:
- //
- // {
- // php: 'language-php',
- // python: 'language-python',
- // javascript: 'js',
- // ...
- // }
- const languagesToClasses = getPropertyAssociation( languageDefs, 'language', 'class' );
- // Language labels:
- //
- // {
- // php: 'PHP',
- // python: 'Python',
- // javascript: 'JavaScript',
- // ...
- // }
- const languagesToLabels = getPropertyAssociation( languageDefs, 'language', 'label' );
- return ( evt, data, conversionApi ) => {
- const { writer, mapper, consumable } = conversionApi;
- if ( !consumable.consume( data.item, 'insert' ) ) {
- return;
- }
- const codeBlockLanguage = data.item.getAttribute( 'language' );
- const targetViewPosition = mapper.toViewPosition( model.createPositionBefore( data.item ) );
- const preAttributes = {};
- // Attributes added only in the editing view.
- if ( useLabels ) {
- preAttributes[ 'data-language' ] = languagesToLabels[ codeBlockLanguage ];
- preAttributes.spellcheck = 'false';
- }
- const pre = writer.createContainerElement( 'pre', preAttributes );
- const code = writer.createContainerElement( 'code', {
- class: languagesToClasses[ codeBlockLanguage ] || null
- } );
- writer.insert( writer.createPositionAt( pre, 0 ), code );
- writer.insert( targetViewPosition, pre );
- mapper.bindElements( data.item, code );
- };
- }
- /**
- * A model-to-data view converter for the new line (`softBreak`) separator.
- *
- * Sample input:
- *
- * <codeBlock ...>foo();<softBreak></softBreak>bar();</codeBlock>
- *
- * Sample output:
- *
- * <pre><code ...>foo();\nbar();</code></pre>
- *
- * @param {module:engine/model/model~Model} model
- * @returns {Function} Returns a conversion callback.
- */
- export function modelToDataViewSoftBreakInsertion( model ) {
- return ( evt, data, conversionApi ) => {
- if ( data.item.parent.name !== 'codeBlock' ) {
- return;
- }
- const { writer, mapper, consumable } = conversionApi;
- if ( !consumable.consume( data.item, 'insert' ) ) {
- return;
- }
- const position = mapper.toViewPosition( model.createPositionBefore( data.item ) );
- writer.insert( position, writer.createText( '\n' ) );
- };
- }
- /**
- * A view-to-model converter for `<pre>` with the `<code>` HTML.
- *
- * Sample input:
- *
- * <pre><code class="language-javascript">foo();\nbar();</code></pre>
- *
- * Sample output:
- *
- * <codeBlock language="javascript">foo();<softBreak></softBreak>bar();</codeBlock>
- *
- * @param {module:engine/view/view~View} editingView
- * @param {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>} languageDefs The normalized language
- * configuration passed to the feature.
- * @returns {Function} Returns a conversion callback.
- */
- export function dataViewToModelCodeBlockInsertion( editingView, languageDefs ) {
- // Language names associated with CSS classes:
- //
- // {
- // 'language-php': 'php',
- // 'language-python': 'python',
- // js: 'javascript',
- // ...
- // }
- const classesToLanguages = getPropertyAssociation( languageDefs, 'class', 'language' );
- const defaultLanguageName = languageDefs[ 0 ].language;
- return ( evt, data, conversionApi ) => {
- const viewItem = data.viewItem;
- const viewChild = viewItem.getChild( 0 );
- if ( !viewChild || !viewChild.is( 'element', 'code' ) ) {
- return;
- }
- const { consumable, writer } = conversionApi;
- if ( !consumable.test( viewItem, { name: true } ) || !consumable.test( viewChild, { name: true } ) ) {
- return;
- }
- const codeBlock = writer.createElement( 'codeBlock' );
- const viewChildClasses = [ ...viewChild.getClassNames() ];
- // As we're to associate each class with a model language, a lack of class (empty class) can be
- // also associated with a language if the language definition was configured so. Pushing an empty
- // string to make sure the association will work.
- if ( !viewChildClasses.length ) {
- viewChildClasses.push( '' );
- }
- // Figure out if any of the <code> element's class names is a valid programming
- // language class. If so, use it on the model element (becomes the language of the entire block).
- for ( const className of viewChildClasses ) {
- const language = classesToLanguages[ className ];
- if ( language ) {
- writer.setAttribute( 'language', language, codeBlock );
- break;
- }
- }
- // If no language value was set, use the default language from the config.
- if ( !codeBlock.hasAttribute( 'language' ) ) {
- writer.setAttribute( 'language', defaultLanguageName, codeBlock );
- }
- // HTML elements are invalid content for `<code>`.
- // Read only text nodes.
- const textData = [ ...editingView.createRangeIn( viewChild ) ]
- .filter( current => current.type === 'text' )
- .map( ( { item } ) => item.data )
- .join( '' );
- const fragment = rawSnippetTextToModelDocumentFragment( writer, textData );
- writer.append( fragment, codeBlock );
- // Let's try to insert code block.
- if ( !conversionApi.safeInsert( codeBlock, data.modelCursor ) ) {
- return;
- }
- consumable.consume( viewItem, { name: true } );
- consumable.consume( viewChild, { name: true } );
- conversionApi.updateConversionResult( codeBlock, data );
- };
- }
|