mui-tiptap
Version:
A Material-UI (MUI) styled WYSIWYG rich text editor, using Tiptap
164 lines (163 loc) • 8.31 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinkMenuState = void 0;
const core_1 = require("@tiptap/core");
const state_1 = require("@tiptap/pm/state");
var LinkMenuState;
(function (LinkMenuState) {
LinkMenuState[LinkMenuState["HIDDEN"] = 0] = "HIDDEN";
LinkMenuState[LinkMenuState["VIEW_LINK_DETAILS"] = 1] = "VIEW_LINK_DETAILS";
LinkMenuState[LinkMenuState["EDIT_LINK"] = 2] = "EDIT_LINK";
})(LinkMenuState || (exports.LinkMenuState = LinkMenuState = {}));
/**
* To be used in conjunction with the `LinkBubbleMenu` component, as this
* extension provides editor commands to control the state of the link bubble
* menu.
*
* The Tiptap Link extension (@tiptap/extension-link) should also be installed
* and included in your extensions when using LinkBubbleMenuHandler:
* https://tiptap.dev/api/marks/link.
*/
const LinkBubbleMenuHandler = core_1.Extension.create({
name: "linkBubbleMenuHandler",
addStorage() {
return {
state: LinkMenuState.HIDDEN,
bubbleMenuOptions: undefined,
};
},
addCommands() {
return {
openLinkBubbleMenu: (bubbleMenuOptions = {}) => ({ editor, chain, dispatch }) => {
const currentMenuState = this.storage.state;
let newMenuState;
if (editor.isActive("link")) {
// If their cursor is currently on a link, we'll open the link menu to
// view the details.
if (currentMenuState !== LinkMenuState.VIEW_LINK_DETAILS) {
// If the user isn't already in the "View Link Details" menu, we'll first
// change the selection to encompass the entire link to make it obvious which
// link is being edited and what text it includes. We also focus in case the
// user clicked the Link menu button (so we re-focus on the editor).
// NOTE: there is a bug in Tiptap where `extendMarkRange` will not
// work despite `isActive("link")` having returning true if the
// click/cursor is at the end of a link
// https://github.com/ueberdosis/tiptap/issues/2535. This leads to
// confusing behavior and should probably be handled with a workaround
// (like checking whether `extendMarkRange` had any effect) so that we
// don't open the link menu unless we know we've selected the entire
// link.
chain().extendMarkRange("link").focus().run();
}
newMenuState = LinkMenuState.VIEW_LINK_DETAILS;
}
else {
// Otherwise open the edit link menu for the user to add a new link
newMenuState = LinkMenuState.EDIT_LINK;
}
if (dispatch) {
// Only change the state if this is not a dry-run
// https://tiptap.dev/api/commands#dry-run-for-commands. Note that
// this happens automatically for the Tiptap built-in commands
// called with `chain()` above.
this.storage.state = newMenuState;
this.storage.bubbleMenuOptions = bubbleMenuOptions;
}
return true;
},
editLinkInBubbleMenu: () => ({ dispatch }) => {
const currentMenuState = this.storage.state;
const newMenuState = LinkMenuState.EDIT_LINK;
if (currentMenuState === newMenuState) {
return false;
}
if (dispatch) {
// Only change the state if this is not a dry-run
// https://tiptap.dev/api/commands#dry-run-for-commands.
this.storage.state = newMenuState;
}
return true;
},
closeLinkBubbleMenu: () => ({ commands, dispatch }) => {
const currentMenuState = this.storage.state;
if (currentMenuState === LinkMenuState.HIDDEN) {
return false;
}
// Re-focus on the editor (e.g. for re-selection) since the user was
// previously editing and has now canceled
commands.focus();
if (dispatch) {
// Only change the state if this is not a dry-run
// https://tiptap.dev/api/commands#dry-run-for-commands. Note that
// this happens automatically for the Tiptap built-in commands
// called with `commands` above.
this.storage.state = LinkMenuState.HIDDEN;
}
return true;
},
};
},
onSelectionUpdate() {
// To ensure we maintain the proper bubble menu state, if someone is
// viewing/editing a link but moves off of it (e.g. with their keyboard
// arrow keys, or by clicking out, or by typing over the currently selected
// link), we'll close the bubble menu. Note that when in "view" mode (and
// not "edit") for an existing link, we only close if the state shows the
// user is not on an active link anymore, since the selection can be updated
// via `openLinkBubbleMenu` (and we don't want to immediately close it upon
// initial opening of the bubble menu). By contrast in "edit" mode, the
// user's focus should be in the edit form and selection shouldn't
// automatically update during opening or otherwise, so clicking out (i.e.
// changing selection) definitively indicates cancellation.
// onSelectionUpdate runs before handleClick, so we need to promptly close
// in that scenario.
if (this.storage.state === LinkMenuState.EDIT_LINK) {
this.editor.commands.closeLinkBubbleMenu();
}
else if (this.storage.state === LinkMenuState.VIEW_LINK_DETAILS &&
!this.editor.isActive("link")) {
this.editor.commands.closeLinkBubbleMenu();
}
},
addKeyboardShortcuts() {
return {
"Mod-Shift-u": () => {
this.editor.commands.openLinkBubbleMenu();
return true;
},
};
},
addProseMirrorPlugins() {
return [
new state_1.Plugin({
key: new state_1.PluginKey("handleClickLinkForMenu"),
props: {
handleClick: (view, pos, event) => {
const attrs = (0, core_1.getAttributes)(view.state, "link");
const link = event.target.closest("a");
// If the user has clicked on a link and the menu isn't already
// open, we'll open it. Otherwise we close it. (Closing the menu if
// it's already open allows a user to put their cursor at a specific
// point within the link text and implicitly close the bubble menu,
// like the Slack UI does, if they don't want to use the bubble menu
// but instead want to use regular cursor/keyboard interaction with
// the link text.)
if (link &&
attrs.href &&
this.storage.state === LinkMenuState.HIDDEN) {
this.editor.commands.openLinkBubbleMenu();
}
else {
this.editor.commands.closeLinkBubbleMenu();
}
// Return false so that the click still propagates to any other
// handlers, without `preventDefault` (see note on boolean return
// values here https://prosemirror.net/docs/ref/#view.EditorProps)
return false;
},
},
}),
];
},
});
exports.default = LinkBubbleMenuHandler;
;