@ckeditor/ckeditor5-image
Advanced tools
Comparing version 1.0.0-alpha.2 to 1.0.0-beta.1
Changelog | ||
========= | ||
## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-image/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (2018-03-15) | ||
### Features | ||
* Intorduced the `ImageUpload` feature. It was moved from the `@ckeditor/ckeditor5-upload` package. See [ckeditor/ckeditor5-upload#22](https://github.com/ckeditor/ckeditor5-upload/issues/22). ([b974bb0](https://github.com/ckeditor/ckeditor5-image/commit/b974bb0)) | ||
* Simplified the text alternative UI, aligning the image package to the redesigned Lark theme (see [ckeditor/ckeditor5#645](https://github.com/ckeditor/ckeditor5/issues/645)). ([9a069b0](https://github.com/ckeditor/ckeditor5-image/commit/9a069b0)) | ||
### Other changes | ||
* Aligned feature class naming to the new scheme. ([8690765](https://github.com/ckeditor/ckeditor5-image/commit/8690765)) | ||
* Migrated package styles to PostCSS. Moved visual styles to `@ckeditor/ckeditor5-theme-lark` (see [ckeditor/ckeditor5-ui#144](https://github.com/ckeditor/ckeditor5-ui/issues/144)). ([ed6e1cf](https://github.com/ckeditor/ckeditor5-image/commit/ed6e1cf)) | ||
* Removed the `.ck-editor-toolbar` and `.ck-editor-toolbar-container` classes from the UI (see [ckeditor/ckeditor5-theme-lark#135](https://github.com/ckeditor/ckeditor5-theme-lark/issues/135)). ([1c08fdd](https://github.com/ckeditor/ckeditor5-image/commit/1c08fdd)) | ||
* Renamed `uploadImage` command and button to `imageUpload`. Closes [#184](https://github.com/ckeditor/ckeditor5-image/issues/184). ([6f891b8](https://github.com/ckeditor/ckeditor5-image/commit/6f891b8)) | ||
* Updated naming of UI components & commands. ([2e7fbee](https://github.com/ckeditor/ckeditor5-image/commit/2e7fbee)) | ||
* Updated translations. ([02f9cf5](https://github.com/ckeditor/ckeditor5-image/commit/02f9cf5)) | ||
### BREAKING CHANGES | ||
* `uploadImage` command and button are now called `imageUpload`. | ||
* Renamed the `'imageUpload'` command to `'uploadImage'`. | ||
* The `'imageStyleFull'`, `'imageStyleSide'`, `'imageStyleAlignLeft'`, `'imageStyleAlignRight'` and `'imageStyleAlignCenter'` commands are no longer available. They were replaced by the `'imageStyle'` command that accepts name of an image style as a value. | ||
* The `'imageStyleFull'`, `'imageStyleSide'`, `'imageStyleAlignLeft'`, `'imageStyleAlignRight'` and `'imageStyleAlignCenter'` UI components are no longer available. Replaced by `'imageStyle:full'`, `'imageStyle:side'`, `'imageStyle:alignLeft'`, `'imageStyle:alignRight'` and `'imageStyle:alignCenter'`. | ||
* The `ImageStyleCommand#value` property is no longer a boolean only. Now it represents a name of an image style of the currently selected image element. | ||
* The `ImageStyleCommand` constructor's second parameter is now an array of supported image styles. | ||
* The DOM structure of the text alternative form has changed. | ||
## 0.0.1 (2017-11-06) | ||
Internal changes only (updated dependencies, documentation, etc.). | ||
## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-image/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2017-11-14) | ||
@@ -5,0 +37,0 @@ |
@@ -10,3 +10,5 @@ { | ||
"Text alternative": "Label for the image text alternative input.", | ||
"Enter image caption": "Placeholder text for image caption displayed when caption is empty." | ||
"Enter image caption": "Placeholder text for image caption displayed when caption is empty.", | ||
"Insert image": "Label for the insert image toolbar button.", | ||
"Upload failed": "Title of the notification displayed when upload fails." | ||
} |
@@ -5,3 +5,3 @@ Software License Agreement | ||
**CKEditor 5 Image Feature** – https://github.com/ckeditor/ckeditor5-image <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-image", | ||
"version": "1.0.0-alpha.2", | ||
"version": "1.0.0-beta.1", | ||
"description": "Image feature for CKEditor 5.", | ||
@@ -10,25 +10,26 @@ "keywords": [ | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-core": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-engine": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-ui": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-utils": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-theme-lark": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-widget": "^1.0.0-alpha.2" | ||
"@ckeditor/ckeditor5-core": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-engine": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-ui": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-utils": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-theme-lark": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-upload": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-widget": "^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-essentials": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-heading": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-link": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-list": "^1.0.0-alpha.2", | ||
"@ckeditor/ckeditor5-paragraph": "^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-essentials": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-heading": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-link": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-list": "^1.0.0-beta.1", | ||
"@ckeditor/ckeditor5-paragraph": "^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" | ||
}, | ||
@@ -35,0 +36,0 @@ "engines": { |
@@ -7,3 +7,5 @@ CKEditor 5 image feature | ||
[![Build Status](https://travis-ci.org/ckeditor/ckeditor5-image.svg)](https://travis-ci.org/ckeditor/ckeditor5-image) | ||
[![Test Coverage](https://codeclimate.com/github/ckeditor/ckeditor5-image/badges/coverage.svg)](https://codeclimate.com/github/ckeditor/ckeditor5-image/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-image/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-image?branch=master) | ||
<br> | ||
[![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-image/status.svg)](https://david-dm.org/ckeditor/ckeditor5-image) | ||
@@ -10,0 +12,0 @@ [![devDependency Status](https://david-dm.org/ckeditor/ckeditor5-image/dev-status.svg)](https://david-dm.org/ckeditor/ckeditor5-image?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,8 +11,7 @@ */ | ||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; | ||
import ImageEngine from './image/imageengine'; | ||
import ImageEditing from '../src/image/imageediting'; | ||
import Widget from '@ckeditor/ckeditor5-widget/src/widget'; | ||
import ImageTextAlternative from './imagetextalternative'; | ||
import { isImageWidgetSelected } from './image/utils'; | ||
import '../theme/theme.scss'; | ||
import '../theme/image.css'; | ||
@@ -22,3 +21,3 @@ /** | ||
* | ||
* Uses the {@link module:image/image/imageengine~ImageEngine}. | ||
* Uses the {@link module:image/image/imageediting~ImageEditing}. | ||
* | ||
@@ -32,3 +31,3 @@ * @extends module:core/plugin~Plugin | ||
static get requires() { | ||
return [ ImageEngine, Widget, ImageTextAlternative ]; | ||
return [ ImageEditing, Widget, ImageTextAlternative ]; | ||
} | ||
@@ -42,21 +41,2 @@ | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
init() { | ||
const editor = this.editor; | ||
const contextualToolbar = editor.plugins.get( 'ContextualToolbar' ); | ||
// If `ContextualToolbar` plugin is loaded, it should be disabled for images | ||
// which have their own toolbar to avoid duplication. | ||
// https://github.com/ckeditor/ckeditor5-image/issues/110 | ||
if ( contextualToolbar ) { | ||
this.listenTo( contextualToolbar, 'show', evt => { | ||
if ( isImageWidgetSelected( editor.editing.view.selection ) ) { | ||
evt.stop(); | ||
} | ||
}, { priority: 'high' } ); | ||
} | ||
} | ||
} | ||
@@ -63,0 +43,0 @@ |
/** | ||
* @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,4 +11,3 @@ */ | ||
import ModelPosition from '@ckeditor/ckeditor5-engine/src/model/position'; | ||
import ModelDocumentFragment from '@ckeditor/ckeditor5-engine/src/model/documentfragment'; | ||
import modelWriter from '@ckeditor/ckeditor5-engine/src/model/writer'; | ||
import first from '@ckeditor/ckeditor5-utils/src/first'; | ||
@@ -30,18 +29,17 @@ /** | ||
export function viewFigureToModel() { | ||
return ( evt, data, consumable, conversionApi ) => { | ||
return dispatcher => { | ||
dispatcher.on( 'element:figure', converter ); | ||
}; | ||
function converter( evt, data, conversionApi ) { | ||
// Do not convert if this is not an "image figure". | ||
if ( !consumable.test( data.input, { name: true, class: 'image' } ) ) { | ||
if ( !conversionApi.consumable.test( data.viewItem, { name: true, class: 'image' } ) ) { | ||
return; | ||
} | ||
// Do not convert if image cannot be placed in model at this context. | ||
if ( !conversionApi.schema.check( { name: 'image', inside: data.context, attributes: 'src' } ) ) { | ||
return; | ||
} | ||
// Find an image element inside the figure element. | ||
const viewImage = Array.from( data.input.getChildren() ).find( viewChild => viewChild.is( 'img' ) ); | ||
const viewImage = Array.from( data.viewItem.getChildren() ).find( viewChild => viewChild.is( 'img' ) ); | ||
// Do not convert if image element is absent, is missing src attribute or was already converted. | ||
if ( !viewImage || !viewImage.hasAttribute( 'src' ) || !consumable.test( viewImage, { name: true } ) ) { | ||
if ( !viewImage || !viewImage.hasAttribute( 'src' ) || !conversionApi.consumable.test( viewImage, { name: true } ) ) { | ||
return; | ||
@@ -51,33 +49,20 @@ } | ||
// Convert view image to model image. | ||
const modelImage = conversionApi.convertItem( viewImage, consumable, data ); | ||
const conversionResult = conversionApi.convertItem( viewImage, data.modelCursor ); | ||
// Convert rest of figure element's children, but in the context of model image, because those converted | ||
// children will be added as model image children. | ||
data.context.push( modelImage ); | ||
// Get image element from conversion result. | ||
const modelImage = first( conversionResult.modelRange.getItems() ); | ||
const modelChildren = conversionApi.convertChildren( data.input, consumable, data ); | ||
// When image wasn't successfully converted then finish conversion. | ||
if ( !modelImage ) { | ||
return; | ||
} | ||
data.context.pop(); | ||
// Convert rest of the figure element's children as an image children. | ||
conversionApi.convertChildren( data.viewItem, ModelPosition.createAt( modelImage ) ); | ||
// Add converted children to model image. | ||
modelWriter.insert( ModelPosition.createAt( modelImage ), modelChildren ); | ||
// Set image range as conversion result. | ||
data.modelRange = conversionResult.modelRange; | ||
// Set model image as conversion result. | ||
data.output = modelImage; | ||
}; | ||
} | ||
/** | ||
* Creates the image attribute converter for provided model conversion dispatchers. | ||
* | ||
* @param {Array.<module:engine/conversion/modelconversiondispatcher~ModelConversionDispatcher>} dispatchers | ||
* @param {String} attributeName | ||
* @param {Function} [converter] Custom converter for the attribute - default one converts attribute from model `image` element | ||
* to the same attribute in `img` in the view. | ||
*/ | ||
export function createImageAttributeConverter( dispatchers, attributeName, converter = modelToViewAttributeConverter ) { | ||
for ( const dispatcher of dispatchers ) { | ||
dispatcher.on( `addAttribute:${ attributeName }:image`, converter() ); | ||
dispatcher.on( `changeAttribute:${ attributeName }:image`, converter() ); | ||
dispatcher.on( `removeAttribute:${ attributeName }:image`, converter() ); | ||
// Continue conversion where image conversion ends. | ||
data.modelCursor = conversionResult.modelCursor; | ||
} | ||
@@ -92,24 +77,24 @@ } | ||
export function srcsetAttributeConverter() { | ||
return ( evt, data, consumable, conversionApi ) => { | ||
const parts = evt.name.split( ':' ); | ||
const consumableType = parts[ 0 ] + ':' + parts[ 1 ]; | ||
const modelImage = data.item; | ||
return dispatcher => { | ||
dispatcher.on( 'attribute:srcset:image', converter ); | ||
}; | ||
if ( !consumable.consume( modelImage, consumableType ) ) { | ||
function converter( evt, data, conversionApi ) { | ||
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { | ||
return; | ||
} | ||
const figure = conversionApi.mapper.toViewElement( modelImage ); | ||
const writer = conversionApi.writer; | ||
const figure = conversionApi.mapper.toViewElement( data.item ); | ||
const img = figure.getChild( 0 ); | ||
const type = parts[ 0 ]; | ||
if ( type == 'removeAttribute' ) { | ||
if ( data.attributeNewValue === null ) { | ||
const srcset = data.attributeOldValue; | ||
if ( srcset.data ) { | ||
img.removeAttribute( 'srcset' ); | ||
img.removeAttribute( 'sizes' ); | ||
writer.removeAttribute( 'srcset', img ); | ||
writer.removeAttribute( 'sizes', img ); | ||
if ( srcset.width ) { | ||
img.removeAttribute( 'width' ); | ||
writer.removeAttribute( 'width', img ); | ||
} | ||
@@ -121,207 +106,34 @@ } | ||
if ( srcset.data ) { | ||
img.setAttribute( 'srcset', srcset.data ); | ||
writer.setAttribute( 'srcset', srcset.data, img ); | ||
// Always outputting `100vw`. See https://github.com/ckeditor/ckeditor5-image/issues/2. | ||
img.setAttribute( 'sizes', '100vw' ); | ||
writer.setAttribute( 'sizes', '100vw', img ); | ||
if ( srcset.width ) { | ||
img.setAttribute( 'width', srcset.width ); | ||
writer.setAttribute( 'width', srcset.width, img ); | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
// Returns model to view image converter converting given attribute, and adding it to `img` element nested inside `figure` element. | ||
// | ||
// @private | ||
function modelToViewAttributeConverter() { | ||
return ( evt, data, consumable, conversionApi ) => { | ||
const parts = evt.name.split( ':' ); | ||
const consumableType = parts[ 0 ] + ':' + parts[ 1 ]; | ||
const modelImage = data.item; | ||
export function modelToViewAttributeConverter( attributeKey ) { | ||
return dispatcher => { | ||
dispatcher.on( `attribute:${ attributeKey }:image`, converter ); | ||
}; | ||
if ( !consumable.consume( modelImage, consumableType ) ) { | ||
function converter( evt, data, conversionApi ) { | ||
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { | ||
return; | ||
} | ||
const figure = conversionApi.mapper.toViewElement( modelImage ); | ||
const viewWriter = conversionApi.writer; | ||
const figure = conversionApi.mapper.toViewElement( data.item ); | ||
const img = figure.getChild( 0 ); | ||
const type = parts[ 0 ]; | ||
if ( type == 'removeAttribute' ) { | ||
img.removeAttribute( data.attributeKey ); | ||
if ( data.attributeNewValue !== null ) { | ||
viewWriter.setAttribute( data.attributeKey, data.attributeNewValue, img ); | ||
} else { | ||
img.setAttribute( data.attributeKey, data.attributeNewValue ); | ||
viewWriter.removeAttribute( data.attributeKey, img ); | ||
} | ||
}; | ||
} | ||
// Holds all images that were converted for autohoisting. | ||
const autohoistedImages = new WeakSet(); | ||
/** | ||
* A converter which converts `<img>` {@link module:engine/view/element~Element view elements} that can be hoisted. | ||
* | ||
* If an `<img>` view element has not been converted, this converter checks if that element could be converted in any | ||
* context "above". If it could, the converter converts the `<img>` element even though it is not allowed in the current | ||
* context and marks it to be autohoisted. Then {@link module:image/image/converters~hoistImageThroughElement another converter} | ||
* moves the converted element to the correct location. | ||
*/ | ||
export function convertHoistableImage( evt, data, consumable, conversionApi ) { | ||
const img = data.input; | ||
// If the image has not been consumed (converted)... | ||
if ( !consumable.test( img, { name: true, attribute: [ 'src' ] } ) ) { | ||
return; | ||
} | ||
// At this point the image has not been converted because it was not allowed by schema. It might be in wrong | ||
// context or missing an attribute, but above we already checked whether the image has mandatory src attribute. | ||
// If the image would be allowed if it was in one of its ancestors... | ||
const allowedContext = _findAllowedContext( { name: 'image', attributes: [ 'src' ] }, data.context, conversionApi.schema ); | ||
if ( !allowedContext ) { | ||
return; | ||
} | ||
// Convert it in that context... | ||
const newData = Object.assign( {}, data ); | ||
newData.context = allowedContext; | ||
data.output = conversionApi.convertItem( img, consumable, newData ); | ||
// And mark that image to be hoisted. | ||
autohoistedImages.add( data.output ); | ||
} | ||
// Basing on passed `context`, searches for "closest" context in which model element represented by `modelData` | ||
// would be allowed by `schema`. | ||
// | ||
// @private | ||
// @param {Object} modelData Object describing model element to check. Has two properties: `name` with model element name | ||
// and `attributes` with keys of attributes of that model element. | ||
// @param {Array} context Context in which original conversion was supposed to take place. | ||
// @param {module:engine/model/schema~Schema} schema Schema to check with. | ||
// @returns {Array|null} Context in which described model element would be allowed by `schema` or `null` if such context | ||
// could not been found. | ||
function _findAllowedContext( modelData, context, schema ) { | ||
// Copy context array so we won't modify original array. | ||
context = context.slice(); | ||
// Prepare schema query to check with schema. | ||
// Since `inside` property is passed as reference to `context` variable, we don't need to modify `schemaQuery`. | ||
const schemaQuery = { | ||
name: modelData.name, | ||
attributes: modelData.attributes, | ||
inside: context | ||
}; | ||
// Try out all possible contexts. | ||
while ( context.length && !schema.check( schemaQuery ) ) { | ||
const parent = context.pop(); | ||
const parentName = typeof parent === 'string' ? parent : parent.name; | ||
// Do not try to autohoist "above" limiting element. | ||
if ( schema.limits.has( parentName ) ) { | ||
return null; | ||
} | ||
} | ||
// If `context` has any items it means that image is allowed in that context. Return that context. | ||
// If `context` has no items it means that image was not allowed in any of possible contexts. Return `null`. | ||
return context.length ? context : null; | ||
} | ||
/** | ||
* A converter which hoists `<image>` {@link module:engine/model/element~Element model elements} to allowed context. | ||
* | ||
* It looks through all children of the converted {@link module:engine/view/element~Element view element} if it | ||
* was converted to a model element. It breaks the model element if an `<image>` to-be-hoisted is found. | ||
* | ||
* <div><paragraph>x<image src="foo.jpg"></image>x</paragraph></div> -> | ||
* <div><paragraph>x</paragraph></div><image src="foo.jpg"></image><div><paragraph>x</paragraph></div> | ||
* | ||
* This works deeply, as shown in the example. This converter added for the `<paragraph>` element will break the `<paragraph>` | ||
* element and pass the {@link module:engine/model/documentfragment~DocumentFragment document fragment} in `data.output`. | ||
* Then, the `<div>` will be handled by this converter and will be once again broken to hoist the `<image>` up to the root. | ||
* | ||
* **Note:** This converter should be executed only after the view element has already been converted, which means that | ||
* `data.output` for that view element should be already generated when this converter is fired. | ||
*/ | ||
export function hoistImageThroughElement( evt, data ) { | ||
// If this element has been properly converted... | ||
if ( !data.output ) { | ||
return; | ||
} | ||
// And it is an element... | ||
// (If it is document fragment autohoisting does not have to break anything anyway.) | ||
// (And if it is text there are no children here.) | ||
if ( !data.output.is( 'element' ) ) { | ||
return; | ||
} | ||
// This will hold newly generated output. At the beginning it is only the original element. | ||
const newOutput = []; | ||
// Check if any of its children is to be hoisted... | ||
// Start from the last child - it is easier to break that way. | ||
for ( let i = data.output.childCount - 1; i >= 0; i-- ) { | ||
const child = data.output.getChild( i ); | ||
if ( autohoistedImages.has( child ) ) { | ||
// Break autohoisted element's parent: | ||
// <parent>{ left-children... }<authoistedElement />{ right-children... }</parent> ---> | ||
// <parent>{ left-children... }</parent><autohoistedElement /><parent>{ right-children... }</parent> | ||
// | ||
// or | ||
// | ||
// <parent>{ left-children... }<autohoistedElement /></parent> ---> | ||
// <parent>{ left-children... }</parent><autohoistedElement /> | ||
// | ||
// or | ||
// | ||
// <parent><autohoistedElement />{ right-children... }</parent> ---> | ||
// <autohoistedElement /><parent>{ right-children... }</parent> | ||
// | ||
// or | ||
// | ||
// <parent><autohoistedElement /></parent> ---> <autohoistedElement /> | ||
// Check how many right-children there are. | ||
const rightChildrenCount = data.output.childCount - i - 1; | ||
let rightParent = null; | ||
// If there are any right-children, clone the prent element and insert those children there. | ||
if ( rightChildrenCount > 0 ) { | ||
rightParent = data.output.clone( false ); | ||
rightParent.appendChildren( data.output.removeChildren( i + 1, rightChildrenCount ) ); | ||
} | ||
// Remove the autohoisted element from its parent. | ||
child.remove(); | ||
// Break "leading" `data.output` in `newOutput` into one or more pieces: | ||
// Remove "leading" `data.output` (note that `data.output` is always first item in `newOutput`). | ||
newOutput.shift(); | ||
// Add the newly created parent of the right-children at the beginning. | ||
if ( rightParent ) { | ||
newOutput.unshift( rightParent ); | ||
} | ||
// Add autohoisted element at the beginning. | ||
newOutput.unshift( child ); | ||
// Add `data.output` at the beginning, if there is anything left in it. | ||
if ( data.output.childCount > 0 ) { | ||
newOutput.unshift( data.output ); | ||
} | ||
} | ||
} | ||
// If the output has changed pass it further. | ||
if ( newOutput.length ) { | ||
data.output = new ModelDocumentFragment( newOutput ); | ||
} | ||
} |
/** | ||
* @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. | ||
@@ -23,3 +23,3 @@ */ | ||
if ( isImageWidgetSelected( editor.editing.view.selection ) ) { | ||
if ( isImageWidgetSelected( editor.editing.view.document.selection ) ) { | ||
const position = getBalloonPositionData( editor ); | ||
@@ -44,8 +44,12 @@ | ||
return { | ||
target: editingView.domConverter.viewToDom( editingView.selection.getSelectedElement() ), | ||
target: editingView.domConverter.viewToDom( editingView.document.selection.getSelectedElement() ), | ||
positions: [ | ||
defaultPositions.northArrowSouth, | ||
defaultPositions.southArrowNorth | ||
defaultPositions.northArrowSouthWest, | ||
defaultPositions.northArrowSouthEast, | ||
defaultPositions.southArrowNorth, | ||
defaultPositions.southArrowNorthWest, | ||
defaultPositions.southArrowNorthEast | ||
] | ||
}; | ||
} |
/** | ||
* @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. | ||
@@ -17,13 +17,14 @@ */ | ||
* Converts a given {@link module:engine/view/element~Element} to an image widget: | ||
* * adds a {@link module:engine/view/element~Element#setCustomProperty custom property} allowing to recognize the image widget element, | ||
* * adds a {@link module:engine/view/element~Element#_setCustomProperty custom property} allowing to recognize the image widget element, | ||
* * calls the {@link module:widget/utils~toWidget toWidget} function with the proper element's label creator. | ||
* | ||
* @param {module:engine/view/element~Element} viewElement | ||
* @param {module:engine/view/writer~Writer} writer Instance of view writer. | ||
* @param {String} label Element's label. It will be concatenated with the image `alt` attribute if one is present. | ||
* @returns {module:engine/view/element~Element} | ||
*/ | ||
export function toImageWidget( viewElement, label ) { | ||
viewElement.setCustomProperty( imageSymbol, true ); | ||
export function toImageWidget( viewElement, writer, label ) { | ||
writer.setCustomProperty( imageSymbol, true, viewElement ); | ||
return toWidget( viewElement, { label: labelCreator } ); | ||
return toWidget( viewElement, writer, { label: labelCreator } ); | ||
@@ -30,0 +31,0 @@ function labelCreator() { |
/** | ||
* @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,5 +11,6 @@ */ | ||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; | ||
import ImageCaptionEngine from './imagecaption/imagecaptionengine'; | ||
import '../theme/imagecaption/theme.scss'; | ||
import ImageCaptionEditing from './imagecaption/imagecaptionediting'; | ||
import '../theme/imagecaption.css'; | ||
/** | ||
@@ -25,3 +26,3 @@ * The image caption plugin. | ||
static get requires() { | ||
return [ ImageCaptionEngine ]; | ||
return [ ImageCaptionEditing ]; | ||
} | ||
@@ -28,0 +29,0 @@ |
/** | ||
* @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 ModelElement from '@ckeditor/ckeditor5-engine/src/model/element'; | ||
import ViewEditableElement from '@ckeditor/ckeditor5-engine/src/view/editableelement'; | ||
import { attachPlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder'; | ||
@@ -21,14 +20,13 @@ import { toWidgetEditable } from '@ckeditor/ckeditor5-widget/src/utils'; | ||
* | ||
* @param {module:engine/view/document~Document} viewDocument | ||
* @param {module:engine/view/view~View} view | ||
* @param {String} placeholderText The text to be displayed when the caption is empty. | ||
* @return {Function} | ||
*/ | ||
export function captionElementCreator( viewDocument, placeholderText ) { | ||
return () => { | ||
const editable = new ViewEditableElement( 'figcaption' ); | ||
editable.document = viewDocument; | ||
editable.setCustomProperty( captionSymbol, true ); | ||
attachPlaceholder( editable, placeholderText ); | ||
export function captionElementCreator( view, placeholderText ) { | ||
return writer => { | ||
const editable = writer.createEditableElement( 'figcaption' ); | ||
writer.setCustomProperty( captionSymbol, true, editable ); | ||
attachPlaceholder( view, editable, placeholderText ); | ||
return toWidgetEditable( editable ); | ||
return toWidgetEditable( editable, writer ); | ||
}; | ||
@@ -35,0 +33,0 @@ } |
/** | ||
* @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,4 +11,4 @@ */ | ||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; | ||
import ImageStyleEngine from './imagestyle/imagestyleengine'; | ||
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; | ||
import ImageStyleEditing from './imagestyle/imagestyleediting'; | ||
import ImageStyleUI from './imagestyle/imagestyleui'; | ||
@@ -18,3 +18,4 @@ /** | ||
* | ||
* Uses the {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine}. | ||
* It loads the {@link module:image/imagestyle/imagestyleediting~ImageStyleEditing} | ||
* and {@link module:image/imagestyle/imagestyleui~ImageStyleUI} plugins. | ||
* | ||
@@ -28,3 +29,3 @@ * @extends module:core/plugin~Plugin | ||
static get requires() { | ||
return [ ImageStyleEngine ]; | ||
return [ ImageStyleEditing, ImageStyleUI ]; | ||
} | ||
@@ -38,42 +39,2 @@ | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
init() { | ||
const editor = this.editor; | ||
const styles = editor.plugins.get( ImageStyleEngine ).imageStyles; | ||
for ( const style of styles ) { | ||
this._createButton( style ); | ||
} | ||
} | ||
/** | ||
* Creates a button for each style and stores it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}. | ||
* | ||
* @private | ||
* @param {module:image/imagestyle/imagestyleengine~ImageStyleFormat} style | ||
*/ | ||
_createButton( style ) { | ||
const editor = this.editor; | ||
const command = editor.commands.get( style.name ); | ||
editor.ui.componentFactory.add( style.name, locale => { | ||
const view = new ButtonView( locale ); | ||
view.set( { | ||
label: style.title, | ||
icon: style.icon, | ||
tooltip: true | ||
} ); | ||
view.bind( 'isEnabled' ).to( command, 'isEnabled' ); | ||
view.bind( 'isOn' ).to( command, 'value' ); | ||
this.listenTo( view, 'execute', () => editor.execute( style.name ) ); | ||
return view; | ||
} ); | ||
} | ||
} | ||
@@ -83,8 +44,7 @@ | ||
* Available image styles. | ||
* The option is used by the {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine} feature. | ||
* | ||
* The default value is: | ||
* | ||
* const imageConfig = { | ||
* styles: [ 'imageStyleFull', 'imageStyleSide' ] | ||
* const imageConfig = { | ||
* styles: [ 'full', 'side' ] | ||
* }; | ||
@@ -97,8 +57,8 @@ * | ||
* | ||
* See {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultStyles} to learn more about default | ||
* See {@link module:image/imagestyle/utils~defaultStyles} to learn more about default | ||
* styles provided by the image feature. | ||
* | ||
* The {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultStyles default styles} can be customized, | ||
* The {@link module:image/imagestyle/utils~defaultStyles default styles} can be customized, | ||
* e.g. to change the icon, title or CSS class of the style. The feature also provides several | ||
* {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine.defaultIcons default icons} to chose from. | ||
* {@link module:image/imagestyle/utils~defaultIcons default icons} to chose from. | ||
* | ||
@@ -113,6 +73,6 @@ * import customIcon from 'custom-icon.svg'; | ||
* // Note: 'right' is one of default icons provided by the feature. | ||
* { name: 'imageStyleFull', icon: 'right' }, | ||
* { name: 'full', icon: 'right' }, | ||
* | ||
* // This will customize the icon, title and CSS class of the default "side" style. | ||
* { name: 'imageStyleSide', icon: customIcon, title: 'My side style', class: 'custom-side-image' } | ||
* { name: 'side', icon: customIcon, title: 'My side style', class: 'custom-side-image' } | ||
* ] | ||
@@ -137,6 +97,6 @@ * }; | ||
* | ||
* Note: Setting `title` to one of {@link module:image/imagestyle/imagestyleengine~ImageStyleEngine#localizedDefaultStylesTitles} | ||
* Note: Setting `title` to one of {@link module:image/imagestyle/imagestyleui~ImageStyleUI#localizedDefaultStylesTitles} | ||
* will automatically translate it to the language of the editor. | ||
* | ||
* Read more about styling images in the {@glink features/image#Image-styles Image styles guide}. | ||
* Read more about styling images in the {@glink features/image#image-styles Image styles guide}. | ||
* | ||
@@ -146,13 +106,13 @@ * The feature creates commands based on defined styles, so you can change the style of a selected image by executing | ||
* | ||
* editor.execute( 'imageStyleSide' ); | ||
* editor.execute( 'imageStyle' { value: 'side' } ); | ||
* | ||
* The features creates also buttons which execute the commands, so assuming that you use the | ||
* default image styles setting you can {@link module:image/image~ImageConfig#toolbar configure the image toolbar} | ||
* to contain these options: | ||
* The feature creates also buttons which execute the command. So, assuming that you use the | ||
* default image styles setting, you can {@link module:image/image~ImageConfig#toolbar configure the image toolbar} | ||
* (or any other toolbar) to contain these options: | ||
* | ||
* const imageConfig = { | ||
* toolbar: [ 'imageStyleFull', 'imageStyleSide' ] | ||
* toolbar: [ 'imageStyle:full', 'imageStyle:side' ] | ||
* }; | ||
* | ||
* @member {Array.<module:image/imagestyle/imagestyleengine~ImageStyleFormat>} module:image/image~ImageConfig#styles | ||
* @member {Array.<module:image/imagestyle/imagestyleediting~ImageStyleFormat>} module:image/image~ImageConfig#styles | ||
*/ |
/** | ||
* @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. | ||
*/ | ||
import first from '@ckeditor/ckeditor5-utils/src/first'; | ||
/** | ||
@@ -10,8 +12,6 @@ * @module image/imagestyle/converters | ||
import { isImage } from '../image/utils'; | ||
/** | ||
* Returns a converter for the `imageStyle` attribute. It can be used for adding, changing and removing the attribute. | ||
* | ||
* @param {Object} styles An object containing available styles. See {@link module:image/imagestyle/imagestyleengine~ImageStyleFormat} | ||
* @param {Object} styles An object containing available styles. See {@link module:image/imagestyle/imagestyleediting~ImageStyleFormat} | ||
* for more details. | ||
@@ -21,7 +21,4 @@ * @returns {Function} A model-to-view attribute converter. | ||
export function modelToViewStyleAttribute( styles ) { | ||
return ( evt, data, consumable, conversionApi ) => { | ||
const eventType = evt.name.split( ':' )[ 0 ]; | ||
const consumableType = eventType + ':imageStyle'; | ||
if ( !consumable.test( data.item, consumableType ) ) { | ||
return ( evt, data, conversionApi ) => { | ||
if ( !conversionApi.consumable.consume( data.item, evt.name ) ) { | ||
return; | ||
@@ -33,10 +30,12 @@ } | ||
const oldStyle = getStyleByName( data.attributeOldValue, styles ); | ||
const viewElement = conversionApi.mapper.toViewElement( data.item ); | ||
const viewWriter = conversionApi.writer; | ||
const isRemovalHandled = handleRemoval( eventType, oldStyle, viewElement ); | ||
const isAdditionHandled = handleAddition( eventType, newStyle, viewElement ); | ||
if ( oldStyle ) { | ||
viewWriter.removeClass( oldStyle.className, viewElement ); | ||
} | ||
// https://github.com/ckeditor/ckeditor5-image/issues/132 | ||
if ( isRemovalHandled || isAdditionHandled ) { | ||
consumable.consume( data.item, consumableType ); | ||
if ( newStyle ) { | ||
viewWriter.addClass( newStyle.className, viewElement ); | ||
} | ||
@@ -49,3 +48,3 @@ }; | ||
* | ||
* @param {Array.<module:image/imagestyle/imagestyleengine~ImageStyleFormat>} styles Styles for which the converter is created. | ||
* @param {Array.<module:image/imagestyle/imagestyleediting~ImageStyleFormat>} styles Styles for which the converter is created. | ||
* @returns {Function} A view-to-model converter. | ||
@@ -57,41 +56,24 @@ */ | ||
return ( evt, data, consumable, conversionApi ) => { | ||
for ( const style of filteredStyles ) { | ||
viewToModelImageStyle( style, data, consumable, conversionApi ); | ||
return ( evt, data, conversionApi ) => { | ||
if ( !data.modelRange ) { | ||
return; | ||
} | ||
}; | ||
} | ||
// Converter from view to model converting single style. | ||
// For more information see {@link module:engine/conversion/viewconversiondispatcher~ViewConversionDispatcher}; | ||
// | ||
// @param {module:image/imagestyle/imagestyleengine~ImageStyleFormat} style | ||
// @param {Object} data | ||
// @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable | ||
// @param {Object} conversionApi | ||
function viewToModelImageStyle( style, data, consumable, conversionApi ) { | ||
const viewFigureElement = data.input; | ||
const modelImageElement = data.output; | ||
const viewFigureElement = data.viewItem; | ||
const modelImageElement = first( data.modelRange.getItems() ); | ||
// *** Step 1: Validate conversion. | ||
// Check if view element has proper class to consume. | ||
if ( !consumable.test( viewFigureElement, { class: style.className } ) ) { | ||
return; | ||
} | ||
// Check if `imageStyle` attribute is allowed for current element. | ||
if ( !conversionApi.schema.checkAttribute( modelImageElement, 'imageStyle' ) ) { | ||
return; | ||
} | ||
// Check if figure is converted to image. | ||
if ( !isImage( modelImageElement ) ) { | ||
return; | ||
} | ||
// Check if image element can be placed in current context wit additional attribute. | ||
const attributes = [ ...modelImageElement.getAttributeKeys(), 'imageStyle' ]; | ||
if ( !conversionApi.schema.check( { name: 'image', inside: data.context, attributes } ) ) { | ||
return; | ||
} | ||
// *** Step2: Convert to model. | ||
consumable.consume( viewFigureElement, { class: style.className } ); | ||
modelImageElement.setAttribute( 'imageStyle', style.name ); | ||
// Convert style one by one. | ||
for ( const style of filteredStyles ) { | ||
// Try to consume class corresponding with style. | ||
if ( conversionApi.consumable.consume( viewFigureElement, { class: style.className } ) ) { | ||
// And convert this style to model attribute. | ||
conversionApi.writer.setAttribute( 'imageStyle', style.name, modelImageElement ); | ||
} | ||
} | ||
}; | ||
} | ||
@@ -102,4 +84,4 @@ | ||
// @param {String} name | ||
// @param {Array.<module:image/imagestyle/imagestyleengine~ImageStyleFormat> } styles | ||
// @return {module:image/imagestyle/imagestyleengine~ImageStyleFormat|undefined} | ||
// @param {Array.<module:image/imagestyle/imagestyleediting~ImageStyleFormat> } styles | ||
// @return {module:image/imagestyle/imagestyleediting~ImageStyleFormat|undefined} | ||
function getStyleByName( name, styles ) { | ||
@@ -112,35 +94,1 @@ for ( const style of styles ) { | ||
} | ||
// Handles converting removal of the attribute. | ||
// Returns `true` when handling was processed correctly and further conversion can be performed. | ||
// | ||
// @param {String} eventType Type of the event. | ||
// @param {module:image/imagestyle/imagestyleengine~ImageStyleFormat} style | ||
// @param {module:engine/view/element~Element} viewElement | ||
// @returns {Boolean} Whether the change was handled. | ||
function handleRemoval( eventType, style, viewElement ) { | ||
if ( style && ( eventType == 'changeAttribute' || eventType == 'removeAttribute' ) ) { | ||
viewElement.removeClass( style.className ); | ||
return true; | ||
} | ||
return false; | ||
} | ||
// Handles converting addition of the attribute. | ||
// Returns `true` when handling was processed correctly and further conversion can be performed. | ||
// | ||
// @param {String} eventType Type of the event. | ||
// @param {module:image/imagestyle/imagestyleengine~ImageStyleFormat} style | ||
// @param {module:engine/view/element~Element} viewElement | ||
// @returns {Boolean} Whether the change was handled. | ||
function handleAddition( evenType, style, viewElement ) { | ||
if ( style && ( evenType == 'addAttribute' || evenType == 'changeAttribute' ) ) { | ||
viewElement.addClass( style.className ); | ||
return true; | ||
} | ||
return false; | ||
} |
/** | ||
* @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. | ||
@@ -23,15 +23,14 @@ */ | ||
* @param {module:core/editor/editor~Editor} editor The editor instance. | ||
* @param {module:image/imagestyle/imagestyleengine~ImageStyleFormat} styles A style to be applied by this command. | ||
* @param {Array.<module:image/imagestyle/imagestyleediting~ImageStyleFormat>} styles A styles that this command supports. | ||
*/ | ||
constructor( editor, style ) { | ||
constructor( editor, styles ) { | ||
super( editor ); | ||
/** | ||
* The value of the command — `true` if a style handled by the command is applied on a currently selected image, | ||
* `false` otherwise. | ||
* Cached name of the default style if present. If there is no default style it defaults to `false`. | ||
* | ||
* @readonly | ||
* @observable | ||
* @member {Boolean} #value | ||
* @type {Boolean|String} | ||
* @private | ||
*/ | ||
this._defaultStyle = false; | ||
@@ -42,5 +41,13 @@ /** | ||
* @readonly | ||
* @member {module:image/imagestyle/imagestyleengine~ImageStyleFormat} #style | ||
* @member {Array.<module:image/imagestyle/imagestyleediting~ImageStyleFormat>} #styles | ||
*/ | ||
this.style = style; | ||
this.styles = styles.reduce( ( styles, style ) => { | ||
styles[ style.name ] = style; | ||
if ( style.isDefault ) { | ||
this._defaultStyle = style.name; | ||
} | ||
return styles; | ||
}, {} ); | ||
} | ||
@@ -52,3 +59,3 @@ | ||
refresh() { | ||
const element = this.editor.document.selection.getSelectedElement(); | ||
const element = this.editor.model.document.selection.getSelectedElement(); | ||
@@ -59,6 +66,7 @@ this.isEnabled = isImage( element ); | ||
this.value = false; | ||
} else if ( this.style.isDefault ) { | ||
this.value = !element.hasAttribute( 'imageStyle' ); | ||
} else if ( element.hasAttribute( 'imageStyle' ) ) { | ||
const attributeValue = element.getAttribute( 'imageStyle' ); | ||
this.value = this.styles[ attributeValue ] ? attributeValue : false; | ||
} else { | ||
this.value = ( element.getAttribute( 'imageStyle' ) == this.style.name ); | ||
this.value = this._defaultStyle; | ||
} | ||
@@ -70,24 +78,26 @@ } | ||
* | ||
* editor.execute( 'imageStyle', { value: 'side' } ); | ||
* | ||
* @param {Object} options | ||
* @param {String} options.value The name of the style (based on the | ||
* {@link module:image/image~ImageConfig#styles `image.styles`} configuration option). | ||
* @fires execute | ||
* @param {Object} options | ||
* @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. | ||
*/ | ||
execute( options = {} ) { | ||
if ( this.value ) { | ||
const styleName = options.value; | ||
if ( !this.styles[ styleName ] ) { | ||
return; | ||
} | ||
const doc = this.editor.document; | ||
const imageElement = doc.selection.getSelectedElement(); | ||
const model = this.editor.model; | ||
const imageElement = model.document.selection.getSelectedElement(); | ||
doc.enqueueChanges( () => { | ||
const batch = options.batch || doc.batch(); | ||
model.change( writer => { | ||
// Default style means that there is no `imageStyle` attribute in the model. | ||
// https://github.com/ckeditor/ckeditor5-image/issues/147 | ||
if ( this.style.isDefault ) { | ||
batch.removeAttribute( imageElement, 'imageStyle' ); | ||
if ( this.styles[ styleName ].isDefault ) { | ||
writer.removeAttribute( 'imageStyle', imageElement ); | ||
} else { | ||
batch.setAttribute( imageElement, 'imageStyle', this.style.name ); | ||
writer.setAttribute( 'imageStyle', styleName, imageElement ); | ||
} | ||
@@ -94,0 +104,0 @@ } ); |
/** | ||
* @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,17 +11,10 @@ */ | ||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; | ||
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; | ||
import ImageTextAlternativeEngine from './imagetextalternative/imagetextalternativeengine'; | ||
import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler'; | ||
import TextAlternativeFormView from './imagetextalternative/ui/textalternativeformview'; | ||
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; | ||
import textAlternativeIcon from '@ckeditor/ckeditor5-core/theme/icons/low-vision.svg'; | ||
import { repositionContextualBalloon, getBalloonPositionData } from './image/ui/utils'; | ||
import { isImageWidgetSelected } from './image/utils'; | ||
import ImageTextAlternativeEditing from './imagetextalternative/imagetextalternativeediting'; | ||
import ImageTextAlternativeUI from './imagetextalternative/imagetextalternativeui'; | ||
import '../theme/imagetextalternative/theme.scss'; | ||
/** | ||
* The image text alternative plugin. | ||
* | ||
* The plugin uses the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon}. | ||
* It loads the {@link module:image/imagetextalternative/imagetextalternativeediting~ImageTextAlternativeEditing} | ||
* and {@link module:image/imagetextalternative/imagetextalternativeui~ImageTextAlternativeUI} plugins. | ||
* | ||
@@ -35,3 +28,3 @@ * @extends module:core/plugin~Plugin | ||
static get requires() { | ||
return [ ImageTextAlternativeEngine, ContextualBalloon ]; | ||
return [ ImageTextAlternativeEditing, ImageTextAlternativeUI ]; | ||
} | ||
@@ -45,162 +38,2 @@ | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
init() { | ||
this._createButton(); | ||
this._createForm(); | ||
} | ||
/** | ||
* Creates a button showing the balloon panel for changing the image text alternative and | ||
* registers it in the editor {@link module:ui/componentfactory~ComponentFactory ComponentFactory}. | ||
* | ||
* @private | ||
*/ | ||
_createButton() { | ||
const editor = this.editor; | ||
const command = editor.commands.get( 'imageTextAlternative' ); | ||
const t = editor.t; | ||
editor.ui.componentFactory.add( 'imageTextAlternative', locale => { | ||
const view = new ButtonView( locale ); | ||
view.set( { | ||
label: t( 'Change image text alternative' ), | ||
icon: textAlternativeIcon, | ||
tooltip: true | ||
} ); | ||
view.bind( 'isEnabled' ).to( command, 'isEnabled' ); | ||
this.listenTo( view, 'execute', () => this._showForm() ); | ||
return view; | ||
} ); | ||
} | ||
/** | ||
* Creates the {@link module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView} | ||
* form. | ||
* | ||
* @private | ||
*/ | ||
_createForm() { | ||
const editor = this.editor; | ||
const editingView = editor.editing.view; | ||
/** | ||
* The contextual balloon plugin instance. | ||
* | ||
* @private | ||
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon} | ||
*/ | ||
this._balloon = this.editor.plugins.get( 'ContextualBalloon' ); | ||
/** | ||
* A form containing a textarea and buttons, used to change the `alt` text value. | ||
* | ||
* @member {module:image/imagetextalternative/ui/textalternativeformview~TextAlternativeFormView} #form | ||
*/ | ||
this._form = new TextAlternativeFormView( editor.locale ); | ||
// Render the form so its #element is available for clickOutsideHandler. | ||
this._form.render(); | ||
this.listenTo( this._form, 'submit', () => { | ||
editor.execute( 'imageTextAlternative', { | ||
newValue: this._form.labeledInput.inputView.element.value | ||
} ); | ||
this._hideForm( true ); | ||
} ); | ||
this.listenTo( this._form, 'cancel', () => { | ||
this._hideForm( true ); | ||
} ); | ||
// Close the form on Esc key press. | ||
this._form.keystrokes.set( 'Esc', ( data, cancel ) => { | ||
this._hideForm( true ); | ||
cancel(); | ||
} ); | ||
// Reposition the balloon or hide the form if an image widget is no longer selected. | ||
this.listenTo( editingView, 'render', () => { | ||
if ( !isImageWidgetSelected( editingView.selection ) ) { | ||
this._hideForm( true ); | ||
} else if ( this._isVisible ) { | ||
repositionContextualBalloon( editor ); | ||
} | ||
}, { priority: 'low' } ); | ||
// Close on click outside of balloon panel element. | ||
clickOutsideHandler( { | ||
emitter: this._form, | ||
activator: () => this._isVisible, | ||
contextElements: [ this._form.element ], | ||
callback: () => this._hideForm() | ||
} ); | ||
} | ||
/** | ||
* Shows the {@link #_form} in the {@link #_balloon}. | ||
* | ||
* @private | ||
*/ | ||
_showForm() { | ||
if ( this._isVisible ) { | ||
return; | ||
} | ||
const editor = this.editor; | ||
const command = editor.commands.get( 'imageTextAlternative' ); | ||
const labeledInput = this._form.labeledInput; | ||
if ( !this._balloon.hasView( this._form ) ) { | ||
this._balloon.add( { | ||
view: this._form, | ||
position: getBalloonPositionData( editor ) | ||
} ); | ||
} | ||
// Make sure that each time the panel shows up, the field remains in sync with the value of | ||
// the command. If the user typed in the input, then canceled the balloon (`labeledInput#value` | ||
// stays unaltered) and re-opened it without changing the value of the command, they would see the | ||
// old value instead of the actual value of the command. | ||
// https://github.com/ckeditor/ckeditor5-image/issues/114 | ||
labeledInput.value = labeledInput.inputView.element.value = command.value || ''; | ||
this._form.labeledInput.select(); | ||
} | ||
/** | ||
* Removes the {@link #_form} from the {@link #_balloon}. | ||
* | ||
* @param {Boolean} [focusEditable=false] Controls whether the editing view is focused afterwards. | ||
* @private | ||
*/ | ||
_hideForm( focusEditable ) { | ||
if ( !this._isVisible ) { | ||
return; | ||
} | ||
this._balloon.remove( this._form ); | ||
if ( focusEditable ) { | ||
this.editor.editing.view.focus(); | ||
} | ||
} | ||
/** | ||
* Returns `true` when the {@link #_form} is the visible view | ||
* in the {@link #_balloon}. | ||
* | ||
* @private | ||
* @type {Boolean} | ||
*/ | ||
get _isVisible() { | ||
return this._balloon.visibleView == this._form; | ||
} | ||
} |
/** | ||
* @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. | ||
@@ -31,3 +31,3 @@ */ | ||
refresh() { | ||
const element = this.editor.document.selection.getSelectedElement(); | ||
const element = this.editor.model.document.selection.getSelectedElement(); | ||
@@ -49,15 +49,11 @@ this.isEnabled = isImage( element ); | ||
* @param {String} options.newValue The new value of the `alt` attribute to set. | ||
* @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. | ||
*/ | ||
execute( options ) { | ||
const doc = this.editor.document; | ||
const imageElement = doc.selection.getSelectedElement(); | ||
const model = this.editor.model; | ||
const imageElement = model.document.selection.getSelectedElement(); | ||
doc.enqueueChanges( () => { | ||
const batch = options.batch || doc.batch(); | ||
batch.setAttribute( imageElement, 'alt', options.newValue ); | ||
model.change( writer => { | ||
writer.setAttribute( 'alt', options.newValue, imageElement ); | ||
} ); | ||
} | ||
} |
/** | ||
* @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. | ||
@@ -22,2 +22,6 @@ */ | ||
import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg'; | ||
import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg'; | ||
import '../../../theme/textalternativeform.css'; | ||
/** | ||
@@ -65,3 +69,3 @@ * The TextAlternativeFormView class. | ||
*/ | ||
this.saveButtonView = this._createButton( t( 'Save' ) ); | ||
this.saveButtonView = this._createButton( t( 'Save' ), checkIcon ); | ||
this.saveButtonView.type = 'submit'; | ||
@@ -74,3 +78,3 @@ | ||
*/ | ||
this.cancelButtonView = this._createButton( t( 'Cancel' ), 'cancel' ); | ||
this.cancelButtonView = this._createButton( t( 'Cancel' ), cancelIcon, 'cancel' ); | ||
@@ -119,3 +123,3 @@ /** | ||
class: [ | ||
'cke-text-alternative-form', | ||
'ck-text-alternative-form', | ||
], | ||
@@ -129,16 +133,4 @@ | ||
this.labeledInput, | ||
{ | ||
tag: 'div', | ||
attributes: { | ||
class: [ | ||
'cke-text-alternative-form__actions' | ||
] | ||
}, | ||
children: [ | ||
this.saveButtonView, | ||
this.cancelButtonView | ||
] | ||
} | ||
this.saveButtonView, | ||
this.cancelButtonView | ||
] | ||
@@ -173,10 +165,14 @@ } ); | ||
* @param {String} label The button label | ||
* @param {String} icon The button's icon. | ||
* @param {String} [eventName] The event name that the ButtonView#execute event will be delegated to. | ||
* @returns {module:ui/button/buttonview~ButtonView} The button view instance. | ||
*/ | ||
_createButton( label, eventName ) { | ||
_createButton( label, icon, eventName ) { | ||
const button = new ButtonView( this.locale ); | ||
button.label = label; | ||
button.withText = true; | ||
button.set( { | ||
label, | ||
icon, | ||
tooltip: true | ||
} ); | ||
@@ -199,3 +195,5 @@ if ( eventName ) { | ||
const labeledInput = new LabeledInputView( this.locale, InputTextView ); | ||
labeledInput.label = t( 'Text alternative' ); | ||
labeledInput.inputView.placeholder = t( 'Text alternative' ); | ||
@@ -202,0 +200,0 @@ return labeledInput; |
/** | ||
* @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. | ||
@@ -16,3 +16,3 @@ */ | ||
const balloonClassName = 'ck-toolbar-container ck-editor-toolbar-container'; | ||
const balloonClassName = 'ck-toolbar-container'; | ||
@@ -47,2 +47,21 @@ /** | ||
*/ | ||
init() { | ||
const editor = this.editor; | ||
const balloonToolbar = editor.plugins.get( 'BalloonToolbar' ); | ||
// If `BalloonToolbar` plugin is loaded, it should be disabled for images | ||
// which have their own toolbar to avoid duplication. | ||
// https://github.com/ckeditor/ckeditor5-image/issues/110 | ||
if ( balloonToolbar ) { | ||
this.listenTo( balloonToolbar, 'show', evt => { | ||
if ( isImageWidgetSelected( editor.editing.view.document.selection ) ) { | ||
evt.stop(); | ||
} | ||
}, { priority: 'high' } ); | ||
} | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
afterInit() { | ||
@@ -74,9 +93,2 @@ const editor = this.editor; | ||
// Add CSS class to the toolbar. | ||
this._toolbar.extendTemplate( { | ||
attributes: { | ||
class: 'ck-editor-toolbar' | ||
} | ||
} ); | ||
// Add buttons to the toolbar. | ||
@@ -88,3 +100,3 @@ this._toolbar.fillFromConfig( toolbarConfig, editor.ui.componentFactory ); | ||
this._checkIsVisible(); | ||
}, { priority: 'low' } ); | ||
} ); | ||
@@ -109,3 +121,3 @@ // There is no render method after focus is back in editor, we need to check if balloon panel should be visible. | ||
} else { | ||
if ( isImageWidgetSelected( editor.editing.view.selection ) ) { | ||
if ( isImageWidgetSelected( editor.editing.view.document.selection ) ) { | ||
this._showToolbar(); | ||
@@ -174,6 +186,6 @@ } else { | ||
* Three toolbar items will be available in {@link module:ui/componentfactory~ComponentFactory}: | ||
* `'imageStyleFull'`, `'imageStyleSide'`, and `'imageTextAlternative'` so you can configure the toolbar like this: | ||
* `'imageStyle:full'`, `'imageStyle:side'`, and `'imageTextAlternative'` so you can configure the toolbar like this: | ||
* | ||
* const imageConfig = { | ||
* toolbar: [ 'imageStyleFull', 'imageStyleSide', '|', 'imageTextAlternative' ] | ||
* toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ] | ||
* }; | ||
@@ -180,0 +192,0 @@ * |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
159315
75
2508
22
7
1
+ Added@ckeditor/ckeditor5-upload@1.0.0-beta.4(transitive)