chore: Upgrade all of prosemirror (#5366)

Co-authored-by: Apoorv Mishra <apoorvmishra101092@gmail.com>
This commit is contained in:
Tom Moor
2023-05-24 22:24:05 -04:00
committed by GitHub
parent e340e568e2
commit d5341a486c
77 changed files with 875 additions and 675 deletions

View File

@@ -1,8 +1,6 @@
import { findParentNode } from "prosemirror-utils";
import React from "react";
import useDictionary from "~/hooks/useDictionary";
import getMenuItems from "../menus/block";
import { useEditor } from "./EditorContext";
import SuggestionsMenu, {
Props as SuggestionsMenuProps,
} from "./SuggestionsMenu";
@@ -10,28 +8,18 @@ import SuggestionsMenuItem from "./SuggestionsMenuItem";
type Props = Omit<
SuggestionsMenuProps,
"renderMenuItem" | "items" | "onClearSearch"
"renderMenuItem" | "items" | "trigger"
> &
Required<Pick<SuggestionsMenuProps, "onLinkToolbarOpen" | "embeds">>;
function BlockMenu(props: Props) {
const { view } = useEditor();
const dictionary = useDictionary();
const clearSearch = React.useCallback(() => {
const { state, dispatch } = view;
const parent = findParentNode((node) => !!node)(state.selection);
if (parent) {
dispatch(state.tr.insertText("", parent.pos, state.selection.to));
}
}, [view]);
return (
<SuggestionsMenu
{...props}
filterable
onClearSearch={clearSearch}
trigger="/"
renderMenuItem={(item, _index, options) => (
<SuggestionsMenuItem
onClick={options.onClick}

View File

@@ -16,9 +16,7 @@ export default class ComponentView {
node: ProsemirrorNode;
view: EditorView;
getPos: () => number;
decorations: Decoration<{
[key: string]: any;
}>[];
decorations: Decoration[];
isSelected = false;
dom: HTMLElement | null;
@@ -39,9 +37,7 @@ export default class ComponentView {
node: ProsemirrorNode;
view: EditorView;
getPos: () => number;
decorations: Decoration<{
[key: string]: any;
}>[];
decorations: Decoration[];
}
) {
this.component = component;

View File

@@ -1,7 +1,6 @@
import FuzzySearch from "fuzzy-search";
import gemojies from "gemoji";
import React from "react";
import { useEditor } from "./EditorContext";
import EmojiMenuItem from "./EmojiMenuItem";
import SuggestionsMenu, {
Props as SuggestionsMenuProps,
@@ -26,12 +25,11 @@ const searcher = new FuzzySearch<{
type Props = Omit<
SuggestionsMenuProps<Emoji>,
"renderMenuItem" | "items" | "onLinkToolbarOpen" | "embeds" | "onClearSearch"
"renderMenuItem" | "items" | "onLinkToolbarOpen" | "embeds" | "trigger"
>;
const EmojiMenu = (props: Props) => {
const { search = "" } = props;
const { view } = useEditor();
const items = React.useMemo(() => {
const n = search.toLowerCase();
@@ -50,24 +48,11 @@ const EmojiMenu = (props: Props) => {
return result.slice(0, 10);
}, [search]);
const clearSearch = React.useCallback(() => {
const { state, dispatch } = view;
// clear search input
dispatch(
state.tr.insertText(
"",
state.selection.$from.pos - (props.search ?? "").length - 1,
state.selection.to
)
);
}, [view, props.search]);
return (
<SuggestionsMenu
{...props}
trigger=":"
filterable={false}
onClearSearch={clearSearch}
renderMenuItem={(item, _index, options) => (
<EmojiMenuItem
onClick={options.onClick}

View File

@@ -1,5 +1,5 @@
import { NodeSelection } from "prosemirror-state";
import { CellSelection } from "prosemirror-tables";
import { CellSelection, selectedRect } from "prosemirror-tables";
import * as React from "react";
import styled from "styled-components";
import { depths, s } from "@shared/styles";
@@ -87,23 +87,37 @@ function usePosition({
// tables are an oddity, and need their own positioning logic
const isColSelection =
selection instanceof CellSelection &&
selection.isColSelection &&
selection.isColSelection();
selection instanceof CellSelection && selection.isColSelection();
const isRowSelection =
selection instanceof CellSelection &&
selection.isRowSelection &&
selection.isRowSelection();
selection instanceof CellSelection && selection.isRowSelection();
if (isColSelection) {
const { node: element } = view.domAtPos(selection.from);
const { width } = (element as HTMLElement).getBoundingClientRect();
selectionBounds.top -= 20;
selectionBounds.right = selectionBounds.left + width;
}
if (isRowSelection) {
selectionBounds.right = selectionBounds.left = selectionBounds.left - 18;
if (isColSelection && isRowSelection) {
const rect = selectedRect(view.state);
const table = view.domAtPos(rect.tableStart);
const bounds = (table.node as HTMLElement).getBoundingClientRect();
selectionBounds.top = bounds.top - 16;
selectionBounds.left = bounds.left - 10;
selectionBounds.right = bounds.left - 10;
} else if (isColSelection) {
const rect = selectedRect(view.state);
const table = view.domAtPos(rect.tableStart);
const element = (table.node as HTMLElement).querySelector(
`tr > *:nth-child(${rect.left + 1})`
);
const bounds = (element as HTMLElement).getBoundingClientRect();
selectionBounds.top = bounds.top - 16;
selectionBounds.left = bounds.left;
selectionBounds.right = bounds.right;
} else if (isRowSelection) {
const rect = selectedRect(view.state);
const table = view.domAtPos(rect.tableStart);
const element = (table.node as HTMLElement).querySelector(
`tr:nth-child(${rect.top + 1}) > *`
);
const bounds = (element as HTMLElement).getBoundingClientRect();
selectionBounds.top = bounds.top;
selectionBounds.left = bounds.left - 10;
selectionBounds.right = bounds.left - 10;
}
const isImageSelection =

View File

@@ -6,7 +6,7 @@ import {
OpenIcon,
} from "outline-icons";
import { Mark } from "prosemirror-model";
import { setTextSelection } from "prosemirror-utils";
import { Selection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import * as React from "react";
import styled from "styled-components";
@@ -285,7 +285,10 @@ class LinkEditor extends React.Component<Props, State> {
moveSelectionToEnd = () => {
const { to, view } = this.props;
const { state, dispatch } = view;
dispatch(setTextSelection(to)(state.tr));
const nextSelection = Selection.findFrom(state.tr.doc.resolve(to), 1, true);
if (nextSelection) {
dispatch(state.tr.setSelection(nextSelection));
}
view.focus();
};

View File

@@ -8,10 +8,10 @@ import { MentionType } from "@shared/types";
import parseDocumentSlug from "@shared/utils/parseDocumentSlug";
import User from "~/models/User";
import Avatar from "~/components/Avatar";
import { AvatarSize } from "~/components/Avatar/Avatar";
import Flex from "~/components/Flex";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import { useEditor } from "./EditorContext";
import MentionMenuItem from "./MentionMenuItem";
import SuggestionsMenu, {
Props as SuggestionsMenuProps,
@@ -32,7 +32,7 @@ interface MentionItem extends MenuItem {
type Props = Omit<
SuggestionsMenuProps<MentionItem>,
"renderMenuItem" | "items" | "onLinkToolbarOpen" | "embeds" | "onClearSearch"
"renderMenuItem" | "items" | "onLinkToolbarOpen" | "embeds" | "trigger"
>;
function MentionMenu({ search, isActive, ...rest }: Props) {
@@ -42,7 +42,6 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
const { users, auth } = useStores();
const location = useLocation();
const documentId = parseDocumentSlug(location.pathname);
const { view } = useEditor();
const { data, loading, request } = useRequest(
React.useCallback(
() =>
@@ -80,19 +79,6 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
}
}, [auth.user?.id, loading, data]);
const clearSearch = () => {
const { state, dispatch } = view;
// clear search input
dispatch(
state.tr.insertText(
"",
state.selection.$from.pos - (search ?? "").length - 1,
state.selection.to
)
);
};
// Prevent showing the menu until we have data otherwise it will be positioned
// incorrectly due to the height being unknown.
if (!loaded) {
@@ -104,7 +90,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
{...rest}
isActive={isActive}
filterable={false}
onClearSearch={clearSearch}
trigger="@"
search={search}
renderMenuItem={(item, _index, options) => (
<MentionMenuItem
@@ -122,7 +108,7 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
model={item.user}
showBorder={false}
alt={t("Profile picture")}
size={16}
size={AvatarSize.Small}
/>
</Flex>
}

View File

@@ -1,14 +1,12 @@
import { some } from "lodash";
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { CellSelection } from "prosemirror-tables";
import * as React from "react";
import createAndInsertLink from "@shared/editor/commands/createAndInsertLink";
import filterExcessSeparators from "@shared/editor/lib/filterExcessSeparators";
import getColumnIndex from "@shared/editor/queries/getColumnIndex";
import getMarkRange from "@shared/editor/queries/getMarkRange";
import getRowIndex from "@shared/editor/queries/getRowIndex";
import isMarkActive from "@shared/editor/queries/isMarkActive";
import isNodeActive from "@shared/editor/queries/isNodeActive";
import { getColumnIndex, getRowIndex } from "@shared/editor/queries/table";
import { MenuItem } from "@shared/editor/types";
import { creatingUrlPrefix } from "@shared/utils/urls";
import useBoolean from "~/hooks/useBoolean";
@@ -197,8 +195,8 @@ export default function SelectionToolbar(props: Props) {
return null;
}
const colIndex = getColumnIndex(state.selection as unknown as CellSelection);
const rowIndex = getRowIndex(state.selection as unknown as CellSelection);
const colIndex = getColumnIndex(state);
const rowIndex = getRowIndex(state);
const isTableSelection = colIndex !== undefined && rowIndex !== undefined;
const link = isMarkActive(state.schema.marks.link)(state);
const range = getMarkRange(selection.$from, state.schema.marks.link);

View File

@@ -1,6 +1,5 @@
import commandScore from "command-score";
import { capitalize } from "lodash";
import { findParentNode } from "prosemirror-utils";
import * as React from "react";
import { Trans } from "react-i18next";
import { VisuallyHidden } from "reakit/VisuallyHidden";
@@ -8,6 +7,7 @@ import styled from "styled-components";
import insertFiles from "@shared/editor/commands/insertFiles";
import { EmbedDescriptor } from "@shared/editor/embeds";
import filterExcessSeparators from "@shared/editor/lib/filterExcessSeparators";
import { findParentNode } from "@shared/editor/queries/findParentNode";
import { MenuItem } from "@shared/editor/types";
import { depths, s } from "@shared/styles";
import { getEventFiles } from "@shared/utils/files";
@@ -56,12 +56,12 @@ export type Props<T extends MenuItem = MenuItem> = {
rtl: boolean;
isActive: boolean;
search: string;
trigger: string;
uploadFile?: (file: File) => Promise<string>;
onFileUploadStart?: () => void;
onFileUploadStop?: () => void;
onLinkToolbarOpen?: () => void;
onClose: (insertNewLine?: boolean) => void;
onClearSearch: () => void;
embeds?: EmbedDescriptor[];
renderMenuItem: (
item: T,
@@ -163,6 +163,30 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
[view]
);
const handleClearSearch = React.useCallback(() => {
const { state, dispatch } = view;
const poss = state.doc.cut(
state.selection.from - (props.search ?? "").length - 1,
state.selection.from
);
const trimTrigger = poss.textContent.startsWith(props.trigger);
if (!props.search && !trimTrigger) {
return;
}
// clear search input
dispatch(
state.tr.insertText(
"",
state.selection.from -
(props.search ?? "").length -
(trimTrigger ? 1 : 0),
state.selection.to
)
);
}, [props.search, props.trigger, view]);
React.useEffect(() => {
if (!props.isActive) {
return;
@@ -185,7 +209,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
const insertNode = React.useCallback(
(item: MenuItem | EmbedDescriptor) => {
props.onClearSearch();
handleClearSearch();
const command = item.name ? commands[item.name] : undefined;
@@ -201,7 +225,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
props.onClose();
},
[commands, props, view]
[commands, handleClearSearch, props, view]
);
const handleClickItem = React.useCallback(
@@ -216,7 +240,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
case "embed":
return triggerLinkInput(item);
case "link": {
props.onClearSearch();
handleClearSearch();
props.onClose();
props.onLinkToolbarOpen?.();
return;
@@ -225,7 +249,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
insertNode(item);
}
},
[insertNode, props]
[insertNode, handleClearSearch, props]
);
const close = React.useCallback(() => {
@@ -313,7 +337,7 @@ function SuggestionsMenu<T extends MenuItem>(props: Props<T>) {
const files = getEventFiles(event);
const parent = findParentNode((node) => !!node)(view.state.selection);
props.onClearSearch();
handleClearSearch();
if (!uploadFile) {
throw new Error("uploadFile prop is required to replace files");