Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ckeditor/ckeditor5-heading

Package Overview
Dependencies
Maintainers
1
Versions
706
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-heading - npm Package Compare versions

Comparing version 0.7.0 to 0.8.0

.github/PULL_REQUEST_TEMPLATE.md

22

package.json
{
"name": "@ckeditor/ckeditor5-heading",
"version": "0.7.0",
"version": "0.8.0",
"description": "Headings feature for CKEditor 5.",
"keywords": [],
"dependencies": {
"@ckeditor/ckeditor5-core": "*",
"@ckeditor/ckeditor5-ui": "*",
"@ckeditor/ckeditor5-utils": "*",
"@ckeditor/ckeditor5-engine": "*",
"@ckeditor/ckeditor5-paragraph": "*"
"@ckeditor/ckeditor5-core": "^0.7.0",
"@ckeditor/ckeditor5-ui": "^0.7.1",
"@ckeditor/ckeditor5-utils": "^0.8.0",
"@ckeditor/ckeditor5-engine": "^0.8.0",
"@ckeditor/ckeditor5-paragraph": "^0.6.1"
},
"devDependencies": {
"@ckeditor/ckeditor5-dev-lint": "^2.0.0",
"@ckeditor/ckeditor5-enter": "*",
"@ckeditor/ckeditor5-editor-classic": "*",
"@ckeditor/ckeditor5-typing": "*",
"@ckeditor/ckeditor5-undo": "*",
"@ckeditor/ckeditor5-dev-lint": "^2.0.2",
"@ckeditor/ckeditor5-enter": "^0.8.0",
"@ckeditor/ckeditor5-editor-classic": "^0.7.1",
"@ckeditor/ckeditor5-typing": "^0.8.0",
"@ckeditor/ckeditor5-undo": "^0.7.1",
"gulp": "^3.9.0",

@@ -20,0 +20,0 @@ "guppy-pre-commit": "^0.4.0"

@@ -39,10 +39,9 @@ /**

const command = editor.commands.get( 'heading' );
const formats = command.formats;
const options = command.options;
const collection = new Collection();
// Add formats to collection.
for ( let format of formats ) {
// Add options to collection.
for ( const { id, label } of options ) {
collection.add( new Model( {
formatId: format.id,
label: format.label
id, label
} ) );

@@ -53,3 +52,2 @@ }

const dropdownModel = new Model( {
label: 'Heading',
withText: true,

@@ -61,3 +59,3 @@ items: collection

dropdownModel.bind( 'isEnabled' ).to( command, 'isEnabled' );
dropdownModel.bind( 'label' ).to( command, 'value', format => format.label );
dropdownModel.bind( 'label' ).to( command, 'value', option => option.label );

@@ -69,4 +67,4 @@ // Register UI component.

// Execute command when an item from the dropdown is selected.
this.listenTo( dropdown, 'execute', ( { source: { formatId } } ) => {
editor.execute( 'heading', { formatId } );
this.listenTo( dropdown, 'execute', ( { source: { id } } ) => {
editor.execute( 'heading', { id } );
editor.editing.view.focus();

@@ -73,0 +71,0 @@ } );

@@ -23,23 +23,32 @@ /**

* @param {module:core/editor/editor~Editor} editor Editor instance.
* @param {Array.<module:heading/headingcommand~HeadingFormat>} formats Heading formats to be used by the command instance.
* @param {Array.<module:heading/headingcommand~HeadingOption>} options Heading options to be used by the command instance.
*/
constructor( editor, formats ) {
constructor( editor, options, defaultOptionId ) {
super( editor );
/**
* Heading formats used by this command.
* Heading options used by this command.
*
* @readonly
* @member {module:heading/headingcommand~HeadingFormat}
* @member {module:heading/headingcommand~HeadingOption}
*/
this.formats = formats;
this.options = options;
/**
* The currently selected heading format.
* The id of the default option among {@link #options}.
*
* @readonly
* @private
* @member {module:heading/headingcommand~HeadingOption#id}
*/
this._defaultOptionId = defaultOptionId;
/**
* The currently selected heading option.
*
* @readonly
* @observable
* @member {module:heading/headingcommand~HeadingFormat} #value
* @member {module:heading/headingcommand~HeadingOption} #value
*/
this.set( 'value', this.defaultFormat );
this.set( 'value', this.defaultOption );

@@ -51,9 +60,9 @@ // Update current value each time changes are done on document.

/**
* The default format.
* The default option.
*
* @member {module:heading/headingcommand~HeadingFormat} #defaultFormat
* @member {module:heading/headingcommand~HeadingOption} #defaultOption
*/
get defaultFormat() {
get defaultOption() {
// See https://github.com/ckeditor/ckeditor5/issues/98.
return this._getFormatById( 'paragraph' );
return this._getOptionById( this._defaultOptionId );
}

@@ -66,6 +75,6 @@

* @param {Object} [options] Options for executed command.
* @param {String} [options.formatId] The identifier of the heading format that should be applied. It should be one of the
* {@link module:heading/headingcommand~HeadingFormat heading formats} provided to the command constructor. If this parameter is not
* @param {String} [options.id] The identifier of the heading option that should be applied. It should be one of the
* {@link module:heading/headingcommand~HeadingOption heading options} provided to the command constructor. If this parameter is not
* provided,
* the value from {@link #defaultFormat defaultFormat} will be used.
* the value from {@link #defaultOption defaultOption} will be used.
* @param {module:engine/model/batch~Batch} [options.batch] Batch to collect all the change steps.

@@ -75,4 +84,4 @@ * New batch will be created if this option is not set.

_doExecute( options = {} ) {
// TODO: What should happen if format is not found?
const formatId = options.formatId || this.defaultFormat.id;
// TODO: What should happen if option is not found?
const id = options.id || this.defaultOption.id;
const doc = this.editor.document;

@@ -85,6 +94,6 @@ const selection = doc.selection;

const isSelectionBackward = selection.isBackward;
// If current format is same as new format - toggle already applied format back to default one.
const shouldRemove = ( formatId === this.value.id );
// If current option is same as new option - toggle already applied option back to default one.
const shouldRemove = ( id === this.value.id );
// Collect elements to change format.
// Collect elements to change option.
// This implementation may not be future proof but it's satisfactory at this stage.

@@ -115,11 +124,11 @@ if ( selection.isCollapsed ) {

for ( let element of elements ) {
// When removing applied format.
// When removing applied option.
if ( shouldRemove ) {
if ( element.name === formatId ) {
batch.rename( element, this.defaultFormat.id );
if ( element.name === id ) {
batch.rename( element, this.defaultOption.id );
}
}
// When applying new format.
// When applying new option.
else {
batch.rename( element, formatId );
batch.rename( element, id );
}

@@ -135,10 +144,10 @@ }

/**
* Returns the format by a given ID.
* Returns the option by a given ID.
*
* @private
* @param {String} id
* @returns {module:heading/headingcommand~HeadingFormat}
* @returns {module:heading/headingcommand~HeadingOption}
*/
_getFormatById( id ) {
return this.formats.find( item => item.id === id ) || this.defaultFormat;
_getOptionById( id ) {
return this.options.find( item => item.id === id ) || this.defaultOption;
}

@@ -156,3 +165,3 @@

if ( block ) {
this.value = this._getFormatById( block.name );
this.value = this._getOptionById( block.name );
}

@@ -188,8 +197,8 @@ }

/**
* Heading format descriptor.
* Heading option descriptor.
*
* @typedef {Object} module:heading/headingcommand~HeadingFormat
* @property {String} id Format identifier. It will be used as the element's name in the model.
* @property {String} viewElement The name of the view element that will be used to represent the model element in the view.
* @property {String} label The display name of the format.
* @typedef {Object} module:heading/headingcommand~HeadingOption
* @property {String} id Option identifier. It will be used as the element's name in the model.
* @property {String} element The name of the view element that will be used to represent the model element in the view.
* @property {String} label The display name of the option.
*/

@@ -16,8 +16,3 @@ /**

const formats = [
{ id: 'paragraph', viewElement: 'p', label: 'Paragraph' },
{ id: 'heading1', viewElement: 'h2', label: 'Heading 1' },
{ id: 'heading2', viewElement: 'h3', label: 'Heading 2' },
{ id: 'heading3', viewElement: 'h4', label: 'Heading 3' }
];
const defaultOptionId = 'paragraph';

@@ -34,2 +29,18 @@ /**

*/
constructor( editor ) {
super( editor );
editor.config.define( 'heading', {
options: [
{ id: 'paragraph', element: 'p', label: 'Paragraph' },
{ id: 'heading1', element: 'h2', label: 'Heading 1' },
{ id: 'heading2', element: 'h3', label: 'Heading 2' },
{ id: 'heading3', element: 'h4', label: 'Heading 3' }
]
} );
}
/**
* @inheritDoc
*/
static get requires() {

@@ -46,18 +57,19 @@ return [ Paragraph ];

const editing = editor.editing;
const options = this._getLocalizedOptions();
for ( let format of formats ) {
for ( let option of options ) {
// Skip paragraph - it is defined in required Paragraph feature.
if ( format.id !== 'paragraph' ) {
if ( option.id !== defaultOptionId ) {
// Schema.
editor.document.schema.registerItem( format.id, '$block' );
editor.document.schema.registerItem( option.id, '$block' );
// Build converter from model to view for data and editing pipelines.
buildModelConverter().for( data.modelToView, editing.modelToView )
.fromElement( format.id )
.toElement( format.viewElement );
.fromElement( option.id )
.toElement( option.element );
// Build converter from view to model for data pipeline.
buildViewConverter().for( data.viewToModel )
.fromElement( format.viewElement )
.toElement( format.id );
.fromElement( option.element )
.toElement( option.id );
}

@@ -67,3 +79,3 @@ }

// Register the heading command.
const command = new HeadingCommand( editor, formats );
const command = new HeadingCommand( editor, options, defaultOptionId );
editor.commands.set( 'heading', command );

@@ -78,6 +90,6 @@ }

// Enter at the end of a heading element should create a paragraph.
const editor = this.editor;
const command = editor.commands.get( 'heading' );
const enterCommand = editor.commands.get( 'enter' );
const options = this._getLocalizedOptions();

@@ -88,6 +100,6 @@ if ( enterCommand ) {

const batch = data.batch;
const isHeading = formats.some( ( format ) => format.id == positionParent.name );
const isHeading = options.some( option => option.id == positionParent.name );
if ( isHeading && positionParent.name != command.defaultFormat.id && positionParent.childCount === 0 ) {
batch.rename( positionParent, command.defaultFormat.id );
if ( isHeading && positionParent.name != command.defaultOption.id && positionParent.childCount === 0 ) {
batch.rename( positionParent, command.defaultOption.id );
}

@@ -97,2 +109,50 @@ } );

}
/**
* Returns heading options as defined in `config.heading.options` but processed to consider
* editor localization, i.e. to display {@link module:heading/headingcommand~HeadingOption#label}
* in the correct language.
*
* Note: The reason behind this method is that there's no way to use {@link utils/locale~Locale#t}
* when the user config is defined because the editor does not exist yet.
*
* @private
* @returns {Array.<module:heading/headingcommand~HeadingOption>}.
*/
_getLocalizedOptions() {
if ( this._cachedLocalizedOptions ) {
return this._cachedLocalizedOptions;
}
const editor = this.editor;
const t = editor.t;
const localizedLabels = {
Paragraph: t( 'Paragraph' ),
'Heading 1': t( 'Heading 1' ),
'Heading 2': t( 'Heading 2' ),
'Heading 3': t( 'Heading 3' )
};
/**
* Cached localized version of `config.heading.options` generated by
* {@link module:heading/headingengine~HeadingEngine#_localizedOptions}.
*
* @private
* @readonly
* @member {Array.<module:heading/headingcommand~HeadingOption>} #_cachedLocalizedOptions
*/
this._cachedLocalizedOptions = editor.config.get( 'heading.options' )
.map( option => {
if ( localizedLabels[ option.label ] ) {
// Clone the option to avoid altering the original `config.heading.options`.
option = Object.assign( {}, option, {
label: localizedLabels[ option.label ]
} );
}
return option;
} );
return this._cachedLocalizedOptions;
}
}

@@ -12,4 +12,11 @@ /**

import DropdownView from '@ckeditor/ckeditor5-ui/src/dropdown/dropdownview';
import { add } from '@ckeditor/ckeditor5-utils/src/translation-service';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
add( 'pl', {
'Paragraph': 'Akapit',
'Heading 1': 'Nagłówek 1',
'Heading 2': 'Nagłówek 2',
} );
testUtils.createSinonSandbox();

@@ -47,3 +54,3 @@

describe( 'init()', () => {
it( 'should register formats feature component', () => {
it( 'should register options feature component', () => {
const dropdown = editor.ui.componentFactory.create( 'headings' );

@@ -61,7 +68,7 @@

dropdown.formatId = 'foo';
dropdown.id = 'foo';
dropdown.fire( 'execute' );
sinon.assert.calledOnce( executeSpy );
sinon.assert.calledWithExactly( executeSpy, 'heading', { formatId: 'foo' } );
sinon.assert.calledWithExactly( executeSpy, 'heading', { id: 'foo' } );
} );

@@ -93,7 +100,59 @@

expect( dropdown.buttonView.label ).to.equal( 'Paragraph' );
command.value = command.formats[ 1 ];
command.value = command.options[ 1 ];
expect( dropdown.buttonView.label ).to.equal( 'Heading 1' );
} );
} );
describe( 'localization', () => {
let command;
beforeEach( () => {
const editorElement = document.createElement( 'div' );
return ClassicTestEditor.create( editorElement, {
plugins: [ Heading ],
toolbar: [ 'heading' ],
lang: 'pl',
heading: {
options: [
{ id: 'paragraph', element: 'p', label: 'Paragraph' },
{ id: 'heading1', element: 'h2', label: 'Heading 1' },
{ id: 'heading2', element: 'h3', label: 'Not automatically localized' }
]
}
} )
.then( newEditor => {
editor = newEditor;
dropdown = editor.ui.componentFactory.create( 'headings' );
command = editor.commands.get( 'heading' );
} );
} );
it( 'does not alter the original config', () => {
expect( editor.config.get( 'heading.options' ) ).to.deep.equal( [
{ id: 'paragraph', element: 'p', label: 'Paragraph' },
{ id: 'heading1', element: 'h2', label: 'Heading 1' },
{ id: 'heading2', element: 'h3', label: 'Not automatically localized' }
] );
} );
it( 'works for the #buttonView', () => {
const buttonView = dropdown.buttonView;
expect( buttonView.label ).to.equal( 'Akapit' );
command.value = command.options[ 1 ];
expect( buttonView.label ).to.equal( 'Nagłówek 1' );
} );
it( 'works for the listView#items in the panel', () => {
const listView = dropdown.listView;
expect( listView.items.map( item => item.label ) ).to.deep.equal( [
'Akapit',
'Nagłówek 1',
'Not automatically localized'
] );
} );
} );
} );
} );

@@ -11,7 +11,7 @@ /**

const formats = [
{ id: 'paragraph', viewElement: 'p', default: true },
{ id: 'heading1', viewElement: 'h2' },
{ id: 'heading2', viewElement: 'h3' },
{ id: 'heading3', viewElement: 'h4' }
const options = [
{ id: 'paragraph', element: 'p' },
{ id: 'heading1', element: 'h2' },
{ id: 'heading2', element: 'h3' },
{ id: 'heading3', element: 'h4' }
];

@@ -23,15 +23,14 @@

beforeEach( () => {
return ModelTestEditor.create()
.then( newEditor => {
editor = newEditor;
document = editor.document;
command = new HeadingCommand( editor, formats );
schema = document.schema;
return ModelTestEditor.create().then( newEditor => {
editor = newEditor;
document = editor.document;
command = new HeadingCommand( editor, options, 'paragraph' );
schema = document.schema;
for ( let format of formats ) {
schema.registerItem( format.id, '$block' );
}
for ( let option of options ) {
schema.registerItem( option.id, '$block' );
}
root = document.getRoot();
} );
root = document.getRoot();
} );
} );

@@ -44,17 +43,17 @@

describe( 'value', () => {
for ( let format of formats ) {
test( format );
for ( let option of options ) {
test( option );
}
function test( format ) {
it( `equals ${ format.id } when collapsed selection is placed inside ${ format.id } element`, () => {
setData( document, `<${ format.id }>foobar</${ format.id }>` );
function test( option ) {
it( `equals ${ option.id } when collapsed selection is placed inside ${ option.id } element`, () => {
setData( document, `<${ option.id }>foobar</${ option.id }>` );
const element = root.getChild( 0 );
document.selection.addRange( Range.createFromParentsAndOffsets( element, 3, element, 3 ) );
expect( command.value ).to.equal( format );
expect( command.value ).to.equal( option );
} );
}
it( 'should be equal to defaultFormat if format has not been found', () => {
it( 'should be equal to #defaultOption if option has not been found', () => {
schema.registerItem( 'div', '$block' );

@@ -65,3 +64,3 @@ setData( document, '<div>xyz</div>' );

expect( command.value ).to.equal( command.defaultFormat );
expect( command.value ).to.equal( command.defaultOption );
} );

@@ -73,3 +72,3 @@ } );

setData( document, '<paragraph>[]</paragraph>' );
command._doExecute( { formatId: 'heading1' } );
command._doExecute( { id: 'heading1' } );

@@ -79,3 +78,3 @@ expect( getData( document ) ).to.equal( '<heading1>[]</heading1>' );

expect( command.value.id ).to.equal( 'heading1' );
expect( command.value.viewElement ).to.equal( 'h2' );
expect( command.value.element ).to.equal( 'h2' );
} );

@@ -97,7 +96,7 @@

describe( 'collapsed selection', () => {
let convertTo = formats[ formats.length - 1 ];
let convertTo = options[ options.length - 1 ];
for ( let format of formats ) {
test( format, convertTo );
convertTo = format;
for ( let option of options ) {
test( option, convertTo );
convertTo = option;
}

@@ -112,5 +111,5 @@

it( 'converts to default format when executed with already applied format', () => {
it( 'converts to default option when executed with already applied option', () => {
setData( document, '<heading1>foo[]bar</heading1>' );
command._doExecute( { formatId: 'heading1' } );
command._doExecute( { id: 'heading1' } );

@@ -125,3 +124,3 @@ expect( getData( document ) ).to.equal( '<paragraph>foo[]bar</paragraph>' );

setData( document, '<heading1><inlineImage>foo[]</inlineImage>bar</heading1>' );
command._doExecute( { formatId: 'heading1' } );
command._doExecute( { id: 'heading1' } );

@@ -134,3 +133,3 @@ expect( getData( document ) ).to.equal( '<paragraph><inlineImage>foo[]</inlineImage>bar</paragraph>' );

setData( document, `<${ from.id }>foo[]bar</${ from.id }>` );
command._doExecute( { formatId: to.id } );
command._doExecute( { id: to.id } );

@@ -143,7 +142,7 @@ expect( getData( document ) ).to.equal( `<${ to.id }>foo[]bar</${ to.id }>` );

describe( 'non-collapsed selection', () => {
let convertTo = formats[ formats.length - 1 ];
let convertTo = options[ options.length - 1 ];
for ( let format of formats ) {
test( format, convertTo );
convertTo = format;
for ( let option of options ) {
test( option, convertTo );
convertTo = option;
}

@@ -153,3 +152,3 @@

setData( document, '<heading1>foo[</heading1><heading2>bar</heading2><heading2>]baz</heading2>' );
command._doExecute( { formatId: 'paragraph' } );
command._doExecute( { id: 'paragraph' } );

@@ -161,5 +160,5 @@ expect( getData( document ) ).to.equal(

it( 'resets to default value all elements with same format', () => {
it( 'resets to default value all elements with same option', () => {
setData( document, '<heading1>foo[</heading1><heading1>bar</heading1><heading2>baz</heading2>]' );
command._doExecute( { formatId: 'heading1' } );
command._doExecute( { id: 'heading1' } );

@@ -174,3 +173,3 @@ expect( getData( document ) ).to.equal(

setData( document, `<${ from.id }>foo[bar</${ from.id }><${ from.id }>baz]qux</${ from.id }>` );
command._doExecute( { formatId: to.id } );
command._doExecute( { id: to.id } );

@@ -177,0 +176,0 @@ expect( getData( document ) ).to.equal( `<${ to.id }>foo[bar</${ to.id }><${ to.id }>baz]qux</${ to.id }>` );

@@ -11,4 +11,12 @@ /**

import Enter from '@ckeditor/ckeditor5-enter/src/enter';
import { add } from '@ckeditor/ckeditor5-utils/src/translation-service';
import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
add( 'pl', {
'Paragraph': 'Akapit',
'Heading 1': 'Nagłówek 1',
'Heading 2': 'Nagłówek 2',
'Heading 3': 'Nagłówek 3',
} );
describe( 'HeadingEngine', () => {

@@ -50,3 +58,3 @@ let editor, document;

it( 'should register format command', () => {
it( 'should register option command', () => {
expect( editor.commands.has( 'heading' ) ).to.be.true;

@@ -79,3 +87,3 @@ const command = editor.commands.get( 'heading' );

it( 'should make enter command insert a defaultFormat block if selection ended at the end of heading block', () => {
it( 'should make enter command insert a defaultOption block if selection ended at the end of heading block', () => {
editor.setData( '<h2>foobar</h2>' );

@@ -98,2 +106,43 @@ document.selection.collapse( document.getRoot().getChild( 0 ), 'end' );

} );
describe( 'config', () => {
describe( 'options', () => {
describe( 'default value', () => {
it( 'should be set', () => {
expect( editor.config.get( 'heading.options' ) ).to.deep.equal( [
{ id: 'paragraph', element: 'p', label: 'Paragraph' },
{ id: 'heading1', element: 'h2', label: 'Heading 1' },
{ id: 'heading2', element: 'h3', label: 'Heading 2' },
{ id: 'heading3', element: 'h4', label: 'Heading 3' }
] );
} );
} );
it( 'should customize options', () => {
const options = [
{ id: 'paragraph', element: 'p', label: 'Paragraph' },
{ id: 'h4', element: 'h4', label: 'H4' }
];
return VirtualTestEditor.create( {
plugins: [ Enter, HeadingEngine ],
heading: {
options: options
}
} )
.then( editor => {
document = editor.document;
expect( editor.commands.get( 'heading' ).options ).to.deep.equal( options );
expect( document.schema.hasItem( 'paragraph' ) ).to.be.true;
expect( document.schema.hasItem( 'h4' ) ).to.be.true;
expect( document.schema.hasItem( 'heading1' ) ).to.be.false;
expect( document.schema.hasItem( 'heading2' ) ).to.be.false;
expect( document.schema.hasItem( 'heading3' ) ).to.be.false;
} );
} );
} );
} );
} );

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc