From f2e21ee179b9bafd026f0e2bdcfaa38eea6fbb2c Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 2 Jul 2024 17:23:46 -0400 Subject: [PATCH] fix a lot of little text-selection and toolbar issues Namely switch to iterating over every position in a slection and checking if there is a mark, and then updating the mark if neccessary instead of toggling it. Also add a specific case for handling links since they aren't inclusive of the last position --- components/TextBlock/index.tsx | 15 ++- components/Toolbar/HighlightButton.tsx | 18 ++- components/Toolbar/LinkButton.tsx | 17 ++- components/Toolbar/TextDecorationButton.tsx | 13 +- src/utils/prosemirror/rangeHasMark.ts | 30 +++++ src/utils/prosemirror/setMark.ts | 138 ++++++++++++++++++++ 6 files changed, 210 insertions(+), 21 deletions(-) create mode 100644 src/utils/prosemirror/rangeHasMark.ts create mode 100644 src/utils/prosemirror/setMark.ts diff --git a/components/TextBlock/index.tsx b/components/TextBlock/index.tsx index 695fd041..f1098d51 100644 --- a/components/TextBlock/index.tsx +++ b/components/TextBlock/index.tsx @@ -17,7 +17,7 @@ import { } from "src/replicache"; import { isVisible } from "src/utils/isVisible"; -import { EditorState } from "prosemirror-state"; +import { EditorState, TextSelection } from "prosemirror-state"; import { ySyncPlugin } from "y-prosemirror"; import { Replicache } from "replicache"; import { generateKeyBetween } from "fractional-indexing"; @@ -35,6 +35,8 @@ import { BlockOptions } from "components/BlockOptions"; import { setEditorState, useEditorStates } from "src/state/useEditorState"; import { isIOS } from "@react-aria/utils"; import { useIsMobile } from "src/hooks/isMobile"; +import { setMark } from "src/utils/prosemirror/setMark"; +import { rangeHasMark } from "src/utils/prosemirror/rangeHasMark"; export function TextBlock(props: BlockProps & { className: string }) { let initialized = useInitialPageLoad(); @@ -429,7 +431,16 @@ export function BaseTextBlock(props: BlockProps & { className: string }) { function CommandHandler(props: { entityID: string }) { let cb = useEditorEventCallback( (view, args: { mark: MarkType; attrs?: any }) => { - toggleMark(args.mark, args.attrs)(view.state, view.dispatch); + let { to, from, $cursor, $to, $from } = view.state + .selection as TextSelection; + let mark = rangeHasMark(view.state, args.mark, from, to); + if ( + mark && + (!args.attrs || + JSON.stringify(args.attrs) === JSON.stringify(mark.attrs)) + ) { + toggleMark(args.mark, args.attrs)(view.state, view.dispatch); + } else setMark(args.mark, args.attrs)(view.state, view.dispatch); }, ); useAppEventListener(props.entityID, "toggleMark", cb, []); diff --git a/components/Toolbar/HighlightButton.tsx b/components/Toolbar/HighlightButton.tsx index f54b6609..38568219 100644 --- a/components/Toolbar/HighlightButton.tsx +++ b/components/Toolbar/HighlightButton.tsx @@ -28,6 +28,7 @@ import { useEntity, useReplicache } from "src/replicache"; import { useMemo, useState } from "react"; import { useColorAttribute } from "components/ThemeManager/useColorAttribute"; import { useParams } from "next/navigation"; +import { rangeHasMark } from "src/utils/prosemirror/rangeHasMark"; export const HighlightColorButton = (props: { color: "1" | "2" | "3"; @@ -40,16 +41,20 @@ export const HighlightColorButton = (props: { let hasMark: boolean = false; if (focusedEditor) { let { to, from, $cursor } = focusedEditor.editor.selection as TextSelection; + + let mark = rangeHasMark( + focusedEditor.editor, + schema.marks.highlight, + from, + to, + ); if ($cursor) hasMark = !!schema.marks.highlight.isInSet( focusedEditor.editor.storedMarks || $cursor.marks(), ); - else - hasMark = focusedEditor.editor.doc.rangeHasMark( - from, - to, - schema.marks.highlight, - ); + else { + hasMark = !!mark; + } } return (