Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link properties/decorators view as a standalone form #17534

Merged
merged 2 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ckeditor5-core/theme/icons/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/ckeditor5-link/lang/contexts.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Link URL": "Label for the URL input in the Link URL editing balloon.",
"Link URL must not be empty.": "An error text displayed when user attempted to enter an empty URL.",
"Link image": "Label for the image link button.",
"Link properties": "Label for the link properties link balloon title.",
"Edit link": "Button opening the Link URL editing balloon.",
"Open link in new tab": "Button opening the link in new browser tab.",
"This link has no URL": "Label explaining that a link has no URL set (the URL is empty).",
Expand All @@ -14,7 +15,6 @@
"Move out of a link": "Keystroke description for assistive technologies: keystroke for moving out of a link.",
"Bookmarks": "Title for a feature displaying a list of bookmarks.",
"No bookmarks available.": "A message displayed instead of a list of bookmarks if it is empty.",
"Advanced": "Title for a feature displaying advanced link settings.",
"Displayed text": "The label of the input field for the displayed text of the link.",
"Back": "The label of the button that returns to the previous view."
}
6 changes: 5 additions & 1 deletion packages/ckeditor5-link/src/linkconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,20 @@ export interface LinkConfig {
*
* * `'linkPreview'`,
* * `'editLink'`,
* * `'linkProperties'`
* * `'unlink'`.
*
* The default configuration for link toolbar is:
*
* ```ts
* const linkConfig = {
* toolbar: [ 'linkPreview', '|', 'editLink', 'unlink' ]
* toolbar: [ 'linkPreview', '|', 'editLink', 'linkProperties', 'unlink' ]
Mati365 marked this conversation as resolved.
Show resolved Hide resolved
* };
* ```
*
* The `linkProperties` toolbar item is only available when at least one manual decorator is defined in the
* {@link module:link/linkconfig~LinkConfig#decorators decorators configuration}.
*
* Of course, the same buttons can also be used in the
* {@link module:core/editor/editorconfig~EditorConfig#toolbar main editor toolbar}.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-link/src/linkediting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default class LinkEditing extends Plugin {
editor.config.define( 'link', {
allowCreatingEmptyLinks: false,
addTargetToExternalLinks: false,
toolbar: [ 'linkPreview', '|', 'editLink', 'unlink' ]
toolbar: [ 'linkPreview', '|', 'editLink', 'linkProperties', 'unlink' ]
Mati365 marked this conversation as resolved.
Show resolved Hide resolved
} );
}

Expand Down
143 changes: 92 additions & 51 deletions packages/ckeditor5-link/src/linkui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { isWidget } from 'ckeditor5/src/widget.js';
import LinkPreviewButtonView, { type LinkPreviewButtonNavigateEvent } from './ui/linkpreviewbuttonview.js';
import LinkFormView, { type LinkFormValidatorCallback } from './ui/linkformview.js';
import LinkBookmarksView from './ui/linkbookmarksview.js';
import LinkAdvancedView from './ui/linkadvancedview.js';
import LinkPropertiesView from './ui/linkpropertiesview.js';
import LinkButtonView from './ui/linkbuttonview.js';
import type LinkCommand from './linkcommand.js';
import type UnlinkCommand from './unlinkcommand.js';
Expand Down Expand Up @@ -78,9 +78,9 @@ export default class LinkUI extends Plugin {
public bookmarksView: LinkBookmarksView | null = null;

/**
* The form view displaying advanced link settings.
* The form view displaying properties link settings.
*/
public advancedView: LinkAdvancedView | null = null;
public propertiesView: LinkPropertiesView & ViewWithCssTransitionDisabler | null = null;

/**
* The selected text of the link or text that is selected and can become a link.
Expand Down Expand Up @@ -188,8 +188,8 @@ export default class LinkUI extends Plugin {
super.destroy();

// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
if ( this.advancedView ) {
this.advancedView.destroy();
if ( this.propertiesView ) {
this.propertiesView.destroy();
}

if ( this.formView ) {
Expand All @@ -209,9 +209,14 @@ export default class LinkUI extends Plugin {
* Creates views.
*/
private _createViews() {
const linkCommand: LinkCommand = this.editor.commands.get( 'link' )!;

this.toolbarView = this._createToolbarView();
this.formView = this._createFormView();
this.advancedView = this._createAdvancedView();

if ( linkCommand.manualDecorators.length ) {
this.propertiesView = this._createPropertiesView();
}

if ( this.editor.plugins.has( 'BookmarkEditing' ) ) {
this.bookmarksView = this._createBookmarksView();
Expand All @@ -228,10 +233,18 @@ export default class LinkUI extends Plugin {
private _createToolbarView(): ToolbarView {
const editor = this.editor;
const toolbarView = new ToolbarView( editor.locale );
const linkCommand: LinkCommand = editor.commands.get( 'link' )!;

toolbarView.class = 'ck-link-toolbar';

toolbarView.fillFromConfig( editor.config.get( 'link.toolbar' )!, editor.ui.componentFactory );
// Remove the linkProperties button if there are no manual decorators, as it would be useless.
let toolbarItems = editor.config.get( 'link.toolbar' )!;

if ( !linkCommand.manualDecorators.length ) {
toolbarItems = toolbarItems.filter( item => item !== 'linkProperties' );
}

toolbarView.fillFromConfig( toolbarItems, editor.ui.componentFactory );

// Close the panel on esc key press when the **link toolbar have focus**.
toolbarView.keystrokes.set( 'Esc', ( data, cancel ) => {
Expand Down Expand Up @@ -281,9 +294,6 @@ export default class LinkUI extends Plugin {
// Disable the "save" button if the command is disabled.
formView.saveButtonView.bind( 'isEnabled' ).to( linkCommand, 'isEnabled' );

// Show the "Advanced" button only when there are manual decorators.
formView.settingsButtonView.bind( 'isVisible' ).to( linkCommand, 'manualDecorators', decorators => decorators.length > 0 );

// Change the "Save" button label depending on the command state.
formView.saveButtonView.bind( 'label' ).to( linkCommand, 'value', value => value ? t( 'Update' ) : t( 'Insert' ) );

Expand Down Expand Up @@ -315,15 +325,6 @@ export default class LinkUI extends Plugin {
this._closeFormView();
} );

this.listenTo( formView.settingsButtonView, 'execute', () => {
this._balloon.add( {
view: this.advancedView!,
position: this._getBalloonPositionData()
} );

this.advancedView!.focus();
} );

// Close the panel on esc key press when the **form has focus**.
formView.keystrokes.set( 'Esc', ( data, cancel ) => {
this._closeFormView();
Expand Down Expand Up @@ -392,21 +393,20 @@ export default class LinkUI extends Plugin {
}

/**
* Creates the {@link module:link/ui/linkadvancedview~LinkAdvancedView} instance.
* Creates the {@link module:link/ui/linkpropertiesview~LinkPropertiesView} instance.
*/
private _createAdvancedView(): LinkAdvancedView {
private _createPropertiesView(): LinkPropertiesView & ViewWithCssTransitionDisabler {
const editor = this.editor;
const linkCommand: LinkCommand = this.editor.commands.get( 'link' )!;
const view = new LinkAdvancedView( this.editor.locale );

const view = new ( CssTransitionDisablerMixin( LinkPropertiesView ) )( editor.locale );

// Hide the panel after clicking the back button.
this.listenTo( view, 'back', () => {
// Make sure the focus always gets back to the editable _before_ removing the focused form view.
// Doing otherwise causes issues in some browsers. See https://github.com/ckeditor/ckeditor5-link/issues/193.
// Move focus back to the editing view to prevent from losing it while current view is removed.
editor.editing.view.focus();

this._removeAdvancedView();
this.formView!.focus();
this._removePropertiesView();
} );

view.listChildren.bindTo( linkCommand.manualDecorators ).using( manualDecorator => {
Expand All @@ -425,6 +425,7 @@ export default class LinkUI extends Plugin {

button.on( 'execute', () => {
manualDecorator.set( 'value', !button.isOn );
editor.execute( 'link', linkCommand.value!, this._getDecoratorSwitchesState() );
} );

return button;
Expand Down Expand Up @@ -556,6 +557,31 @@ export default class LinkUI extends Plugin {

return button;
} );

editor.ui.componentFactory.add( 'linkProperties', locale => {
const linkCommand: LinkCommand = editor.commands.get( 'link' )!;
const button = new ButtonView( locale );
const t = locale.t;

button.set( {
label: t( 'Link properties' ),
icon: icons.settings,
tooltip: true
} );

button.bind( 'isEnabled' ).to(
linkCommand, 'isEnabled',
linkCommand, 'value',
linkCommand, 'manualDecorators',
( isEnabled, href, manualDecorators ) => isEnabled && !!href && manualDecorators.length > 0
);

this.listenTo<ButtonExecuteEvent>( button, 'execute', () => {
this._addPropertiesView();
} );

return button;
} );
}

/**
Expand Down Expand Up @@ -732,6 +758,29 @@ export default class LinkUI extends Plugin {
this.formView!.enableCssTransitions();
}

/**
* Adds the {@link #propertiesView} to the {@link #_balloon}.
*/
private _addPropertiesView(): void {
if ( !this.propertiesView ) {
this._createViews();
}

if ( this._arePropertiesInPanel ) {
return;
}

this.propertiesView!.disableCssTransitions();

this._balloon.add( {
view: this.propertiesView!,
position: this._getBalloonPositionData()
} );

this.propertiesView!.enableCssTransitions();
this.propertiesView!.focus();
}

/**
* Adds the {@link #bookmarksView} to the {@link #_balloon}.
*/
Expand All @@ -753,33 +802,25 @@ export default class LinkUI extends Plugin {
/**
* Closes the form view. Decides whether the balloon should be hidden completely or if the action view should be shown. This is
* decided upon the link command value (which has a value if the document selection is in the link).
*
* Additionally, if any {@link module:link/linkconfig~LinkConfig#decorators} are defined in the editor configuration, the state of
* switch buttons responsible for manual decorator handling is restored.
*/
private _closeFormView(): void {
const linkCommand: LinkCommand = this.editor.commands.get( 'link' )!;

// Restore manual decorator states to represent the current model state. This case is important to reset the switch buttons
// when the user cancels the editing form.
linkCommand.restoreManualDecoratorStates();

this.selectedLinkableText = undefined;

if ( linkCommand.value !== undefined ) {
this._removeAdvancedView();
this._removeFormView();
} else {
this._hideUI();
}
}

/**
* Removes the {@link #advancedView} from the {@link #_balloon}.
* Removes the {@link #propertiesView} from the {@link #_balloon}.
*/
private _removeAdvancedView(): void {
if ( this._isAdvancedInPanel ) {
this._balloon.remove( this.advancedView! );
private _removePropertiesView(): void {
if ( this._arePropertiesInPanel ) {
this._balloon.remove( this.propertiesView! );
}
}

Expand Down Expand Up @@ -885,10 +926,10 @@ export default class LinkUI extends Plugin {
// If the bookmarks view is visible, remove it because it can be on top of the stack.
this._removeBookmarksView();

// If the advanced form view is visible, remove it because it can be on top of the stack.
this._removeAdvancedView();
// If the properties form view is visible, remove it because it can be on top of the stack.
this._removePropertiesView();

// Then remove the form view because it's beneath the advanced form.
// Then remove the form view because it's beneath the properties form.
this._removeFormView();

// Finally, remove the link toolbar view because it's last in the stack.
Expand Down Expand Up @@ -956,10 +997,10 @@ export default class LinkUI extends Plugin {
}

/**
* Returns `true` when {@link #advancedView} is in the {@link #_balloon}.
* Returns `true` when {@link #propertiesView} is in the {@link #_balloon}.
*/
private get _isAdvancedInPanel(): boolean {
return !!this.advancedView && this._balloon.hasView( this.advancedView );
private get _arePropertiesInPanel(): boolean {
return !!this.propertiesView && this._balloon.hasView( this.propertiesView );
}

/**
Expand All @@ -984,11 +1025,11 @@ export default class LinkUI extends Plugin {
}

/**
* Returns `true` when {@link #advancedView} is in the {@link #_balloon} and it is
* Returns `true` when {@link #propertiesView} is in the {@link #_balloon} and it is
* currently visible.
*/
private get _isAdvancedVisible(): boolean {
return !!this.advancedView && this._balloon.visibleView === this.advancedView;
private get _isPropertiesVisible(): boolean {
return !!this.propertiesView && this._balloon.visibleView === this.propertiesView;
}

/**
Expand Down Expand Up @@ -1016,19 +1057,19 @@ export default class LinkUI extends Plugin {
}

/**
* Returns `true` when {@link #advancedView}, {@link #toolbarView}, {@link #bookmarksView}
* Returns `true` when {@link #propertiesView}, {@link #toolbarView}, {@link #bookmarksView}
* or {@link #formView} is in the {@link #_balloon}.
*/
private get _isUIInPanel(): boolean {
return this._isAdvancedInPanel || this._areBookmarksInPanel || this._isFormInPanel || this._isToolbarInPanel;
return this._arePropertiesInPanel || this._areBookmarksInPanel || this._isFormInPanel || this._isToolbarInPanel;
}

/**
* Returns `true` when {@link #advancedView}, {@link #bookmarksView}, {@link #toolbarView}
* Returns `true` when {@link #propertiesView}, {@link #bookmarksView}, {@link #toolbarView}
* or {@link #formView} is in the {@link #_balloon} and it is currently visible.
*/
private get _isUIVisible(): boolean {
return this._isAdvancedVisible || this._areBookmarksVisible || this._isFormVisible || this._isToolbarVisible;
return this._isPropertiesVisible || this._areBookmarksVisible || this._isFormVisible || this._isToolbarVisible;
}

/**
Expand Down
Loading