Skip to content

Commit

Permalink
refactor(link): Introduce index.ts and augmentation.ts
Browse files Browse the repository at this point in the history
Includes augmenting `LinkUI` by new `LinkActionsView` and
`LinkFormView` supporting our (again augmented) properties
`contentUriPath` and `contentName`. Due to several issues
regarding not providing `LinkFormView` and `LinkActionsView`
as named exports (which blocks augmenting them due to an
unresolved TypeScript Issue), and `SingleBindChain.to`
having too strict typing - at least from the implementation
point of view here, this resulted in a bunch of changes
to get typings straight.

See-also: microsoft/TypeScript#14080
See-also: ckeditor/ckeditor5#13864
See-also: ckeditor/ckeditor5#13965
  • Loading branch information
mmichaelis committed Apr 25, 2023
1 parent 5889fbd commit 0fe31b1
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 77 deletions.
1 change: 1 addition & 0 deletions packages/ckeditor5-coremedia-link/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"jest:coverage": "jest --collect-coverage --passWithNoTests",
"npm-check-updates": "npm-check-updates --upgrade"
},
"main": "./src/index.ts",
"exports": {
"./*": "./dist/*.js"
},
Expand Down
37 changes: 37 additions & 0 deletions packages/ckeditor5-coremedia-link/src/augmentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type {
ContentLinkActionsViewExtension,
ContentLinkClipboardPlugin,
ContentLinkCommandHook,
ContentLinkFormViewExtension,
ContentLinks,
CustomLinkTargetUI,
LinkTarget,
LinkTargetActionsViewExtension,
LinkTargetCommand,
LinkTargetModelView,
LinkUserActionsPlugin,
} from "./index";
// TODO[cke] Use index import.
import type { OpenInTabCommand } from "@coremedia/ckeditor5-coremedia-content/commands/OpenInTabCommand";

