mirror of
https://github.com/docmost/docmost.git
synced 2026-08-21 14:21:37 +10:00
feat: package updates (#2300)
* package updates * update tiptap * fix editor crash * fix * update list * fix(theme): size badges to their content * fix mantine avatar color regression
This commit is contained in:
@@ -15,6 +15,7 @@ import { currentUserAtom } from "@/features/user/atoms/current-user-atom";
|
||||
import { useCreateCommentMutation } from "@/features/comment/queries/comment-query";
|
||||
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom";
|
||||
import { useEditor } from "@tiptap/react";
|
||||
import { isEditorReady } from "@docmost/editor-ext";
|
||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -48,11 +49,14 @@ function CommentDialog({ editor, pageId, readOnly }: CommentDialogProps) {
|
||||
setReadOnlyCommentData(null);
|
||||
} else {
|
||||
setShowCommentPopup(false);
|
||||
editor.chain().focus().unsetCommentDecoration().run();
|
||||
if (isEditorReady(editor)) {
|
||||
editor.chain().focus().unsetCommentDecoration().run();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedText = () => {
|
||||
if (!isEditorReady(editor)) return "";
|
||||
const { from, to } = editor.state.selection;
|
||||
return editor.state.doc.textBetween(from, to);
|
||||
};
|
||||
@@ -74,24 +78,28 @@ function CommentDialog({ editor, pageId, readOnly }: CommentDialogProps) {
|
||||
|
||||
const createdComment =
|
||||
await createCommentMutation.mutateAsync(commentData);
|
||||
editor
|
||||
.chain()
|
||||
.setComment(createdComment.id)
|
||||
.unsetCommentDecoration()
|
||||
.run();
|
||||
if (isEditorReady(editor)) {
|
||||
editor
|
||||
.chain()
|
||||
.setComment(createdComment.id)
|
||||
.unsetCommentDecoration()
|
||||
.run();
|
||||
editor.commands.setTextSelection({
|
||||
from: editor.view.state.selection.from,
|
||||
to: editor.view.state.selection.from,
|
||||
});
|
||||
}
|
||||
setActiveCommentId(createdComment.id);
|
||||
|
||||
editor.commands.setTextSelection({ from: editor.view.state.selection.from, to: editor.view.state.selection.from });
|
||||
|
||||
setAsideState({ tab: "comments", isAsideOpen: true });
|
||||
setTimeout(() => {
|
||||
const selector = `div[data-comment-id="${createdComment.id}"]`;
|
||||
const commentElement = document.querySelector(selector);
|
||||
commentElement?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
|
||||
editor.view.dispatch(
|
||||
editor.state.tr.scrollIntoView()
|
||||
);
|
||||
if (isEditorReady(editor)) {
|
||||
editor.view.dispatch(editor.state.tr.scrollIntoView());
|
||||
}
|
||||
}, 400);
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -112,22 +112,24 @@ const CommentEditor = forwardRef(
|
||||
// websocket on another browser). Skip for editable editors to avoid
|
||||
// resetting the cursor position on every keystroke.
|
||||
useEffect(() => {
|
||||
if (!editable && commentEditor && defaultContent) {
|
||||
if (!editable && commentEditor && !commentEditor.isDestroyed && defaultContent) {
|
||||
commentEditor.commands.setContent(defaultContent);
|
||||
}
|
||||
}, [defaultContent, editable, commentEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (autofocus) {
|
||||
commentEditor?.commands.focus("end");
|
||||
if (autofocus && commentEditor && !commentEditor.isDestroyed) {
|
||||
commentEditor.commands.focus("end");
|
||||
}
|
||||
}, 10);
|
||||
}, [commentEditor, autofocus]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
clearContent: () => {
|
||||
commentEditor.commands.clearContent();
|
||||
if (commentEditor && !commentEditor.isDestroyed) {
|
||||
commentEditor.commands.clearContent();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useAtom, useAtomValue } from "jotai";
|
||||
import { useTimeAgo } from "@/hooks/use-time-ago";
|
||||
import CommentEditor from "@/features/comment/components/comment-editor";
|
||||
import { pageEditorAtom } from "@/features/editor/atoms/editor-atoms";
|
||||
import { isEditorReady } from "@docmost/editor-ext";
|
||||
import CommentActions from "@/features/comment/components/comment-actions";
|
||||
import CommentMenu from "@/features/comment/components/comment-menu";
|
||||
import { useHasFeature } from "@/ee/hooks/use-feature";
|
||||
@@ -75,7 +76,9 @@ function CommentListItem({
|
||||
async function handleDeleteComment() {
|
||||
try {
|
||||
await deleteCommentMutation.mutateAsync(comment.id);
|
||||
editor?.commands.unsetComment(comment.id);
|
||||
if (isEditorReady(editor)) {
|
||||
editor.commands.unsetComment(comment.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete comment:", error);
|
||||
}
|
||||
@@ -93,7 +96,7 @@ function CommentListItem({
|
||||
resolved: !isResolved,
|
||||
});
|
||||
|
||||
if (editor) {
|
||||
if (isEditorReady(editor)) {
|
||||
editor.commands.setCommentResolved(comment.id, !isResolved);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/features/comment/atoms/comment-atom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getRelativeSelection, ySyncPluginKey } from "@tiptap/y-tiptap";
|
||||
import { isEditorReady } from "@docmost/editor-ext";
|
||||
|
||||
type ReadonlyBubbleMenuProps = {
|
||||
editor: Editor;
|
||||
@@ -29,6 +30,10 @@ export const ReadonlyBubbleMenu: FC<ReadonlyBubbleMenuProps> = ({ editor }) => {
|
||||
|
||||
const updateMenuPosition = useCallback(() => {
|
||||
if (isInteractingRef.current) return;
|
||||
if (!isEditorReady(editor)) {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const pmSelection = editor.state.selection;
|
||||
if (!(pmSelection instanceof TextSelection) || pmSelection.empty) {
|
||||
@@ -97,7 +102,7 @@ export const ReadonlyBubbleMenu: FC<ReadonlyBubbleMenuProps> = ({ editor }) => {
|
||||
}, [showReadOnlyCommentPopup]);
|
||||
|
||||
const handleCommentClick = () => {
|
||||
if (!editor) return;
|
||||
if (!isEditorReady(editor)) return;
|
||||
|
||||
const view = editor.view;
|
||||
const ystate = ySyncPluginKey.getState(view.state);
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ export const MoreInsertsGroup: FC<Props> = ({ editor, templateMode }) => {
|
||||
<Menu.Item
|
||||
leftSection={<IconRotate2 size={16} />}
|
||||
onClick={() =>
|
||||
editor.chain().focus().insertTransclusionSource().run()
|
||||
editor.chain().focus().toggleTransclusionSource().run()
|
||||
}
|
||||
>
|
||||
{t("Synced block")}
|
||||
|
||||
@@ -28,7 +28,7 @@ import { usePageQuery } from "@/features/page/queries/page-query.ts";
|
||||
import { useSharePageQuery } from "@/features/share/queries/share-query.ts";
|
||||
import { buildSharedPageUrl } from "@/features/page/page.utils.ts";
|
||||
import { extractPageSlugId } from "@/lib";
|
||||
import { sanitizeUrl, copyToClipboard } from "@docmost/editor-ext";
|
||||
import { sanitizeUrl, copyToClipboard, isEditorReady } from "@docmost/editor-ext";
|
||||
import { normalizeUrl } from "@/lib/utils";
|
||||
|
||||
const parseInternalLink = (
|
||||
@@ -313,7 +313,9 @@ export default function LinkView(props: MarkViewProps) {
|
||||
);
|
||||
|
||||
const handleRemoveLink = useCallback(() => {
|
||||
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
||||
if (isEditorReady(editor)) {
|
||||
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
||||
}
|
||||
setPopoverState("closed");
|
||||
}, [editor]);
|
||||
|
||||
@@ -345,7 +347,7 @@ export default function LinkView(props: MarkViewProps) {
|
||||
NodeFilter.SHOW_TEXT,
|
||||
);
|
||||
const textNode = walker.nextNode();
|
||||
if (textNode) {
|
||||
if (textNode && isEditorReady(editor)) {
|
||||
const view = editor.view as any;
|
||||
view.domObserver.stop();
|
||||
textNode.nodeValue = val;
|
||||
|
||||
+10
-2
@@ -17,6 +17,7 @@ import {
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { useEditor } from "@tiptap/react";
|
||||
import { isEditorReady } from "@docmost/editor-ext";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { searchAndReplaceStateAtom } from "@/features/editor/components/search-and-replace/atoms/search-and-replace-state-atom.ts";
|
||||
import { useAtom } from "jotai";
|
||||
@@ -64,13 +65,13 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo
|
||||
replaceButtonToggle();
|
||||
}
|
||||
// Clear search term in editor
|
||||
if (editor) {
|
||||
if (isEditorReady(editor)) {
|
||||
editor.commands.setSearchTerm("");
|
||||
}
|
||||
};
|
||||
|
||||
const goToSelection = () => {
|
||||
if (!editor) return;
|
||||
if (!isEditorReady(editor)) return;
|
||||
|
||||
const { results, resultIndex } = editor.storage.searchAndReplace;
|
||||
//TODO: check type error
|
||||
@@ -90,27 +91,32 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.nextSearchResult();
|
||||
goToSelection();
|
||||
};
|
||||
|
||||
const previous = () => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.previousSearchResult();
|
||||
goToSelection();
|
||||
};
|
||||
|
||||
const replace = () => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.setReplaceTerm(replaceText);
|
||||
editor.commands.replace();
|
||||
goToSelection();
|
||||
};
|
||||
|
||||
const replaceAll = () => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.setReplaceTerm(replaceText);
|
||||
editor.commands.replaceAll();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.setSearchTerm(searchText);
|
||||
editor.commands.resetIndex();
|
||||
editor.commands.selectCurrentItem();
|
||||
@@ -118,6 +124,7 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo
|
||||
|
||||
const handleOpenEvent = (e) => {
|
||||
setPageFindState({ isOpen: true });
|
||||
if (!isEditorReady(editor)) return;
|
||||
const selectedText = editor.state.doc.textBetween(
|
||||
editor.state.selection.from,
|
||||
editor.state.selection.to,
|
||||
@@ -149,6 +156,7 @@ function SearchAndReplaceDialog({ editor, editable = true }: PageFindDialogDialo
|
||||
}, [pageFindState.isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.setCaseSensitive(caseSensitive.isCaseSensitive);
|
||||
editor.commands.resetIndex();
|
||||
goToSelection();
|
||||
|
||||
@@ -50,6 +50,7 @@ export const TableOfContents: FC<TableOfContentsProps> = (props) => {
|
||||
const headerPaddingRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleScrollToHeading = (position: number) => {
|
||||
if (!props.editor || props.editor.isDestroyed) return;
|
||||
const { view } = props.editor;
|
||||
|
||||
const headerOffset = parseInt(
|
||||
@@ -73,16 +74,21 @@ export const TableOfContents: FC<TableOfContentsProps> = (props) => {
|
||||
};
|
||||
|
||||
const handleUpdate = () => {
|
||||
const result = recalculateLinks(props.editor?.$nodes("heading"));
|
||||
if (!props.editor || props.editor.isDestroyed) return;
|
||||
|
||||
const result = recalculateLinks(props.editor.$nodes("heading"));
|
||||
|
||||
setLinks(result.links);
|
||||
setHeadingDOMNodes(result.nodes);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// "create" repopulates once the editor view mounts after this component
|
||||
props.editor?.on("create", handleUpdate);
|
||||
props.editor?.on("update", handleUpdate);
|
||||
|
||||
return () => {
|
||||
props.editor?.off("create", handleUpdate);
|
||||
props.editor?.off("update", handleUpdate);
|
||||
};
|
||||
}, [props.editor]);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Menu, UnstyledButton } from "@mantine/core";
|
||||
import { IconChevronDown } from "@tabler/icons-react";
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isCellSelection } from "@docmost/editor-ext";
|
||||
import { isCellSelection, isEditorReady } from "@docmost/editor-ext";
|
||||
import { CellChevronMenu } from "./menus/cell-chevron-menu";
|
||||
import classes from "./handle.module.css";
|
||||
|
||||
@@ -27,7 +27,9 @@ export const CellChevron = React.memo(function CellChevron({
|
||||
tablePos,
|
||||
}: CellChevronProps) {
|
||||
const { t } = useTranslation();
|
||||
const cellDom = editor.view.nodeDOM(cellPos) as HTMLElement | null;
|
||||
const cellDom = isEditorReady(editor)
|
||||
? (editor.view.nodeDOM(cellPos) as HTMLElement | null)
|
||||
: null;
|
||||
|
||||
const { refs, floatingStyles, middlewareData } = useFloating({
|
||||
placement: "top-end",
|
||||
@@ -61,6 +63,7 @@ export const CellChevron = React.memo(function CellChevron({
|
||||
});
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
const current = editor.state.selection;
|
||||
|
||||
// Preserve an existing multi-cell CellSelection that already covers
|
||||
@@ -86,6 +89,7 @@ export const CellChevron = React.memo(function CellChevron({
|
||||
}, [editor, cellPos]);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.unfreezeHandles();
|
||||
}, [editor]);
|
||||
|
||||
|
||||
+3
@@ -1,6 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
import type { Editor } from "@tiptap/react";
|
||||
import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import { isEditorReady } from "@docmost/editor-ext";
|
||||
import { buildRowOrColumnSelection, Orientation } from "../lib/select-row-column";
|
||||
|
||||
interface Args {
|
||||
@@ -19,6 +20,7 @@ export function useColumnRowMenuLifecycle({
|
||||
tablePos,
|
||||
}: Args) {
|
||||
const onOpen = useCallback(() => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
const selection = buildRowOrColumnSelection(
|
||||
editor.state,
|
||||
tableNode,
|
||||
@@ -33,6 +35,7 @@ export function useColumnRowMenuLifecycle({
|
||||
}, [editor, orientation, index, tableNode, tablePos]);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
editor.commands.unfreezeHandles();
|
||||
}, [editor]);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback } from "react";
|
||||
import type { Editor } from "@tiptap/react";
|
||||
import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import { TableMap } from "@tiptap/pm/tables";
|
||||
import { isEditorReady } from "@docmost/editor-ext";
|
||||
|
||||
type Scope =
|
||||
| { kind: "col"; index: number }
|
||||
@@ -15,6 +16,7 @@ export function useTableClear(
|
||||
scope: Scope,
|
||||
) {
|
||||
return useCallback(() => {
|
||||
if (!isEditorReady(editor)) return;
|
||||
const tr = editor.state.tr;
|
||||
const tableStart = tablePos + 1;
|
||||
const map = TableMap.get(tableNode);
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { useCallback, useMemo } from "react";
|
||||
import type { Editor } from "@tiptap/react";
|
||||
import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import { TableMap } from "@tiptap/pm/tables";
|
||||
import { moveColumn, moveRow } from "@docmost/editor-ext";
|
||||
import { isEditorReady, moveColumn, moveRow } from "@docmost/editor-ext";
|
||||
|
||||
export type MoveDirection = "left" | "right" | "up" | "down";
|
||||
|
||||
@@ -25,7 +25,7 @@ export function useTableMoveRowColumn(
|
||||
const canMove = target >= 0 && target <= maxIndex;
|
||||
|
||||
const handleMove = useCallback(() => {
|
||||
if (!canMove) return;
|
||||
if (!canMove || !isEditorReady(editor)) return;
|
||||
const tr = editor.state.tr;
|
||||
const moved =
|
||||
orientation === "col"
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Node as ProseMirrorNode } from "@tiptap/pm/model";
|
||||
import {
|
||||
convertArrayOfRowsToTableNode,
|
||||
convertTableNodeToArrayOfRows,
|
||||
isEditorReady,
|
||||
transpose,
|
||||
} from "@docmost/editor-ext";
|
||||
import {
|
||||
@@ -63,7 +64,7 @@ export function useTableSort({
|
||||
}, [tableNode, orientation, index]);
|
||||
|
||||
const handleSort = useCallback(() => {
|
||||
if (!canSort) return;
|
||||
if (!canSort || !isEditorReady(editor)) return;
|
||||
|
||||
const rows = convertTableNodeToArrayOfRows(tableNode);
|
||||
const axes = orientation === "col" ? rows : transpose(rows);
|
||||
|
||||
@@ -152,7 +152,11 @@ export function TitleEditor({
|
||||
const debounceUpdate = useDebouncedCallback(saveTitle, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (titleEditor && title !== titleEditor.getText()) {
|
||||
if (
|
||||
titleEditor &&
|
||||
!titleEditor.isDestroyed &&
|
||||
title !== titleEditor.getText()
|
||||
) {
|
||||
titleEditor.commands.setContent(title);
|
||||
}
|
||||
}, [pageId, title, titleEditor]);
|
||||
|
||||
@@ -34,7 +34,7 @@ export function HistoryEditor({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor || !content) return;
|
||||
if (!editor || editor.isDestroyed || !content) return;
|
||||
|
||||
let decorationSet = DecorationSet.empty;
|
||||
let addedCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user