ckeditor / ckeditor5

Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.
https://ckeditor.com/ckeditor-5
Other
8.7k stars 3.62k forks source link

Custom link button: LinkUI looses caret focus when inserting link #13374

Open leevigraham opened 1 year ago

leevigraham commented 1 year ago

πŸ“ Provide detailed reproduction steps (if any)

I'm trying to add custom buttons to the Link Balloon which opens a CMS modal. When closing the modal and adding the link the link is created in the wrong position.

What I want to ultimately know is… what is the correct way to launch a third party modal, take it's selection, populate the link balloon, and submit the link balloon form.

Here's the pseudo code:

  1. Create a plugin
  2. Create new ButtonView and append to LlinkUI.formView`
  3. on button execute, hide the linkUI balloon, launch a CMS modal
  4. on modal selection, show the linkUI ballon, fill in the input, focus the input
  5. Submit the balloon form, link is placed at the start of the block, not the selected text

https://user-images.githubusercontent.com/25124/216180273-42044ee7-fd93-491c-af05-9708fd95ee5d.mov

Plugin code:

import {Plugin} from 'ckeditor5/src/core';
import {ButtonView, View} from 'ckeditor5/src/ui';
import {LinkUI} from "@ckeditor/ckeditor5-link";

export default class CraftLinkUI extends Plugin {
    static get pluginName() {
        return 'CraftLinkUi';
    }

    init() {
        const editor = this.editor;
        const t = editor.t;

        this.linkUI = editor.plugins.get(LinkUI);
        this.linkFormView = this.linkUI.formView;

        /**
         * Create the buttons
         */
        const linkToAssetButton = new ButtonView();
        linkToAssetButton.set({
            label: t('Link to an Asset'),
            withText: true,
            _elementType: 'craft\\elements\\Asset',
            _refHandle: 'asset',
            _commandName: 'craftLinkElementCommand',
        });
        linkToAssetButton.on('execute', (evt) => {
            this._openModal({
                elementType: evt.source._elementType,
                refHandle: evt.source._refHandle
            })
        });

        const linkToEntryButton = new ButtonView();
        linkToEntryButton.set({
            label: t('Link to an Entry'),
            withText: true,
            _elementType: 'craft\\elements\\Entry',
            _refHandle: 'entry',
            _commandName: 'craftLinkElementCommand',
        });
        linkToEntryButton.on('execute', (evt) => {
            this._openModal({
                elementType: evt.source._elementType,
                refHandle: evt.source._refHandle
            })
        });

        const additionalButtonsView = new View();
        additionalButtonsView.setTemplate({
            tag: 'div',
            children: [linkToAssetButton, linkToEntryButton]
        });

        /**
         * Add the buttons to the link form view
         */
        this.linkFormView.template.children.unshift(additionalButtonsView);
    }

    _openModal(args) {
        // Hide the linkUI Balloon - This is a private method
        this.linkUI._hideUI();
        Craft.createElementSelectorModal(args.elementType, {
            onSelect: (elements) => {
                if (elements.length) {
                    const [element] = elements;
                    const url = `${element.url}#${args.refHandle}:${element.id}@${element.siteId}`;
                    // Show the linkUI Balloon - This is a private method
                    this.linkUI._showUI(true);
                    this.linkUI.formView.urlInputView.fieldView.element.value = url;
                    // Focus the input
                    setTimeout(() => {
                        this.linkUI.formView.urlInputView.fieldView.element.focus();
                    }, 100)
                }
            },
        });
    }
}

Removing this.linkUI._hideUI(); from _openModal(args) resolves the issue.

https://user-images.githubusercontent.com/25124/216181113-661aa551-71a4-4eb0-ae9d-6c1c4e52c187.mov

βœ”οΈ Expected result

Link should be added to selected text.

❌ Actual result

Link is added to the start of the block

Related tickets:

https://github.com/ckeditor/ckeditor5/issues/4836

πŸ“ƒ Other details

quicksketch commented 1 year ago

Hi @leevigraham, I'm working on solving the same overall feature: Opening a CMS-provided modal to the normal Link plugin. While I understand your overall question, "What's the proper way to insert a link from a modal", I'm not sure why this is flagged as a bug. You mention that removing this line solves the problem:

Removing this.linkUI._hideUI(); from _openModal(args) resolves the issue.

But since that line is in custom code (and on top of that calling a private method) this does seem like it would be a bug.

I'd still like to know the same question, I have an issue where CKEditor is loosing focus when the modal opens, and the balloon closes, causing the whole link not to be saved.

Witoso commented 1 year ago

I think you will need to add the dom element to the editor.ui.focusTracker. @oleq, probably you have a better understanding here. The focus tracking article may be of help here.

oleq commented 1 year ago

I think you will need to add the dom element to the editor.ui.focusTracker. @oleq, probably you have a better understanding here. The focus tracking article may be of help here.

I think this is not the case here.

LinkUI#_hideUI() calls editor.editing.view.focus() internally. If the editor is not focused, this will move the selection to the beginning of the editing root. You can call editor.editing.view.focus() from the console in any blurred editor instance to see how it works.

Why do you want to call LinkUI#_hideUI() in the first place when the modal gets open?

But since that line is in custom code (and on top of that calling a private method) this does seem like it would be a bug.

Yes, this is a private method for a reason: it was not meant to be used by integrators; it was created to satisfy the needs of the default link UI.

CKEditorBot commented 1 week ago

There has been no activity on this issue for the past year. We've marked it as stale and will close it in 30 days. We understand it may still be relevant, so if you're interested in the solution, leave a comment or reaction under this issue.