mirror of
https://github.com/docmost/docmost.git
synced 2026-08-29 18:14:58 +08:00
feat: Migrate tippy.js menus to Floating UI
This commit is contained in:
@@ -55,7 +55,6 @@
|
|||||||
"react-router-dom": "^7.0.1",
|
"react-router-dom": "^7.0.1",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"tippy.js": "^6.3.7",
|
|
||||||
"tiptap-extension-global-drag-handle": "^0.1.18",
|
"tiptap-extension-global-drag-handle": "^0.1.18",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,16 +1,41 @@
|
|||||||
import { ReactRenderer, useEditor } from "@tiptap/react";
|
import { ReactRenderer, useEditor } from "@tiptap/react";
|
||||||
import EmojiList from "./emoji-list";
|
import EmojiList from "./emoji-list";
|
||||||
import tippy from "tippy.js";
|
|
||||||
import { init } from "emoji-mart";
|
import { init } from "emoji-mart";
|
||||||
|
import {
|
||||||
|
autoUpdate,
|
||||||
|
computePosition,
|
||||||
|
flip,
|
||||||
|
offset,
|
||||||
|
shift,
|
||||||
|
} from "@floating-ui/dom";
|
||||||
|
|
||||||
const renderEmojiItems = () => {
|
const renderEmojiItems = () => {
|
||||||
let component: ReactRenderer | null = null;
|
let component: ReactRenderer | null = null;
|
||||||
let popup: any | null = null;
|
let popup: HTMLDivElement | null = null;
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
let getReferenceClientRect: (() => DOMRect) | null = null;
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
if (cleanup) {
|
||||||
|
cleanup();
|
||||||
|
cleanup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (popup) {
|
||||||
|
popup.remove();
|
||||||
|
popup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component) {
|
||||||
|
component.destroy();
|
||||||
|
component = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onBeforeStart: (props: {
|
onBeforeStart: (props: {
|
||||||
editor: ReturnType<typeof useEditor>;
|
editor: ReturnType<typeof useEditor>;
|
||||||
clientRect: DOMRect;
|
clientRect: () => DOMRect;
|
||||||
}) => {
|
}) => {
|
||||||
init({
|
init({
|
||||||
data: async () => (await import("@emoji-mart/data")).default,
|
data: async () => (await import("@emoji-mart/data")).default,
|
||||||
@@ -25,51 +50,61 @@ const renderEmojiItems = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
getReferenceClientRect = props.clientRect;
|
||||||
popup = tippy("body", {
|
popup = document.createElement("div");
|
||||||
getReferenceClientRect: props.clientRect,
|
popup.style.zIndex = "9999";
|
||||||
appendTo: () => document.body,
|
popup.style.position = "absolute";
|
||||||
content: component.element,
|
popup.style.top = "0";
|
||||||
showOnCreate: true,
|
popup.style.left = "0";
|
||||||
interactive: true,
|
popup.appendChild(component.element);
|
||||||
trigger: "manual",
|
document.body.appendChild(popup);
|
||||||
placement: "bottom",
|
|
||||||
|
const virtualElement = {
|
||||||
|
getBoundingClientRect: () => {
|
||||||
|
return getReferenceClientRect
|
||||||
|
? getReferenceClientRect()
|
||||||
|
: new DOMRect(0, 0, 0, 0);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanup = autoUpdate(virtualElement, popup, () => {
|
||||||
|
if (!popup) return;
|
||||||
|
|
||||||
|
computePosition(virtualElement, popup, {
|
||||||
|
placement: "bottom-start",
|
||||||
|
middleware: [offset(10), flip(), shift()],
|
||||||
|
}).then(({ x, y }) => {
|
||||||
|
if (!popup) return;
|
||||||
|
|
||||||
|
Object.assign(popup.style, {
|
||||||
|
transform: `translate(${x}px, ${y}px)`,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onStart: (props: {
|
onStart: (props: {
|
||||||
editor: ReturnType<typeof useEditor>;
|
editor: ReturnType<typeof useEditor>;
|
||||||
clientRect: DOMRect;
|
clientRect: () => DOMRect;
|
||||||
}) => {
|
}) => {
|
||||||
component?.updateProps({...props, isLoading: false});
|
component?.updateProps({ ...props, isLoading: false });
|
||||||
|
|
||||||
if (!props.clientRect) {
|
if (props.clientRect) {
|
||||||
return;
|
getReferenceClientRect = props.clientRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
popup &&
|
|
||||||
popup[0].setProps({
|
|
||||||
getReferenceClientRect: props.clientRect,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onUpdate: (props: {
|
onUpdate: (props: {
|
||||||
editor: ReturnType<typeof useEditor>;
|
editor: ReturnType<typeof useEditor>;
|
||||||
clientRect: DOMRect;
|
clientRect: () => DOMRect;
|
||||||
}) => {
|
}) => {
|
||||||
component?.updateProps(props);
|
component?.updateProps(props);
|
||||||
|
|
||||||
if (!props.clientRect) {
|
if (props.clientRect) {
|
||||||
return;
|
getReferenceClientRect = props.clientRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
popup &&
|
|
||||||
popup[0].setProps({
|
|
||||||
getReferenceClientRect: props.clientRect,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onKeyDown: (props: { event: KeyboardEvent }) => {
|
onKeyDown: (props: { event: KeyboardEvent }) => {
|
||||||
if (props.event.key === "Escape") {
|
if (props.event.key === "Escape") {
|
||||||
popup?.[0].hide();
|
destroy();
|
||||||
component?.destroy()
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -78,13 +113,7 @@ const renderEmojiItems = () => {
|
|||||||
return component?.ref?.onKeyDown(props);
|
return component?.ref?.onKeyDown(props);
|
||||||
},
|
},
|
||||||
onExit: () => {
|
onExit: () => {
|
||||||
if (popup && !popup[0]?.state.isDestroyed) {
|
destroy();
|
||||||
popup[0]?.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component) {
|
|
||||||
component?.destroy();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { ReactRenderer, useEditor } from "@tiptap/react";
|
import { ReactRenderer, useEditor } from "@tiptap/react";
|
||||||
import tippy from "tippy.js";
|
import {
|
||||||
|
autoUpdate,
|
||||||
|
computePosition,
|
||||||
|
flip,
|
||||||
|
offset,
|
||||||
|
shift,
|
||||||
|
} from "@floating-ui/dom";
|
||||||
import MentionList from "@/features/editor/components/mention/mention-list.tsx";
|
import MentionList from "@/features/editor/components/mention/mention-list.tsx";
|
||||||
|
|
||||||
function getWhitespaceCount(query: string) {
|
function getWhitespaceCount(query: string) {
|
||||||
@@ -9,16 +15,27 @@ function getWhitespaceCount(query: string) {
|
|||||||
|
|
||||||
const mentionRenderItems = () => {
|
const mentionRenderItems = () => {
|
||||||
let component: ReactRenderer | null = null;
|
let component: ReactRenderer | null = null;
|
||||||
let popup: any | null = null;
|
let activeClientRect: (() => DOMRect) | null = null;
|
||||||
|
let updatePositionCleanup: (() => void) | null = null;
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
updatePositionCleanup?.();
|
||||||
|
updatePositionCleanup = null;
|
||||||
|
component?.destroy();
|
||||||
|
if (component?.element?.parentNode) {
|
||||||
|
component.element.parentNode.removeChild(component.element);
|
||||||
|
}
|
||||||
|
component = null;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onStart: (props: {
|
onStart: (props: {
|
||||||
editor: ReturnType<typeof useEditor>;
|
editor: ReturnType<typeof useEditor>;
|
||||||
clientRect: DOMRect;
|
clientRect: () => DOMRect;
|
||||||
query: string;
|
query: string;
|
||||||
}) => {
|
}) => {
|
||||||
// query must not start with a whitespace
|
// query must not start with a whitespace
|
||||||
if (props.query.charAt(0) === ' '){
|
if (props.query.charAt(0) === " ") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,37 +54,63 @@ const mentionRenderItems = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
activeClientRect = props.clientRect;
|
||||||
popup = tippy("body", {
|
|
||||||
getReferenceClientRect: props.clientRect,
|
const { element } = component;
|
||||||
appendTo: () => document.body,
|
document.body.appendChild(element);
|
||||||
content: component.element,
|
|
||||||
showOnCreate: true,
|
updatePositionCleanup = autoUpdate(
|
||||||
interactive: true,
|
{
|
||||||
trigger: "manual",
|
getBoundingClientRect: () =>
|
||||||
placement: "bottom-start",
|
activeClientRect ? activeClientRect() : new DOMRect(),
|
||||||
});
|
},
|
||||||
|
element,
|
||||||
|
() => {
|
||||||
|
if (!component?.element) return;
|
||||||
|
computePosition(
|
||||||
|
{
|
||||||
|
getBoundingClientRect: () => {
|
||||||
|
return activeClientRect ? activeClientRect() : new DOMRect();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
element,
|
||||||
|
{
|
||||||
|
placement: "bottom-start",
|
||||||
|
middleware: [offset(0), flip(), shift()],
|
||||||
|
}
|
||||||
|
).then(({ x, y }) => {
|
||||||
|
Object.assign(element.style, {
|
||||||
|
left: `${x}px`,
|
||||||
|
top: `${y}px`,
|
||||||
|
position: "absolute",
|
||||||
|
zIndex: "9999",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onUpdate: (props: {
|
onUpdate: (props: {
|
||||||
editor: ReturnType<typeof useEditor>;
|
editor: ReturnType<typeof useEditor>;
|
||||||
clientRect: DOMRect;
|
clientRect: () => DOMRect;
|
||||||
query: string;
|
query: string;
|
||||||
}) => {
|
}) => {
|
||||||
// query must not start with a whitespace
|
// query must not start with a whitespace
|
||||||
if (props.query.charAt(0) === ' '){
|
if (props.query.charAt(0) === " ") {
|
||||||
component?.destroy();
|
destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only update component if popup is not destroyed
|
// only update component if popup is not destroyed
|
||||||
if (!popup?.[0].state.isDestroyed) {
|
if (component) {
|
||||||
component?.updateProps(props);
|
component.updateProps(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props || !props.clientRect) {
|
if (!props || !props.clientRect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeClientRect = props.clientRect;
|
||||||
|
|
||||||
const whitespaceCount = getWhitespaceCount(props.query);
|
const whitespaceCount = getWhitespaceCount(props.query);
|
||||||
|
|
||||||
// destroy component if space is greater 3 without a match
|
// destroy component if space is greater 3 without a match
|
||||||
@@ -76,37 +119,23 @@ const mentionRenderItems = () => {
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
props.editor.storage.mentionItems.length === 0
|
props.editor.storage.mentionItems.length === 0
|
||||||
) {
|
) {
|
||||||
popup?.[0]?.destroy();
|
destroy();
|
||||||
component?.destroy();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
popup &&
|
|
||||||
!popup?.[0].state.isDestroyed &&
|
|
||||||
popup?.[0].setProps({
|
|
||||||
getReferenceClientRect: props.clientRect,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onKeyDown: (props: { event: KeyboardEvent }) => {
|
onKeyDown: (props: { event: KeyboardEvent }) => {
|
||||||
if (props.event.key)
|
if (props.event.key)
|
||||||
if (
|
if (
|
||||||
props.event.key === "Escape" ||
|
props.event.key === "Escape" ||
|
||||||
(props.event.key === "Enter" && !popup?.[0].state.isShown)
|
(props.event.key === "Enter" && !component)
|
||||||
) {
|
) {
|
||||||
popup?.[0].destroy();
|
destroy();
|
||||||
component?.destroy();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (component?.ref as any)?.onKeyDown(props);
|
return (component?.ref as any)?.onKeyDown(props);
|
||||||
},
|
},
|
||||||
onExit: () => {
|
onExit: () => {
|
||||||
if (popup && !popup?.[0].state.isDestroyed) {
|
destroy();
|
||||||
popup[0].destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component) {
|
|
||||||
component.destroy();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,35 @@
|
|||||||
import { ReactRenderer, useEditor } from "@tiptap/react";
|
import { ReactRenderer, useEditor } from "@tiptap/react";
|
||||||
import CommandList from "@/features/editor/components/slash-menu/command-list";
|
import CommandList from "@/features/editor/components/slash-menu/command-list";
|
||||||
import tippy from "tippy.js";
|
import {
|
||||||
|
autoUpdate,
|
||||||
|
computePosition,
|
||||||
|
flip,
|
||||||
|
offset,
|
||||||
|
shift,
|
||||||
|
} from "@floating-ui/dom";
|
||||||
|
|
||||||
const renderItems = () => {
|
const renderItems = () => {
|
||||||
let component: ReactRenderer | null = null;
|
let component: ReactRenderer | null = null;
|
||||||
let popup: any | null = null;
|
let popup: HTMLElement | null = null;
|
||||||
|
let cleanup: (() => void) | null = null;
|
||||||
|
let getReferenceClientRect: (() => DOMRect) | null = null;
|
||||||
|
|
||||||
|
const updatePosition = () => {
|
||||||
|
if (!popup || !getReferenceClientRect) return;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const rect = getReferenceClientRect();
|
||||||
|
|
||||||
|
computePosition({ getBoundingClientRect: () => rect }, popup, {
|
||||||
|
placement: "bottom-start",
|
||||||
|
middleware: [offset(0), flip(), shift()],
|
||||||
|
}).then(({ x, y }) => {
|
||||||
|
if (popup) {
|
||||||
|
popup.style.left = `${x}px`;
|
||||||
|
popup.style.top = `${y}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onStart: (props: {
|
onStart: (props: {
|
||||||
@@ -21,15 +46,29 @@ const renderItems = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
popup = tippy("body", {
|
getReferenceClientRect = props.clientRect;
|
||||||
getReferenceClientRect: props.clientRect,
|
|
||||||
appendTo: () => document.body,
|
popup = document.createElement("div");
|
||||||
content: component.element,
|
popup.style.zIndex = "9999";
|
||||||
showOnCreate: true,
|
popup.style.position = "absolute";
|
||||||
interactive: true,
|
popup.style.top = "0";
|
||||||
trigger: "manual",
|
popup.style.left = "0";
|
||||||
placement: "bottom-start",
|
|
||||||
});
|
document.body.appendChild(popup);
|
||||||
|
popup.appendChild(component.element);
|
||||||
|
|
||||||
|
cleanup = autoUpdate(
|
||||||
|
// @ts-ignore
|
||||||
|
{
|
||||||
|
getBoundingClientRect: () => {
|
||||||
|
return getReferenceClientRect
|
||||||
|
? getReferenceClientRect()
|
||||||
|
: new DOMRect();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
popup,
|
||||||
|
updatePosition
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onUpdate: (props: {
|
onUpdate: (props: {
|
||||||
editor: ReturnType<typeof useEditor>;
|
editor: ReturnType<typeof useEditor>;
|
||||||
@@ -41,14 +80,15 @@ const renderItems = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
popup &&
|
// @ts-ignore
|
||||||
popup[0].setProps({
|
getReferenceClientRect = props.clientRect;
|
||||||
getReferenceClientRect: props.clientRect,
|
updatePosition();
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onKeyDown: (props: { event: KeyboardEvent }) => {
|
onKeyDown: (props: { event: KeyboardEvent }) => {
|
||||||
if (props.event.key === "Escape") {
|
if (props.event.key === "Escape") {
|
||||||
popup?.[0].hide();
|
if (popup) {
|
||||||
|
popup.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -57,12 +97,19 @@ const renderItems = () => {
|
|||||||
return component?.ref?.onKeyDown(props);
|
return component?.ref?.onKeyDown(props);
|
||||||
},
|
},
|
||||||
onExit: () => {
|
onExit: () => {
|
||||||
if (popup && !popup[0].state.isDestroyed) {
|
if (cleanup) {
|
||||||
popup[0].destroy();
|
cleanup();
|
||||||
|
cleanup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (popup) {
|
||||||
|
popup.remove();
|
||||||
|
popup = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component) {
|
if (component) {
|
||||||
component.destroy();
|
component.destroy();
|
||||||
|
component = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const SubpagesMenu = React.memo(
|
|||||||
|
|
||||||
return editor.isActive("subpages");
|
return editor.isActive("subpages");
|
||||||
},
|
},
|
||||||
[editor],
|
[editor]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getReferenceClientRect = useCallback(() => {
|
const getReferenceClientRect = useCallback(() => {
|
||||||
@@ -60,16 +60,6 @@ export const SubpagesMenu = React.memo(
|
|||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey={`subpages-menu`}
|
pluginKey={`subpages-menu`}
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
/* tippyOptions={{
|
|
||||||
getReferenceClientRect,
|
|
||||||
offset: [0, 8],
|
|
||||||
zIndex: 99,
|
|
||||||
popperOptions: {
|
|
||||||
modifiers: [{ name: "flip", enabled: false }],
|
|
||||||
},
|
|
||||||
plugins: [sticky],
|
|
||||||
sticky: "popper",
|
|
||||||
}}*/
|
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
<Tooltip position="top" label={t("Delete")}>
|
<Tooltip position="top" label={t("Delete")}>
|
||||||
@@ -85,7 +75,7 @@ export const SubpagesMenu = React.memo(
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</BaseBubbleMenu>
|
</BaseBubbleMenu>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default SubpagesMenu;
|
export default SubpagesMenu;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
EditorMenuProps,
|
EditorMenuProps,
|
||||||
ShouldShowProps,
|
ShouldShowProps,
|
||||||
} from "@/features/editor/components/table/types/types.ts";
|
} from "@/features/editor/components/table/types/types.ts";
|
||||||
import { isCellSelection, TiptapTippyBubbleMenu } from '@docmost/editor-ext';
|
import { isCellSelection } from "@docmost/editor-ext";
|
||||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconBoxMargin,
|
IconBoxMargin,
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TableBackgroundColor } from "./table-background-color";
|
import { TableBackgroundColor } from "./table-background-color";
|
||||||
import { TableTextAlignment } from "./table-text-alignment";
|
import { TableTextAlignment } from "./table-text-alignment";
|
||||||
|
import { BubbleMenu } from "@tiptap/react/menus";
|
||||||
|
|
||||||
export const TableCellMenu = React.memo(
|
export const TableCellMenu = React.memo(
|
||||||
({ editor, appendTo }: EditorMenuProps): JSX.Element => {
|
({ editor, appendTo }: EditorMenuProps): JSX.Element => {
|
||||||
@@ -27,7 +28,7 @@ export const TableCellMenu = React.memo(
|
|||||||
|
|
||||||
return isCellSelection(state.selection);
|
return isCellSelection(state.selection);
|
||||||
},
|
},
|
||||||
[editor],
|
[editor]
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergeCells = useCallback(() => {
|
const mergeCells = useCallback(() => {
|
||||||
@@ -51,16 +52,20 @@ export const TableCellMenu = React.memo(
|
|||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TiptapTippyBubbleMenu
|
<BubbleMenu
|
||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey="table-cell-menu"
|
pluginKey="table-cell-menu"
|
||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
tippyOptions={{
|
appendTo={() => {
|
||||||
appendTo: () => {
|
return appendTo?.current;
|
||||||
return appendTo?.current;
|
}}
|
||||||
|
ref={(element) => {
|
||||||
|
element.style.zIndex = "99";
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
offset: {
|
||||||
|
mainAxis: 15,
|
||||||
},
|
},
|
||||||
offset: [0, 15],
|
|
||||||
zIndex: 99,
|
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
@@ -123,9 +128,9 @@ export const TableCellMenu = React.memo(
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ActionIcon.Group>
|
</ActionIcon.Group>
|
||||||
</TiptapTippyBubbleMenu>
|
</BubbleMenu>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default TableCellMenu;
|
export default TableCellMenu;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import {
|
|||||||
IconTableRow,
|
IconTableRow,
|
||||||
IconTrashX,
|
IconTrashX,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { isCellSelection, TiptapTippyBubbleMenu } from "@docmost/editor-ext";
|
import { BubbleMenu } from "@tiptap/react/menus";
|
||||||
|
import { isCellSelection } from "@docmost/editor-ext";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const TableMenu = React.memo(
|
export const TableMenu = React.memo(
|
||||||
@@ -31,20 +32,28 @@ export const TableMenu = React.memo(
|
|||||||
|
|
||||||
return editor.isActive("table") && !isCellSelection(state.selection);
|
return editor.isActive("table") && !isCellSelection(state.selection);
|
||||||
},
|
},
|
||||||
[editor],
|
[editor]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getReferenceClientRect = useCallback(() => {
|
const getReferencedVirtualElement = useCallback(() => {
|
||||||
const { selection } = editor.state;
|
const { selection } = editor.state;
|
||||||
const predicate = (node: PMNode) => node.type.name === "table";
|
const predicate = (node: PMNode) => node.type.name === "table";
|
||||||
const parent = findParentNode(predicate)(selection);
|
const parent = findParentNode(predicate)(selection);
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement;
|
const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement;
|
||||||
return dom.getBoundingClientRect();
|
const rect = dom.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
getBoundingClientRect: () => rect,
|
||||||
|
getClientRects: () => [rect],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return posToDOMRect(editor.view, selection.from, selection.to);
|
const rect = posToDOMRect(editor.view, selection.from, selection.to);
|
||||||
|
return {
|
||||||
|
getBoundingClientRect: () => rect,
|
||||||
|
getClientRects: () => [rect],
|
||||||
|
};
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
const toggleHeaderColumn = useCallback(() => {
|
const toggleHeaderColumn = useCallback(() => {
|
||||||
@@ -84,35 +93,27 @@ export const TableMenu = React.memo(
|
|||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TiptapTippyBubbleMenu
|
<BubbleMenu
|
||||||
editor={editor}
|
editor={editor}
|
||||||
pluginKey="table-menu"
|
pluginKey="table-menu"
|
||||||
updateDelay={0}
|
resizeDelay={0}
|
||||||
tippyOptions={{
|
getReferencedVirtualElement={getReferencedVirtualElement}
|
||||||
getReferenceClientRect: getReferenceClientRect,
|
ref={(element) => {
|
||||||
offset: [0, 15],
|
element.style.zIndex = "99";
|
||||||
zIndex: 99,
|
}}
|
||||||
popperOptions: {
|
options={{
|
||||||
modifiers: [
|
placement: "top",
|
||||||
{
|
offset: {
|
||||||
name: "preventOverflow",
|
mainAxis: 15,
|
||||||
enabled: true,
|
},
|
||||||
options: {
|
flip: {
|
||||||
altAxis: true,
|
fallbackPlacements: ["top", "bottom"],
|
||||||
boundary: "clippingParents",
|
padding: { top: 35 + 15, left: 8, right: 8, bottom: -Infinity },
|
||||||
padding: 8,
|
boundary: editor.options.element as HTMLElement,
|
||||||
},
|
},
|
||||||
},
|
shift: {
|
||||||
{
|
padding: 8 + 15,
|
||||||
name: "flip",
|
crossAxis: true,
|
||||||
enabled: true,
|
|
||||||
options: {
|
|
||||||
boundary: editor.options.element,
|
|
||||||
fallbackPlacements: ["top", "bottom"],
|
|
||||||
padding: { top: 35, left: 8, right: 8, bottom: -Infinity },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
@@ -218,9 +219,9 @@ export const TableMenu = React.memo(
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ActionIcon.Group>
|
</ActionIcon.Group>
|
||||||
</TiptapTippyBubbleMenu>
|
</BubbleMenu>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default TableMenu;
|
export default TableMenu;
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ export * from "./lib/subpages";
|
|||||||
export * from "./lib/highlight";
|
export * from "./lib/highlight";
|
||||||
export * from "./lib/heading/heading";
|
export * from "./lib/heading/heading";
|
||||||
export * from "./lib/unique-id";
|
export * from "./lib/unique-id";
|
||||||
export * from "./lib/tippy-bubble-menu";
|
|
||||||
|
|||||||
@@ -1,349 +0,0 @@
|
|||||||
// Source: https://github.com/ueberdosis/tiptap/blob/v2/packages/extension-bubble-menu/src/bubble-menu-plugin.ts - MIT
|
|
||||||
import {
|
|
||||||
Editor,
|
|
||||||
isNodeSelection,
|
|
||||||
isTextSelection,
|
|
||||||
posToDOMRect,
|
|
||||||
} from "@tiptap/core";
|
|
||||||
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
|
||||||
import { EditorView } from "@tiptap/pm/view";
|
|
||||||
import tippy, { Instance, Props } from "tippy.js";
|
|
||||||
|
|
||||||
export interface BubbleMenuPluginProps {
|
|
||||||
/**
|
|
||||||
* The plugin key.
|
|
||||||
* @type {PluginKey | string}
|
|
||||||
* @default 'bubbleMenu'
|
|
||||||
*/
|
|
||||||
pluginKey: PluginKey | string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The editor instance.
|
|
||||||
*/
|
|
||||||
editor: Editor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DOM element that contains your menu.
|
|
||||||
* @type {HTMLElement}
|
|
||||||
* @default null
|
|
||||||
*/
|
|
||||||
element: HTMLElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The options for the tippy.js instance.
|
|
||||||
* @see https://atomiks.github.io/tippyjs/v6/all-props/
|
|
||||||
*/
|
|
||||||
tippyOptions?: Partial<Props>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The delay in milliseconds before the menu should be updated.
|
|
||||||
* This can be useful to prevent performance issues.
|
|
||||||
* @type {number}
|
|
||||||
* @default 250
|
|
||||||
*/
|
|
||||||
updateDelay?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that determines whether the menu should be shown or not.
|
|
||||||
* If this function returns `false`, the menu will be hidden, otherwise it will be shown.
|
|
||||||
*/
|
|
||||||
shouldShow?:
|
|
||||||
| ((props: {
|
|
||||||
editor: Editor;
|
|
||||||
element: HTMLElement;
|
|
||||||
view: EditorView;
|
|
||||||
state: EditorState;
|
|
||||||
oldState?: EditorState;
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
}) => boolean)
|
|
||||||
| null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BubbleMenuViewProps = BubbleMenuPluginProps & {
|
|
||||||
view: EditorView;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class BubbleMenuView {
|
|
||||||
public editor: Editor;
|
|
||||||
|
|
||||||
public element: HTMLElement;
|
|
||||||
|
|
||||||
public view: EditorView;
|
|
||||||
|
|
||||||
public preventHide = false;
|
|
||||||
|
|
||||||
public tippy: Instance | undefined;
|
|
||||||
|
|
||||||
public tippyOptions?: Partial<Props>;
|
|
||||||
|
|
||||||
public updateDelay: number;
|
|
||||||
|
|
||||||
private updateDebounceTimer: number | undefined;
|
|
||||||
|
|
||||||
public shouldShow: Exclude<BubbleMenuPluginProps["shouldShow"], null> = ({
|
|
||||||
view,
|
|
||||||
state,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
}) => {
|
|
||||||
const { doc, selection } = state;
|
|
||||||
const { empty } = selection;
|
|
||||||
|
|
||||||
// Sometime check for `empty` is not enough.
|
|
||||||
// Doubleclick an empty paragraph returns a node size of 2.
|
|
||||||
// So we check also for an empty text size.
|
|
||||||
const isEmptyTextBlock =
|
|
||||||
!doc.textBetween(from, to).length && isTextSelection(state.selection);
|
|
||||||
|
|
||||||
// When clicking on a element inside the bubble menu the editor "blur" event
|
|
||||||
// is called and the bubble menu item is focussed. In this case we should
|
|
||||||
// consider the menu as part of the editor and keep showing the menu
|
|
||||||
const isChildOfMenu = this.element.contains(document.activeElement);
|
|
||||||
|
|
||||||
const hasEditorFocus = view.hasFocus() || isChildOfMenu;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!hasEditorFocus ||
|
|
||||||
empty ||
|
|
||||||
isEmptyTextBlock ||
|
|
||||||
!this.editor.isEditable
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
editor,
|
|
||||||
element,
|
|
||||||
view,
|
|
||||||
tippyOptions = {},
|
|
||||||
updateDelay = 250,
|
|
||||||
shouldShow,
|
|
||||||
}: BubbleMenuViewProps) {
|
|
||||||
this.editor = editor;
|
|
||||||
this.element = element;
|
|
||||||
this.view = view;
|
|
||||||
this.updateDelay = updateDelay;
|
|
||||||
|
|
||||||
if (shouldShow) {
|
|
||||||
this.shouldShow = shouldShow;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element.addEventListener("mousedown", this.mousedownHandler, {
|
|
||||||
capture: true,
|
|
||||||
});
|
|
||||||
this.view.dom.addEventListener("dragstart", this.dragstartHandler);
|
|
||||||
this.editor.on("focus", this.focusHandler);
|
|
||||||
this.editor.on("blur", this.blurHandler);
|
|
||||||
this.tippyOptions = tippyOptions;
|
|
||||||
// Detaches menu content from its current parent
|
|
||||||
this.element.remove();
|
|
||||||
this.element.style.visibility = "visible";
|
|
||||||
}
|
|
||||||
|
|
||||||
mousedownHandler = () => {
|
|
||||||
this.preventHide = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
dragstartHandler = () => {
|
|
||||||
this.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
focusHandler = () => {
|
|
||||||
// we use `setTimeout` to make sure `selection` is already updated
|
|
||||||
setTimeout(() => this.update(this.editor.view));
|
|
||||||
};
|
|
||||||
|
|
||||||
blurHandler = ({ event }: { event: FocusEvent }) => {
|
|
||||||
if (this.preventHide) {
|
|
||||||
this.preventHide = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
event?.relatedTarget &&
|
|
||||||
this.element.parentNode?.contains(event.relatedTarget as Node)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event?.relatedTarget === this.editor.view.dom) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
tippyBlurHandler = (event: FocusEvent) => {
|
|
||||||
this.blurHandler({ event });
|
|
||||||
};
|
|
||||||
|
|
||||||
createTooltip() {
|
|
||||||
const { element: editorElement } = this.editor.options;
|
|
||||||
//@ts-ignore
|
|
||||||
const editorIsAttached = !!editorElement.parentElement;
|
|
||||||
|
|
||||||
this.element.tabIndex = 0;
|
|
||||||
|
|
||||||
if (this.tippy || !editorIsAttached) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
this.tippy = tippy(editorElement, {
|
|
||||||
duration: 0,
|
|
||||||
getReferenceClientRect: null,
|
|
||||||
content: this.element,
|
|
||||||
interactive: true,
|
|
||||||
trigger: "manual",
|
|
||||||
placement: "top",
|
|
||||||
hideOnClick: "toggle",
|
|
||||||
...this.tippyOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
// maybe we have to hide tippy on its own blur event as well
|
|
||||||
if (this.tippy.popper.firstChild) {
|
|
||||||
(this.tippy.popper.firstChild as HTMLElement).addEventListener(
|
|
||||||
"blur",
|
|
||||||
this.tippyBlurHandler,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update(view: EditorView, oldState?: EditorState) {
|
|
||||||
const { state } = view;
|
|
||||||
const hasValidSelection = state.selection.from !== state.selection.to;
|
|
||||||
|
|
||||||
if (this.updateDelay > 0 && hasValidSelection) {
|
|
||||||
this.handleDebouncedUpdate(view, oldState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectionChanged = !oldState?.selection.eq(view.state.selection);
|
|
||||||
const docChanged = !oldState?.doc.eq(view.state.doc);
|
|
||||||
|
|
||||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDebouncedUpdate = (view: EditorView, oldState?: EditorState) => {
|
|
||||||
const selectionChanged = !oldState?.selection.eq(view.state.selection);
|
|
||||||
const docChanged = !oldState?.doc.eq(view.state.doc);
|
|
||||||
|
|
||||||
if (!selectionChanged && !docChanged) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.updateDebounceTimer) {
|
|
||||||
clearTimeout(this.updateDebounceTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateDebounceTimer = window.setTimeout(() => {
|
|
||||||
this.updateHandler(view, selectionChanged, docChanged, oldState);
|
|
||||||
}, this.updateDelay);
|
|
||||||
};
|
|
||||||
|
|
||||||
updateHandler = (
|
|
||||||
view: EditorView,
|
|
||||||
selectionChanged: boolean,
|
|
||||||
docChanged: boolean,
|
|
||||||
oldState?: EditorState,
|
|
||||||
) => {
|
|
||||||
const { state, composing } = view;
|
|
||||||
const { selection } = state;
|
|
||||||
|
|
||||||
const isSame = !selectionChanged && !docChanged;
|
|
||||||
|
|
||||||
if (composing || isSame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createTooltip();
|
|
||||||
|
|
||||||
// support for CellSelections
|
|
||||||
const { ranges } = selection;
|
|
||||||
const from = Math.min(...ranges.map((range) => range.$from.pos));
|
|
||||||
const to = Math.max(...ranges.map((range) => range.$to.pos));
|
|
||||||
|
|
||||||
const shouldShow = this.shouldShow?.({
|
|
||||||
editor: this.editor,
|
|
||||||
element: this.element,
|
|
||||||
view,
|
|
||||||
state,
|
|
||||||
oldState,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!shouldShow) {
|
|
||||||
this.hide();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tippy?.setProps({
|
|
||||||
getReferenceClientRect:
|
|
||||||
this.tippyOptions?.getReferenceClientRect ||
|
|
||||||
(() => {
|
|
||||||
if (isNodeSelection(state.selection)) {
|
|
||||||
let node = view.nodeDOM(from) as HTMLElement;
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
const nodeViewWrapper = node.dataset.nodeViewWrapper
|
|
||||||
? node
|
|
||||||
: node.querySelector("[data-node-view-wrapper]");
|
|
||||||
|
|
||||||
if (nodeViewWrapper) {
|
|
||||||
node = nodeViewWrapper.firstChild as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
return node.getBoundingClientRect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return posToDOMRect(view, from, to);
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.show();
|
|
||||||
};
|
|
||||||
|
|
||||||
show() {
|
|
||||||
this.tippy?.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
this.tippy?.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
if (this.tippy?.popper.firstChild) {
|
|
||||||
(this.tippy.popper.firstChild as HTMLElement).removeEventListener(
|
|
||||||
"blur",
|
|
||||||
this.tippyBlurHandler,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.tippy?.destroy();
|
|
||||||
this.element.removeEventListener("mousedown", this.mousedownHandler, {
|
|
||||||
capture: true,
|
|
||||||
});
|
|
||||||
this.view.dom.removeEventListener("dragstart", this.dragstartHandler);
|
|
||||||
this.editor.off("focus", this.focusHandler);
|
|
||||||
this.editor.off("blur", this.blurHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BubbleMenuPlugin = (options: BubbleMenuPluginProps) => {
|
|
||||||
return new Plugin({
|
|
||||||
key:
|
|
||||||
typeof options.pluginKey === "string"
|
|
||||||
? new PluginKey(options.pluginKey)
|
|
||||||
: options.pluginKey,
|
|
||||||
view: (view) => new BubbleMenuView({ view, ...options }),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
// Source: https://github.com/ueberdosis/tiptap/blob/v2/packages/react/src/BubbleMenu.tsx - MIT
|
|
||||||
import { BubbleMenuPlugin, BubbleMenuPluginProps } from "./bubble-menu-plugin";
|
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useCurrentEditor } from "@tiptap/react";
|
|
||||||
|
|
||||||
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
||||||
|
|
||||||
export type BubbleMenuProps = Omit<
|
|
||||||
Optional<BubbleMenuPluginProps, "pluginKey">,
|
|
||||||
"element" | "editor"
|
|
||||||
> & {
|
|
||||||
editor: BubbleMenuPluginProps["editor"] | null;
|
|
||||||
className?: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
updateDelay?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BubbleMenu = (props: BubbleMenuProps) => {
|
|
||||||
const [element, setElement] = useState<HTMLDivElement | null>(null);
|
|
||||||
const { editor: currentEditor } = useCurrentEditor();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.editor?.isDestroyed || currentEditor?.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
pluginKey = "bubbleMenu",
|
|
||||||
editor,
|
|
||||||
tippyOptions = {},
|
|
||||||
updateDelay,
|
|
||||||
shouldShow = null,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const menuEditor = editor || currentEditor;
|
|
||||||
|
|
||||||
if (!menuEditor) {
|
|
||||||
console.warn(
|
|
||||||
"BubbleMenu component is not rendered inside of an editor component or does not have editor prop.",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const plugin = BubbleMenuPlugin({
|
|
||||||
updateDelay,
|
|
||||||
editor: menuEditor,
|
|
||||||
element,
|
|
||||||
pluginKey,
|
|
||||||
shouldShow,
|
|
||||||
tippyOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
menuEditor.registerPlugin(plugin);
|
|
||||||
return () => {
|
|
||||||
menuEditor.unregisterPlugin(pluginKey);
|
|
||||||
};
|
|
||||||
}, [props.editor, currentEditor, element]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={setElement}
|
|
||||||
className={props.className}
|
|
||||||
style={{ visibility: "hidden" }}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { BubbleMenu as TiptapTippyBubbleMenu } from "./bubble-menu-react";
|
|
||||||
Generated
-15
@@ -337,9 +337,6 @@ importers:
|
|||||||
socket.io-client:
|
socket.io-client:
|
||||||
specifier: ^4.8.1
|
specifier: ^4.8.1
|
||||||
version: 4.8.1
|
version: 4.8.1
|
||||||
tippy.js:
|
|
||||||
specifier: ^6.3.7
|
|
||||||
version: 6.3.7
|
|
||||||
tiptap-extension-global-drag-handle:
|
tiptap-extension-global-drag-handle:
|
||||||
specifier: ^0.1.18
|
specifier: ^0.1.18
|
||||||
version: 0.1.18
|
version: 0.1.18
|
||||||
@@ -3350,9 +3347,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
'@popperjs/core@2.11.8':
|
|
||||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.1':
|
'@radix-ui/primitive@1.1.1':
|
||||||
resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
|
resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
|
||||||
|
|
||||||
@@ -9423,9 +9417,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
tippy.js@6.3.7:
|
|
||||||
resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==}
|
|
||||||
|
|
||||||
tiptap-extension-global-drag-handle@0.1.18:
|
tiptap-extension-global-drag-handle@0.1.18:
|
||||||
resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==}
|
resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==}
|
||||||
|
|
||||||
@@ -13620,8 +13611,6 @@ snapshots:
|
|||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.1': {}
|
'@radix-ui/primitive@1.1.1': {}
|
||||||
|
|
||||||
'@radix-ui/react-arrow@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-arrow@1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
@@ -20710,10 +20699,6 @@ snapshots:
|
|||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
|
|
||||||
tippy.js@6.3.7:
|
|
||||||
dependencies:
|
|
||||||
'@popperjs/core': 2.11.8
|
|
||||||
|
|
||||||
tiptap-extension-global-drag-handle@0.1.18: {}
|
tiptap-extension-global-drag-handle@0.1.18: {}
|
||||||
|
|
||||||
tldts-core@6.1.72: {}
|
tldts-core@6.1.72: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user