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

@ckeditor/ckeditor5-paragraph

Package Overview
Dependencies
Maintainers
1
Versions
705
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-paragraph - npm Package Compare versions

Comparing version 1.0.0-alpha.2 to 1.0.0-beta.1

7

CHANGELOG.md
Changelog
=========
## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-paragraph/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (2018-03-15)
### Other changes
* Aligned feature class naming to the new scheme. ([69e98d3](https://github.com/ckeditor/ckeditor5-paragraph/commit/69e98d3))
## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-paragraph/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2017-11-14)

@@ -5,0 +12,0 @@

2

LICENSE.md

@@ -5,3 +5,3 @@ Software License Agreement

**CKEditor 5 Paragraph Feature** – https://github.com/ckeditor/ckeditor5-paragraph <br>
Copyright (c) 2003-2017, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.

@@ -8,0 +8,0 @@ Licensed under the terms of any of the following licenses at your choice:

{
"name": "@ckeditor/ckeditor5-paragraph",
"version": "1.0.0-alpha.2",
"version": "1.0.0-beta.1",
"description": "Paragraph feature for CKEditor 5.",

@@ -10,19 +10,19 @@ "keywords": [

"dependencies": {
"@ckeditor/ckeditor5-core": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-engine": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-utils": "^1.0.0-alpha.2"
"@ckeditor/ckeditor5-core": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-engine": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-utils": "^1.0.0-beta.1"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-clipboard": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-editor-classic": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-enter": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-heading": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-link": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-typing": "^1.0.0-alpha.2",
"@ckeditor/ckeditor5-undo": "^1.0.0-alpha.2",
"eslint": "^4.8.0",
"eslint-config-ckeditor5": "^1.0.6",
"@ckeditor/ckeditor5-basic-styles": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-clipboard": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-editor-classic": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-enter": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-heading": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-link": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-typing": "^1.0.0-beta.1",
"@ckeditor/ckeditor5-undo": "^1.0.0-beta.1",
"eslint": "^4.15.0",
"eslint-config-ckeditor5": "^1.0.7",
"husky": "^0.14.3",
"lint-staged": "^4.2.3"
"lint-staged": "^6.0.0"
},

@@ -29,0 +29,0 @@ "engines": {

@@ -7,3 +7,5 @@ CKEditor 5 paragraph feature

[![Build Status](https://travis-ci.org/ckeditor/ckeditor5-paragraph.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-paragraph)
[![Test Coverage](https://codeclimate.com/github/ckeditor/ckeditor5-paragraph/badges/coverage.svg)](https://codeclimate.com/github/ckeditor/ckeditor5-paragraph/coverage)
[![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://www.browserstack.com/automate/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)
[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-paragraph/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-paragraph?branch=master)
<br>
[![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-paragraph/status.svg)](https://david-dm.org/ckeditor/ckeditor5-paragraph)

@@ -10,0 +12,0 @@ [![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-paragraph/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-paragraph?type=dev)

/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.

@@ -11,10 +11,8 @@ */

import ParagraphCommand from './paragraphcommand';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import { SchemaContext } from '@ckeditor/ckeditor5-engine/src/model/schema';
import Position from '@ckeditor/ckeditor5-engine/src/model/position';
import Range from '@ckeditor/ckeditor5-engine/src/model/range';
import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position';
import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
/**

@@ -39,5 +37,4 @@ * The paragraph feature for the editor.

const editor = this.editor;
const doc = editor.document;
const model = editor.model;
const data = editor.data;
const editing = editor.editing;

@@ -47,34 +44,64 @@ editor.commands.add( 'paragraph', new ParagraphCommand( editor ) );

// Schema.
doc.schema.registerItem( 'paragraph', '$block' );
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
// Build converter from model to view for data and editing pipelines.
buildModelConverter().for( data.modelToView, editing.modelToView )
.fromElement( 'paragraph' )
.toElement( 'p' );
editor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
// Build converter from view to model for data pipeline.
buildViewConverter().for( data.viewToModel )
.fromElement( 'p' )
.toElement( 'paragraph' );
// Content autoparagraphing. --------------------------------------------------
// Step 1.
// "Second chance" converters for elements and texts which were not allowed in their original locations.
// They check if this element/text could be converted if it was in a paragraph.
// Forcefully converted items will be temporarily in an invalid context. It's going to be fixed in step 2.
// Handles elements not converted by plugins and checks if would be converted if
// we wraps them by a paragraph or changes them to a paragraph.
data.upcastDispatcher.on( 'element', ( evt, data, conversionApi ) => {
// When element is already consumed by higher priority converters then do nothing.
if ( !conversionApi.consumable.test( data.viewItem, { name: data.viewItem.name } ) ) {
return;
}
// Executed after converter added by a feature, but before "default" to-model-fragment converter.
data.viewToModel.on( 'element', convertAutoparagraphableItem, { priority: 'low' } );
// Executed after default text converter.
data.viewToModel.on( 'text', convertAutoparagraphableItem, { priority: 'lowest' } );
// When element is paragraph-like lets try to change it into a paragraph.
if ( Paragraph.paragraphLikeElements.has( data.viewItem.name ) ) {
if ( data.viewItem.isEmpty ) {
return;
}
// Step 2.
// After an item is "forced" to be converted by `convertAutoparagraphableItem`, we need to actually take
// care of adding the paragraph (assumed in `convertAutoparagraphableItem`) and wrap that item in it.
const paragraph = conversionApi.writer.createElement( 'paragraph' );
// Executed after all converters (even default ones).
data.viewToModel.on( 'element', autoparagraphItems, { priority: 'lowest' } );
data.viewToModel.on( 'documentFragment', autoparagraphItems, { priority: 'lowest' } );
// Find allowed parent for paragraph that we are going to insert.
// If current parent does not allow to insert paragraph but one of the ancestors does
// then split nodes to allowed parent.
const splitResult = conversionApi.splitToAllowedParent( paragraph, data.modelCursor );
// When there is no split result it means that we can't insert paragraph in this position.
if ( !splitResult ) {
return;
}
// Insert paragraph in allowed position.
conversionApi.writer.insert( paragraph, splitResult.position );
// Convert children to paragraph.
const { modelRange } = conversionApi.convertChildren( data.viewItem, Position.createAt( paragraph ) );
// Output range starts before paragraph but ends inside it after last child.
// This is because we want to keep siblings inside the same paragraph as long as it is possible.
// When next node won't be allowed in a paragraph it will split this paragraph anyway.
data.modelRange = new Range( Position.createBefore( paragraph ), modelRange.end );
data.modelCursor = data.modelRange.end;
// When element is not paragraph-like lets try to wrap it by a paragraph.
} else if ( isParagraphable( data.viewItem, data.modelCursor, conversionApi.schema ) ) {
data = Object.assign( data, wrapInParagraph( data.viewItem, data.modelCursor, conversionApi ) );
}
}, { priority: 'low' } );
// Handles not converted text nodes and checks if would be converted if we wraps then by a paragraph.
data.upcastDispatcher.on( 'text', ( evt, data, conversionApi ) => {
// When node is already converted then do nothing.
if ( data.modelRange ) {
return;
}
if ( isParagraphable( data.viewItem, data.modelCursor, conversionApi.schema ) ) {
data = Object.assign( data, wrapInParagraph( data.viewItem, data.modelCursor, conversionApi ) );
}
}, { priority: 'lowest' } );
// Empty roots autoparagraphing. -----------------------------------------------

@@ -85,15 +112,31 @@

// if initial data is empty or setData() wasn't even called there will be no #change fired.
doc.on( 'change', ( evt, type, changes, batch ) => {
if ( batch.type == 'transparent' ) {
return;
}
model.document.registerPostFixer( writer => this._autoparagraphEmptyRoots( writer ) );
findEmptyRoots( doc, batch );
} );
doc.on( 'changesDone', autoparagraphEmptyRoots, { priority: 'lowest' } );
editor.on( 'dataReady', () => {
findEmptyRoots( doc, doc.batch( 'transparent' ) );
autoparagraphEmptyRoots();
model.enqueueChange( 'transparent', writer => this._autoparagraphEmptyRoots( writer ) );
}, { priority: 'lowest' } );
}
/**
* Fixes all empty roots.
*
* @private
* @returns {Boolean} `true` if any change has been applied, `false` otherwise.
*/
_autoparagraphEmptyRoots( writer ) {
const model = this.editor.model;
for ( const rootName of model.document.getRootNames() ) {
const root = model.document.getRoot( rootName );
if ( root.isEmpty && root.rootName != '$graveyard' ) {
// If paragraph element is allowed in the root, create paragraph element.
if ( model.schema.checkChild( root, 'paragraph' ) ) {
writer.insertElement( 'paragraph', root );
return true;
}
}
}
}
}

@@ -148,116 +191,19 @@

// This converter forces a conversion of a non-consumed view item, if that item would be allowed by schema and converted it if was
// inside a paragraph element. The converter checks whether conversion would be possible if there was a paragraph element
// between `data.input` item and its parent. If the conversion would be allowed, the converter adds `"paragraph"` to the
// context and fires conversion for `data.input` again.
function convertAutoparagraphableItem( evt, data, consumable, conversionApi ) {
// If the item wasn't consumed by some of the dedicated converters...
if ( !consumable.test( data.input, { name: data.input.name } ) ) {
return;
}
function wrapInParagraph( input, position, conversionApi ) {
const paragraph = conversionApi.writer.createElement( 'paragraph' );
// But would be allowed if it was in a paragraph...
if ( !isParagraphable( data.input, data.context, conversionApi.schema, false ) ) {
return;
}
// Convert that item in a paragraph context.
data.context.push( 'paragraph' );
const item = conversionApi.convertItem( data.input, consumable, data );
data.context.pop();
data.output = item;
conversionApi.writer.insert( paragraph, position );
return conversionApi.convertItem( input, Position.createAt( paragraph ) );
}
// This converter checks all children of an element or document fragment that has been converted and wraps
// children in a paragraph element if it is allowed by schema.
//
// Basically, after an item is "forced" to be converted by `convertAutoparagraphableItem`, we need to actually take
// care of adding the paragraph (assumed in `convertAutoparagraphableItem`) and wrap that item in it.
function autoparagraphItems( evt, data, consumable, conversionApi ) {
// Autoparagraph only if the element has been converted.
if ( !data.output ) {
return;
}
function isParagraphable( node, position, schema ) {
const context = new SchemaContext( position );
const isParagraphLike = Paragraph.paragraphLikeElements.has( data.input.name ) && !data.output.is( 'element' );
// Keep in mind that this converter is added to all elements and document fragments.
// This means that we have to make a smart decision in which elements (at what level) auto-paragraph should be inserted.
// There are three situations when it is correct to add paragraph:
// - we are converting a view document fragment: this means that we are at the top level of conversion and we should
// add paragraph elements for "bare" texts (unless converting in $clipboardHolder, but this is covered by schema),
// - we are converting an element that was converted to model element: this means that it will be represented in model
// and has added its context when converting children - we should add paragraph for those items that passed
// in `convertAutoparagraphableItem`, because it is correct for them to be autoparagraphed,
// - we are converting "paragraph-like" element, which children should always be autoparagraphed (if it is allowed by schema,
// so we won't end up with, i.e., paragraph inside paragraph, if paragraph was in paragraph-like element).
const shouldAutoparagraph =
( data.input.is( 'documentFragment' ) ) ||
( data.input.is( 'element' ) && data.output.is( 'element' ) ) ||
isParagraphLike;
if ( !shouldAutoparagraph ) {
return;
}
// Take care of proper context. This is important for `isParagraphable` checks.
const needsNewContext = data.output.is( 'element' );
if ( needsNewContext ) {
data.context.push( data.output );
}
// `paragraph` element that will wrap auto-paragraphable children.
let autoParagraph = null;
// Check children and wrap them in a `paragraph` element if they need to be wrapped.
// Be smart when wrapping children and put all auto-paragraphable siblings in one `paragraph` parent:
// foo<$text bold="true">bar</$text><paragraph>xxx</paragraph>baz --->
// <paragraph>foo<$text bold="true">bar</$text></paragraph><paragraph>xxx</paragraph><paragraph>baz</paragraph>
for ( let i = 0; i < data.output.childCount; i++ ) {
const child = data.output.getChild( i );
if ( isParagraphable( child, data.context, conversionApi.schema, isParagraphLike ) ) {
// If there is no wrapping `paragraph` element, create it.
if ( !autoParagraph ) {
autoParagraph = new ModelElement( 'paragraph' );
data.output.insertChildren( child.index, autoParagraph );
}
// Otherwise, use existing `paragraph` and just fix iterator.
// Thanks to reusing `paragraph` element, multiple siblings ends up in same container.
else {
i--;
}
child.remove();
autoParagraph.appendChildren( child );
} else {
// That was not a paragraphable children, reset `paragraph` wrapper - following auto-paragraphable children
// need to be placed in a new `paragraph` element.
autoParagraph = null;
}
}
if ( needsNewContext ) {
data.context.pop();
}
}
function isParagraphable( node, context, schema, insideParagraphLikeElement ) {
const name = node.name || '$text';
// Node is paragraphable if it is inside paragraph like element, or...
// It is not allowed at this context...
if ( !insideParagraphLikeElement && schema.check( { name, inside: context } ) ) {
// When paragraph is allowed in this context...
if ( !schema.checkChild( context, 'paragraph' ) ) {
return false;
}
// And paragraph is allowed in this context...
if ( !schema.check( { name: 'paragraph', inside: context } ) ) {
return false;
}
// And a node would be allowed in this paragraph...
if ( !schema.check( { name, inside: context.concat( 'paragraph' ) } ) ) {
if ( !schema.checkChild( context.push( 'paragraph' ), node ) ) {
return false;

@@ -268,41 +214,1 @@ }

}
// Looks through all roots created in document and marks every empty root, saving which batch made it empty.
const rootsToFix = new Map();
function findEmptyRoots( doc, batch ) {
for ( const rootName of doc.getRootNames() ) {
const root = doc.getRoot( rootName );
if ( root.isEmpty ) {
if ( !rootsToFix.has( root ) ) {
rootsToFix.set( root, batch );
}
} else {
rootsToFix.delete( root );
}
}
}
// Fixes all empty roots.
function autoparagraphEmptyRoots() {
for ( const [ root, batch ] of rootsToFix ) {
// Only empty roots are in `rootsToFix`. Even if root got content during `changesDone` event (because of, for example
// other feature), this will fire `findEmptyRoots` and remove that root from `rootsToFix`. So we are guaranteed
// to have only empty roots here.
const query = { name: 'paragraph', inside: [ root ] };
const doc = batch.document;
const schema = doc.schema;
// If paragraph element is allowed in the root, create paragraph element.
if ( schema.check( query ) ) {
doc.enqueueChanges( () => {
// Remove root from `rootsToFix` here, before executing batch, to prevent infinite loops.
rootsToFix.delete( root );
// Fix empty root.
batch.insert( ModelPosition.createAt( root ), new ModelElement( 'paragraph' ) );
} );
}
}
}
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.

@@ -11,3 +11,2 @@ */

import Command from '@ckeditor/ckeditor5-core/src/command';
import Position from '@ckeditor/ckeditor5-engine/src/model/position';
import first from '@ckeditor/ckeditor5-utils/src/first';

@@ -33,7 +32,8 @@

refresh() {
const document = this.editor.document;
const model = this.editor.model;
const document = model.document;
const block = first( document.selection.getSelectedBlocks() );
this.value = !!block && block.is( 'paragraph' );
this.isEnabled = !!block && checkCanBecomeParagraph( block, document.schema );
this.isEnabled = !!block && checkCanBecomeParagraph( block, model.schema );
}

@@ -47,4 +47,2 @@

* @param {Object} [options] Options for the executed command.
* @param {module:engine/model/batch~Batch} [options.batch] A batch to collect all the change steps.
* A new batch will be created if this option is not set.
* @param {module:engine/model/selection~Selection} [options.selection] The selection that the command should be applied to.

@@ -54,11 +52,11 @@ * By default, if not provided, the command is applied to the {@link module:engine/model/document~Document#selection}.

execute( options = {} ) {
const document = this.editor.document;
const model = this.editor.model;
const document = model.document;
document.enqueueChanges( () => {
const batch = options.batch || document.batch();
model.change( writer => {
const blocks = ( options.selection || document.selection ).getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'paragraph' ) && checkCanBecomeParagraph( block, document.schema ) ) {
batch.rename( block, 'paragraph' );
if ( !block.is( 'paragraph' ) && checkCanBecomeParagraph( block, model.schema ) ) {
writer.rename( block, 'paragraph' );
}

@@ -77,6 +75,3 @@ }

function checkCanBecomeParagraph( block, schema ) {
return schema.check( {
name: 'paragraph',
inside: Position.createBefore( block )
} );
return schema.checkChild( block.parent, 'paragraph' ) && !schema.isObject( block );
}
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