Skip to content

Commit

Permalink
Merge pull request #6794 from ckeditor/i/407-simple
Browse files Browse the repository at this point in the history
Feature (widget): Brought the feature allowing users to type in tight spots around block widgets where web browsers do not allow the caret to be placed (see #407). Closes #6740. Closes #6688. Closes #6689. Closes #6695.

Internal (horizontal-line): Updated the styling of .ck-horizontal-line from overflow: hidden to display: flow-root to allow the typing around UI inside the widgets (see #407, #6795).

Tests (image): Updated various image test to take the widget typing around feature into account (see #407).

Fix (media-mebed): The media widget conversion should not discard widget internals (drag or resize handlers, buttons to insert paragraphs, etc.) injected by other features when converting the URL (see #407).

Feature (theme-lark): Brought styles for the feature allowing users to type in tight spots around block widgets (see #407).

MINOR BREAKING CHANGE: The new --ck-color-focus-border-coordinates CSS custom property has been added and the existing --ck-color-focus-border property now uses it internally. If your integration overrides the latter, we recommend you update the former to avoid compatibility issues with various editor UI features.
  • Loading branch information
Reinmar authored May 14, 2020
2 parents 4c96e91 + 4fbd1a8 commit dbf24a2
Show file tree
Hide file tree
Showing 24 changed files with 1,513 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

.ck-editor__editable .ck-horizontal-line {
/* Necessary to render properly next to floated objects, e.g. side image case. */
overflow: hidden;
display: flow-root;
}

.ck-content hr {
Expand Down
52 changes: 41 additions & 11 deletions packages/ckeditor5-image/tests/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,13 @@ describe( 'Image', () => {
setModelData( model, '[<image alt="alt text" src="/assets/sample.png"></image>]' );

expect( getViewData( view ) ).to.equal(
'[<figure class="ck-widget ck-widget_selected image" contenteditable="false">' +
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>]'
);

Expand All @@ -75,8 +80,13 @@ describe( 'Image', () => {
setModelData( model, '[<image src="/assets/sample.png" alt=""></image>]' );

expect( getViewData( view ) ).to.equal(
'[<figure class="ck-widget ck-widget_selected image" contenteditable="false">' +
'<img alt="" src="/assets/sample.png"></img>' +
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>]'
);

Expand All @@ -91,11 +101,21 @@ describe( 'Image', () => {
);

expect( getViewData( view ) ).to.equal(
'[<figure class="ck-widget ck-widget_selected image" contenteditable="false">' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>]' +
'<figure class="ck-widget image" contenteditable="false">' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>'
);

Expand All @@ -105,11 +125,21 @@ describe( 'Image', () => {
} );

expect( getViewData( view ) ).to.equal(
'<figure class="ck-widget image" contenteditable="false">' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>' +
'[<figure class="ck-widget ck-widget_selected image" contenteditable="false">' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'[<figure class="' +
'ck-widget ' +
'ck-widget_can-type-around_after ck-widget_can-type-around_before ' +
'ck-widget_selected image" contenteditable="false"' +
'>' +
'<img alt="alt text" src="/assets/sample.png"></img>' +
'<div class="ck ck-reset_all ck-widget__type-around"></div>' +
'</figure>]'
);
} );
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-image/tests/imageresize.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ describe( 'ImageResize', () => {
const resizerPosition = 'bottom-left';
const domParts = getWidgetDomParts( editor, widget, resizerPosition );
const initialPointerPosition = getHandleCenterPoint( domParts.widget, resizerPosition );
const resizeWrapperView = widget.getChild( 1 );
const resizeWrapperView = widget.getChild( 2 );

resizerMouseSimulator.down( editor, domParts.resizeHandle );

Expand Down
6 changes: 4 additions & 2 deletions packages/ckeditor5-media-embed/src/converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ export function modelToViewUrlAttributeConverter( registry, options ) {
const url = data.attributeNewValue;
const viewWriter = conversionApi.writer;
const figure = conversionApi.mapper.toViewElement( data.item );
const mediaContentElement = [ ...figure.getChildren() ]
.find( child => child.getCustomProperty( 'media-content' ) );

// TODO: removing it and creating it from scratch is a hack. We can do better than that.
viewWriter.remove( viewWriter.createRangeIn( figure ) );
// TODO: removing the wrapper and creating it from scratch is a hack. We can do better than that.
viewWriter.remove( mediaContentElement );

const mediaViewElement = registry.getMediaViewElement( viewWriter, url, options );

Expand Down
9 changes: 7 additions & 2 deletions packages/ckeditor5-media-embed/src/mediaregistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ class Media {
*/
getViewElement( writer, options ) {
const attributes = {};
let viewElement;

if ( options.renderForEditingView || ( options.renderMediaPreview && this.url && this._previewRenderer ) ) {
if ( this.url ) {
Expand All @@ -233,7 +234,7 @@ class Media {

const mediaHtml = this._getPreviewHtml( options );

return writer.createUIElement( 'div', attributes, function( domDocument ) {
viewElement = writer.createUIElement( 'div', attributes, function( domDocument ) {
const domElement = this.toDomElement( domDocument );

domElement.innerHTML = mediaHtml;
Expand All @@ -245,8 +246,12 @@ class Media {
attributes.url = this.url;
}

return writer.createEmptyElement( 'oembed', attributes );
viewElement = writer.createEmptyElement( 'oembed', attributes );
}

writer.setCustomProperty( 'media-content', true, viewElement );

return viewElement;
}

/**
Expand Down
42 changes: 42 additions & 0 deletions packages/ckeditor5-media-embed/tests/mediaembedediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,48 @@ describe( 'MediaEmbedEditing', () => {
'</figure>'
);
} );

// Related to https://github.com/ckeditor/ckeditor5/issues/407.
it( 'should not discard internals (e.g. UI) injected by other features when converting the url attribute', () => {
setModelData( model, '<media url="https://ckeditor.com"></media>' );
const media = doc.getRoot().getChild( 0 );

editor.editing.view.change( writer => {
const widgetViewElement = editor.editing.mapper.toViewElement( media );

const externalUIElement = writer.createUIElement( 'div', null, function( domDocument ) {
const domElement = this.toDomElement( domDocument );

domElement.innerHTML = 'external UI';

return domElement;
} );

writer.insert( writer.createPositionAt( widgetViewElement, 'end' ), externalUIElement );
} );

expect( getViewData( view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
'<figure class="ck-widget media" contenteditable="false">' +
'<div class="ck-media__wrapper" data-oembed-url="https://ckeditor.com">' +
'allow-everything, id=https://ckeditor.com' +
'</div>' +
'<div>external UI</div>' +
'</figure>'
);

model.change( writer => {
writer.setAttribute( 'url', 'https://cksource.com', media );
} );

expect( getViewData( view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
'<figure class="ck-widget media" contenteditable="false">' +
'<div class="ck-media__wrapper" data-oembed-url="https://cksource.com">' +
'allow-everything, id=https://cksource.com' +
'</div>' +
'<div>external UI</div>' +
'</figure>'
);
} );
} );
}
} );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

/* -- Generic colors ------------------------------------------------------------------------ */

--ck-color-focus-border: hsl(208, 79%, 51%);
--ck-color-focus-border-coordinates: 208, 79%, 51%;
--ck-color-focus-border: hsl(var(--ck-color-focus-border-coordinates));
--ck-color-focus-outer-shadow: hsl(207, 89%, 86%);
--ck-color-focus-disabled-shadow: hsla(209, 90%, 72%,.3);
--ck-color-focus-error-shadow: hsla(9,100%,56%,.3);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

:root {
--ck-widget-type-around-button-size: 20px;
--ck-color-widget-type-around-button-active: var(--ck-color-focus-border);
--ck-color-widget-type-around-button-hover: var(--ck-color-widget-hover-border);
--ck-color-widget-type-around-button-blurred-editable: var(--ck-color-widget-blurred-border);
--ck-color-widget-type-around-button-radar-start-alpha: 0;
--ck-color-widget-type-around-button-radar-end-alpha: .3;
--ck-color-widget-type-around-button-icon: var(--ck-color-base-background);
}

.ck .ck-widget {
/*
* Styles of the type around buttons
*/
& .ck-widget__type-around__button {
cursor: pointer;
width: var(--ck-widget-type-around-button-size);
height: var(--ck-widget-type-around-button-size);
background: var(--ck-color-widget-type-around-button);
border-radius: 100px;
animation: fadein linear 300ms 1 normal forwards;
transition: opacity 100ms linear;

& svg {
width: 10px;
height: 8px;
transform: translate(-50%,-50%);
transition: transform .5s ease;
margin-top: 1px;

& * {
stroke-dasharray: 10;
stroke-dashoffset: 0;

fill: none;
stroke: var(--ck-color-widget-type-around-button-icon);
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}

& line {
stroke-dasharray: 7;
}
}

&:hover {
/*
* Display the "sonar" around the button when hovered.
*/
animation: ck-widget-type-around-button-sonar 1s ease infinite;

/*
* Animate active button's icon.
*/
& svg {
& polyline {
animation: ck-widget-type-around-arrow-dash 2s linear;
}

& line {
animation: ck-widget-type-around-arrow-tip-dash 2s linear;
}
}
}
}

/*
* Styles for the buttons when the widget is NOT selected (but the buttons are visible
* and still can be hovered).
*/
&:not(.ck-widget_selected) > .ck-widget__type-around > .ck-widget__type-around__button {
background: var(--ck-color-widget-type-around-button-hover);
}

/*
* Styles for the buttons when:
* - the widget is selected,
* - or the button is being hovered (regardless of the widget state).
*/
&.ck-widget_selected > .ck-widget__type-around > .ck-widget__type-around__button,
& > .ck-widget__type-around > .ck-widget__type-around__button:hover {
background: var(--ck-color-widget-type-around-button-active);

&::after {
width: calc(var(--ck-widget-type-around-button-size) - 2px);
height: calc(var(--ck-widget-type-around-button-size) - 2px);
border-radius: 100px;
background: linear-gradient(135deg, hsla(0,0%,100%,0) 0%, hsla(0,0%,100%,.3) 100%);
}
}

/*
* Styles for the "before" button when the widget has a selection handle. Because some space
* is consumed by the handle, the button must be moved slightly to the right to let it breathe.
*/
&.ck-widget_with-selection-handle > .ck-widget__type-around > .ck-widget__type-around__button_before {
margin-left: 20px;
}
}

/*
* Styles for the buttons when the widget is selected but the user clicked outside of the editor (blurred the editor).
*/
.ck-editor__editable.ck-blurred .ck-widget.ck-widget_selected > .ck-widget__type-around > .ck-widget__type-around__button:not(:hover) {
background: var(--ck-color-widget-type-around-button-blurred-editable);

& svg * {
stroke: hsl(0,0%,60%);
}
}

@keyframes ck-widget-type-around-arrow-dash {
0% {
stroke-dashoffset: 10;
}
20%, 100% {
stroke-dashoffset: 0;
}
}

@keyframes ck-widget-type-around-arrow-tip-dash {
0%, 20% {
stroke-dashoffset: 7;
}
40%, 100% {
stroke-dashoffset: 0;
}
}

@keyframes ck-widget-type-around-button-sonar {
0% {
box-shadow: 0 0 0 0 hsla(var(--ck-color-focus-border-coordinates), var(--ck-color-widget-type-around-button-radar-start-alpha));
}
50% {
box-shadow: 0 0 0 5px hsla(var(--ck-color-focus-border-coordinates), var(--ck-color-widget-type-around-button-radar-end-alpha));
}
100% {
box-shadow: 0 0 0 5px hsla(var(--ck-color-focus-border-coordinates), var(--ck-color-widget-type-around-button-radar-start-alpha));
}
}
4 changes: 3 additions & 1 deletion packages/ckeditor5-widget/lang/contexts.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"Widget toolbar": "The label used by assistive technologies describing a toolbar attached to a widget."
"Widget toolbar": "The label used by assistive technologies describing a toolbar attached to a widget.",
"Insert paragraph before block": "The title displayed when a mouse is over a button that inserts a paragraph before a block.",
"Insert paragraph after block": "The title displayed when a mouse is over a button that inserts a paragraph after a block."
}
2 changes: 2 additions & 0 deletions packages/ckeditor5-widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"@ckeditor/ckeditor5-enter": "^19.0.0",
"@ckeditor/ckeditor5-essentials": "^19.0.0",
"@ckeditor/ckeditor5-heading": "^19.0.0",
"@ckeditor/ckeditor5-horizontal-line": "^19.0.0",
"@ckeditor/ckeditor5-media-embed": "^19.0.0",
"@ckeditor/ckeditor5-paragraph": "^19.0.0",
"@ckeditor/ckeditor5-table": "^19.0.0",
"@ckeditor/ckeditor5-typing": "^19.0.0",
Expand Down
8 changes: 8 additions & 0 deletions packages/ckeditor5-widget/src/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import MouseObserver from '@ckeditor/ckeditor5-engine/src/view/observer/mouseobserver';
import WidgetTypeAround from './widgettypearound/widgettypearound';
import { getLabel, isWidget, WIDGET_SELECTED_CLASS_NAME } from './utils';
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
import env from '@ckeditor/ckeditor5-utils/src/env';
Expand Down Expand Up @@ -38,6 +39,13 @@ export default class Widget extends Plugin {
return 'Widget';
}

/**
* @inheritDoc
*/
static get requires() {
return [ WidgetTypeAround ];
}

/**
* @inheritDoc
*/
Expand Down
Loading

0 comments on commit dbf24a2

Please sign in to comment.