diff --git a/packages/editor/dist/components/button.d.ts b/packages/editor/dist/components/button.d.ts
index ccd1c4605..f118730d4 100644
--- a/packages/editor/dist/components/button.d.ts
+++ b/packages/editor/dist/components/button.d.ts
@@ -1,3 +1,3 @@
///
import { ButtonProps } from "rebass";
-export declare const Button: import("react").ForwardRefExoticComponent & import("react").RefAttributes>;
+export declare const Button: import("react").ForwardRefExoticComponent & import("react").RefAttributes>;
diff --git a/packages/editor/dist/components/menu/usefocus.d.ts b/packages/editor/dist/components/menu/usefocus.d.ts
index 36b4ee2ca..66f324efc 100644
--- a/packages/editor/dist/components/menu/usefocus.d.ts
+++ b/packages/editor/dist/components/menu/usefocus.d.ts
@@ -1,4 +1,3 @@
-///
import { MenuItem } from "./types";
export declare function useFocus(items: MenuItem[], onAction: (event: KeyboardEvent) => void, onClose: (event: KeyboardEvent) => void): {
focusIndex: number;
diff --git a/packages/editor/dist/components/popuppresenter/index.d.ts b/packages/editor/dist/components/popuppresenter/index.d.ts
index 56597fe8d..14ae76e5d 100644
--- a/packages/editor/dist/components/popuppresenter/index.d.ts
+++ b/packages/editor/dist/components/popuppresenter/index.d.ts
@@ -26,5 +26,5 @@ declare type ShowPopupOptions = {
theme: Theme;
popup: (closePopup: () => void) => React.ReactNode;
} & Partial;
-export declare function showPopup(options: ShowPopupOptions): void;
+export declare function showPopup(options: ShowPopupOptions): () => void;
export {};
diff --git a/packages/editor/dist/components/popuppresenter/index.js b/packages/editor/dist/components/popuppresenter/index.js
index cac9e03de..63a438a54 100644
--- a/packages/editor/dist/components/popuppresenter/index.js
+++ b/packages/editor/dist/components/popuppresenter/index.js
@@ -43,6 +43,7 @@ function _PopupPresenter(props) {
var popupPosition = getPosition(popup, position);
popup.style.top = popupPosition.top + "px";
popup.style.left = popupPosition.left + "px";
+ console.log("popup", popupPosition);
}, [position]);
useEffect(function () {
repositionPopup();
@@ -216,4 +217,5 @@ export function showPopup(options) {
align: "end",
yOffset: 10,
}, blocking: true, focusOnRender: true }, props, { children: popup(hide) })) })), getPopupContainer());
+ return hide;
}
diff --git a/packages/editor/dist/extensions.d.ts b/packages/editor/dist/extensions.d.ts
index 27031ae24..04757fdce 100644
--- a/packages/editor/dist/extensions.d.ts
+++ b/packages/editor/dist/extensions.d.ts
@@ -25,7 +25,6 @@ import "./extensions/search-replace";
import "./extensions/embed";
import "./extensions/code-block";
import "./extensions/list-item";
-import "./extensions/link";
import "./extensions/outline-list";
import "./extensions/outline-list-item";
import "./extensions/table";
diff --git a/packages/editor/dist/extensions.js b/packages/editor/dist/extensions.js
index 27031ae24..04757fdce 100644
--- a/packages/editor/dist/extensions.js
+++ b/packages/editor/dist/extensions.js
@@ -25,7 +25,6 @@ import "./extensions/search-replace";
import "./extensions/embed";
import "./extensions/code-block";
import "./extensions/list-item";
-import "./extensions/link";
import "./extensions/outline-list";
import "./extensions/outline-list-item";
import "./extensions/table";
diff --git a/packages/editor/dist/extensions/link/link.js b/packages/editor/dist/extensions/link/link.js
index 35ac3919e..64bb22597 100644
--- a/packages/editor/dist/extensions/link/link.js
+++ b/packages/editor/dist/extensions/link/link.js
@@ -1,23 +1,91 @@
+var __read = (this && this.__read) || function (o, n) {
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
+ if (!m) return o;
+ var i = m.call(o), r, ar = [], e;
+ try {
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
+ }
+ catch (error) { e = { error: error }; }
+ finally {
+ try {
+ if (r && !r.done && (m = i["return"])) m.call(i);
+ }
+ finally { if (e) throw e.error; }
+ }
+ return ar;
+};
+var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
+ if (ar || !(i in from)) {
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
+ ar[i] = from[i];
+ }
+ }
+ return to.concat(ar || Array.prototype.slice.call(from));
+};
+import { jsx as _jsx } from "react/jsx-runtime";
import TiptapLink from "@tiptap/extension-link";
+import { Plugin, PluginKey } from "prosemirror-state";
+import { showPopup } from "../../components/popup-presenter";
+import { ToolbarGroup } from "../../toolbar/components/toolbar-group";
+var linkHoverPluginKey = new PluginKey("linkHover");
export var Link = TiptapLink.extend({
-// addProseMirrorPlugins() {
-// return [
-// ...(this.parent?.() || []),
-// new Plugin({
-// key: new PluginKey("hoverHandler"),
-// props: {
-// handleDOMEvents: {
-// mouseover: (view, event) => {
-// if (
-// event.target instanceof HTMLElement &&
-// event.target.nodeName === "A"
-// ) {
-// console.log("Got it!");
-// }
-// },
-// },
-// },
-// }),
-// ];
-// },
+ addProseMirrorPlugins: function () {
+ var _this = this;
+ var _a;
+ var linkRef = null;
+ return __spreadArray(__spreadArray([], __read((((_a = this.parent) === null || _a === void 0 ? void 0 : _a.call(this)) || [])), false), [
+ new Plugin({
+ key: linkHoverPluginKey,
+ props: {
+ handleDOMEvents: {
+ mouseover: function (view, event) {
+ var _a;
+ if (event.target instanceof HTMLElement &&
+ ((_a = event.target) === null || _a === void 0 ? void 0 : _a.classList.contains("ProseMirror"))) {
+ return;
+ }
+ if (event.target instanceof HTMLElement &&
+ event.target.nodeName === "A") {
+ if (linkRef)
+ return;
+ var pos_1 = view.posAtDOM(event.target, 0);
+ var node_1 = view.state.doc.nodeAt(pos_1);
+ console.log(node_1, pos_1);
+ if (!(node_1 === null || node_1 === void 0 ? void 0 : node_1.isText) ||
+ node_1.marks.length <= 0 ||
+ !node_1.marks.some(function (mark) { return mark.type === _this.type; }))
+ return;
+ linkRef = showPopup({
+ popup: function () { return (_jsx(ToolbarGroup, { force: true, tools: ["editLink", "removeLink", "openLink"], editor: _this.editor, selectedNode: {
+ node: node_1,
+ from: pos_1,
+ to: pos_1 + node_1.nodeSize,
+ }, sx: {
+ bg: "background",
+ boxShadow: "menu",
+ borderRadius: "default",
+ p: 1,
+ } })); },
+ theme: _this.editor.storage.theme,
+ blocking: false,
+ focusOnRender: false,
+ position: {
+ target: event.target,
+ align: "center",
+ location: "top",
+ isTargetAbsolute: true,
+ },
+ });
+ }
+ else if (linkRef) {
+ linkRef();
+ linkRef = null;
+ }
+ },
+ },
+ },
+ }),
+ ], false);
+ },
});
diff --git a/packages/editor/dist/index.js b/packages/editor/dist/index.js
index e7f3fd760..f330d0604 100644
--- a/packages/editor/dist/index.js
+++ b/packages/editor/dist/index.js
@@ -52,7 +52,7 @@ import { SearchReplace } from "./extensions/search-replace";
import { EmbedNode } from "./extensions/embed";
import { CodeBlock } from "./extensions/code-block";
import { ListItem } from "./extensions/list-item";
-import { Link } from "./extensions/link";
+import { Link } from "@tiptap/extension-link";
import { NodeViewSelectionNotifier, usePortalProvider, } from "./extensions/react";
import { OutlineList } from "./extensions/outline-list";
import { OutlineListItem } from "./extensions/outline-list-item";
diff --git a/packages/editor/dist/toolbar/components/toolbargroup.d.ts b/packages/editor/dist/toolbar/components/toolbargroup.d.ts
index 4e13e964c..a76c88195 100644
--- a/packages/editor/dist/toolbar/components/toolbargroup.d.ts
+++ b/packages/editor/dist/toolbar/components/toolbargroup.d.ts
@@ -2,9 +2,12 @@
import { ToolbarGroupDefinition, ToolButtonVariant } from "../types";
import { FlexProps } from "rebass";
import { Editor } from "@tiptap/core";
+import { NodeWithOffset } from "../utils/prosemirror";
export declare type ToolbarGroupProps = FlexProps & {
tools: ToolbarGroupDefinition;
editor: Editor;
variant?: ToolButtonVariant;
+ force?: boolean;
+ selectedNode?: NodeWithOffset;
};
export declare function ToolbarGroup(props: ToolbarGroupProps): JSX.Element;
diff --git a/packages/editor/dist/toolbar/components/toolbargroup.js b/packages/editor/dist/toolbar/components/toolbargroup.js
index f39dce669..b969c71eb 100644
--- a/packages/editor/dist/toolbar/components/toolbargroup.js
+++ b/packages/editor/dist/toolbar/components/toolbargroup.js
@@ -26,7 +26,7 @@ import { Flex } from "rebass";
import { MoreTools } from "./more-tools";
import { getToolDefinition } from "../tool-definitions";
export function ToolbarGroup(props) {
- var tools = props.tools, editor = props.editor, flexProps = __rest(props, ["tools", "editor"]);
+ var tools = props.tools, editor = props.editor, force = props.force, selectedNode = props.selectedNode, flexProps = __rest(props, ["tools", "editor", "force", "selectedNode"]);
return (_jsx(Flex, __assign({ className: "toolbar-group" }, flexProps, { children: tools.map(function (toolId) {
if (Array.isArray(toolId)) {
return (_jsx(MoreTools, { title: "More", icon: "more", popupId: toolId.join(""), tools: toolId, editor: editor }, "more-tools"));
@@ -34,7 +34,7 @@ export function ToolbarGroup(props) {
else {
var Component = findTool(toolId);
var toolDefinition = getToolDefinition(toolId);
- return (_jsx(Component, __assign({ editor: editor }, toolDefinition), toolDefinition.title));
+ return (_jsx(Component, __assign({ editor: editor, force: force, selectedNode: selectedNode }, toolDefinition), toolDefinition.title));
}
}) })));
}
diff --git a/packages/editor/dist/toolbar/components/toolbutton.d.ts b/packages/editor/dist/toolbar/components/toolbutton.d.ts
index daa747276..9040ee68a 100644
--- a/packages/editor/dist/toolbar/components/toolbutton.d.ts
+++ b/packages/editor/dist/toolbar/components/toolbutton.d.ts
@@ -14,8 +14,8 @@ export declare type ToolButtonProps = ButtonProps & {
};
export declare const ToolButton: React.NamedExoticComponent | undefined;
variant?: ToolButtonVariant | undefined;
diff --git a/packages/editor/dist/toolbar/floatingmenus/hover/handler.d.ts b/packages/editor/dist/toolbar/floatingmenus/hover/handler.d.ts
new file mode 100644
index 000000000..e02fa5f5e
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hover/handler.d.ts
@@ -0,0 +1,5 @@
+import { Editor } from "../../../types";
+export interface ElementHoverHandler {
+ nodeName: T;
+ handler: (editor: Editor) => void;
+}
diff --git a/packages/editor/dist/toolbar/floatingmenus/hover/handler.js b/packages/editor/dist/toolbar/floatingmenus/hover/handler.js
new file mode 100644
index 000000000..f72411272
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hover/handler.js
@@ -0,0 +1,25 @@
+import { useEffect } from "react";
+import { LinkHandler } from "./link";
+var elementHandlers = [LinkHandler];
+function HoverHandler(props) {
+ var editor = props.editor;
+ useEffect(function () {
+ function onMouseOver(e) {
+ var _a;
+ if (!(e.target instanceof HTMLElement))
+ return;
+ if ((_a = e.target) === null || _a === void 0 ? void 0 : _a.classList.contains("ProseMirror"))
+ return;
+ var nodeName = e.target.nodeName.toLowerCase();
+ var handler = elementHandlers.find(function (h) { return h.nodeName === nodeName; });
+ if (!handler)
+ return;
+ handler.handler(editor);
+ }
+ window.addEventListener("mouseover", onMouseOver);
+ return function () {
+ window.removeEventListener("mouseover", onMouseOver);
+ };
+ }, []);
+ return null;
+}
diff --git a/packages/editor/dist/toolbar/floatingmenus/hover/link.d.ts b/packages/editor/dist/toolbar/floatingmenus/hover/link.d.ts
new file mode 100644
index 000000000..13f34fc08
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hover/link.d.ts
@@ -0,0 +1,2 @@
+import { ElementHoverHandler } from "./handler";
+export declare const LinkHandler: ElementHoverHandler<"a">;
diff --git a/packages/editor/dist/toolbar/floatingmenus/hover/link.js b/packages/editor/dist/toolbar/floatingmenus/hover/link.js
new file mode 100644
index 000000000..3ce2270bc
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hover/link.js
@@ -0,0 +1,4 @@
+export var LinkHandler = {
+ nodeName: "a",
+ handler: function (editor) { },
+};
diff --git a/packages/editor/dist/toolbar/floatingmenus/hoverpopup/index.d.ts b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/index.d.ts
new file mode 100644
index 000000000..e23f86eea
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/index.d.ts
@@ -0,0 +1,8 @@
+import { Editor } from "../../../types";
+import { NodeWithOffset } from "../../utils/prosemirror";
+import { FloatingMenuProps } from "../types";
+export declare type HoverPopupProps = {
+ editor: Editor;
+ selectedNode: NodeWithOffset;
+};
+export declare function HoverPopupHandler(props: FloatingMenuProps): null;
diff --git a/packages/editor/dist/toolbar/floatingmenus/hoverpopup/index.js b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/index.js
new file mode 100644
index 000000000..242147ed9
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/index.js
@@ -0,0 +1,77 @@
+var __assign = (this && this.__assign) || function () {
+ __assign = Object.assign || function(t) {
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
+ s = arguments[i];
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+ t[p] = s[p];
+ }
+ return t;
+ };
+ return __assign.apply(this, arguments);
+};
+import { jsx as _jsx } from "react/jsx-runtime";
+import { useEffect, useRef } from "react";
+import { showPopup } from "../../../components/popup-presenter";
+import { LinkHoverPopupHandler } from "./link";
+var handlers = __assign({}, LinkHoverPopupHandler);
+var HOVER_TIMEOUT = 500;
+export function HoverPopupHandler(props) {
+ var editor = props.editor;
+ var hoverTimeoutId = useRef();
+ var activePopup = useRef();
+ useEffect(function () {
+ function onMouseOver(e) {
+ if (!e.target ||
+ !(e.target instanceof HTMLElement) ||
+ e.target.classList.contains("ProseMirror"))
+ return;
+ var element = e.target;
+ if (activePopup.current) {
+ var isOutsideEditor = !element.closest(".ProseMirror");
+ var isInsidePopup = element.closest(".popup-presenter-portal");
+ var isActiveElement = activePopup.current.element === element;
+ if (isInsidePopup)
+ return;
+ if (isOutsideEditor || !isActiveElement) {
+ console.log("HIDING", isOutsideEditor, isActiveElement, element);
+ activePopup.current.hide();
+ activePopup.current = undefined;
+ return;
+ }
+ }
+ clearTimeout(hoverTimeoutId.current);
+ hoverTimeoutId.current = setTimeout(function () {
+ var nodeName = element.nodeName.toLowerCase();
+ var PopupHandler = handlers[nodeName];
+ if (!PopupHandler || !editor.current)
+ return;
+ var pos = editor.current.view.posAtDOM(element, 0);
+ var node = editor.current.view.state.doc.nodeAt(pos);
+ if (!node)
+ return;
+ var hidePopup = showPopup({
+ popup: function () { return (_jsx(PopupHandler, { editor: editor, selectedNode: {
+ node: node,
+ from: pos,
+ to: pos + node.nodeSize,
+ } })); },
+ theme: editor.storage.theme,
+ blocking: false,
+ focusOnRender: false,
+ position: {
+ target: element,
+ align: "center",
+ location: "top",
+ isTargetAbsolute: true,
+ },
+ });
+ activePopup.current = { element: element, hide: hidePopup };
+ }, HOVER_TIMEOUT, {});
+ }
+ window.addEventListener("mouseover", onMouseOver);
+ return function () {
+ window.removeEventListener("mouseover", onMouseOver);
+ };
+ }, []);
+ return null;
+}
diff --git a/packages/editor/dist/toolbar/floatingmenus/hoverpopup/link.d.ts b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/link.d.ts
new file mode 100644
index 000000000..6463e2600
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/link.d.ts
@@ -0,0 +1,7 @@
+///
+import { HoverPopupProps } from ".";
+declare function LinkHoverPopup(props: HoverPopupProps): JSX.Element | null;
+export declare const LinkHoverPopupHandler: {
+ a: typeof LinkHoverPopup;
+};
+export {};
diff --git a/packages/editor/dist/toolbar/floatingmenus/hoverpopup/link.js b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/link.js
new file mode 100644
index 000000000..688b0be4d
--- /dev/null
+++ b/packages/editor/dist/toolbar/floatingmenus/hoverpopup/link.js
@@ -0,0 +1,17 @@
+import { jsx as _jsx } from "react/jsx-runtime";
+import { ToolbarGroup } from "../../components/toolbar-group";
+function LinkHoverPopup(props) {
+ var editor = props.editor, selectedNode = props.selectedNode;
+ var node = selectedNode.node;
+ if (!node.isText ||
+ node.marks.length <= 0 ||
+ !node.marks.some(function (mark) { return mark.type.name === "link"; }))
+ return null;
+ return (_jsx(ToolbarGroup, { force: true, tools: ["openLink", "editLink", "removeLink"], editor: editor, selectedNode: selectedNode, sx: {
+ bg: "background",
+ boxShadow: "menu",
+ borderRadius: "default",
+ p: 1,
+ } }));
+}
+export var LinkHoverPopupHandler = { a: LinkHoverPopup };
diff --git a/packages/editor/dist/toolbar/floatingmenus/index.js b/packages/editor/dist/toolbar/floatingmenus/index.js
index 329ddc6f0..4da653a3a 100644
--- a/packages/editor/dist/toolbar/floatingmenus/index.js
+++ b/packages/editor/dist/toolbar/floatingmenus/index.js
@@ -9,8 +9,9 @@ var __assign = (this && this.__assign) || function () {
};
return __assign.apply(this, arguments);
};
-import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
+import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
+import { HoverPopupHandler } from "./hover-popup";
import { SearchReplaceFloatingMenu } from "./search-replace";
export function EditorFloatingMenus(props) {
- return (_jsx(_Fragment, { children: _jsx(SearchReplaceFloatingMenu, __assign({}, props)) }));
+ return (_jsxs(_Fragment, { children: [_jsx(SearchReplaceFloatingMenu, __assign({}, props)), _jsx(HoverPopupHandler, __assign({}, props))] }));
}
diff --git a/packages/editor/dist/toolbar/floatingmenus/types.d.ts b/packages/editor/dist/toolbar/floatingmenus/types.d.ts
index ce475de33..f3ea5ebf6 100644
--- a/packages/editor/dist/toolbar/floatingmenus/types.d.ts
+++ b/packages/editor/dist/toolbar/floatingmenus/types.d.ts
@@ -1,4 +1,4 @@
-import { Editor } from "@tiptap/core";
+import { Editor } from "../../types";
export declare type FloatingMenuProps = {
editor: Editor;
};
diff --git a/packages/editor/dist/toolbar/icons.d.ts b/packages/editor/dist/toolbar/icons.d.ts
index 76bc98db7..f5147bf49 100644
--- a/packages/editor/dist/toolbar/icons.d.ts
+++ b/packages/editor/dist/toolbar/icons.d.ts
@@ -23,7 +23,9 @@ export declare const Icons: {
textColor: string;
link: string;
linkRemove: string;
+ openLink: string;
linkEdit: string;
+ linkSettings: string;
url: string;
image: string;
imageSettings: string;
diff --git a/packages/editor/dist/toolbar/icons.js b/packages/editor/dist/toolbar/icons.js
index 0a02c91b2..7a9147fe4 100644
--- a/packages/editor/dist/toolbar/icons.js
+++ b/packages/editor/dist/toolbar/icons.js
@@ -1,4 +1,4 @@
-import { mdiAttachment, mdiBorderHorizontal, mdiCheck, mdiChevronDown, mdiCodeBraces, mdiCodeTags, mdiDotsVertical, mdiFormatAlignCenter, mdiFormatAlignJustify, mdiFormatAlignLeft, mdiFormatAlignRight, mdiFormatBold, mdiFormatClear, mdiFormatColorHighlight, mdiFormatColorText, mdiFormatItalic, mdiFormatListBulleted, mdiFormatListNumbered, mdiFormatQuoteClose, mdiFormatStrikethrough, mdiFormatSubscript, mdiFormatSuperscript, mdiFormatTextdirectionLToR, mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiImage, mdiInvertColorsOff, mdiLinkPlus, mdiLoading, mdiTable, mdiTableBorder, mdiTableRowPlusAfter, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiFormatColorFill, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, mdiFormatListCheckbox, mdiDrag, mdiCheckboxMarkedOutline, mdiChevronUp, mdiArrowUp, mdiArrowDown, mdiRegex, mdiFormatLetterCase, mdiFormatLetterMatches, mdiMoviePlusOutline, mdiLink, mdiChevronRight, mdiTableColumnWidth, mdiTableRowHeight, mdiMinus, mdiPaletteOutline, mdiCircle, mdiChevronLeft, mdiTableCog, mdiTableOff, mdiRectangle, mdiImageEditOutline, mdiArrowLeft, mdiMovieCogOutline, mdiLinkOff, } from "@mdi/js";
+import { mdiAttachment, mdiBorderHorizontal, mdiCheck, mdiChevronDown, mdiCodeBraces, mdiCodeTags, mdiDotsVertical, mdiFormatAlignCenter, mdiFormatAlignJustify, mdiFormatAlignLeft, mdiFormatAlignRight, mdiFormatBold, mdiFormatClear, mdiFormatColorHighlight, mdiFormatColorText, mdiFormatItalic, mdiFormatListBulleted, mdiFormatListNumbered, mdiFormatQuoteClose, mdiFormatStrikethrough, mdiFormatSubscript, mdiFormatSuperscript, mdiFormatTextdirectionLToR, mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiImage, mdiInvertColorsOff, mdiLinkPlus, mdiLoading, mdiTable, mdiTableBorder, mdiTableRowPlusAfter, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiFormatColorFill, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, mdiFormatListCheckbox, mdiDrag, mdiCheckboxMarkedOutline, mdiChevronUp, mdiArrowUp, mdiArrowDown, mdiRegex, mdiFormatLetterCase, mdiFormatLetterMatches, mdiMoviePlusOutline, mdiLink, mdiChevronRight, mdiTableColumnWidth, mdiTableRowHeight, mdiMinus, mdiPaletteOutline, mdiCircle, mdiChevronLeft, mdiTableCog, mdiTableOff, mdiRectangle, mdiImageEditOutline, mdiArrowLeft, mdiMovieCogOutline, mdiLinkOff, mdiOpenInNew, } from "@mdi/js";
export var Icons = {
bold: mdiFormatBold,
italic: mdiFormatItalic,
@@ -24,7 +24,9 @@ export var Icons = {
textColor: mdiFormatColorText,
link: mdiLinkPlus,
linkRemove: mdiLinkOff,
+ openLink: mdiOpenInNew,
linkEdit: "m19 14 1.28 1.28c.22.21.22.56 0 .77l-1 1L17.23 15l1-1c.11-.11.25-.17.39-.17s.27.06.38.17m-.3 3.63-6.06 6.07h-2.06v-2.06l6.07-6.06zM7 7h4v2H7c-1.6568542 0-3 1.343146-3 3s1.3431458 3 3 3h4v2H7c-2.7614237 0-5-2.238576-5-5 0-2.7614237 2.2385763-5 5-5m10 0c2.761424 0 5 2.2385763 5 5h-2c0-1.656854-1.343146-3-3-3h-4V7h4m-9 4h8v2H8v-2",
+ linkSettings: "M7 7h4v2H7c-1.6568542 0-3 1.343146-3 3s1.3431458 3 3 3h4v2H7c-2.7614237 0-5-2.238576-5-5 0-2.7614237 2.2385763-5 5-5m10 0c2.761424 0 5 2.2385763 5 5h-2c0-1.656854-1.343146-3-3-3h-4V7h4m-9 4h8v2H8v-2m15.119777 8.323608-1.07-.82c.02-.17.04-.33.04-.5 0-.17-.01-.33-.04-.5l1.06-.82c.09258-.07939.117526-.212463.06-.32l-1-1.73c-.06-.13-.19-.13-.33-.13l-1.22.5c-.28-.18-.54-.35-.85-.47l-.19-1.32c-.01-.12-.12-.21-.24-.21h-2c-.12 0-.23.09-.25.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82c-.03989.33214-.03989.66786 0 1l-1.06.82c-.09258.07939-.117526.212462-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.25.21h2c.12 0 .23-.09.25-.21l.19-1.32c.3-.13.56-.29.84-.47l1.22.5c.14 0 .27 0 .34-.13l1-1.73c.05753-.107538.03258-.240607-.06-.32m-4.78.18c-.83 0-1.5-.67-1.5-1.5s.68-1.5 1.5-1.5 1.5.67 1.5 1.5-.66 1.5-1.5 1.5z",
url: mdiLink,
image: mdiImage,
imageSettings: mdiImageEditOutline,
diff --git a/packages/editor/dist/toolbar/toolbar.js b/packages/editor/dist/toolbar/toolbar.js
index 2a647d319..8f2313852 100644
--- a/packages/editor/dist/toolbar/toolbar.js
+++ b/packages/editor/dist/toolbar/toolbar.js
@@ -32,7 +32,7 @@ export function Toolbar(props) {
"imageSettings",
"embedSettings",
"attachmentSettings",
- "linkRemove",
+ "linkSettings",
"codeRemove",
],
[
@@ -51,7 +51,7 @@ export function Toolbar(props) {
["fontSize"],
["headings", "fontFamily"],
["numberedList", "bulletList"],
- ["link"],
+ ["addLink"],
["alignCenter", ["alignLeft", "alignRight", "alignJustify", "ltr", "rtl"]],
["clearformatting"],
];
diff --git a/packages/editor/dist/toolbar/tooldefinitions.js b/packages/editor/dist/toolbar/tooldefinitions.js
index 94d007542..2367c6561 100644
--- a/packages/editor/dist/toolbar/tooldefinitions.js
+++ b/packages/editor/dist/toolbar/tooldefinitions.js
@@ -15,13 +15,28 @@ var tools = {
icon: "strikethrough",
title: "Strikethrough",
},
- link: {
+ addLink: {
icon: "link",
title: "Link",
},
- linkRemove: {
+ editLink: {
+ icon: "linkEdit",
+ title: "Edit link",
+ conditional: true,
+ },
+ removeLink: {
icon: "linkRemove",
- title: "Link remove",
+ title: "Remove link",
+ conditional: true,
+ },
+ openLink: {
+ icon: "openLink",
+ title: "Open link",
+ conditional: true,
+ },
+ linkSettings: {
+ icon: "linkSettings",
+ title: "Link settings",
conditional: true,
},
code: {
diff --git a/packages/editor/dist/toolbar/tools/index.d.ts b/packages/editor/dist/toolbar/tools/index.d.ts
index 63eab4098..598ca9b2d 100644
--- a/packages/editor/dist/toolbar/tools/index.d.ts
+++ b/packages/editor/dist/toolbar/tools/index.d.ts
@@ -1,6 +1,6 @@
import React from "react";
import { ToolProps } from "../types";
-import { Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, ClearFormatting, Link, LinkRemove, CodeRemove } from "./inline";
+import { Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, ClearFormatting, CodeRemove } from "./inline";
import { InsertBlock } from "./block";
import { FontSize, FontFamily } from "./font";
import { AlignCenter, AlignLeft, AlignRight, AlignJustify } from "./alignment";
@@ -12,6 +12,7 @@ import { TableSettings, ColumnProperties, RowProperties, CellProperties, CellBac
import { ImageSettings, ImageAlignCenter, ImageAlignLeft, ImageAlignRight, ImageProperties } from "./image";
import { AttachmentSettings, DownloadAttachment, RemoveAttachment } from "./attachment";
import { EmbedAlignCenter, EmbedAlignLeft, EmbedAlignRight, EmbedProperties, EmbedSettings } from "./embed";
+import { AddLink, EditLink, RemoveLink, LinkSettings, OpenLink } from "./link";
export declare type ToolId = keyof typeof tools;
declare const tools: {
bold: typeof Bold;
@@ -23,8 +24,11 @@ declare const tools: {
subscript: typeof Subscript;
superscript: typeof Superscript;
clearformatting: typeof ClearFormatting;
- link: typeof Link;
- linkRemove: typeof LinkRemove;
+ addLink: typeof AddLink;
+ editLink: typeof EditLink;
+ removeLink: typeof RemoveLink;
+ linkSettings: typeof LinkSettings;
+ openLink: typeof OpenLink;
insertBlock: typeof InsertBlock;
numberedList: typeof NumberedList;
bulletList: typeof BulletList;
diff --git a/packages/editor/dist/toolbar/tools/index.js b/packages/editor/dist/toolbar/tools/index.js
index 5d8af3eee..bcb64f5dd 100644
--- a/packages/editor/dist/toolbar/tools/index.js
+++ b/packages/editor/dist/toolbar/tools/index.js
@@ -1,4 +1,4 @@
-import { Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, ClearFormatting, Link, LinkRemove, CodeRemove, } from "./inline";
+import { Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, ClearFormatting, CodeRemove, } from "./inline";
import { InsertBlock } from "./block";
import { FontSize, FontFamily } from "./font";
import { AlignCenter, AlignLeft, AlignRight, AlignJustify } from "./alignment";
@@ -10,6 +10,7 @@ import { TableSettings, ColumnProperties, RowProperties, CellProperties, InsertC
import { ImageSettings, ImageAlignCenter, ImageAlignLeft, ImageAlignRight, ImageProperties, } from "./image";
import { AttachmentSettings, DownloadAttachment, RemoveAttachment, } from "./attachment";
import { EmbedAlignCenter, EmbedAlignLeft, EmbedAlignRight, EmbedProperties, EmbedSettings, } from "./embed";
+import { AddLink, EditLink, RemoveLink, LinkSettings, OpenLink } from "./link";
var tools = {
bold: Bold,
italic: Italic,
@@ -20,8 +21,11 @@ var tools = {
subscript: Subscript,
superscript: Superscript,
clearformatting: ClearFormatting,
- link: Link,
- linkRemove: LinkRemove,
+ addLink: AddLink,
+ editLink: EditLink,
+ removeLink: RemoveLink,
+ linkSettings: LinkSettings,
+ openLink: OpenLink,
insertBlock: InsertBlock,
numberedList: NumberedList,
bulletList: BulletList,
diff --git a/packages/editor/dist/toolbar/tools/inline.d.ts b/packages/editor/dist/toolbar/tools/inline.d.ts
index 71a3fec4e..50238429c 100644
--- a/packages/editor/dist/toolbar/tools/inline.d.ts
+++ b/packages/editor/dist/toolbar/tools/inline.d.ts
@@ -8,6 +8,4 @@ export declare function Bold(props: ToolProps): JSX.Element;
export declare function Subscript(props: ToolProps): JSX.Element;
export declare function Superscript(props: ToolProps): JSX.Element;
export declare function ClearFormatting(props: ToolProps): JSX.Element;
-export declare function LinkRemove(props: ToolProps): JSX.Element | null;
export declare function CodeRemove(props: ToolProps): JSX.Element | null;
-export declare function Link(props: ToolProps): JSX.Element;
diff --git a/packages/editor/dist/toolbar/tools/inline.js b/packages/editor/dist/toolbar/tools/inline.js
index f6b32e755..e1a71f437 100644
--- a/packages/editor/dist/toolbar/tools/inline.js
+++ b/packages/editor/dist/toolbar/tools/inline.js
@@ -9,28 +9,8 @@ var __assign = (this && this.__assign) || function () {
};
return __assign.apply(this, arguments);
};
-var __read = (this && this.__read) || function (o, n) {
- var m = typeof Symbol === "function" && o[Symbol.iterator];
- if (!m) return o;
- var i = m.call(o), r, ar = [], e;
- try {
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
- }
- catch (error) { e = { error: error }; }
- finally {
- try {
- if (r && !r.done && (m = i["return"])) m.call(i);
- }
- finally { if (e) throw e.error; }
- }
- return ar;
-};
-import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
+import { jsx as _jsx } from "react/jsx-runtime";
import { ToolButton } from "../components/tool-button";
-import { useCallback, useRef, useState } from "react";
-import { ResponsivePresenter } from "../../components/responsive";
-import { Popup } from "../components/popup";
-import { LinkPopup } from "../popups/link-popup";
import { useToolbarLocation } from "../stores/toolbar-store";
export function Italic(props) {
var editor = props.editor;
@@ -67,13 +47,6 @@ export function ClearFormatting(props) {
return (_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().clearNodes().unsetAllMarks().unsetMark("link").run();
} })));
}
-export function LinkRemove(props) {
- var editor = props.editor;
- var isBottom = useToolbarLocation() === "bottom";
- if (!editor.isActive("link") || !isBottom)
- return null;
- return (_jsx(ToolButton, __assign({}, props, { toggled: false, onClick: function () { var _a; return (_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().unsetMark("link").run(); } })));
-}
export function CodeRemove(props) {
var editor = props.editor;
var isBottom = useToolbarLocation() === "bottom";
@@ -81,49 +54,3 @@ export function CodeRemove(props) {
return null;
return (_jsx(ToolButton, __assign({}, props, { toggled: false, onClick: function () { var _a; return (_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().unsetMark("code").run(); } })));
}
-export function Link(props) {
- var editor = props.editor, title = props.title, icon = props.icon;
- var buttonRef = useRef(null);
- var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1];
- var _b = __read(useState(), 2), href = _b[0], setHref = _b[1];
- var _c = __read(useState(), 2), text = _c[0], setText = _c[1];
- var currentUrl = editor.getAttributes("link").href;
- var isEditing = !!currentUrl;
- var onDone = useCallback(function (href, text) {
- var _a;
- if (!href)
- return;
- var commandChain = (_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus();
- if (!commandChain)
- return;
- commandChain
- .extendMarkRange("link")
- .toggleLink({ href: href, target: "_blank" })
- .insertContent(text || href)
- .focus()
- .unsetMark("link")
- .insertContent(" ")
- .run();
- setIsOpen(false);
- }, []);
- return (_jsxs(_Fragment, { children: [_jsx(ToolButton, { id: icon, buttonRef: buttonRef, title: title, icon: isEditing ? "linkEdit" : icon, onClick: function () {
- if (isEditing)
- setHref(currentUrl);
- var _a = editor.state.selection, from = _a.from, to = _a.to, $from = _a.$from;
- var selectedNode = $from.node();
- var selectedText = isEditing
- ? selectedNode.textContent
- : editor.state.doc.textBetween(from, to);
- setText(selectedText);
- setIsOpen(true);
- }, toggled: isOpen || !!isEditing }), _jsx(ResponsivePresenter, __assign({ mobile: "sheet", desktop: "menu", position: {
- target: buttonRef.current || undefined,
- isTargetAbsolute: true,
- location: "below",
- align: "center",
- yOffset: 5,
- }, title: isEditing ? "Edit link" : "Insert link", isOpen: isOpen, items: [], onClose: function () { return setIsOpen(false); }, focusOnRender: false }, { children: _jsx(Popup, __assign({ title: isEditing ? "Edit link" : "Insert link", onClose: function () { return setIsOpen(false); } }, { children: _jsx(LinkPopup, { href: href, text: text, isEditing: isEditing, onDone: function (_a) {
- var href = _a.href, text = _a.text;
- onDone(href, text);
- } }) })) }))] }));
-}
diff --git a/packages/editor/dist/toolbar/tools/link.d.ts b/packages/editor/dist/toolbar/tools/link.d.ts
new file mode 100644
index 000000000..58223feb4
--- /dev/null
+++ b/packages/editor/dist/toolbar/tools/link.d.ts
@@ -0,0 +1,7 @@
+///
+import { ToolProps } from "../types";
+export declare function LinkSettings(props: ToolProps): JSX.Element | null;
+export declare function AddLink(props: ToolProps): JSX.Element;
+export declare function EditLink(props: ToolProps): JSX.Element;
+export declare function RemoveLink(props: ToolProps): JSX.Element;
+export declare function OpenLink(props: ToolProps): JSX.Element | null;
diff --git a/packages/editor/dist/toolbar/tools/link.js b/packages/editor/dist/toolbar/tools/link.js
new file mode 100644
index 000000000..a9e0231d5
--- /dev/null
+++ b/packages/editor/dist/toolbar/tools/link.js
@@ -0,0 +1,149 @@
+var __assign = (this && this.__assign) || function () {
+ __assign = Object.assign || function(t) {
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
+ s = arguments[i];
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+ t[p] = s[p];
+ }
+ return t;
+ };
+ return __assign.apply(this, arguments);
+};
+var __read = (this && this.__read) || function (o, n) {
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
+ if (!m) return o;
+ var i = m.call(o), r, ar = [], e;
+ try {
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
+ }
+ catch (error) { e = { error: error }; }
+ finally {
+ try {
+ if (r && !r.done && (m = i["return"])) m.call(i);
+ }
+ finally { if (e) throw e.error; }
+ }
+ return ar;
+};
+import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
+import { ToolButton } from "../components/tool-button";
+import { useCallback, useRef, useState } from "react";
+import { ResponsivePresenter } from "../../components/responsive";
+import { Popup } from "../components/popup";
+import { LinkPopup } from "../popups/link-popup";
+import { useToolbarLocation } from "../stores/toolbar-store";
+import { MoreTools } from "../components/more-tools";
+import { useRefValue } from "../../hooks/use-ref-value";
+import { findMark, selectionToOffset } from "../utils/prosemirror";
+import { setTextSelection } from "prosemirror-utils";
+import { Flex, Text } from "rebass";
+export function LinkSettings(props) {
+ var editor = props.editor;
+ var isBottom = useToolbarLocation() === "bottom";
+ if (!editor.isActive("link") || !isBottom)
+ return null;
+ return (_jsx(MoreTools, __assign({}, props, { autoCloseOnUnmount: true, popupId: "linkSettings", tools: ["openLink", "editLink", "removeLink"] })));
+}
+export function AddLink(props) {
+ var editor = props.editor;
+ var isActive = props.editor.isActive("link");
+ var onDone = useCallback(function (href, text) {
+ var _a;
+ if (!href)
+ return;
+ var commandChain = (_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus();
+ if (!commandChain)
+ return;
+ commandChain
+ .extendMarkRange("link")
+ .toggleLink({ href: href, target: "_blank" })
+ .insertContent(text || href)
+ .focus()
+ .unsetMark("link")
+ .insertContent(" ")
+ .run();
+ }, []);
+ if (isActive)
+ return _jsx(EditLink, __assign({}, props));
+ return (_jsx(LinkTool, __assign({}, props, { onDone: onDone, onClick: function () {
+ var _a = editor.state.selection, from = _a.from, to = _a.to;
+ var selectedText = editor.state.doc.textBetween(from, to);
+ return { text: selectedText };
+ } })));
+}
+export function EditLink(props) {
+ var editor = props.editor, _selectedNode = props.selectedNode;
+ var selectedNode = useRefValue(_selectedNode || selectionToOffset(editor.state.selection));
+ var onDone = useCallback(function (href, text) {
+ if (!href || !editor.current)
+ return;
+ var _a = selectedNode.current, from = _a.from, node = _a.node, to = _a.to;
+ var mark = findMark(node, "link");
+ if (!mark)
+ return;
+ editor.current
+ .chain()
+ .command(function (_a) {
+ var tr = _a.tr;
+ tr.removeMark(from, to, mark.type);
+ tr.addMark(from, to, mark.type.create({ href: href }));
+ tr.insertText(text || node.textContent, from, to);
+ setTextSelection(tr.mapping.map(from))(tr);
+ return true;
+ })
+ .focus(undefined, { scrollIntoView: true })
+ .run();
+ }, []);
+ return (_jsx(LinkTool, __assign({}, props, { isEditing: true, onDone: onDone, onClick: function () {
+ var node = selectedNode.current.node;
+ var selectedText = node.textContent;
+ var mark = findMark(node, "link");
+ if (!mark)
+ return;
+ return { text: selectedText, href: mark.attrs.href };
+ } })));
+}
+export function RemoveLink(props) {
+ var editor = props.editor, selectedNode = props.selectedNode;
+ return (_jsx(ToolButton, __assign({}, props, { toggled: false, onClick: function () {
+ var _a;
+ if (selectedNode)
+ editor.commands.setTextSelection(selectedNode.from);
+ (_a = editor.current) === null || _a === void 0 ? void 0 : _a.chain().focus().unsetLink().run();
+ } })));
+}
+export function OpenLink(props) {
+ var editor = props.editor, selectedNode = props.selectedNode;
+ var node = (selectedNode === null || selectedNode === void 0 ? void 0 : selectedNode.node) || editor.state.selection.$anchor.node();
+ var link = selectedNode ? findMark(node, "link") : null;
+ if (!link)
+ return null;
+ var href = link === null || link === void 0 ? void 0 : link.attrs.href;
+ return (_jsxs(Flex, __assign({ sx: { alignItems: "center" } }, { children: [_jsx(Text, __assign({ as: "a", href: href, target: "_blank", variant: "body", sx: { mr: 1 } }, { children: href })), _jsx(ToolButton, __assign({}, props, { toggled: false, onClick: function () { return window.open(href, "_blank"); } }))] })));
+}
+function LinkTool(props) {
+ var isEditing = props.isEditing, onClick = props.onClick, onDone = props.onDone;
+ var buttonRef = useRef(null);
+ var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1];
+ var _b = __read(useState(), 2), href = _b[0], setHref = _b[1];
+ var _c = __read(useState(), 2), text = _c[0], setText = _c[1];
+ return (_jsxs(_Fragment, { children: [_jsx(ToolButton, __assign({}, props, { buttonRef: buttonRef, onClick: function () {
+ var result = onClick();
+ if (!result)
+ return;
+ var text = result.text, href = result.href;
+ setHref(href);
+ setText(text);
+ setIsOpen(true);
+ }, toggled: isOpen })), _jsx(ResponsivePresenter, __assign({ mobile: "sheet", desktop: "menu", position: {
+ target: buttonRef.current || undefined,
+ isTargetAbsolute: true,
+ location: "below",
+ align: "center",
+ yOffset: 5,
+ }, title: isEditing ? "Edit link" : "Insert link", isOpen: isOpen, items: [], onClose: function () { return setIsOpen(false); }, focusOnRender: false }, { children: _jsx(Popup, __assign({ title: isEditing ? "Edit link" : "Insert link", onClose: function () { return setIsOpen(false); } }, { children: _jsx(LinkPopup, { href: href, text: text, isEditing: isEditing, onDone: function (_a) {
+ var href = _a.href, text = _a.text;
+ onDone(href, text);
+ setIsOpen(false);
+ } }) })) }))] }));
+}
diff --git a/packages/editor/dist/toolbar/types.d.ts b/packages/editor/dist/toolbar/types.d.ts
index b15f7fc8c..08982355e 100644
--- a/packages/editor/dist/toolbar/types.d.ts
+++ b/packages/editor/dist/toolbar/types.d.ts
@@ -1,10 +1,13 @@
import { Editor } from "../types";
import { IconNames } from "./icons";
import { ToolId } from "./tools";
+import { NodeWithOffset } from "./utils/prosemirror";
export declare type ToolButtonVariant = "small" | "normal";
export declare type ToolProps = ToolDefinition & {
editor: Editor;
variant?: ToolButtonVariant;
+ force?: boolean;
+ selectedNode?: NodeWithOffset;
};
export declare type ToolDefinition = {
icon: IconNames;
diff --git a/packages/editor/dist/toolbar/utils/prosemirror.d.ts b/packages/editor/dist/toolbar/utils/prosemirror.d.ts
index bfd6b17fe..5dd849997 100644
--- a/packages/editor/dist/toolbar/utils/prosemirror.d.ts
+++ b/packages/editor/dist/toolbar/utils/prosemirror.d.ts
@@ -1,4 +1,12 @@
import { Editor } from "@tiptap/core";
-import { Node } from "prosemirror-model";
+import { Node, Mark } from "prosemirror-model";
+import { Selection } from "prosemirror-state";
+export declare type NodeWithOffset = {
+ node: Node;
+ from: number;
+ to: number;
+};
export declare function findSelectedDOMNode(editor: Editor, types: string[]): HTMLElement | null;
export declare function findSelectedNode(editor: Editor, type: string): Node | null;
+export declare function findMark(node: Node, type: string): Mark | undefined;
+export declare function selectionToOffset(selection: Selection): NodeWithOffset;
diff --git a/packages/editor/dist/toolbar/utils/prosemirror.js b/packages/editor/dist/toolbar/utils/prosemirror.js
index f7acbd3ea..da12ccabb 100644
--- a/packages/editor/dist/toolbar/utils/prosemirror.js
+++ b/packages/editor/dist/toolbar/utils/prosemirror.js
@@ -21,3 +21,11 @@ export function findSelectedNode(editor, type) {
return null;
return editor.state.doc.nodeAt(pos);
}
+export function findMark(node, type) {
+ var mark = node.marks.find(function (m) { return m.type.name === type; });
+ return mark;
+}
+export function selectionToOffset(selection) {
+ var $from = selection.$from, from = selection.from;
+ return { node: $from.node(), from: from, to: from + $from.node().nodeSize };
+}
diff --git a/packages/editor/dist/utils/position.js b/packages/editor/dist/utils/position.js
index a5befc6a8..fb3f7ae25 100644
--- a/packages/editor/dist/utils/position.js
+++ b/packages/editor/dist/utils/position.js
@@ -41,11 +41,14 @@ export function getPosition(element, options) {
else if (location === "top")
position.top = y - elementHeight;
}
- if (target !== "mouse" && align === "center" && elementWidth > 0) {
- position.left -= elementWidth / 2 - target.clientWidth / 2;
+ if (width && target !== "mouse" && align === "center" && elementWidth > 0) {
+ position.left -= elementWidth / 2 - width / 2;
}
- else if (target !== "mouse" && align === "end" && elementWidth > 0) {
- position.left -= elementWidth - target.clientWidth;
+ else if (width &&
+ target !== "mouse" &&
+ align === "end" &&
+ elementWidth > 0) {
+ position.left -= elementWidth - width;
}
// Adjust menu height
if (elementHeight > windowHeight - position.top) {
diff --git a/packages/editor/package-lock.json b/packages/editor/package-lock.json
index 3685e4ece..cd20c2f32 100644
--- a/packages/editor/package-lock.json
+++ b/packages/editor/package-lock.json
@@ -52,6 +52,7 @@
"shortid": "^2.2.16",
"strip-indent": "^4.0.0",
"tinycolor2": "^1.4.2",
+ "unfurl.js": "^5.7.0",
"zustand": "^3.7.2"
},
"devDependencies": {
@@ -6745,8 +6746,7 @@
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/buffer-indexof": {
"version": "1.1.1",
@@ -11730,7 +11730,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true,
"bin": {
"he": "bin/he"
}
@@ -12159,7 +12158,6 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
@@ -12377,8 +12375,7 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ini": {
"version": "1.3.8",
@@ -15917,8 +15914,7 @@
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/multicast-dns": {
"version": "6.2.3",
@@ -16064,7 +16060,6 @@
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
- "dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
@@ -20301,8 +20296,7 @@
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sane": {
"version": "4.1.0",
@@ -21389,7 +21383,6 @@
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -21399,7 +21392,6 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -21722,7 +21714,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
@@ -21731,7 +21722,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -22756,8 +22746,7 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
- "dev": true
+ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"node_modules/tryer": {
"version": "1.0.1",
@@ -23103,6 +23092,111 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/unfurl.js": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/unfurl.js/-/unfurl.js-5.7.0.tgz",
+ "integrity": "sha512-r6jvA/I6bDFMaSCGLVv3KznzbsNr+pQGcanH8ezYH1bjEJEDVdZr2jcPrgdrnTlFRCDGy+sns2jG/kVwm/kMzg==",
+ "dependencies": {
+ "debug": "^3.1.0",
+ "he": "^1.2.0",
+ "htmlparser2": "^3.9.2",
+ "iconv-lite": "^0.4.24",
+ "node-fetch": "^2.6.7",
+ "source-map-support": "^0.5.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/dom-serializer": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "entities": "^2.0.0"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/dom-serializer/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ]
+ },
+ "node_modules/unfurl.js/node_modules/dom-serializer/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
+ },
+ "node_modules/unfurl.js/node_modules/domhandler": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+ "dependencies": {
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/domutils": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+ "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+ "dependencies": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/entities": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
+ },
+ "node_modules/unfurl.js/node_modules/htmlparser2": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
+ "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+ "dependencies": {
+ "domelementtype": "^1.3.1",
+ "domhandler": "^2.3.0",
+ "domutils": "^1.5.1",
+ "entities": "^1.1.1",
+ "inherits": "^2.0.1",
+ "readable-stream": "^3.1.1"
+ }
+ },
+ "node_modules/unfurl.js/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -23409,8 +23503,7 @@
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"node_modules/util.promisify": {
"version": "1.0.0",
@@ -25046,7 +25139,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
- "dev": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -25055,8 +25147,7 @@
"node_modules/whatwg-url/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
- "dev": true
+ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"node_modules/which": {
"version": "2.0.2",
@@ -30562,8 +30653,7 @@
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"buffer-indexof": {
"version": "1.1.1",
@@ -34436,8 +34526,7 @@
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "dev": true
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
"hex-color-regex": {
"version": "1.1.0",
@@ -34794,7 +34883,6 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
@@ -34947,8 +35035,7 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.8",
@@ -37644,8 +37731,7 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"multicast-dns": {
"version": "6.2.3",
@@ -37772,7 +37858,6 @@
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
- "dev": true,
"requires": {
"whatwg-url": "^5.0.0"
}
@@ -41213,8 +41298,7 @@
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sane": {
"version": "4.1.0",
@@ -42102,7 +42186,6 @@
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -42111,8 +42194,7 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
@@ -42391,7 +42473,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
"requires": {
"safe-buffer": "~5.2.0"
},
@@ -42399,8 +42480,7 @@
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
},
@@ -43188,8 +43268,7 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
- "dev": true
+ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"tryer": {
"version": "1.0.1",
@@ -43424,6 +43503,100 @@
"which-boxed-primitive": "^1.0.2"
}
},
+ "unfurl.js": {
+ "version": "5.7.0",
+ "resolved": "https://registry.npmjs.org/unfurl.js/-/unfurl.js-5.7.0.tgz",
+ "integrity": "sha512-r6jvA/I6bDFMaSCGLVv3KznzbsNr+pQGcanH8ezYH1bjEJEDVdZr2jcPrgdrnTlFRCDGy+sns2jG/kVwm/kMzg==",
+ "requires": {
+ "debug": "^3.1.0",
+ "he": "^1.2.0",
+ "htmlparser2": "^3.9.2",
+ "iconv-lite": "^0.4.24",
+ "node-fetch": "^2.6.7",
+ "source-map-support": "^0.5.9"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "dom-serializer": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "entities": "^2.0.0"
+ },
+ "dependencies": {
+ "domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
+ },
+ "entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
+ }
+ }
+ },
+ "domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
+ },
+ "domhandler": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+ "requires": {
+ "domelementtype": "1"
+ }
+ },
+ "domutils": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+ "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "entities": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
+ },
+ "htmlparser2": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
+ "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+ "requires": {
+ "domelementtype": "^1.3.1",
+ "domhandler": "^2.3.0",
+ "domutils": "^1.5.1",
+ "entities": "^1.1.1",
+ "inherits": "^2.0.1",
+ "readable-stream": "^3.1.1"
+ }
+ },
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ }
+ }
+ },
"unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -43674,8 +43847,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"util.promisify": {
"version": "1.0.0",
@@ -45010,7 +45182,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
- "dev": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -45019,8 +45190,7 @@
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
- "dev": true
+ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
}
}
},
diff --git a/packages/editor/package.json b/packages/editor/package.json
index 4ce44ad1c..fad4ce9d7 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -48,6 +48,7 @@
"shortid": "^2.2.16",
"strip-indent": "^4.0.0",
"tinycolor2": "^1.4.2",
+ "unfurl.js": "^5.7.0",
"zustand": "^3.7.2"
},
"devDependencies": {
diff --git a/packages/editor/src/components/popup-presenter/index.tsx b/packages/editor/src/components/popup-presenter/index.tsx
index d4c3db9fe..54e38179c 100644
--- a/packages/editor/src/components/popup-presenter/index.tsx
+++ b/packages/editor/src/components/popup-presenter/index.tsx
@@ -54,6 +54,7 @@ function _PopupPresenter(props: PropsWithChildren) {
const popupPosition = getPosition(popup, position);
popup.style.top = popupPosition.top + "px";
popup.style.left = popupPosition.left + "px";
+ console.log("popup", popupPosition);
}, [position]);
useEffect(() => {
@@ -348,4 +349,6 @@ export function showPopup(options: ShowPopupOptions) {
,
getPopupContainer()
);
+
+ return hide;
}
diff --git a/packages/editor/src/extensions.ts b/packages/editor/src/extensions.ts
index 27031ae24..04757fdce 100644
--- a/packages/editor/src/extensions.ts
+++ b/packages/editor/src/extensions.ts
@@ -25,7 +25,6 @@ import "./extensions/search-replace";
import "./extensions/embed";
import "./extensions/code-block";
import "./extensions/list-item";
-import "./extensions/link";
import "./extensions/outline-list";
import "./extensions/outline-list-item";
import "./extensions/table";
diff --git a/packages/editor/src/extensions/link/index.ts b/packages/editor/src/extensions/link/index.ts
deleted file mode 100644
index b44e8fde1..000000000
--- a/packages/editor/src/extensions/link/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Link } from "./link";
-
-export * from "./link";
-
-export default Link;
diff --git a/packages/editor/src/extensions/link/link.ts b/packages/editor/src/extensions/link/link.ts
deleted file mode 100644
index 315756968..000000000
--- a/packages/editor/src/extensions/link/link.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import TiptapLink from "@tiptap/extension-link";
-import { Plugin, PluginKey } from "prosemirror-state";
-
-export const Link = TiptapLink.extend({
- // addProseMirrorPlugins() {
- // return [
- // ...(this.parent?.() || []),
- // new Plugin({
- // key: new PluginKey("hoverHandler"),
- // props: {
- // handleDOMEvents: {
- // mouseover: (view, event) => {
- // if (
- // event.target instanceof HTMLElement &&
- // event.target.nodeName === "A"
- // ) {
- // console.log("Got it!");
- // }
- // },
- // },
- // },
- // }),
- // ];
- // },
-});
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index b38616b44..d8b0be02a 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -31,10 +31,8 @@ import { SearchReplace } from "./extensions/search-replace";
import { EmbedNode } from "./extensions/embed";
import { CodeBlock } from "./extensions/code-block";
import { ListItem } from "./extensions/list-item";
-import { Link } from "./extensions/link";
+import { Link } from "@tiptap/extension-link";
import {
- PortalProviderAPI,
- EventDispatcher,
NodeViewSelectionNotifier,
usePortalProvider,
} from "./extensions/react";
diff --git a/packages/editor/src/toolbar/components/toolbar-group.tsx b/packages/editor/src/toolbar/components/toolbar-group.tsx
index fb416e62f..b4a451ad2 100644
--- a/packages/editor/src/toolbar/components/toolbar-group.tsx
+++ b/packages/editor/src/toolbar/components/toolbar-group.tsx
@@ -4,14 +4,17 @@ import { Flex, FlexProps } from "rebass";
import { Editor } from "@tiptap/core";
import { MoreTools } from "./more-tools";
import { getToolDefinition } from "../tool-definitions";
+import { NodeWithOffset } from "../utils/prosemirror";
export type ToolbarGroupProps = FlexProps & {
tools: ToolbarGroupDefinition;
editor: Editor;
variant?: ToolButtonVariant;
+ force?: boolean;
+ selectedNode?: NodeWithOffset;
};
export function ToolbarGroup(props: ToolbarGroupProps) {
- const { tools, editor, ...flexProps } = props;
+ const { tools, editor, force, selectedNode, ...flexProps } = props;
return (
@@ -34,6 +37,8 @@ export function ToolbarGroup(props: ToolbarGroupProps) {
);
diff --git a/packages/editor/src/toolbar/floating-menus/hover-popup/index.tsx b/packages/editor/src/toolbar/floating-menus/hover-popup/index.tsx
new file mode 100644
index 000000000..38dbd7052
--- /dev/null
+++ b/packages/editor/src/toolbar/floating-menus/hover-popup/index.tsx
@@ -0,0 +1,95 @@
+import { useEffect, useRef } from "react";
+import { showPopup } from "../../../components/popup-presenter";
+import { Editor } from "../../../types";
+import { NodeWithOffset } from "../../utils/prosemirror";
+import { FloatingMenuProps } from "../types";
+import { LinkHoverPopupHandler } from "./link";
+
+export type HoverPopupProps = {
+ editor: Editor;
+ selectedNode: NodeWithOffset;
+};
+
+const handlers: Record JSX.Element | null> =
+ { ...LinkHoverPopupHandler };
+
+const HOVER_TIMEOUT = 500;
+
+export function HoverPopupHandler(props: FloatingMenuProps) {
+ const { editor } = props;
+ const hoverTimeoutId = useRef();
+ const activePopup = useRef<{ element: HTMLElement; hide: () => void }>();
+
+ useEffect(() => {
+ function onMouseOver(e: MouseEvent) {
+ if (
+ !e.target ||
+ !(e.target instanceof HTMLElement) ||
+ e.target.classList.contains("ProseMirror")
+ )
+ return;
+
+ const element = e.target;
+
+ if (activePopup.current) {
+ const isOutsideEditor = !element.closest(".ProseMirror");
+ const isInsidePopup = element.closest(".popup-presenter-portal");
+ const isActiveElement = activePopup.current.element === element;
+ if (isInsidePopup) return;
+
+ if (isOutsideEditor || !isActiveElement) {
+ console.log("HIDING", isOutsideEditor, isActiveElement, element);
+ activePopup.current.hide();
+ activePopup.current = undefined;
+ return;
+ }
+ }
+
+ clearTimeout(hoverTimeoutId.current);
+
+ hoverTimeoutId.current = setTimeout(
+ () => {
+ const nodeName = element.nodeName.toLowerCase();
+ const PopupHandler = handlers[nodeName];
+ if (!PopupHandler || !editor.current) return;
+
+ const pos = editor.current.view.posAtDOM(element, 0);
+ const node = editor.current.view.state.doc.nodeAt(pos);
+
+ if (!node) return;
+
+ const hidePopup = showPopup({
+ popup: () => (
+
+ ),
+ theme: editor.storage.theme,
+ blocking: false,
+ focusOnRender: false,
+ position: {
+ target: element,
+ align: "center",
+ location: "top",
+ isTargetAbsolute: true,
+ },
+ });
+ activePopup.current = { element, hide: hidePopup };
+ },
+ HOVER_TIMEOUT,
+ {}
+ );
+ }
+ window.addEventListener("mouseover", onMouseOver);
+ return () => {
+ window.removeEventListener("mouseover", onMouseOver);
+ };
+ }, []);
+
+ return null;
+}
diff --git a/packages/editor/src/toolbar/floating-menus/hover-popup/link.tsx b/packages/editor/src/toolbar/floating-menus/hover-popup/link.tsx
new file mode 100644
index 000000000..3185a766d
--- /dev/null
+++ b/packages/editor/src/toolbar/floating-menus/hover-popup/link.tsx
@@ -0,0 +1,31 @@
+import { ToolbarGroup } from "../../components/toolbar-group";
+import { HoverPopupProps } from ".";
+
+function LinkHoverPopup(props: HoverPopupProps) {
+ const { editor, selectedNode } = props;
+ const { node } = selectedNode;
+
+ if (
+ !node.isText ||
+ node.marks.length <= 0 ||
+ !node.marks.some((mark) => mark.type.name === "link")
+ )
+ return null;
+
+ return (
+
+ );
+}
+
+export const LinkHoverPopupHandler = { a: LinkHoverPopup };
diff --git a/packages/editor/src/toolbar/floating-menus/index.tsx b/packages/editor/src/toolbar/floating-menus/index.tsx
index 68559a8b5..5d8fd6b0b 100644
--- a/packages/editor/src/toolbar/floating-menus/index.tsx
+++ b/packages/editor/src/toolbar/floating-menus/index.tsx
@@ -1,3 +1,4 @@
+import { HoverPopupHandler } from "./hover-popup";
import { SearchReplaceFloatingMenu } from "./search-replace";
import { FloatingMenuProps } from "./types";
@@ -5,6 +6,7 @@ export function EditorFloatingMenus(props: FloatingMenuProps) {
return (
<>
+
>
);
}
diff --git a/packages/editor/src/toolbar/floating-menus/types.ts b/packages/editor/src/toolbar/floating-menus/types.ts
index 7e3bdf5b4..ff5a1d681 100644
--- a/packages/editor/src/toolbar/floating-menus/types.ts
+++ b/packages/editor/src/toolbar/floating-menus/types.ts
@@ -1,2 +1,2 @@
-import { Editor } from "@tiptap/core";
+import { Editor } from "../../types";
export type FloatingMenuProps = { editor: Editor };
diff --git a/packages/editor/src/toolbar/icons.ts b/packages/editor/src/toolbar/icons.ts
index b1161b8fd..bfe0e4b03 100644
--- a/packages/editor/src/toolbar/icons.ts
+++ b/packages/editor/src/toolbar/icons.ts
@@ -97,6 +97,7 @@ import {
mdiArrowLeft,
mdiMovieCogOutline,
mdiLinkOff,
+ mdiOpenInNew,
} from "@mdi/js";
export const Icons = {
@@ -124,8 +125,11 @@ export const Icons = {
textColor: mdiFormatColorText,
link: mdiLinkPlus,
linkRemove: mdiLinkOff,
+ openLink: mdiOpenInNew,
linkEdit:
"m19 14 1.28 1.28c.22.21.22.56 0 .77l-1 1L17.23 15l1-1c.11-.11.25-.17.39-.17s.27.06.38.17m-.3 3.63-6.06 6.07h-2.06v-2.06l6.07-6.06zM7 7h4v2H7c-1.6568542 0-3 1.343146-3 3s1.3431458 3 3 3h4v2H7c-2.7614237 0-5-2.238576-5-5 0-2.7614237 2.2385763-5 5-5m10 0c2.761424 0 5 2.2385763 5 5h-2c0-1.656854-1.343146-3-3-3h-4V7h4m-9 4h8v2H8v-2",
+ linkSettings:
+ "M7 7h4v2H7c-1.6568542 0-3 1.343146-3 3s1.3431458 3 3 3h4v2H7c-2.7614237 0-5-2.238576-5-5 0-2.7614237 2.2385763-5 5-5m10 0c2.761424 0 5 2.2385763 5 5h-2c0-1.656854-1.343146-3-3-3h-4V7h4m-9 4h8v2H8v-2m15.119777 8.323608-1.07-.82c.02-.17.04-.33.04-.5 0-.17-.01-.33-.04-.5l1.06-.82c.09258-.07939.117526-.212463.06-.32l-1-1.73c-.06-.13-.19-.13-.33-.13l-1.22.5c-.28-.18-.54-.35-.85-.47l-.19-1.32c-.01-.12-.12-.21-.24-.21h-2c-.12 0-.23.09-.25.21l-.19 1.32c-.3.13-.59.29-.85.47l-1.24-.5c-.11 0-.24 0-.31.13l-1 1.73c-.06.11-.04.24.06.32l1.06.82c-.03989.33214-.03989.66786 0 1l-1.06.82c-.09258.07939-.117526.212462-.06.32l1 1.73c.06.13.19.13.31.13l1.24-.5c.26.18.54.35.85.47l.19 1.32c.02.12.12.21.25.21h2c.12 0 .23-.09.25-.21l.19-1.32c.3-.13.56-.29.84-.47l1.22.5c.14 0 .27 0 .34-.13l1-1.73c.05753-.107538.03258-.240607-.06-.32m-4.78.18c-.83 0-1.5-.67-1.5-1.5s.68-1.5 1.5-1.5 1.5.67 1.5 1.5-.66 1.5-1.5 1.5z",
url: mdiLink,
image: mdiImage,
diff --git a/packages/editor/src/toolbar/tool-definitions.ts b/packages/editor/src/toolbar/tool-definitions.ts
index 29df22592..25971db12 100644
--- a/packages/editor/src/toolbar/tool-definitions.ts
+++ b/packages/editor/src/toolbar/tool-definitions.ts
@@ -18,13 +18,28 @@ const tools: Record = {
icon: "strikethrough",
title: "Strikethrough",
},
- link: {
+ addLink: {
icon: "link",
title: "Link",
},
- linkRemove: {
+ editLink: {
+ icon: "linkEdit",
+ title: "Edit link",
+ conditional: true,
+ },
+ removeLink: {
icon: "linkRemove",
- title: "Link remove",
+ title: "Remove link",
+ conditional: true,
+ },
+ openLink: {
+ icon: "openLink",
+ title: "Open link",
+ conditional: true,
+ },
+ linkSettings: {
+ icon: "linkSettings",
+ title: "Link settings",
conditional: true,
},
code: {
diff --git a/packages/editor/src/toolbar/toolbar.tsx b/packages/editor/src/toolbar/toolbar.tsx
index 972bdda97..3e9cedf5b 100644
--- a/packages/editor/src/toolbar/toolbar.tsx
+++ b/packages/editor/src/toolbar/toolbar.tsx
@@ -57,7 +57,7 @@ export function Toolbar(props: ToolbarProps) {
"imageSettings",
"embedSettings",
"attachmentSettings",
- "linkRemove",
+ "linkSettings",
"codeRemove",
],
[
@@ -76,7 +76,7 @@ export function Toolbar(props: ToolbarProps) {
["fontSize"],
["headings", "fontFamily"],
["numberedList", "bulletList"],
- ["link"],
+ ["addLink"],
["alignCenter", ["alignLeft", "alignRight", "alignJustify", "ltr", "rtl"]],
["clearformatting"],
];
diff --git a/packages/editor/src/toolbar/tools/index.ts b/packages/editor/src/toolbar/tools/index.ts
index 24fa7f82f..c1f6164c9 100644
--- a/packages/editor/src/toolbar/tools/index.ts
+++ b/packages/editor/src/toolbar/tools/index.ts
@@ -9,8 +9,6 @@ import {
Subscript,
Superscript,
ClearFormatting,
- Link,
- LinkRemove,
CodeRemove,
} from "./inline";
import { InsertBlock } from "./block";
@@ -62,6 +60,7 @@ import {
EmbedProperties,
EmbedSettings,
} from "./embed";
+import { AddLink, EditLink, RemoveLink, LinkSettings, OpenLink } from "./link";
export type ToolId = keyof typeof tools;
const tools = {
@@ -74,8 +73,11 @@ const tools = {
subscript: Subscript,
superscript: Superscript,
clearformatting: ClearFormatting,
- link: Link,
- linkRemove: LinkRemove,
+ addLink: AddLink,
+ editLink: EditLink,
+ removeLink: RemoveLink,
+ linkSettings: LinkSettings,
+ openLink: OpenLink,
insertBlock: InsertBlock,
numberedList: NumberedList,
bulletList: BulletList,
diff --git a/packages/editor/src/toolbar/tools/inline.tsx b/packages/editor/src/toolbar/tools/inline.tsx
index 4f77c5d44..9ffd8eb8f 100644
--- a/packages/editor/src/toolbar/tools/inline.tsx
+++ b/packages/editor/src/toolbar/tools/inline.tsx
@@ -1,9 +1,5 @@
import { ToolProps } from "../types";
import { ToolButton } from "../components/tool-button";
-import { useCallback, useMemo, useRef, useState } from "react";
-import { ResponsivePresenter } from "../../components/responsive";
-import { Popup } from "../components/popup";
-import { LinkPopup } from "../popups/link-popup";
import { useToolbarLocation } from "../stores/toolbar-store";
export function Italic(props: ToolProps) {
@@ -104,19 +100,6 @@ export function ClearFormatting(props: ToolProps) {
);
}
-export function LinkRemove(props: ToolProps) {
- const { editor } = props;
- const isBottom = useToolbarLocation() === "bottom";
- if (!editor.isActive("link") || !isBottom) return null;
- return (
- editor.current?.chain().focus().unsetMark("link").run()}
- />
- );
-}
-
export function CodeRemove(props: ToolProps) {
const { editor } = props;
const isBottom = useToolbarLocation() === "bottom";
@@ -129,141 +112,3 @@ export function CodeRemove(props: ToolProps) {
/>
);
}
-
-export function Link(props: ToolProps) {
- const { editor, title, icon } = props;
- const buttonRef = useRef(null);
- const [isOpen, setIsOpen] = useState(false);
-
- const [href, setHref] = useState();
- const [text, setText] = useState();
- const currentUrl = editor.getAttributes("link").href;
- const isEditing = !!currentUrl;
-
- const onDone = useCallback((href: string, text: string) => {
- if (!href) return;
-
- let commandChain = editor.current?.chain().focus();
- if (!commandChain) return;
-
- commandChain
- .extendMarkRange("link")
- .toggleLink({ href, target: "_blank" })
- .insertContent(text || href)
- .focus()
- .unsetMark("link")
- .insertContent(" ")
- .run();
-
- setIsOpen(false);
- }, []);
-
- return (
- <>
- {
- if (isEditing) setHref(currentUrl);
-
- let { from, to, $from } = editor.state.selection;
- const selectedNode = $from.node();
-
- const selectedText = isEditing
- ? selectedNode.textContent
- : editor.state.doc.textBetween(from, to);
-
- setText(selectedText);
- setIsOpen(true);
- }}
- toggled={isOpen || !!isEditing}
- />
- setIsOpen(false)}
- focusOnRender={false}
- >
- setIsOpen(false)}
- >
- {
- onDone(href, text);
- }}
- />
-
-
- >
- );
-}
-
-// export function Link(props: ToolProps) {
-// const { editor, title, icon } = props;
-// const buttonRef = useRef(null);
-// const [isOpen, setIsOpen] = useState(false);
-
-// const [href, setHref] = useState();
-// const [text, setText] = useState();
-// const currentUrl = editor.getAttributes("link").href;
-// const isEditing = !!currentUrl;
-
-// const onDone = useCallback((href: string, text: string) => {
-// if (!href) return;
-
-// let commandChain = editor.current?.chain().focus();
-// if (!commandChain) return;
-
-// commandChain
-// .extendMarkRange("link")
-// .toggleLink({ href, target: "_blank" })
-// .insertContent(text || href)
-// .focus()
-// .unsetMark("link")
-// .insertContent(" ")
-// .run();
-
-// setIsOpen(false);
-// }, []);
-
-// return (
-// <>
-// {
-// if (isEditing) setHref(currentUrl);
-
-// let { from, to, $from } = editor.state.selection;
-// const selectedNode = $from.node();
-
-// const selectedText = isEditing
-// ? selectedNode.textContent
-// : editor.state.doc.textBetween(from, to);
-
-// setText(selectedText);
-// setIsOpen(true);
-// }}
-// toggled={isOpen || !!isEditing}
-// />
-// >
-// );
-// }
diff --git a/packages/editor/src/toolbar/tools/link.tsx b/packages/editor/src/toolbar/tools/link.tsx
new file mode 100644
index 000000000..e50609085
--- /dev/null
+++ b/packages/editor/src/toolbar/tools/link.tsx
@@ -0,0 +1,203 @@
+import { ToolProps } from "../types";
+import { ToolButton } from "../components/tool-button";
+import { useCallback, useRef, useState } from "react";
+import { ResponsivePresenter } from "../../components/responsive";
+import { Popup } from "../components/popup";
+import { LinkPopup } from "../popups/link-popup";
+import { useToolbarLocation } from "../stores/toolbar-store";
+import { MoreTools } from "../components/more-tools";
+import { useRefValue } from "../../hooks/use-ref-value";
+import { findMark, selectionToOffset } from "../utils/prosemirror";
+import { setTextSelection } from "prosemirror-utils";
+import { Flex, Text } from "rebass";
+
+export function LinkSettings(props: ToolProps) {
+ const { editor } = props;
+ const isBottom = useToolbarLocation() === "bottom";
+ if (!editor.isActive("link") || !isBottom) return null;
+
+ return (
+
+ );
+}
+
+export function AddLink(props: ToolProps) {
+ const { editor } = props;
+
+ const isActive = props.editor.isActive("link");
+
+ const onDone = useCallback((href: string, text: string) => {
+ if (!href) return;
+
+ let commandChain = editor.current?.chain().focus();
+ if (!commandChain) return;
+
+ commandChain
+ .extendMarkRange("link")
+ .toggleLink({ href, target: "_blank" })
+ .insertContent(text || href)
+ .focus()
+ .unsetMark("link")
+ .insertContent(" ")
+ .run();
+ }, []);
+
+ if (isActive) return ;
+ return (
+ {
+ let { from, to } = editor.state.selection;
+ const selectedText = editor.state.doc.textBetween(from, to);
+ return { text: selectedText };
+ }}
+ />
+ );
+}
+
+export function EditLink(props: ToolProps) {
+ const { editor, selectedNode: _selectedNode } = props;
+ const selectedNode = useRefValue(
+ _selectedNode || selectionToOffset(editor.state.selection)
+ );
+
+ const onDone = useCallback((href: string, text: string) => {
+ if (!href || !editor.current) return;
+
+ const { from, node, to } = selectedNode.current;
+ const mark = findMark(node, "link");
+ if (!mark) return;
+
+ editor.current
+ .chain()
+ .command(({ tr }) => {
+ tr.removeMark(from, to, mark.type);
+ tr.addMark(from, to, mark.type.create({ href }));
+ tr.insertText(text || node.textContent, from, to);
+ setTextSelection(tr.mapping.map(from))(tr);
+ return true;
+ })
+ .focus(undefined, { scrollIntoView: true })
+ .run();
+ }, []);
+
+ return (
+ {
+ const { node } = selectedNode.current;
+ const selectedText = node.textContent;
+ const mark = findMark(node, "link");
+
+ if (!mark) return;
+ return { text: selectedText, href: mark.attrs.href };
+ }}
+ />
+ );
+}
+
+export function RemoveLink(props: ToolProps) {
+ const { editor, selectedNode } = props;
+ return (
+ {
+ if (selectedNode) editor.commands.setTextSelection(selectedNode.from);
+ editor.current?.chain().focus().unsetLink().run();
+ }}
+ />
+ );
+}
+
+export function OpenLink(props: ToolProps) {
+ const { editor, selectedNode } = props;
+ const node = selectedNode?.node || editor.state.selection.$anchor.node();
+ const link = selectedNode ? findMark(node, "link") : null;
+ if (!link) return null;
+ const href = link?.attrs.href;
+
+ return (
+
+
+ {href}
+
+ window.open(href, "_blank")}
+ />
+
+ );
+}
+
+type LinkToolProps = ToolProps & {
+ isEditing?: boolean;
+ onDone: (href: string, text: string) => void;
+ onClick: () => { href?: string; text: string } | undefined;
+};
+function LinkTool(props: LinkToolProps) {
+ const { isEditing, onClick, onDone } = props;
+ const buttonRef = useRef(null);
+ const [isOpen, setIsOpen] = useState(false);
+
+ const [href, setHref] = useState();
+ const [text, setText] = useState();
+
+ return (
+ <>
+ {
+ const result = onClick();
+ if (!result) return;
+ const { text, href } = result;
+ setHref(href);
+ setText(text);
+ setIsOpen(true);
+ }}
+ toggled={isOpen}
+ />
+ setIsOpen(false)}
+ focusOnRender={false}
+ >
+ setIsOpen(false)}
+ >
+ {
+ onDone(href, text);
+ setIsOpen(false);
+ }}
+ />
+
+
+ >
+ );
+}
diff --git a/packages/editor/src/toolbar/types.ts b/packages/editor/src/toolbar/types.ts
index 06b429e7d..653bcab87 100644
--- a/packages/editor/src/toolbar/types.ts
+++ b/packages/editor/src/toolbar/types.ts
@@ -1,11 +1,14 @@
import { Editor } from "../types";
import { IconNames } from "./icons";
import { ToolId } from "./tools";
+import { NodeWithOffset } from "./utils/prosemirror";
export type ToolButtonVariant = "small" | "normal";
export type ToolProps = ToolDefinition & {
editor: Editor;
variant?: ToolButtonVariant;
+ force?: boolean;
+ selectedNode?: NodeWithOffset;
};
export type ToolDefinition = {
diff --git a/packages/editor/src/toolbar/utils/prosemirror.ts b/packages/editor/src/toolbar/utils/prosemirror.ts
index 4b2173ab1..4a53bb6b2 100644
--- a/packages/editor/src/toolbar/utils/prosemirror.ts
+++ b/packages/editor/src/toolbar/utils/prosemirror.ts
@@ -4,7 +4,14 @@ import {
findParentNodeClosestToPos,
isNodeSelection,
} from "@tiptap/core";
-import { Node } from "prosemirror-model";
+import { Node, Mark } from "prosemirror-model";
+import { Selection } from "prosemirror-state";
+
+export type NodeWithOffset = {
+ node: Node;
+ from: number;
+ to: number;
+};
export function findSelectedDOMNode(
editor: Editor,
@@ -37,3 +44,13 @@ export function findSelectedNode(editor: Editor, type: string): Node | null {
return editor.state.doc.nodeAt(pos);
}
+
+export function findMark(node: Node, type: string): Mark | undefined {
+ const mark = node.marks.find((m) => m.type.name === type);
+ return mark;
+}
+
+export function selectionToOffset(selection: Selection): NodeWithOffset {
+ const { $from, from } = selection;
+ return { node: $from.node(), from, to: from + $from.node().nodeSize };
+}
diff --git a/packages/editor/src/utils/position.ts b/packages/editor/src/utils/position.ts
index a20a0d931..cc77c5140 100644
--- a/packages/editor/src/utils/position.ts
+++ b/packages/editor/src/utils/position.ts
@@ -77,10 +77,15 @@ export function getPosition(
else if (location === "top") position.top = y - elementHeight;
}
- if (target !== "mouse" && align === "center" && elementWidth > 0) {
- position.left -= elementWidth / 2 - target.clientWidth / 2;
- } else if (target !== "mouse" && align === "end" && elementWidth > 0) {
- position.left -= elementWidth - target.clientWidth;
+ if (width && target !== "mouse" && align === "center" && elementWidth > 0) {
+ position.left -= elementWidth / 2 - width / 2;
+ } else if (
+ width &&
+ target !== "mouse" &&
+ align === "end" &&
+ elementWidth > 0
+ ) {
+ position.left -= elementWidth - width;
}
// Adjust menu height