mirror of
https://github.com/docmost/docmost.git
synced 2026-09-12 08:21:32 +08:00
expand subpages node functionality
This commit is contained in:
@@ -0,0 +1,160 @@
|
|||||||
|
import { Collapse, Anchor, ActionIcon, Stack } from "@mantine/core";
|
||||||
|
import {
|
||||||
|
IconChevronDown,
|
||||||
|
IconChevronRight,
|
||||||
|
IconFileDescription,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useGetSidebarPagesQuery } from "@/features/page/queries/page-query";
|
||||||
|
import { buildPageUrl, buildPublicSpaceUrl, buildSharedPageUrl } from "@/features/page/page.utils";
|
||||||
|
import { useSharedPageSubpages } from "@/features/share/hooks/use-shared-page-subpages";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import styles from "../mention/mention.module.css";
|
||||||
|
import classes from "./subpages.module.css";
|
||||||
|
import {
|
||||||
|
sortSubpages,
|
||||||
|
type SubpageListItem,
|
||||||
|
type SubpagesSortBy,
|
||||||
|
} from "./subpages.utils";
|
||||||
|
|
||||||
|
interface SubpageItemProps {
|
||||||
|
page: SubpageListItem;
|
||||||
|
depth: number;
|
||||||
|
sortBy: SubpagesSortBy;
|
||||||
|
isPublicSpaceRoute: boolean;
|
||||||
|
shareId?: string;
|
||||||
|
spaceSlug?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SubpageItem({
|
||||||
|
page,
|
||||||
|
depth,
|
||||||
|
sortBy,
|
||||||
|
shareId,
|
||||||
|
spaceSlug,
|
||||||
|
isPublicSpaceRoute
|
||||||
|
}: SubpageItemProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
|
const sharedSubpages = useSharedPageSubpages(
|
||||||
|
shareId && opened ? page.id : undefined
|
||||||
|
);
|
||||||
|
const { data, isLoading } = useGetSidebarPagesQuery(
|
||||||
|
!shareId && opened ? { pageId: page.id } : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const children = useMemo(() => {
|
||||||
|
if (shareId) {
|
||||||
|
return sortSubpages(
|
||||||
|
sharedSubpages.map((node) => ({
|
||||||
|
id: node.value,
|
||||||
|
slugId: node.slugId,
|
||||||
|
title: node.name,
|
||||||
|
icon: node.icon,
|
||||||
|
position: node.position,
|
||||||
|
hasChildren: node.hasChildren,
|
||||||
|
})),
|
||||||
|
sortBy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = data?.pages.flatMap((result) => result.items) || [];
|
||||||
|
return sortSubpages(pages, sortBy);
|
||||||
|
}, [data, shareId, sharedSubpages, sortBy]);
|
||||||
|
|
||||||
|
const toggleOpened = () => setOpened((value) => !value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.item}>
|
||||||
|
<div
|
||||||
|
className={classes.row}
|
||||||
|
style={{ paddingLeft: `${depth * 20 + 4}px` }}
|
||||||
|
>
|
||||||
|
{page.hasChildren ? (
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
color="gray"
|
||||||
|
size="sm"
|
||||||
|
className={classes.toggle}
|
||||||
|
onClick={toggleOpened}
|
||||||
|
aria-label={opened ? t("Collapse") : t("Expand")}
|
||||||
|
>
|
||||||
|
{opened ? (
|
||||||
|
<IconChevronDown size={16} />
|
||||||
|
) : (
|
||||||
|
<IconChevronRight size={16} />
|
||||||
|
)}
|
||||||
|
</ActionIcon>
|
||||||
|
) : (
|
||||||
|
<span className={classes.togglePlaceholder}></span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Anchor
|
||||||
|
component={Link}
|
||||||
|
fw={500}
|
||||||
|
to={
|
||||||
|
shareId
|
||||||
|
? buildSharedPageUrl({
|
||||||
|
shareId,
|
||||||
|
pageSlugId: page.slugId,
|
||||||
|
pageTitle: page.title,
|
||||||
|
})
|
||||||
|
: isPublicSpaceRoute
|
||||||
|
? buildPublicSpaceUrl({
|
||||||
|
spaceSlug,
|
||||||
|
pageSlugId: page.slugId,
|
||||||
|
pageTitle: page.title,
|
||||||
|
})
|
||||||
|
: buildPageUrl(spaceSlug, page.slugId, page.title)
|
||||||
|
}
|
||||||
|
underline="never"
|
||||||
|
className={styles.pageMentionLink}
|
||||||
|
draggable={false}
|
||||||
|
>
|
||||||
|
{page.icon ? (
|
||||||
|
<span className={classes.icon}>{page.icon}</span>
|
||||||
|
) : (
|
||||||
|
<ActionIcon
|
||||||
|
variant="transparent"
|
||||||
|
color="gray"
|
||||||
|
component="span"
|
||||||
|
size={18}
|
||||||
|
style={{ verticalAlign: "text-bottom" }}
|
||||||
|
>
|
||||||
|
<IconFileDescription size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
<span className={styles.pageMentionText}>
|
||||||
|
{page.title || t("untitled")}
|
||||||
|
</span>
|
||||||
|
</Anchor>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{page.hasChildren && (
|
||||||
|
<Collapse expanded={opened}>
|
||||||
|
<Stack gap={5}>
|
||||||
|
{isLoading ? (
|
||||||
|
<div style={{ paddingLeft: `${(depth + 2) * 20 + 4}px` }}>
|
||||||
|
...{t("loading")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
children.map((child) => (
|
||||||
|
<SubpageItem
|
||||||
|
key={child.id}
|
||||||
|
page={child}
|
||||||
|
depth={depth + 1}
|
||||||
|
sortBy={sortBy}
|
||||||
|
shareId={shareId}
|
||||||
|
spaceSlug={spaceSlug}
|
||||||
|
isPublicSpaceRoute={isPublicSpaceRoute}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Collapse>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
import { posToDOMRect, findParentNode } from "@tiptap/react";
|
import { posToDOMRect, findParentNode, useEditorState } from "@tiptap/react";
|
||||||
import { Node as PMNode } from "@tiptap/pm/model";
|
import { Node as PMNode } from "@tiptap/pm/model";
|
||||||
import React, { useCallback, type JSX } from "react";
|
import React, { useCallback, type JSX } from "react";
|
||||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
import { ActionIcon, Menu, Tooltip } from "@mantine/core";
|
||||||
import { IconTrash } from "@tabler/icons-react";
|
import { IconArrowsSort, IconCheck, IconTrash } from "@tabler/icons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "@tiptap/core";
|
||||||
import { isEditorReady } from "@docmost/editor-ext";
|
import { isEditorReady } from "@docmost/editor-ext";
|
||||||
|
import type { SubpagesSortBy } from "./subpages.utils";
|
||||||
|
|
||||||
interface SubpagesMenuProps {
|
interface SubpagesMenuProps {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
@@ -57,6 +58,30 @@ export const SubpagesMenu = React.memo(
|
|||||||
.run();
|
.run();
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
|
const editorState = useEditorState({
|
||||||
|
editor,
|
||||||
|
selector: (ctx) => {
|
||||||
|
if (!ctx.editor) return { sortBy: "default" };
|
||||||
|
return {
|
||||||
|
sortBy: (ctx.editor.getAttributes("subpages").sortBy ||
|
||||||
|
"default"),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortBy = editorState?.sortBy || "default";
|
||||||
|
|
||||||
|
const updateSort = useCallback(
|
||||||
|
(value: SubpagesSortBy) => {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus(undefined, { scrollIntoView: false })
|
||||||
|
.setSubpagesSortBy(value)
|
||||||
|
.run();
|
||||||
|
},
|
||||||
|
[editor],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseBubbleMenu
|
<BaseBubbleMenu
|
||||||
editor={editor}
|
editor={editor}
|
||||||
@@ -67,6 +92,40 @@ export const SubpagesMenu = React.memo(
|
|||||||
updateDelay={0}
|
updateDelay={0}
|
||||||
shouldShow={shouldShow}
|
shouldShow={shouldShow}
|
||||||
>
|
>
|
||||||
|
<Menu shadow="md" position="top-start" withinPortal>
|
||||||
|
<Menu.Target>
|
||||||
|
<Tooltip position="top" label={t("Sort")}>
|
||||||
|
<ActionIcon
|
||||||
|
variant="default"
|
||||||
|
size="lg"
|
||||||
|
aria-label={t("Sort")}
|
||||||
|
>
|
||||||
|
<IconArrowsSort size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Label>{t("Sort by")}</Menu.Label>
|
||||||
|
<Menu.Item
|
||||||
|
leftSection={sortBy === "default" ? <IconCheck size={14} /> : null}
|
||||||
|
onClick={() => updateSort("default")}
|
||||||
|
>
|
||||||
|
{t("default")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
leftSection={sortBy === "title-asc" ? <IconCheck size={14} /> : null}
|
||||||
|
onClick={() => updateSort("title-asc")}
|
||||||
|
>
|
||||||
|
{t("Sort A → Z")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
leftSection={sortBy === "title-desc" ? <IconCheck size={14} /> : null}
|
||||||
|
onClick={() => updateSort("title-desc")}
|
||||||
|
>
|
||||||
|
{t("Sort Z → A")}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
<Tooltip position="top" label={t("Delete")}>
|
<Tooltip position="top" label={t("Delete")}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={deleteNode}
|
onClick={deleteNode}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
||||||
import { Stack, Text, Anchor, ActionIcon } from "@mantine/core";
|
import { Stack, Text } from "@mantine/core";
|
||||||
import { IconFileDescription } from "@tabler/icons-react";
|
|
||||||
import { useGetSidebarPagesQuery } from "@/features/page/queries/page-query";
|
import { useGetSidebarPagesQuery } from "@/features/page/queries/page-query";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Link, useLocation, useParams } from "react-router-dom";
|
import { useLocation, useParams } from "react-router-dom";
|
||||||
import classes from "./subpages.module.css";
|
import classes from "./subpages.module.css";
|
||||||
import styles from "../mention/mention.module.css";
|
|
||||||
import {
|
|
||||||
buildPageUrl,
|
|
||||||
buildPublicSpaceUrl,
|
|
||||||
buildSharedPageUrl,
|
|
||||||
} from "@/features/page/page.utils.ts";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { sortPositionKeys } from "@/features/page/tree/utils/utils";
|
|
||||||
import { useSharedPageSubpages } from "@/features/share/hooks/use-shared-page-subpages";
|
import { useSharedPageSubpages } from "@/features/share/hooks/use-shared-page-subpages";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { publicSpaceTreeDataAtom } from "@/features/public-space/atoms/public-space-atoms.ts";
|
import { publicSpaceTreeDataAtom } from "@/features/public-space/atoms/public-space-atoms.ts";
|
||||||
import { findSubpagesInTree } from "@/features/share/utils";
|
import { findSubpagesInTree } from "@/features/share/utils";
|
||||||
import { extractPageSlugId } from "@/lib";
|
import { extractPageSlugId } from "@/lib";
|
||||||
|
import SubpageItem from "./subpage-item";
|
||||||
|
import {
|
||||||
|
sortSubpages,
|
||||||
|
type SubpageListItem,
|
||||||
|
type SubpagesSortBy,
|
||||||
|
} from "./subpages.utils";
|
||||||
|
|
||||||
export default function SubpagesView(props: NodeViewProps) {
|
export default function SubpagesView(props: NodeViewProps) {
|
||||||
const { editor } = props;
|
const { editor } = props;
|
||||||
@@ -44,6 +42,7 @@ export default function SubpagesView(props: NodeViewProps) {
|
|||||||
if (isPublicSpaceRoute) {
|
if (isPublicSpaceRoute) {
|
||||||
currentPageId = routePageId ?? publicSpaceTreeData?.[0]?.slugId;
|
currentPageId = routePageId ?? publicSpaceTreeData?.[0]?.slugId;
|
||||||
}
|
}
|
||||||
|
const sortBy = (props.node.attrs.sortBy || "position") as SubpagesSortBy;
|
||||||
|
|
||||||
// Get subpages from shared tree if we're in a shared context
|
// Get subpages from shared tree if we're in a shared context
|
||||||
const sharedSubpages = useSharedPageSubpages(currentPageId);
|
const sharedSubpages = useSharedPageSubpages(currentPageId);
|
||||||
@@ -61,13 +60,14 @@ export default function SubpagesView(props: NodeViewProps) {
|
|||||||
const subpages = useMemo(() => {
|
const subpages = useMemo(() => {
|
||||||
// If we're in a shared context, use the shared subpages
|
// If we're in a shared context, use the shared subpages
|
||||||
if (shareId && sharedSubpages) {
|
if (shareId && sharedSubpages) {
|
||||||
return sharedSubpages.map((node) => ({
|
return sortSubpages(sharedSubpages.map((node) => ({
|
||||||
id: node.value,
|
id: node.value,
|
||||||
slugId: node.slugId,
|
slugId: node.slugId,
|
||||||
title: node.name,
|
title: node.name,
|
||||||
icon: node.icon,
|
icon: node.icon,
|
||||||
position: node.position,
|
position: node.position,
|
||||||
}));
|
hasChildren: node.hasChildren,
|
||||||
|
})), sortBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPublicSpaceRoute) {
|
if (isPublicSpaceRoute) {
|
||||||
@@ -83,14 +83,8 @@ export default function SubpagesView(props: NodeViewProps) {
|
|||||||
// Otherwise use the API data
|
// Otherwise use the API data
|
||||||
if (!data?.pages) return [];
|
if (!data?.pages) return [];
|
||||||
const allPages = data.pages.flatMap((page) => page.items);
|
const allPages = data.pages.flatMap((page) => page.items);
|
||||||
return sortPositionKeys(allPages);
|
return sortSubpages(allPages, sortBy);
|
||||||
}, [
|
}, [data, shareId, sharedSubpages, isPublicSpaceRoute, publicSpaceSubpages, sortBy]);
|
||||||
data,
|
|
||||||
shareId,
|
|
||||||
sharedSubpages,
|
|
||||||
isPublicSpaceRoute,
|
|
||||||
publicSpaceSubpages,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (isLoading && !isPublicView) {
|
if (isLoading && !isPublicView) {
|
||||||
return null;
|
return null;
|
||||||
@@ -121,49 +115,17 @@ export default function SubpagesView(props: NodeViewProps) {
|
|||||||
return (
|
return (
|
||||||
<NodeViewWrapper data-drag-handle>
|
<NodeViewWrapper data-drag-handle>
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<Stack gap={5}>
|
<Stack gap={5} >
|
||||||
{subpages.map((page) => (
|
{subpages.map((page: SubpageListItem) => (
|
||||||
<Anchor
|
<SubpageItem
|
||||||
key={page.id}
|
key={page.id}
|
||||||
component={Link}
|
page={page}
|
||||||
fw={500}
|
depth={0}
|
||||||
to={
|
sortBy={sortBy}
|
||||||
shareId
|
shareId={shareId}
|
||||||
? buildSharedPageUrl({
|
spaceSlug={spaceSlug}
|
||||||
shareId,
|
isPublicSpaceRoute={isPublicSpaceRoute}
|
||||||
pageSlugId: page.slugId,
|
/>
|
||||||
pageTitle: page.title,
|
|
||||||
})
|
|
||||||
: isPublicSpaceRoute
|
|
||||||
? buildPublicSpaceUrl({
|
|
||||||
spaceSlug,
|
|
||||||
pageSlugId: page.slugId,
|
|
||||||
pageTitle: page.title,
|
|
||||||
})
|
|
||||||
: buildPageUrl(spaceSlug, page.slugId, page.title)
|
|
||||||
}
|
|
||||||
underline="never"
|
|
||||||
className={styles.pageMentionLink}
|
|
||||||
draggable={false}
|
|
||||||
>
|
|
||||||
{page?.icon ? (
|
|
||||||
<span style={{ marginRight: "4px" }}>{page.icon}</span>
|
|
||||||
) : (
|
|
||||||
<ActionIcon
|
|
||||||
variant="transparent"
|
|
||||||
color="gray"
|
|
||||||
component="span"
|
|
||||||
size={18}
|
|
||||||
style={{ verticalAlign: "text-bottom" }}
|
|
||||||
>
|
|
||||||
<IconFileDescription size={18} />
|
|
||||||
</ActionIcon>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<span className={styles.pageMentionText}>
|
|
||||||
{page?.title || t("untitled")}
|
|
||||||
</span>
|
|
||||||
</Anchor>
|
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,3 +7,27 @@
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
flex: 0 0 24px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.togglePlaceholder {
|
||||||
|
display: inline-block;
|
||||||
|
flex: 0 0 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { sortPositionKeys } from "@/features/page/tree/utils/utils";
|
||||||
|
|
||||||
|
export type SubpagesSortBy = "default" | "title-asc" | "title-desc";
|
||||||
|
|
||||||
|
export type SubpageListItem = {
|
||||||
|
id: string;
|
||||||
|
slugId: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
position: string;
|
||||||
|
hasChildren: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function sortSubpages(
|
||||||
|
items: SubpageListItem[],
|
||||||
|
sortBy: SubpagesSortBy = "default"
|
||||||
|
) {
|
||||||
|
const sortedItems = [...items];
|
||||||
|
|
||||||
|
if (sortBy === "default") {
|
||||||
|
return sortPositionKeys(sortedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedItems.sort((a, b) => {
|
||||||
|
const result = (a.title || "untitled").localeCompare(
|
||||||
|
b.title || "untitled",
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
sensitivity: "base",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return sortBy === "title-desc" ? -result : result;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export { Subpages } from "./subpages";
|
export { Subpages } from "./subpages";
|
||||||
export type { SubpagesAttributes, SubpagesOptions } from "./subpages";
|
export type { SubpagesAttributes, SubpagesOptions, SubpagesSortBy } from "./subpages";
|
||||||
|
|||||||
@@ -6,12 +6,17 @@ export interface SubpagesOptions {
|
|||||||
view: any;
|
view: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubpagesAttributes {}
|
export type SubpagesSortBy = "default" | "title-asc" | "title-desc";
|
||||||
|
|
||||||
|
export interface SubpagesAttributes {
|
||||||
|
sortBy?: SubpagesSortBy;
|
||||||
|
}
|
||||||
|
|
||||||
declare module "@tiptap/core" {
|
declare module "@tiptap/core" {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
subpages: {
|
subpages: {
|
||||||
insertSubpages: (attributes?: SubpagesAttributes) => ReturnType;
|
insertSubpages: (attributes?: SubpagesAttributes) => ReturnType;
|
||||||
|
setSubpagesSortBy: (sortBy: SubpagesSortBy) => ReturnType;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +31,19 @@ export const Subpages = Node.create<SubpagesOptions>({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
sortBy: {
|
||||||
|
default: "default",
|
||||||
|
parseHTML: (element: HTMLElement) =>
|
||||||
|
element.getAttribute("data-sort-by") || "default",
|
||||||
|
renderHTML: (attributes: SubpagesAttributes) => ({
|
||||||
|
"data-sort-by": attributes.sortBy || "default",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
group: "block",
|
group: "block",
|
||||||
atom: true,
|
atom: true,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
@@ -60,6 +78,19 @@ export const Subpages = Node.create<SubpagesOptions>({
|
|||||||
attrs: attributes,
|
attrs: attributes,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setSubpagesSortBy:
|
||||||
|
(sortBy) =>
|
||||||
|
({ commands }) => {
|
||||||
|
if (
|
||||||
|
sortBy !== 'default' &&
|
||||||
|
sortBy !== 'title-asc' &&
|
||||||
|
sortBy !== 'title-desc'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return commands.updateAttributes(this.name, { sortBy });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user