Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Initial implementation of the code block feature #1

Merged
merged 82 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
09edfa1
Initial code block implementation.
oskarwrobel Oct 22, 2019
7596429
Introduced very basic manual test.
oskarwrobel Oct 22, 2019
f698c3c
Typo.
oskarwrobel Oct 22, 2019
646b0f8
Added automated tests for CodeBlock and CodeBlockUI plugins.
oskarwrobel Oct 22, 2019
e303f46
Added pluginName property and plugin dependency.
oskarwrobel Oct 23, 2019
430e499
Code improvements.
oskarwrobel Oct 23, 2019
3ac250e
Added tests for CodeBlockCommand plugin.
oskarwrobel Oct 23, 2019
07edaf3
Added test for CodeBlockEditing plugin.
oskarwrobel Oct 23, 2019
1c803e8
Bumped dependencies.
Oct 23, 2019
ed47dda
Added temporary icon.
Oct 23, 2019
24ee9b5
Improved data conversion to properly handle HTML inside a pre tag.
oskarwrobel Oct 24, 2019
ead8aa9
Minor docs improvement.
oskarwrobel Oct 24, 2019
8c491a5
Style improvements.
oskarwrobel Oct 24, 2019
7c366a4
Changed code block view structure from pre to pre > code.
oskarwrobel Oct 25, 2019
c7e1275
Changed new line character to <br> for the editing pipeline.
oskarwrobel Oct 25, 2019
0c3fe6d
Increased CC to 100%.
oskarwrobel Oct 25, 2019
aad8a51
Updated dependencies.
oskarwrobel Oct 25, 2019
b346b64
Improved `.ck-content` styles.
Oct 25, 2019
956c7b1
Updated SVG icon.
Oct 25, 2019
fe3f241
Improved manual test.
oskarwrobel Oct 25, 2019
f10781a
Polish the icon once again.
Oct 29, 2019
431bed8
Merge branch 'master' into t/ckeditor5/436
pomek Oct 29, 2019
e6930bb
Restored the proper content of README.md.
pomek Oct 29, 2019
691def7
Removed package-lock.
pomek Oct 29, 2019
7e57bcc
Tests: Fixed a wrong import in the manual test (should be relative).
oleq Oct 29, 2019
389b92a
The feature should handle clipboard input and make sure the existing …
oleq Nov 4, 2019
cf41cc2
Tests: Extended the manual test.
oleq Nov 4, 2019
50394c6
Tests: Added RTL manual test.
oleq Nov 5, 2019
e679fc5
Allowed setting code block language via the `CodeBlockCommand`.
oleq Nov 5, 2019
dc49a49
Tests: Extended the manual test with more data.
oleq Nov 5, 2019
9057a70
Fixed the code block appeareance in RTL content. Fixed the long code …
oleq Nov 5, 2019
dc054df
Moved layout block styles from ckeditor5-theme-lark.
oleq Nov 5, 2019
c96fdd8
Default feature config and the "language" attribute in the model foll…
oleq Nov 6, 2019
bcd71dc
Docs: Documented the feature config.
oleq Nov 6, 2019
c8fa809
Tests: Added CodeBlockCommand tests to check if the "language" option…
oleq Nov 6, 2019
cb713aa
Code refactoring.
oleq Nov 6, 2019
627453a
Make sure the "Plain text" label from the config is correctly transla…
oleq Nov 6, 2019
e5bcb95
Implemented the "codeBlock" dropdown with a split button and a list o…
oleq Nov 6, 2019
1e1078d
Make sure the code block splits parents when upcasting if not allowed…
oleq Nov 7, 2019
32ebac9
Tests: Added a test to check what happens if an unsplittable parent d…
oleq Nov 7, 2019
88c4a7f
Tests: "Fixed" CodeBlockEditing some failing assertions by adding a c…
oleq Nov 7, 2019
1a16fec
Double enter at the end of the code block should exit it.
oleq Nov 7, 2019
60ac249
Improvements to the code block CSS.
oleq Nov 7, 2019
55b3c91
Double shift enter should not leave the code block when executed at t…
oleq Nov 7, 2019
0883d18
Used tab-size for the pre for better look.
oleq Nov 7, 2019
bf53c02
Indentation retention when pressing enter.
oleq Nov 8, 2019
9c2b363
Ensured the code block is properlu copied to the clipboard (getSelect…
oleq Nov 12, 2019
7f9ccfe
Enabled the Autoformat plugin in the manual test.
oleq Nov 12, 2019
a539459
Used model writer to customize the getSelectedContent() method behavior.
oleq Nov 12, 2019
604f7ff
Do not customize getSelectedContent() when the selection is empty.
oleq Nov 12, 2019
7bd9b0a
Tests: Code refactoring.
oleq Nov 13, 2019
b3e69eb
Created the IndentCodeBlockCommand.
oleq Nov 14, 2019
60c3282
Enabled code block indent and outdent commands in the editing. Define…
oleq Nov 14, 2019
42177ec
Docs and code refactoring.
oleq Nov 14, 2019
31e21c8
Removed the dotAll flag from the extractDataFromCodeElement() regex d…
oleq Nov 14, 2019
a6b1338
Added missing dependencies to package.json.
oleq Nov 14, 2019
dc7daf8
Allowed leaving the code block by pressing enter in an empty line at …
oleq Nov 14, 2019
2812a64
Improved the feature icon.
oleq Nov 15, 2019
0c9065c
Tests: Added missing scenarios to feature's manual test.
oleq Nov 15, 2019
e8a8a09
Updated the feature button's label. Added the contexts.json file.
oleq Nov 15, 2019
aa04ce6
Tests: Fixed a failing test after change of the button's label.
oleq Nov 15, 2019
cd6ff0b
Make sure the "code" attribute is allowed when marking the output of …
oleq Nov 18, 2019
f5467b2
Enhance description of CodeBlockCommand#value tests.
jodator Nov 18, 2019
f71146c
Further enhance description of CodeBlockCommand tests.
jodator Nov 18, 2019
d89c775
Update description of indentcodeblockcommand tests.
jodator Nov 18, 2019
e6f9b30
Simplify canBeCodeBlock() schema check return value.
jodator Nov 18, 2019
ce8e35b
Remove superfluous schema child check for codeBlock in code block.
jodator Nov 18, 2019
011e4f5
Typo fix.
jodator Nov 18, 2019
9096a36
Use explicit schema definition for codeBlock.
jodator Nov 18, 2019
9e04100
Update tests/codeblock.js
oleq Nov 19, 2019
5c2731c
Update src/codeblockcommand.js
oleq Nov 19, 2019
416d93e
Update src/utils.js
oleq Nov 19, 2019
fef2daa
Update src/codeblockediting.js
oleq Nov 19, 2019
433ee42
Changed the language classes in the default config for the out-of-the…
oleq Nov 19, 2019
2a04f58
The code block should not be squashed in a table cell.
oleq Nov 19, 2019
a86b1a4
Code refactoring.
oleq Nov 19, 2019
6b525b9
Code refactoring.
oleq Nov 19, 2019
60e2c9b
Created separated IndentCodeBlockCommand and OutdentCodeBlockCommand …
oleq Nov 19, 2019
c6e69ef
Let's give the code block even more air in tight spaces.
oleq Nov 19, 2019
c2797b1
Minor fix.
oleq Nov 19, 2019
da2349f
Changed the configuration format (CodeBlockLanguageDefinition). CSS c…
oleq Nov 20, 2019
4f3b521
Remove the link to the API page that could break CI.
jodator Nov 20, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
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).
4 changes: 4 additions & 0 deletions lang/contexts.json
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."
}
17 changes: 14 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ckeditor/ckeditor5-code-block",
"version": "0.0.1",
"description": "Block Quote feature for CKEditor 5.",
"description": "Code Block feature for CKEditor 5.",
"keywords": [
"ckeditor",
"ckeditor5",
Expand All @@ -10,11 +10,22 @@
"ckeditor5-plugin"
],
"dependencies": {
"@ckeditor/ckeditor5-core": "^12.3.0",
"@ckeditor/ckeditor5-utils": "^14.0.0",
"@ckeditor/ckeditor5-core": "^15.0.0",
"@ckeditor/ckeditor5-enter": "^15.0.0",
"@ckeditor/ckeditor5-ui": "^15.0.0",
"@ckeditor/ckeditor5-utils": "^15.0.0",
"lodash-es": "^4.17.10"
},
"devDependencies": {
"@ckeditor/ckeditor5-alignment": "^15.0.0",
"@ckeditor/ckeditor5-autoformat": "^15.0.0",
"@ckeditor/ckeditor5-basic-styles": "^15.0.0",
"@ckeditor/ckeditor5-block-quote": "^15.0.0",
"@ckeditor/ckeditor5-editor-classic": "^15.0.0",
"@ckeditor/ckeditor5-engine": "^15.0.0",
"@ckeditor/ckeditor5-indent": "^15.0.0",
"@ckeditor/ckeditor5-paragraph": "^15.0.0",
"@ckeditor/ckeditor5-undo": "^15.0.0",
"eslint": "^5.5.0",
"eslint-config-ckeditor5": "^2.0.0",
"husky": "^1.3.1",
Expand Down
166 changes: 166 additions & 0 deletions src/codeblock.js
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
*/
162 changes: 162 additions & 0 deletions src/codeblockcommand.js
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' );
}
Loading