mirror of
https://github.com/docmost/docmost.git
synced 2026-07-25 06:54:46 +10:00
Merge branch 'main' into tiptap3-migration
This commit is contained in:
@@ -6,15 +6,17 @@ import {
|
||||
EditorMenuProps,
|
||||
ShouldShowProps,
|
||||
} from "@/features/editor/components/table/types/types.ts";
|
||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||
import { ActionIcon, Tooltip, Divider } from "@mantine/core";
|
||||
import {
|
||||
IconAlertTriangleFilled,
|
||||
IconCircleCheckFilled,
|
||||
IconCircleXFilled,
|
||||
IconInfoCircleFilled,
|
||||
IconMoodSmile,
|
||||
} from "@tabler/icons-react";
|
||||
import { CalloutType } from "@docmost/editor-ext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EmojiPicker from "@/components/ui/emoji-picker.tsx";
|
||||
|
||||
export function CalloutMenu({ editor }: EditorMenuProps) {
|
||||
const { t } = useTranslation();
|
||||
@@ -36,6 +38,7 @@ export function CalloutMenu({ editor }: EditorMenuProps) {
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const shouldShow = useCallback(
|
||||
({ state }: ShouldShowProps) => {
|
||||
if (!state) {
|
||||
@@ -70,6 +73,37 @@ export function CalloutMenu({ editor }: EditorMenuProps) {
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
|
||||
const setCalloutIcon = useCallback(
|
||||
(emoji: any) => {
|
||||
const emojiChar = emoji?.native || emoji?.emoji || emoji;
|
||||
editor
|
||||
.chain()
|
||||
.focus(undefined, { scrollIntoView: false })
|
||||
.updateCalloutIcon(emojiChar)
|
||||
.run();
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
|
||||
const removeCalloutIcon = useCallback(() => {
|
||||
editor
|
||||
.chain()
|
||||
.focus(undefined, { scrollIntoView: false })
|
||||
.updateCalloutIcon("")
|
||||
.run();
|
||||
}, [editor]);
|
||||
|
||||
const getCurrentIcon = () => {
|
||||
const { selection } = editor.state;
|
||||
const predicate = (node: PMNode) => node.type.name === "callout";
|
||||
const parent = findParentNode(predicate)(selection);
|
||||
const icon = parent?.node.attrs.icon;
|
||||
return icon || null;
|
||||
};
|
||||
|
||||
const currentIcon = getCurrentIcon();
|
||||
|
||||
return (
|
||||
<BaseBubbleMenu
|
||||
editor={editor}
|
||||
@@ -136,6 +170,18 @@ export function CalloutMenu({ editor }: EditorMenuProps) {
|
||||
<IconCircleXFilled size={18} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
<EmojiPicker
|
||||
onEmojiSelect={setCalloutIcon}
|
||||
removeEmojiAction={removeCalloutIcon}
|
||||
readOnly={false}
|
||||
icon={currentIcon || <IconMoodSmile size={18} />}
|
||||
actionIconProps={{
|
||||
size: "lg",
|
||||
variant: "default",
|
||||
c: undefined,
|
||||
}}
|
||||
/>
|
||||
</ActionIcon.Group>
|
||||
</BaseBubbleMenu>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { CalloutType } from "@docmost/editor-ext";
|
||||
|
||||
export default function CalloutView(props: NodeViewProps) {
|
||||
const { node } = props;
|
||||
const { type } = node.attrs;
|
||||
const { type, icon } = node.attrs;
|
||||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
@@ -19,7 +19,7 @@ export default function CalloutView(props: NodeViewProps) {
|
||||
variant="light"
|
||||
title=""
|
||||
color={getCalloutColor(type)}
|
||||
icon={getCalloutIcon(type)}
|
||||
icon={getCalloutIcon(type, icon)}
|
||||
p="xs"
|
||||
classNames={{
|
||||
message: classes.message,
|
||||
@@ -32,7 +32,11 @@ export default function CalloutView(props: NodeViewProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function getCalloutIcon(type: CalloutType) {
|
||||
function getCalloutIcon(type: CalloutType, customIcon?: string) {
|
||||
if (customIcon && customIcon.trim() !== "") {
|
||||
return <span style={{ fontSize: '18px' }}>{customIcon}</span>;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "info":
|
||||
return <IconInfoCircleFilled />;
|
||||
|
||||
@@ -4,11 +4,7 @@ import mermaid from "mermaid";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import classes from "./code-block.module.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
suppressErrorRendering: true,
|
||||
});
|
||||
import { useComputedColorScheme } from "@mantine/core";
|
||||
|
||||
interface MermaidViewProps {
|
||||
props: NodeViewProps;
|
||||
@@ -16,12 +12,22 @@ interface MermaidViewProps {
|
||||
|
||||
export default function MermaidView({ props }: MermaidViewProps) {
|
||||
const { t } = useTranslation();
|
||||
const computedColorScheme = useComputedColorScheme();
|
||||
const { node } = props;
|
||||
const [preview, setPreview] = useState<string>("");
|
||||
|
||||
// Update Mermaid config when theme changes.
|
||||
useEffect(() => {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
suppressErrorRendering: true,
|
||||
theme: computedColorScheme === "light" ? "default" : "dark",
|
||||
});
|
||||
}, [computedColorScheme]);
|
||||
|
||||
// Re-render the diagram whenever the node content or theme changes.
|
||||
useEffect(() => {
|
||||
const id = `mermaid-${uuidv4()}`;
|
||||
|
||||
if (node.textContent.length > 0) {
|
||||
mermaid
|
||||
.render(id, node.textContent)
|
||||
@@ -40,7 +46,7 @@ export default function MermaidView({ props }: MermaidViewProps) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [node.textContent]);
|
||||
}, [node.textContent, computedColorScheme]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -146,6 +146,7 @@ export default function DrawioView(props: NodeViewProps) {
|
||||
variant="default"
|
||||
color="gray"
|
||||
mx="xs"
|
||||
className="print-hide"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
|
||||
@@ -184,6 +184,7 @@ export default function ExcalidrawView(props: NodeViewProps) {
|
||||
variant="default"
|
||||
color="gray"
|
||||
mx="xs"
|
||||
className="print-hide"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
|
||||
@@ -17,8 +17,10 @@ import {
|
||||
IconTable,
|
||||
IconTypography,
|
||||
IconMenu4,
|
||||
IconCalendar, IconAppWindow,
|
||||
} from '@tabler/icons-react';
|
||||
IconCalendar,
|
||||
IconAppWindow,
|
||||
IconSitemap,
|
||||
} from "@tabler/icons-react";
|
||||
import {
|
||||
CommandProps,
|
||||
SlashMenuGroupedItemsType,
|
||||
@@ -360,6 +362,15 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
.run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Subpages (Child pages)",
|
||||
description: "List all subpages of the current page",
|
||||
searchTerms: ["subpages", "child", "children", "nested", "hierarchy"],
|
||||
icon: IconSitemap,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).insertSubpages().run();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Iframe embed",
|
||||
description: "Embed any Iframe",
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
BubbleMenu as BaseBubbleMenu,
|
||||
posToDOMRect,
|
||||
findParentNode,
|
||||
} from "@tiptap/react";
|
||||
import { Node as PMNode } from "@tiptap/pm/model";
|
||||
import React, { useCallback } from "react";
|
||||
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||
import { IconTrash } from "@tabler/icons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Editor } from "@tiptap/core";
|
||||
import { sticky } from "tippy.js";
|
||||
|
||||
interface SubpagesMenuProps {
|
||||
editor: Editor;
|
||||
}
|
||||
|
||||
interface ShouldShowProps {
|
||||
state: any;
|
||||
from?: number;
|
||||
to?: number;
|
||||
}
|
||||
|
||||
export const SubpagesMenu = React.memo(
|
||||
({ editor }: SubpagesMenuProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const shouldShow = useCallback(
|
||||
({ state }: ShouldShowProps) => {
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return editor.isActive("subpages");
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
|
||||
const getReferenceClientRect = useCallback(() => {
|
||||
const { selection } = editor.state;
|
||||
const predicate = (node: PMNode) => node.type.name === "subpages";
|
||||
const parent = findParentNode(predicate)(selection);
|
||||
|
||||
if (parent) {
|
||||
const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement;
|
||||
return dom.getBoundingClientRect();
|
||||
}
|
||||
|
||||
return posToDOMRect(editor.view, selection.from, selection.to);
|
||||
}, [editor]);
|
||||
|
||||
const deleteNode = useCallback(() => {
|
||||
const { selection } = editor.state;
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setNodeSelection(selection.from)
|
||||
.deleteSelection()
|
||||
.run();
|
||||
}, [editor]);
|
||||
|
||||
return (
|
||||
<BaseBubbleMenu
|
||||
editor={editor}
|
||||
pluginKey={`subpages-menu}`}
|
||||
updateDelay={0}
|
||||
tippyOptions={{
|
||||
getReferenceClientRect,
|
||||
offset: [0, 8],
|
||||
zIndex: 99,
|
||||
popperOptions: {
|
||||
modifiers: [{ name: "flip", enabled: false }],
|
||||
},
|
||||
plugins: [sticky],
|
||||
sticky: "popper",
|
||||
}}
|
||||
shouldShow={shouldShow}
|
||||
>
|
||||
<Tooltip position="top" label={t("Delete")}>
|
||||
<ActionIcon
|
||||
onClick={deleteNode}
|
||||
variant="default"
|
||||
size="lg"
|
||||
color="red"
|
||||
aria-label={t("Delete")}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</BaseBubbleMenu>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default SubpagesMenu;
|
||||
@@ -0,0 +1,120 @@
|
||||
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
||||
import { Stack, Text, Anchor, ActionIcon } from "@mantine/core";
|
||||
import { IconFileDescription } from "@tabler/icons-react";
|
||||
import { useGetSidebarPagesQuery } from "@/features/page/queries/page-query";
|
||||
import { useMemo } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import classes from "./subpages.module.css";
|
||||
import styles from "../mention/mention.module.css";
|
||||
import {
|
||||
buildPageUrl,
|
||||
buildSharedPageUrl,
|
||||
} from "@/features/page/page.utils.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { sortPositionKeys } from "@/features/page/tree/utils/utils";
|
||||
import { useSharedPageSubpages } from "@/features/share/hooks/use-shared-page-subpages";
|
||||
|
||||
export default function SubpagesView(props: NodeViewProps) {
|
||||
const { editor } = props;
|
||||
const { spaceSlug, shareId } = useParams();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const currentPageId = editor.storage.pageId;
|
||||
|
||||
// Get subpages from shared tree if we're in a shared context
|
||||
const sharedSubpages = useSharedPageSubpages(currentPageId);
|
||||
|
||||
const { data, isLoading, error } = useGetSidebarPagesQuery({
|
||||
pageId: currentPageId,
|
||||
});
|
||||
|
||||
const subpages = useMemo(() => {
|
||||
// If we're in a shared context, use the shared subpages
|
||||
if (shareId && sharedSubpages) {
|
||||
return sharedSubpages.map((node) => ({
|
||||
id: node.value,
|
||||
slugId: node.slugId,
|
||||
title: node.name,
|
||||
icon: node.icon,
|
||||
position: node.position,
|
||||
}));
|
||||
}
|
||||
|
||||
// Otherwise use the API data
|
||||
if (!data?.pages) return [];
|
||||
const allPages = data.pages.flatMap((page) => page.items);
|
||||
return sortPositionKeys(allPages);
|
||||
}, [data, shareId, sharedSubpages]);
|
||||
|
||||
if (isLoading && !shareId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (error && !shareId) {
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<Text c="dimmed" size="md" py="md">
|
||||
{t("Failed to load subpages")}
|
||||
</Text>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
if (subpages.length === 0) {
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<div className={classes.container}>
|
||||
<Text c="dimmed" size="md" py="md">
|
||||
{t("No subpages")}
|
||||
</Text>
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeViewWrapper>
|
||||
<div className={classes.container}>
|
||||
<Stack gap={5}>
|
||||
{subpages.map((page) => (
|
||||
<Anchor
|
||||
key={page.id}
|
||||
component={Link}
|
||||
fw={500}
|
||||
to={
|
||||
shareId
|
||||
? buildSharedPageUrl({
|
||||
shareId,
|
||||
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>
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.container {
|
||||
margin: 0;
|
||||
padding-left: 4px;
|
||||
user-select: none;
|
||||
|
||||
a {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
Embed,
|
||||
SearchAndReplace,
|
||||
Mention,
|
||||
Subpages,
|
||||
TableDndExtension,
|
||||
} from "@docmost/editor-ext";
|
||||
import {
|
||||
randomElement,
|
||||
@@ -61,6 +63,7 @@ import CodeBlockView from "@/features/editor/components/code-block/code-block-vi
|
||||
import DrawioView from "../components/drawio/drawio-view";
|
||||
import ExcalidrawView from "@/features/editor/components/excalidraw/excalidraw-view.tsx";
|
||||
import EmbedView from "@/features/editor/components/embed/embed-view.tsx";
|
||||
import SubpagesView from "@/features/editor/components/subpages/subpages-view.tsx";
|
||||
import plaintext from "highlight.js/lib/languages/plaintext";
|
||||
import powershell from "highlight.js/lib/languages/powershell";
|
||||
import abap from "highlightjs-sap-abap";
|
||||
@@ -172,6 +175,7 @@ export const mainExtensions = [
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableDndExtension,
|
||||
MathInline.configure({
|
||||
view: MathInlineView,
|
||||
}),
|
||||
@@ -216,6 +220,9 @@ export const mainExtensions = [
|
||||
Embed.configure({
|
||||
view: EmbedView,
|
||||
}),
|
||||
Subpages.configure({
|
||||
view: SubpagesView,
|
||||
}),
|
||||
MarkdownClipboard.configure({
|
||||
transformPastedText: true,
|
||||
}),
|
||||
|
||||
@@ -32,6 +32,7 @@ import TableMenu from "@/features/editor/components/table/table-menu.tsx";
|
||||
import ImageMenu from "@/features/editor/components/image/image-menu.tsx";
|
||||
import CalloutMenu from "@/features/editor/components/callout/callout-menu.tsx";
|
||||
import VideoMenu from "@/features/editor/components/video/video-menu.tsx";
|
||||
import SubpagesMenu from "@/features/editor/components/subpages/subpages-menu.tsx";
|
||||
import {
|
||||
handleFileDrop,
|
||||
handlePaste,
|
||||
@@ -50,6 +51,7 @@ import { extractPageSlugId } from "@/lib";
|
||||
import { FIVE_MINUTES } from "@/lib/constants.ts";
|
||||
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { searchSpotlight } from '@/features/search/constants.ts';
|
||||
|
||||
interface PageEditorProps {
|
||||
pageId: string;
|
||||
@@ -213,6 +215,10 @@ export default function PageEditor({
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyK') {
|
||||
searchSpotlight.open();
|
||||
return true;
|
||||
}
|
||||
if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
|
||||
const slashCommand = document.querySelector("#slash-command");
|
||||
if (slashCommand) {
|
||||
@@ -390,7 +396,7 @@ export default function PageEditor({
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<div className="editor-container" style={{ position: "relative" }}>
|
||||
<div ref={menuContainerRef}>
|
||||
<EditorContent editor={editor} />
|
||||
|
||||
@@ -406,6 +412,7 @@ export default function PageEditor({
|
||||
<ImageMenu editor={editor} />
|
||||
<VideoMenu editor={editor} />
|
||||
<CalloutMenu editor={editor} />
|
||||
<SubpagesMenu editor={editor} />
|
||||
<ExcalidrawMenu editor={editor} />
|
||||
<DrawioMenu editor={editor} />
|
||||
<LinkMenu editor={editor} appendTo={menuContainerRef} />
|
||||
|
||||
@@ -6,21 +6,19 @@ import { Document } from "@tiptap/extension-document";
|
||||
import { Heading } from "@tiptap/extension-heading";
|
||||
import { Text } from "@tiptap/extension-text";
|
||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
||||
import { useAtom } from "jotai/index";
|
||||
import {
|
||||
pageEditorAtom,
|
||||
readOnlyEditorAtom,
|
||||
} from "@/features/editor/atoms/editor-atoms.ts";
|
||||
import { Editor } from "@tiptap/core";
|
||||
import { useAtom } from "jotai";
|
||||
import { readOnlyEditorAtom } from "@/features/editor/atoms/editor-atoms.ts";
|
||||
|
||||
interface PageEditorProps {
|
||||
title: string;
|
||||
content: any;
|
||||
pageId?: string;
|
||||
}
|
||||
|
||||
export default function ReadonlyPageEditor({
|
||||
title,
|
||||
content,
|
||||
pageId,
|
||||
}: PageEditorProps) {
|
||||
const [, setReadOnlyEditor] = useAtom(readOnlyEditorAtom);
|
||||
|
||||
@@ -56,6 +54,9 @@ export default function ReadonlyPageEditor({
|
||||
content={content}
|
||||
onCreate={({ editor }) => {
|
||||
if (editor) {
|
||||
if (pageId) {
|
||||
editor.storage.pageId = pageId;
|
||||
}
|
||||
// @ts-ignore
|
||||
setReadOnlyEditor(editor);
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0.65em;
|
||||
margin-bottom: 0.65em;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
ul,
|
||||
@@ -57,6 +57,7 @@
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
code {
|
||||
@@ -136,7 +137,7 @@
|
||||
|
||||
.selection,
|
||||
*::selection {
|
||||
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-gray-7));
|
||||
background-color: Highlight;
|
||||
}
|
||||
|
||||
.comment-mark {
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&[data-direction='horizontal'] {
|
||||
rotate: 90deg;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .drag-handle {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
@media print {
|
||||
.mantine-AppShell-header,
|
||||
.mantine-AppShell-navbar,
|
||||
.mantine-AppShell-aside{
|
||||
display: none !important;
|
||||
}
|
||||
.mantine-AppShell-header,
|
||||
.mantine-AppShell-navbar,
|
||||
.mantine-AppShell-aside,
|
||||
.print-hide,
|
||||
.drag-handle {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mantine-AppShell-main {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.mantine-AppShell-main {
|
||||
padding-top: 0 !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
.ProseMirror-selectednode {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.table-dnd-preview {
|
||||
padding: 0;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.table-dnd-drop-indicator {
|
||||
background-color: #adf;
|
||||
}
|
||||
|
||||
.ProseMirror {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
@@ -118,3 +128,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-container:has(.table-dnd-drop-indicator[data-dragging="true"]) {
|
||||
.prosemirror-dropcursor-block {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.prosemirror-dropcursor-inline {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ ul[data-type="taskList"] {
|
||||
display: flex;
|
||||
|
||||
> label {
|
||||
padding-top: 0.2rem;
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5rem;
|
||||
user-select: none;
|
||||
|
||||
@@ -26,6 +26,7 @@ import { UpdateEvent } from "@/features/websocket/types";
|
||||
import localEmitter from "@/lib/local-emitter.ts";
|
||||
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
||||
import { searchSpotlight } from "@/features/search/constants.ts";
|
||||
|
||||
export interface TitleEditorProps {
|
||||
pageId: string;
|
||||
@@ -86,6 +87,20 @@ export function TitleEditor({
|
||||
content: title,
|
||||
immediatelyRender: true,
|
||||
shouldRerenderOnTransaction: false,
|
||||
editorProps: {
|
||||
handleDOMEvents: {
|
||||
keydown: (_view, event) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === "KeyS") {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === "KeyK") {
|
||||
searchSpotlight.open();
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -193,7 +208,7 @@ export function TitleEditor({
|
||||
onKeyDown={(event) => {
|
||||
// First handle the search hotkey
|
||||
getHotkeyHandler([["mod+F", openSearchDialog]])(event);
|
||||
|
||||
|
||||
// Then handle other key events
|
||||
handleTitleKeyDown(event);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user