@mdxeditor/editor
Version:
React component for rich text markdown editing
232 lines (231 loc) • 9.73 kB
JavaScript
import * as RadixPopover from "@radix-ui/react-popover";
import * as Tooltip from "@radix-ui/react-tooltip";
import React__default from "react";
import { editorRootElementRef$, activeEditor$, iconComponentFor$, useTranslation } from "../core/index.js";
import { DownshiftAutoComplete } from "../core/ui/DownshiftAutoComplete.js";
import styles from "../../styles/ui.module.css.js";
import classNames from "classnames";
import { createCommand } from "lexical";
import { useForm } from "react-hook-form";
import { linkDialogState$, linkAutocompleteSuggestions$, onClickLinkCallback$, showLinkTitleField$, onWindowChange$, updateLink$, cancelLinkEdit$, switchFromPreviewToLinkEdit$, removeLink$ } from "./index.js";
import { useCellValues, usePublisher } from "@mdxeditor/gurx";
createCommand();
function LinkEditForm({
url,
title,
text,
onSubmit,
onCancel,
linkAutocompleteSuggestions,
showLinkTitleField,
showAnchorTextField
}) {
const {
register,
handleSubmit,
control,
setValue,
reset: _
} = useForm({
values: {
url,
title,
text
}
});
const t = useTranslation();
return /* @__PURE__ */ React__default.createElement(
"form",
{
onSubmit: (e) => {
void handleSubmit(onSubmit)(e);
e.stopPropagation();
e.preventDefault();
},
onReset: (e) => {
e.stopPropagation();
onCancel();
},
onKeyDown: (e) => {
if (e.key === "Escape") {
e.stopPropagation();
onCancel();
}
},
className: classNames(styles.multiFieldForm, styles.linkDialogEditForm)
},
/* @__PURE__ */ React__default.createElement("div", { className: styles.formField }, /* @__PURE__ */ React__default.createElement("label", { htmlFor: "link-url" }, t("createLink.url", "URL")), /* @__PURE__ */ React__default.createElement(
DownshiftAutoComplete,
{
register,
initialInputValue: url,
inputName: "url",
suggestions: linkAutocompleteSuggestions,
setValue,
control,
placeholder: t("createLink.urlPlaceholder", "Select or paste an URL"),
autofocus: true
}
)),
showAnchorTextField ? /* @__PURE__ */ React__default.createElement("div", { className: styles.formField }, /* @__PURE__ */ React__default.createElement("label", { htmlFor: "link-text", title: t("createLink.textTooltip", "The text to be displayed for the link") }, t("createLink.text", "Anchor text")), /* @__PURE__ */ React__default.createElement("input", { id: "link-text", className: styles.textInput, size: 40, ...register("text") })) : null,
showLinkTitleField ? /* @__PURE__ */ React__default.createElement("div", { className: styles.formField }, /* @__PURE__ */ React__default.createElement("label", { htmlFor: "link-title", title: t("createLink.titleTooltip", "The link's title attribute, shown on hover") }, t("createLink.title", "Link title")), /* @__PURE__ */ React__default.createElement("input", { id: "link-title", className: styles.textInput, size: 40, ...register("title") })) : null,
/* @__PURE__ */ React__default.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: "var(--spacing-2)" } }, /* @__PURE__ */ React__default.createElement(
"button",
{
type: "submit",
title: t("createLink.saveTooltip", "Set URL"),
"aria-label": t("createLink.saveTooltip", "Set URL"),
className: classNames(styles.primaryButton)
},
t("dialogControls.save", "Save")
), /* @__PURE__ */ React__default.createElement(
"button",
{
type: "reset",
title: t("createLink.cancelTooltip", "Cancel change"),
"aria-label": t("createLink.cancelTooltip", "Cancel change"),
className: classNames(styles.secondaryButton)
},
t("dialogControls.cancel", "Cancel")
))
);
}
const LinkDialog = () => {
const [
editorRootElementRef,
activeEditor,
iconComponentFor,
linkDialogState,
linkAutocompleteSuggestions,
onClickLinkCallback,
showLinkTitleField
] = useCellValues(
editorRootElementRef$,
activeEditor$,
iconComponentFor$,
linkDialogState$,
linkAutocompleteSuggestions$,
onClickLinkCallback$,
showLinkTitleField$
);
const publishWindowChange = usePublisher(onWindowChange$);
const updateLink = usePublisher(updateLink$);
const cancelLinkEdit = usePublisher(cancelLinkEdit$);
const switchFromPreviewToLinkEdit = usePublisher(switchFromPreviewToLinkEdit$);
const removeLink = usePublisher(removeLink$);
React__default.useEffect(() => {
const update = () => {
activeEditor == null ? void 0 : activeEditor.getEditorState().read(() => {
publishWindowChange(true);
});
};
window.addEventListener("resize", update);
window.addEventListener("scroll", update);
return () => {
window.removeEventListener("resize", update);
window.removeEventListener("scroll", update);
};
}, [activeEditor, publishWindowChange]);
const [copyUrlTooltipOpen, setCopyUrlTooltipOpen] = React__default.useState(false);
const t = useTranslation();
if (linkDialogState.type === "inactive")
return null;
const theRect = linkDialogState.rectangle;
const urlIsExternal = linkDialogState.type === "preview" && linkDialogState.url.startsWith("http");
return /* @__PURE__ */ React__default.createElement(RadixPopover.Root, { open: true }, /* @__PURE__ */ React__default.createElement(
RadixPopover.Anchor,
{
"data-visible": linkDialogState.type === "edit",
className: styles.linkDialogAnchor,
style: {
top: `${theRect.top}px`,
left: `${theRect.left}px`,
width: `${theRect.width}px`,
height: `${theRect.height}px`
}
}
), /* @__PURE__ */ React__default.createElement(RadixPopover.Portal, { container: editorRootElementRef == null ? void 0 : editorRootElementRef.current }, /* @__PURE__ */ React__default.createElement(
RadixPopover.Content,
{
className: classNames(styles.linkDialogPopoverContent),
sideOffset: 5,
onOpenAutoFocus: (e) => {
e.preventDefault();
},
key: linkDialogState.linkNodeKey
},
linkDialogState.type === "edit" && /* @__PURE__ */ React__default.createElement(
LinkEditForm,
{
url: linkDialogState.url,
title: linkDialogState.title,
text: linkDialogState.text,
onSubmit: updateLink,
onCancel: cancelLinkEdit.bind(null),
linkAutocompleteSuggestions,
showLinkTitleField,
showAnchorTextField: linkDialogState.withAnchorText
}
),
linkDialogState.type === "preview" && /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(
"a",
{
className: styles.linkDialogPreviewAnchor,
href: linkDialogState.url,
...urlIsExternal ? { target: "_blank", rel: "noreferrer" } : {},
onClick: (e) => {
if (onClickLinkCallback !== null) {
e.preventDefault();
onClickLinkCallback(linkDialogState.url);
}
},
title: urlIsExternal ? t("linkPreview.open", `Open {{url}} in new window`, { url: linkDialogState.url }) : linkDialogState.url
},
/* @__PURE__ */ React__default.createElement("span", null, linkDialogState.url),
urlIsExternal && iconComponentFor("open_in_new")
), /* @__PURE__ */ React__default.createElement(
ActionButton,
{
onClick: () => {
switchFromPreviewToLinkEdit();
},
title: t("linkPreview.edit", "Edit link URL"),
"aria-label": t("linkPreview.edit", "Edit link URL")
},
iconComponentFor("edit")
), /* @__PURE__ */ React__default.createElement(Tooltip.Provider, null, /* @__PURE__ */ React__default.createElement(Tooltip.Root, { open: copyUrlTooltipOpen }, /* @__PURE__ */ React__default.createElement(Tooltip.Trigger, { asChild: true }, /* @__PURE__ */ React__default.createElement(
ActionButton,
{
title: t("linkPreview.copyToClipboard", "Copy to clipboard"),
"aria-label": t("linkPreview.copyToClipboard", "Copy to clipboard"),
onClick: () => {
void window.navigator.clipboard.writeText(linkDialogState.url).then(() => {
setCopyUrlTooltipOpen(true);
setTimeout(() => {
setCopyUrlTooltipOpen(false);
}, 1e3);
});
}
},
copyUrlTooltipOpen ? iconComponentFor("check") : iconComponentFor("content_copy")
)), /* @__PURE__ */ React__default.createElement(Tooltip.Portal, { container: editorRootElementRef == null ? void 0 : editorRootElementRef.current }, /* @__PURE__ */ React__default.createElement(Tooltip.Content, { className: classNames(styles.tooltipContent), sideOffset: 5 }, t("linkPreview.copied", "Copied!"), /* @__PURE__ */ React__default.createElement(Tooltip.Arrow, null))))), /* @__PURE__ */ React__default.createElement(
ActionButton,
{
title: t("linkPreview.remove", "Remove link"),
"aria-label": t("linkPreview.remove", "Remove link"),
onClick: () => {
removeLink();
}
},
iconComponentFor("link_off")
)),
/* @__PURE__ */ React__default.createElement(RadixPopover.Arrow, { className: styles.popoverArrow })
)));
};
const ActionButton = React__default.forwardRef(({ className, ...props }, ref) => {
return /* @__PURE__ */ React__default.createElement("button", { className: classNames(styles.actionButton, className), ref, ...props });
});
export {
LinkDialog,
LinkEditForm
};