ckeditor / ckeditor5

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

Improve customizability of the link ui (form view, actions view) #13446

Open JensDallmann opened 1 year ago

JensDallmann commented 1 year ago

📝 Provide a description of the new feature

Currently the link ui is difficult to customize. In our scenario we add some features for users to the form view as well as to the actions view.

Before 36.0.1 we accessed the fields (LinkUI.formView and LinkUI.actionsView) but now they are null until the link balloon has been opened once. Unfortunately there is no information about the creation of the form view/actions view which prevents us from hooking into the lazy loaded views.

We implemented a workaround by overriding the function LinkUI._createViews. The overriden LinkUI._createViews triggers listeners after the creation of each view. The workaround will probably break in an update because we override a function (which may be changed in an update) and we use private API.

Below is an example of the plugin we use to provide a hook when the views are created.

What is the expected behavior of the proposed feature?

A nice public API to extend the feature set of the link dialog, that we do not need to access/override private API.


If you'd like to see this feature implemented, add a 👍 reaction to this post.

Our workaround for now

/* eslint-disable @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment */
import Plugin from "@ckeditor/ckeditor5-core/src/plugin";
import LinkUI from "@ckeditor/ckeditor5-link/src/linkui";

// noinspection JSConstantReassignment
export class LinkUIEventPatch extends Plugin {
  static readonly requires = [LinkUI];

  #afterFormViewListeners: ((linkUI: LinkUI) => void)[] = [];
  #afterActionsViewListeners: ((linkUI: LinkUI) => void)[] = [];

  init(): void {
    const linkUI: LinkUI = this.editor.plugins.get(LinkUI);
    //@ts-expect-error Patching an internal function
    linkUI._createViews = this._createViews(linkUI);
  }

  registerAfterFormViewListener(listener: (linkUI: LinkUI) => void): void {
    this.#afterFormViewListeners.push(listener);
  }

  registerAfterActionsViewListener(listener: (linkUI: LinkUI) => void): void {
    this.#afterActionsViewListeners.push(listener);
  }

  _createViews(linkUI: LinkUI): () => void {
    return () => {
      //@ts-expect-error Patching an internal function
      linkUI.actionsView = linkUI._createActionsView();
      this.#afterActionsViewListeners.forEach((listener) => listener(linkUI));
      //@ts-expect-error Patching an internal function
      linkUI.formView = linkUI._createFormView();
      this.#afterFormViewListeners.forEach((listener) => listener(linkUI));
      // Attach lifecycle actions to the balloon.
      //@ts-expect-error Patching an internal function
      linkUI._enableUserBalloonInteractions();
    };
  }
}
niegowski commented 1 year ago

Maybe this comment would help: https://github.com/ckeditor/ckeditor5/issues/4836#issuecomment-1422740830.

JensDallmann commented 1 year ago

Sorry for the very late response and thank you very much for the hint. I was busy with migrating. It wasn't that easy but we finally did it and it seems to work fine. We could get rid of all private api usages we needed before for applying our customizations.

I was asked to explain our usecase a bit more. We have two different usecases. 1) target attributes for links We added some buttons into the actionsView to enable users to change the link target.

2) internal links We support URIs with internal references (uris). Therefore we adapt the actionsView to exchange the previewButtonView with an own view. Technically we add another view and toggle CSS classes to apply rules with the corresponding display attribute. Furthermore, we add an icon to visualize more information. In the formView we had to do nearly the same but instead of a preview we added our own input field and toggle the urlInputField. For the input we had to adapt the drop behavior as well.

If you are interested in a deeper understanding you might want to have a look into our public repository. Link Target: https://github.com/CoreMedia/ckeditor-plugins/tree/main/packages/ckeditor5-coremedia-link/src/linktarget Internal Links: https://github.com/CoreMedia/ckeditor-plugins/tree/main/packages/ckeditor5-coremedia-link/src/contentlink

This is a link to our main-branch and the migration to CKEditor 36.0.1 is still not merged but available as a pull request: https://github.com/CoreMedia/ckeditor-plugins/pull/136

wimleers commented 1 year ago

+1 for everything @JensDallmann wrote. We face the same problem in Drupal. This has broken multiple CKEditor 5 integrations and has caused a lot of wasted time:

The current situation is unsustainable. If https://ckeditor.com/docs/ckeditor5/latest/updating/guides/update-to-36.html had provided guidance for this, that'd have been a huge mitigation, a huge timesaver.