This repository has been archived by the owner on Jun 26, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from ckeditor/t/ckeditor5/436
Feature: Initial implementation of the code block feature. Closes ckeditor/ckeditor5#436. Closes ckeditor/ckeditor5#5664. Closes ckeditor/ckeditor5#5666.
- Loading branch information
Showing
25 changed files
with
3,950 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,20 @@ | ||
CKEditor 5 code block feature | ||
======================================== | ||
|
||
This is an initial package for development purposes. It does not contain code yet. | ||
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-code-block.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-code-block) | ||
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-code-block.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-code-block) | ||
[![Build Status](https://travis-ci.org/ckeditor/ckeditor5-code-block.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-code-block) | ||
[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-code-block/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-code-block?branch=master) | ||
<br> | ||
[![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-code-block/status.svg)](https://david-dm.org/ckeditor/ckeditor5-code-block) | ||
[![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-code-block/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-code-block?type=dev) | ||
|
||
This package implements the code block feature for CKEditor 5. | ||
|
||
## Documentation | ||
|
||
See the [`@ckeditor/ckeditor5-code-block` package](https://ckeditor.com/docs/ckeditor5/latest/api/code-block.html) page as well as the [Code block feature guide](https://ckeditor.com/docs/ckeditor5/latest/features/code-block.html) in [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/). | ||
|
||
## License | ||
|
||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file or [https://ckeditor.com/legal/ckeditor-oss-license](https://ckeditor.com/legal/ckeditor-oss-license). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"Insert code block": "A label of the button that allows inserting a new code block into the editor content.", | ||
"Plain text": "A language of the code block in the editor content when no specific programming language is associated with it." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/** | ||
* @module code-block/codeblock | ||
*/ | ||
|
||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; | ||
import CodeBlockEditing from './codeblockediting'; | ||
import CodeBlockUI from './codeblockui'; | ||
|
||
/** | ||
* The code block plugin. | ||
* | ||
* For more information about this feature check the package page. | ||
* | ||
* This is a "glue" plugin which loads the {@link module:code-block/codeblockediting~CodeBlockEditing code block editing feature} | ||
* and {@link module:code-block/codeblockui~CodeBlockUI code block UI feature}. | ||
* | ||
* @extends module:core/plugin~Plugin | ||
*/ | ||
export default class CodeBlock extends Plugin { | ||
/** | ||
* @inheritDoc | ||
*/ | ||
static get requires() { | ||
return [ CodeBlockEditing, CodeBlockUI ]; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
static get pluginName() { | ||
return 'CodeBlock'; | ||
} | ||
} | ||
|
||
/** | ||
* The configuration of the {@link module:code-block/codeblock~CodeBlock} feature. | ||
* | ||
* Read more in {@link module:code-block/codeblock~CodeBlockConfig}. | ||
* | ||
* @member {module:code-block/codeblock~CodeBlockConfig} module:core/editor/editorconfig~EditorConfig#codeBlock | ||
*/ | ||
|
||
/** | ||
* The configuration of the {@link module:code-block/codeblock~CodeBlock code block feature}. | ||
* | ||
* ClassicEditor | ||
* .create( editorElement, { | ||
* codeBlock: ... // Code block feature configuration. | ||
* } ) | ||
* .then( ... ) | ||
* .catch( ... ); | ||
* | ||
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}. | ||
* | ||
* @interface CodeBlockConfig | ||
*/ | ||
|
||
/** | ||
* The code block language descriptor. See {@link module:code-block/codeblock~CodeBlockConfig#languages} to learn more. | ||
* | ||
* { | ||
* language: 'javascript', | ||
* label: 'JavaScript' | ||
* } | ||
* | ||
* @typedef {Object} module:code-block/codeblock~CodeBlockLanguageDefinition | ||
* @property {String} language The name of the language that will be stored in the model attribute. Also, when `class` | ||
* is not specified, it will also be used to create the CSS class associated with the language (prefixed by "language-"). | ||
* @property {String} label The human–readable label associated with the language and displayed in the UI. | ||
* @property {String} [class] The CSS class associated with the language. When not specified the `language` | ||
* property is used to create a class prefixed by "language-". | ||
*/ | ||
|
||
/** | ||
* The list of code languages available in the user interface to choose for a particular code block. | ||
* | ||
* The language of the code block is represented as a CSS class (by default prefixed by "language-") set on the | ||
* `<code>` element, both when editing and in the editor data. The CSS class associated with the language | ||
* can be used by third–party code syntax highlighters to detect and apply the correct highlighting. | ||
* | ||
* For instance, this language configuration: | ||
* | ||
* ClassicEditor | ||
* .create( editorElement, { | ||
* codeBlock: { | ||
* languages: [ | ||
* // ... | ||
* { language: 'javascript', label: 'JavaScript' }, | ||
* // ... | ||
* ] | ||
* } | ||
* } ) | ||
* .then( ... ) | ||
* .catch( ... ); | ||
* | ||
* will result in the following structure of JavaScript code blocks in the editor editing and data: | ||
* | ||
* <pre><code class="language-javascript">window.alert( 'Hello world!' )</code></pre> | ||
* | ||
* You can customize the CSS class by specifying an optional `class` property in a language definition: | ||
* | ||
* ClassicEditor | ||
* .create( editorElement, { | ||
* codeBlock: { | ||
* languages: [ | ||
* // Do not render CSS class for the plain text code blocks. | ||
* { language: 'plaintext', label: 'Plain text', class: '' }, | ||
* | ||
* // Use the "php-code" class for PHP code blocks. | ||
* { language: 'php', label: 'PHP', class: 'php-code' }, | ||
* | ||
* // Use the "js" class for JavaScript code blocks. | ||
* { language: 'javascript', label: 'JavaScript', class: 'js' }, | ||
* | ||
* // Python code blocks will have the default "language-python" CSS class. | ||
* { language: 'python', label: 'Python' } | ||
* ] | ||
* } | ||
* } ) | ||
* .then( ... ) | ||
* .catch( ... ); | ||
* | ||
* The default value of the language configuration is as follows: | ||
* | ||
* languages: [ | ||
* { language: 'plaintext', label: 'Plain text' }, // The default language. | ||
* { language: 'c', label: 'C' }, | ||
* { language: 'cs', label: 'C#' }, | ||
* { language: 'cpp', label: 'C++' }, | ||
* { language: 'css', label: 'CSS' }, | ||
* { language: 'diff', label: 'Diff' }, | ||
* { language: 'xml', label: 'HTML/XML' }, | ||
* { language: 'java', label: 'Java' }, | ||
* { language: 'javascript', label: 'JavaScript' }, | ||
* { language: 'php', label: 'PHP' }, | ||
* { language: 'python', label: 'Python' }, | ||
* { language: 'ruby', label: 'Ruby' }, | ||
* { language: 'typescript', label: 'TypeScript' }, | ||
* ] | ||
* | ||
* **Note**: The first language defined in the configuration is considered the default one. This means it will be | ||
* applied to code blocks loaded from data that have no CSS `class` specified (or no matching `class` in the config). | ||
* It will also be used when creating new code blocks using the main UI button. By default it is "Plain text". | ||
* | ||
* @member {Array.<module:code-block/codeblock~CodeBlockLanguageDefinition>} module:code-block/codeblock~CodeBlockConfig#languages | ||
*/ | ||
|
||
/** | ||
* A sequence of characters inserted or removed from the code block lines when its indentation | ||
* is changed by the user, for instance, using <kbd>Tab</kbd> and <kbd>Shift</kbd>+<kbd>Tab</kbd> keys. | ||
* | ||
* The default value is a single tab character (" ", `\u0009` in Unicode). | ||
* | ||
* This configuration is used by `indentCodeBlock` and `outdentCodeBlock` commands (instances of | ||
* {@link module:code-block/indentcodeblockcommand~IndentCodeBlockCommand}). | ||
* | ||
* **Note**: Setting this configuration to `false` will disable the code block indentation commands | ||
* and associated keystrokes. | ||
* | ||
* @member {String} module:code-block/codeblock~CodeBlockConfig#indentSequence | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/** | ||
* @module code-block/codeblockcommand | ||
*/ | ||
|
||
import Command from '@ckeditor/ckeditor5-core/src/command'; | ||
import first from '@ckeditor/ckeditor5-utils/src/first'; | ||
import { getNormalizedAndLocalizedLanguageDefinitions } from './utils'; | ||
|
||
/** | ||
* The code block command plugin. | ||
* | ||
* @extends module:core/command~Command | ||
*/ | ||
export default class CodeBlockCommand extends Command { | ||
/** | ||
* Whether the selection starts in a code block. | ||
* | ||
* @observable | ||
* @readonly | ||
* @member {Boolean} #value | ||
*/ | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
refresh() { | ||
this.value = this._getValue(); | ||
this.isEnabled = this._checkEnabled(); | ||
} | ||
|
||
/** | ||
* Executes the command. When the command {@link #value is on}, all top-most code blocks within | ||
* the selection will be removed. If it is off, all selected blocks will be flattened and | ||
* wrapped by a code block. | ||
* | ||
* @fires execute | ||
* @param {Object} [options] Command options. | ||
* @param {Boolean} [options.forceValue] If set, it will force the command behavior. If `true`, the command will apply a code block, | ||
* otherwise the command will remove the code block. If not set, the command will act basing on its current value. | ||
*/ | ||
execute( options = {} ) { | ||
const editor = this.editor; | ||
const model = editor.model; | ||
const selection = model.document.selection; | ||
const normalizedLanguagesDefs = getNormalizedAndLocalizedLanguageDefinitions( editor ); | ||
const firstLanguageInConfig = normalizedLanguagesDefs[ 0 ]; | ||
|
||
const blocks = Array.from( selection.getSelectedBlocks() ); | ||
const value = ( options.forceValue === undefined ) ? !this.value : options.forceValue; | ||
const language = options.language || firstLanguageInConfig.language; | ||
|
||
model.change( writer => { | ||
if ( value ) { | ||
this._applyCodeBlock( writer, blocks, language ); | ||
} else { | ||
this._removeCodeBlock( writer, blocks ); | ||
} | ||
} ); | ||
} | ||
|
||
/** | ||
* Checks the command's {@link #value}. | ||
* | ||
* @private | ||
* @returns {Boolean} The current value. | ||
*/ | ||
_getValue() { | ||
const selection = this.editor.model.document.selection; | ||
const firstBlock = first( selection.getSelectedBlocks() ); | ||
const isCodeBlock = !!( firstBlock && firstBlock.is( 'codeBlock' ) ); | ||
|
||
return isCodeBlock ? firstBlock.getAttribute( 'language' ) : false; | ||
} | ||
|
||
/** | ||
* Checks whether the command can be enabled in the current context. | ||
* | ||
* @private | ||
* @returns {Boolean} Whether the command should be enabled. | ||
*/ | ||
_checkEnabled() { | ||
if ( this.value ) { | ||
return true; | ||
} | ||
|
||
const selection = this.editor.model.document.selection; | ||
const schema = this.editor.model.schema; | ||
|
||
const firstBlock = first( selection.getSelectedBlocks() ); | ||
|
||
if ( !firstBlock ) { | ||
return false; | ||
} | ||
|
||
return canBeCodeBlock( schema, firstBlock ); | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {module:engine/model/writer~Writer} writer | ||
* @param {Array.<module:engine/model/element~Element>} blocks | ||
* @param {String} [language] | ||
*/ | ||
_applyCodeBlock( writer, blocks, language ) { | ||
const schema = this.editor.model.schema; | ||
const allowedBlocks = blocks.filter( block => canBeCodeBlock( schema, block ) ); | ||
|
||
for ( const block of allowedBlocks ) { | ||
writer.rename( block, 'codeBlock' ); | ||
writer.setAttribute( 'language', language, block ); | ||
schema.removeDisallowedAttributes( [ block ], writer ); | ||
} | ||
|
||
allowedBlocks.reverse().forEach( ( currentBlock, i ) => { | ||
const nextBlock = allowedBlocks[ i + 1 ]; | ||
|
||
if ( currentBlock.previousSibling === nextBlock ) { | ||
writer.appendElement( 'softBreak', nextBlock ); | ||
writer.merge( writer.createPositionBefore( currentBlock ) ); | ||
} | ||
} ); | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {module:engine/model/writer~Writer} writer | ||
* @param {Array.<module:engine/model/element~Element>} blocks | ||
*/ | ||
_removeCodeBlock( writer, blocks ) { | ||
const codeBlocks = blocks.filter( block => block.is( 'codeBlock' ) ); | ||
|
||
for ( const block of codeBlocks ) { | ||
const range = writer.createRangeOn( block ); | ||
|
||
for ( const item of Array.from( range.getItems() ).reverse() ) { | ||
if ( item.is( 'softBreak' ) && item.parent.is( 'codeBlock' ) ) { | ||
const { position } = writer.split( writer.createPositionBefore( item ) ); | ||
|
||
writer.rename( position.nodeAfter, 'paragraph' ); | ||
writer.removeAttribute( 'language', position.nodeAfter ); | ||
writer.remove( item ); | ||
} | ||
} | ||
|
||
writer.rename( block, 'paragraph' ); | ||
writer.removeAttribute( 'language', block ); | ||
} | ||
} | ||
} | ||
|
||
function canBeCodeBlock( schema, element ) { | ||
if ( element.is( 'rootElement' ) || schema.isLimit( element ) ) { | ||
return false; | ||
} | ||
|
||
return schema.checkChild( element.parent, 'codeBlock' ); | ||
} |
Oops, something went wrong.