fix: editor improvements (#583)

* delete unused component

* return page prosemirror content

* prefetch pages

* use prosemirro json content on editor

* cache page query with id and slug as key

* Show notice on collaboration disconnection

* enable scroll while typing

* enable immediatelyRender

* avoid image break in PDF print

* Comment editor rendering props
This commit is contained in:
Philip Okugbe
2025-01-16 12:48:35 +00:00
committed by GitHub
parent 71cfe3cd8e
commit 3cb954db69
16 changed files with 144 additions and 142 deletions

View File

@ -8,6 +8,7 @@ import {
IconMessage,
IconPrinter,
IconTrash,
IconWifiOff,
} from "@tabler/icons-react";
import React from "react";
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
@ -23,18 +24,31 @@ import { extractPageSlugId } from "@/lib";
import { treeApiAtom } from "@/features/page/tree/atoms/tree-api-atom.ts";
import { useDeletePageModal } from "@/features/page/hooks/use-delete-page-modal.tsx";
import { PageWidthToggle } from "@/features/user/components/page-width-pref.tsx";
import PageExportModal from "@/features/page/components/page-export-modal.tsx";
import { useTranslation } from "react-i18next";
import ExportModal from "@/components/common/export-modal";
import { yjsConnectionStatusAtom } from "@/features/editor/atoms/editor-atoms.ts";
interface PageHeaderMenuProps {
readOnly?: boolean;
}
export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
const toggleAside = useToggleAside();
const [yjsConnectionStatus] = useAtom(yjsConnectionStatusAtom);
return (
<>
{yjsConnectionStatus === "disconnected" && (
<Tooltip
label="Real-time editor connection lost. Retrying..."
openDelay={250}
withArrow
>
<ActionIcon variant="default" c="red" style={{ border: "none" }}>
<IconWifiOff size={20} stroke={2} />
</ActionIcon>
</Tooltip>
)}
<Tooltip label="Comments" openDelay={250} withArrow>
<ActionIcon
variant="default"

View File

@ -25,17 +25,31 @@ import { notifications } from "@mantine/notifications";
import { IPagination } from "@/lib/types.ts";
import { queryClient } from "@/main.tsx";
import { buildTree } from "@/features/page/tree/utils";
import { useEffect } from "react";
import { validate as isValidUuid } from "uuid";
import { useTranslation } from "react-i18next";
export function usePageQuery(
pageInput: Partial<IPageInput>,
): UseQueryResult<IPage, Error> {
return useQuery({
const query = useQuery({
queryKey: ["pages", pageInput.pageId],
queryFn: () => getPageById(pageInput),
enabled: !!pageInput.pageId,
staleTime: 5 * 60 * 1000,
});
useEffect(() => {
if (query.data) {
if (isValidUuid(pageInput.pageId)) {
queryClient.setQueryData(["pages", query.data.slugId], query.data);
} else {
queryClient.setQueryData(["pages", query.data.id], query.data);
}
}
}, [query.data]);
return query;
}
export function useCreatePageMutation() {

View File

@ -35,6 +35,7 @@ import {
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
import {
getPageBreadcrumbs,
getPageById,
getSidebarPages,
} from "@/features/page/services/page-service.ts";
import { IPage, SidebarPagesParams } from "@/features/page/types/page.types.ts";
@ -232,6 +233,24 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
const [treeData, setTreeData] = useAtom(treeDataAtom);
const emit = useQueryEmit();
const { spaceSlug } = useParams();
const timerRef = useRef(null);
const prefetchPage = () => {
timerRef.current = setTimeout(() => {
queryClient.prefetchQuery({
queryKey: ["pages", node.data.slugId],
queryFn: () => getPageById({ pageId: node.data.slugId }),
staleTime: 5 * 60 * 1000,
});
}, 150);
};
const cancelPagePrefetch = () => {
if (timerRef.current) {
window.clearTimeout(timerRef.current);
timerRef.current = null;
}
};
async function handleLoadChildren(node: NodeApi<SpaceTreeNode>) {
if (!node.data.hasChildren) return;
@ -330,6 +349,8 @@ function Node({ node, style, dragHandle, tree }: NodeRendererProps<any>) {
className={clsx(classes.node, node.state)}
ref={dragHandle}
onClick={handleClick}
onMouseEnter={prefetchPage}
onMouseLeave={cancelPagePrefetch}
>
<PageArrow node={node} onExpandTree={() => handleLoadChildren(node)} />

View File

@ -1,27 +0,0 @@
.control {
font-weight: 500;
display: block;
width: 100%;
padding: var(--mantine-spacing-xs) var(--mantine-spacing-xs);
color: var(--mantine-color-text);
font-size: var(--mantine-font-size-sm);
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
}
}
.item {
display: block;
text-decoration: none;
padding: var(--mantine-spacing-xs) var(--mantine-spacing-md);
padding-left: 4px;
margin-left: var(--mantine-spacing-sm);
font-size: var(--mantine-font-size-sm);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
}
.chevron {
transition: transform 200ms ease;
}

View File

@ -1,59 +0,0 @@
import React, { ReactNode, useState } from "react";
import {
Group,
Box,
Collapse,
ThemeIcon,
UnstyledButton,
rem,
} from "@mantine/core";
import { IconChevronRight } from "@tabler/icons-react";
import classes from "./tree-collapse.module.css";
interface TreeCollapseProps {
icon?: React.FC<any>;
label: string;
initiallyOpened?: boolean;
children: ReactNode;
}
export function TreeCollapse({
icon: Icon,
label,
initiallyOpened,
children,
}: TreeCollapseProps) {
const [opened, setOpened] = useState(initiallyOpened || false);
return (
<>
<UnstyledButton
onClick={() => setOpened((o) => !o)}
className={classes.control}
>
<Group justify="space-between" gap={0}>
<Box style={{ display: "flex", alignItems: "center" }}>
<ThemeIcon variant="light" size={20}>
<Icon style={{ width: rem(18), height: rem(18) }} />
</ThemeIcon>
<Box ml="md">{label}</Box>
</Box>
<IconChevronRight
className={classes.chevron}
stroke={1.5}
style={{
width: rem(16),
height: rem(16),
transform: opened ? "rotate(90deg)" : "none",
}}
/>
</Group>
</UnstyledButton>
<Collapse in={opened}>
<div className={classes.item}>{children}</div>
</Collapse>
</>
);
}