declare module "@ckeditor/ckeditor5-core" {
interface PluginsMap {
[ContentLinkActionsViewExtension.pluginName]: ContentLinkActionsViewExtension;
[ContentLinkClipboardPlugin.pluginName]: ContentLinkClipboardPlugin;
[ContentLinkCommandHook.pluginName]: ContentLinkCommandHook;
[ContentLinkFormViewExtension.pluginName]: ContentLinkFormViewExtension;
[ContentLinks.pluginName]: ContentLinks;
[CustomLinkTargetUI.pluginName]: CustomLinkTargetUI;
[LinkTarget.pluginName]: LinkTarget;
[LinkTargetActionsViewExtension.pluginName]: LinkTargetActionsViewExtension;
[LinkTargetModelView.pluginName]: LinkTargetModelView;
[LinkUserActionsPlugin.pluginName]: LinkUserActionsPlugin;
}

interface CommandsMap {
// While part of ckeditor5-coremedia-content, the command is added here
// within the ContentLinks plugin. Thus, we should declare it here.
openLinkInTab: OpenInTabCommand;
linkTarget: LinkTargetCommand;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { parseLinkBalloonConfig } from "./LinkBalloonConfig";
import { LazyLinkUIPropertiesNotInitializedYetError } from "./LazyLinkUIPropertiesNotInitializedYetError";
import { hasRequiredInternalLinkUI } from "./InternalLinkUI";
import { Observable } from "@ckeditor/ckeditor5-utils";
import { asAugmentedLinkUI } from "./ui/AugmentedLinkUI";

/**
* This plugin allows content objects to be dropped into the link dialog.
Expand Down Expand Up @@ -58,10 +59,10 @@ export default class ContentLinks extends Plugin {
* @private
*/
#removeEditorFocusAndSelection(): void {
const linkUI: LinkUI = this.editor.plugins.get(LinkUI);
//@ts-expect-error private API
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
linkUI._hideUI();
const linkUI: unknown = this.editor.plugins.get(LinkUI);
if (hasRequiredInternalLinkUI(linkUI)) {
linkUI._hideUI();
}
}

static readonly requires = [
Expand Down Expand Up @@ -122,7 +123,7 @@ export default class ContentLinks extends Plugin {
onHideUiCallback(editor: Editor): () => void {
return () => {
const linkCommand = editor.commands.get("link") as LinkCommand;
const linkUI: LinkUI = editor.plugins.get(LinkUI);
const linkUI = asAugmentedLinkUI(editor.plugins.get(LinkUI));
if (!linkUI || !linkCommand) {
return;
}
Expand All @@ -133,9 +134,7 @@ export default class ContentLinks extends Plugin {

const commandValue: string = linkCommand.value ?? "";
const value = CONTENT_CKE_MODEL_URI_REGEXP.test(commandValue) ? commandValue : undefined;
// @ts-expect-errors since 37.0.0, how to extend the view with another property?
formView.set({ contentUriPath: value });
// @ts-expect-errors since 37.0.0, how to extend the view with another property?
actionsView.set({ contentUriPath: value });
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// TODO[cke] Find better way and better details for this error (e.g., should denote, which property is missing)
export class LazyLinkUIPropertiesNotInitializedYetError extends Error {
constructor() {
super("Lazy LinkUI properties (LinkUI.formView and LinkUI.actionsView) are not initialized yet.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint no-null/no-null: off */

import LinkActionsView from "@ckeditor/ckeditor5-link/src/ui/linkactionsview";

/*
* DevNote:
*
* Due to https://github.com/Microsoft/TypeScript/issues/14080 and `LinkActionsView`
* provided as so-called named export (see https://github.com/ckeditor/ckeditor5/issues/13864),
* transparent augmentation is cumbersome.
*
* For Migrating to CKEditor 5 37.x, we decided to stick to a plain on-demand
* casting.
*/

/**
* Augmented properties for `LinkActionsView`.
*/
export interface LinkActionsViewAugmentation {
/**
* URI path of linked content item.
*/
// Must be non-optional due to: https://github.com/ckeditor/ckeditor5/issues/13965
contentUriPath: string | null | undefined;
}

/**
* Combined type for augmented `LinkActionsView`.
*/
export type AugmentedLinkActionsView = LinkActionsView & LinkActionsViewAugmentation;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint no-null/no-null: off */

import LinkFormView from "@ckeditor/ckeditor5-link/src/ui/linkformview";

/*
* DevNote:
*
* Due to https://github.com/Microsoft/TypeScript/issues/14080 and `LinkFormView`
* provided as so-called named export (see https://github.com/ckeditor/ckeditor5/issues/13864),
* transparent augmentation is cumbersome.
*
* For Migrating to CKEditor 5 37.x, we decided to stick to a plain on-demand
* casting.
*/

/**
* Augmented properties for `LinkFormView`.
*/
export interface LinkFormViewAugmentation {
/**
* Name of the linked content.
*/
// Must be non-optional due to: https://github.com/ckeditor/ckeditor5/issues/13965
contentName: string | null | undefined;
/**
* URI path of linked content item.
*/
// Must be non-optional due to: https://github.com/ckeditor/ckeditor5/issues/13965
contentUriPath: string | null | undefined;
}

/**
* Combined type for augmented `LinkFormView`.
*/
export type AugmentedLinkFormView = LinkFormView & LinkFormViewAugmentation;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { AugmentedLinkActionsView } from "./AugmentedLinkActionsView";
import { AugmentedLinkFormView } from "./AugmentedLinkFormView";
import { ViewWithCssTransitionDisabler } from "@ckeditor/ckeditor5-ui";
import { LinkUI } from "@ckeditor/ckeditor5-link";

/**
* Augmented properties for `LinkUI`.
*/
export interface LinkUIAugmentation {
actionsView: AugmentedLinkActionsView | null;
formView: (AugmentedLinkFormView & ViewWithCssTransitionDisabler) | null;
}

/**
* Combined type for augmented `LinkUI`.
*/
export type AugmentedLinkUI = Omit<LinkUI, "actionsView" | "formView"> & LinkUIAugmentation;

/**
* Cast to `AugmentedLinkUI` without explicit checks applied.
*
* @param linkUI - LinkUI to augment
*/
export const asAugmentedLinkUI = (linkUI: LinkUI): AugmentedLinkUI =>
// We will just ignore the error for now, here. It is mostly dedicated to
// https://github.com/ckeditor/ckeditor5/issues/13965, that we cannot set
// properties to use in the context of observables as optional. Instead, we
// need to make them `undefined´ as an explicitly selected type option, which
// again would require properly setting defaults for augmented properties here
// to `undefined`.
//
// If decision for the given issue is to support optional properties, we
// expect that we can just remove the following ts-expect-error.
// @ts-expect-error - Wait for decision on https://github.com/ckeditor/ckeditor5/issues/13965
linkUI;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint no-null/no-null: off */

import { Plugin, Command } from "@ckeditor/ckeditor5-core";
import { Command, Plugin } from "@ckeditor/ckeditor5-core";
import { LinkUI } from "@ckeditor/ckeditor5-link";
// LinkActionsView: See ckeditor/ckeditor5#12027.
import LinkActionsView from "@ckeditor/ckeditor5-link/src/ui/linkactionsview";
Expand All @@ -15,6 +15,7 @@ import LoggerProvider from "@coremedia/ckeditor5-logging/logging/LoggerProvider"
import { hasContentUriPath } from "./ViewExtensions";
import { showContentLinkField } from "../ContentLinkViewUtils";
import { LazyLinkUIPropertiesNotInitializedYetError } from "../LazyLinkUIPropertiesNotInitializedYetError";
import { asAugmentedLinkUI, AugmentedLinkUI } from "./AugmentedLinkUI";

/**
* Extends the action view for Content link display. This includes:
Expand All @@ -34,7 +35,7 @@ class ContentLinkActionsViewExtension extends Plugin {
init(): void {
const initInformation = reportInitStart(this);
const editor = this.editor;
const linkUI: LinkUI = editor.plugins.get(LinkUI);
const linkUI = asAugmentedLinkUI(editor.plugins.get(LinkUI));
const contextualBalloon: ContextualBalloon = editor.plugins.get(ContextualBalloon);

contextualBalloon.on("change:visibleView", (evt, name, visibleView) => {
Expand All @@ -53,23 +54,23 @@ class ContentLinkActionsViewExtension extends Plugin {
reportInitEnd(initInformation);
}

#initialize(linkUI: LinkUI): void {
#initialize(linkUI: AugmentedLinkUI): void {
const { editor } = linkUI;
const { actionsView } = linkUI;

if (!actionsView) {
ContentLinkActionsViewExtension.#logger.error(
"ActionsView is not initialized but should be. Can't apply the ContentLinkActionsViewExtension."
);
return;
}

actionsView.set({
// @ts-expect-errors since 37.0.0, how to extend the view with another property?
contentUriPath: undefined,
});

const bindContentUriPathTo = (command: Command): void => {
actionsView
// @ts-expect-errors since 37.0.0, how to extend the view with another property?
.bind("contentUriPath")
.to(command, "value", (value: unknown) =>
typeof value === "string" && CONTENT_CKE_MODEL_URI_REGEXP.test(value) ? value : undefined
Expand Down Expand Up @@ -103,7 +104,7 @@ class ContentLinkActionsViewExtension extends Plugin {
this.#extendView(linkUI);
}

#extendView(linkUI: LinkUI): void {
#extendView(linkUI: AugmentedLinkUI): void {
const { formView, actionsView } = linkUI;

if (!actionsView || !formView) {
Expand All @@ -113,17 +114,19 @@ class ContentLinkActionsViewExtension extends Plugin {
const contentLinkView = new ContentLinkView(this.editor, {
renderTypeIcon: true,
});

contentLinkView.set({
renderAsTextLink: true,
});

if (!hasContentUriPath(linkUI.actionsView)) {
ContentLinkActionsViewExtension.#logger.warn(
"ActionsView does not have a property contentUriPath. Is it already bound?",
linkUI.actionsView
);
return;
}
// @ts-expect-errors since 37.0.0, how to extend the view with another property?

contentLinkView.bind("uriPath").to(actionsView, "contentUriPath");

contentLinkView.on("contentClick", () => {
Expand All @@ -146,7 +149,6 @@ class ContentLinkActionsViewExtension extends Plugin {
formView.on("cancel", () => {
const initialValue: string = this.editor.commands.get("link")?.value as string;
actionsView.set({
// @ts-expect-errors since 37.0.0, how to extend the view with another property?
contentUriPath: CONTENT_CKE_MODEL_URI_REGEXP.test(initialValue) ? initialValue : null,
});
});
Expand Down
Loading

0 comments on commit 0fe31b1

Please sign in to comment.