mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat: support cross-space page mentions (#1979)
This commit is contained in:
@@ -33,7 +33,6 @@ export const handlePaste = (
|
||||
const url = clipboardData.trim();
|
||||
const { from: pos, empty } = editor.state.selection;
|
||||
const match = INTERNAL_LINK_REGEX.exec(url);
|
||||
const currentPageMatch = INTERNAL_LINK_REGEX.exec(window.location.href);
|
||||
|
||||
// pasted link must be from the same workspace/domain and must not be on a selection
|
||||
if (!empty || match[2] !== window.location.host) {
|
||||
@@ -41,12 +40,6 @@ export const handlePaste = (
|
||||
return false;
|
||||
}
|
||||
|
||||
// for now, we only support internal links from the same space
|
||||
// compare space name
|
||||
if (currentPageMatch[4].toLowerCase() !== match[4].toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const anchorId = match[6] ? match[6].split("#")[0] : undefined;
|
||||
const urlWithoutAnchor = anchorId
|
||||
? url.substring(0, url.indexOf("#"))
|
||||
|
||||
@@ -31,13 +31,17 @@ import {
|
||||
MentionSuggestionItem,
|
||||
} from "@/features/editor/components/mention/mention.type.ts";
|
||||
import { IPage } from "@/features/page/types/page.types";
|
||||
import { useCreatePageMutation, usePageQuery } from "@/features/page/queries/page-query";
|
||||
import {
|
||||
useCreatePageMutation,
|
||||
usePageQuery,
|
||||
} from "@/features/page/queries/page-query";
|
||||
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom";
|
||||
import { SimpleTree } from "react-arborist";
|
||||
import { SpaceTreeNode } from "@/features/page/tree/types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQueryEmit } from "@/features/websocket/use-query-emit";
|
||||
import { extractPageSlugId } from "@/lib";
|
||||
import { AutoTooltipText } from "@/components/ui/auto-tooltip-text.tsx";
|
||||
|
||||
const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(1);
|
||||
@@ -59,11 +63,11 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
includeUsers: true,
|
||||
includePages: true,
|
||||
spaceId: space.id,
|
||||
limit: 10,
|
||||
limit: props.query ? 10 : 5,
|
||||
preload: true,
|
||||
});
|
||||
|
||||
const createPageItem = (label: string) : MentionSuggestionItem => {
|
||||
const createPageItem = (label: string): MentionSuggestionItem => {
|
||||
return {
|
||||
id: null,
|
||||
label: label,
|
||||
@@ -71,15 +75,15 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
entityId: null,
|
||||
slugId: null,
|
||||
icon: null,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (suggestion && !isLoading) {
|
||||
let items: MentionSuggestionItem[] = [];
|
||||
|
||||
if (suggestion?.users?.length > 0) {
|
||||
items.push({ entityType: "header", label: t("Users") });
|
||||
items.push({ entityType: "header", label: t("People") });
|
||||
|
||||
items = items.concat(
|
||||
suggestion.users.map((user) => ({
|
||||
@@ -97,11 +101,13 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
items = items.concat(
|
||||
suggestion.pages.map((page) => ({
|
||||
id: uuid7(),
|
||||
label: page.title || "Untitled",
|
||||
label: page.title || t("Untitled"),
|
||||
entityType: "page",
|
||||
entityId: page.id,
|
||||
slugId: page.slugId,
|
||||
icon: page.icon,
|
||||
spaceName: page.space?.name,
|
||||
spaceSlug: page.space?.slug,
|
||||
})),
|
||||
);
|
||||
}
|
||||
@@ -129,17 +135,17 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
creatorId: currentUser?.user.id,
|
||||
});
|
||||
}
|
||||
if (item.entityType === "page" && item.id!==null) {
|
||||
if (item.entityType === "page" && item.id !== null) {
|
||||
props.command({
|
||||
id: item.id,
|
||||
label: item.label || "Untitled",
|
||||
label: item.label || t("Untitled"),
|
||||
entityType: "page",
|
||||
entityId: item.entityId,
|
||||
slugId: item.slugId,
|
||||
creatorId: currentUser?.user.id,
|
||||
});
|
||||
}
|
||||
if (item.entityType === "page" && item.id===null) {
|
||||
if (item.entityType === "page" && item.id === null) {
|
||||
createPage(item.label);
|
||||
}
|
||||
}
|
||||
@@ -207,7 +213,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
const payload: { spaceId: string; parentPageId?: string; title: string } = {
|
||||
spaceId: space.id,
|
||||
parentPageId: page.id || null,
|
||||
title: title
|
||||
title: title,
|
||||
};
|
||||
|
||||
let createdPage: IPage;
|
||||
@@ -231,7 +237,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
|
||||
props.command({
|
||||
id: uuid7(),
|
||||
label: createdPage.title || "Untitled",
|
||||
label: createdPage.title || "Untitled",
|
||||
entityType: "page",
|
||||
entityId: createdPage.id,
|
||||
slugId: createdPage.slugId,
|
||||
@@ -239,21 +245,20 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
emit({
|
||||
operation: "addTreeNode",
|
||||
spaceId: space.id,
|
||||
payload: {
|
||||
parentId,
|
||||
index: lastIndex,
|
||||
data,
|
||||
},
|
||||
});
|
||||
}, 50);
|
||||
|
||||
emit({
|
||||
operation: "addTreeNode",
|
||||
spaceId: space.id,
|
||||
payload: {
|
||||
parentId,
|
||||
index: lastIndex,
|
||||
data,
|
||||
},
|
||||
});
|
||||
}, 50);
|
||||
} catch (err) {
|
||||
throw new Error("Failed to create page");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
viewportRef.current
|
||||
@@ -267,15 +272,19 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
return (
|
||||
<Paper id="mention" shadow="md" py="xs" withBorder radius="md">
|
||||
<Text c="dimmed" size="sm" px="sm">
|
||||
{ t("No results") }
|
||||
{t("No results")}
|
||||
</Text>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
const hasUsers = renderItems.some((item) => item.entityType === "user");
|
||||
const hasPages = renderItems.some((item) => item.entityType === "page" && item.id !== null);
|
||||
const createPageItemData = renderItems.find((item) => item.entityType === "page" && item.id === null);
|
||||
const hasPages = renderItems.some(
|
||||
(item) => item.entityType === "page" && item.id !== null,
|
||||
);
|
||||
const createPageItemData = renderItems.find(
|
||||
(item) => item.entityType === "page" && item.id === null,
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper id="mention" shadow="md" withBorder radius="md" py={6}>
|
||||
@@ -283,7 +292,9 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
viewportRef={viewportRef}
|
||||
mah={350}
|
||||
w={popupWidth}
|
||||
scrollbars={"y"}
|
||||
scrollbarSize={6}
|
||||
styles={{ content: { minWidth: 0 } }}
|
||||
>
|
||||
{renderItems?.map((item, index) => {
|
||||
if (item.entityType === "header") {
|
||||
@@ -299,6 +310,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
pt={isFirst ? 2 : 4}
|
||||
pb={4}
|
||||
tt="uppercase"
|
||||
style={{ userSelect: "none" }}
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
@@ -323,9 +335,9 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
/>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text size="sm" fw={500}>
|
||||
<AutoTooltipText size="sm" fw={500}>
|
||||
{item.label}
|
||||
</Text>
|
||||
</AutoTooltipText>
|
||||
</div>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
@@ -355,9 +367,14 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
</ActionIcon>
|
||||
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" fw={500} truncate>
|
||||
<AutoTooltipText size="sm" fw={500} truncate>
|
||||
{item.label}
|
||||
</Text>
|
||||
</AutoTooltipText>
|
||||
{item.spaceName && (
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{item.spaceName}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
@@ -372,9 +389,12 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
{(hasUsers || hasPages) && <Divider my={6} />}
|
||||
<UnstyledButton
|
||||
data-item-index={renderItems.indexOf(createPageItemData)}
|
||||
onClick={() => selectItem(renderItems.indexOf(createPageItemData))}
|
||||
onClick={() =>
|
||||
selectItem(renderItems.indexOf(createPageItemData))
|
||||
}
|
||||
className={clsx(classes.menuBtn, {
|
||||
[classes.selectedItem]: renderItems.indexOf(createPageItemData) === selectedIndex,
|
||||
[classes.selectedItem]:
|
||||
renderItems.indexOf(createPageItemData) === selectedIndex,
|
||||
})}
|
||||
px="sm"
|
||||
>
|
||||
@@ -388,7 +408,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
|
||||
<IconPlus size={16} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ flex: 1, minWidth: 0, overflow: "hidden" }}>
|
||||
<Text size="sm" fw={500} truncate>
|
||||
{t("Create page")}: {createPageItemData.label}
|
||||
</Text>
|
||||
|
||||
@@ -106,7 +106,7 @@ const mentionRenderItems = () => {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
position: "absolute",
|
||||
zIndex: "9999",
|
||||
zIndex: "100",
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -54,12 +54,20 @@ export default function MentionView(props: NodeViewProps) {
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{entityType === "page" && (
|
||||
{entityType === "page" && isError && (
|
||||
<Text component="span" c="dimmed" size="sm">
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{entityType === "page" && !isError && (
|
||||
<Anchor
|
||||
component={Link}
|
||||
fw={500}
|
||||
to={
|
||||
isShareRoute ? shareSlugUrl : buildPageUrl(spaceSlug, slugId, label, anchorId)
|
||||
isShareRoute
|
||||
? shareSlugUrl
|
||||
: buildPageUrl(page?.space?.slug || spaceSlug, slugId, page?.title || label, anchorId)
|
||||
}
|
||||
onClick={handleClick}
|
||||
underline="never"
|
||||
|
||||
@@ -26,4 +26,6 @@ export type MentionSuggestionItem =
|
||||
entityId: string;
|
||||
slugId: string;
|
||||
icon: string;
|
||||
spaceName?: string;
|
||||
spaceSlug?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user