Compare commits

..

1 Commits

Author SHA1 Message Date
Tom Moor 51d39d915d chore: Missing react key in SuggestionMenu 2025-03-30 10:30:42 -04:00
9 changed files with 45 additions and 87 deletions
+1 -5
View File
@@ -2,11 +2,6 @@ import React from "react";
import styled from "styled-components";
import { fadeIn } from "~/styles/animations";
/**
* Fade in animation for a component.
*
* @param timing - The duration of the fade in animation, default is 250ms.
*/
const Fade = styled.span<{ timing?: number | string }>`
animation: ${fadeIn} ${(props) => props.timing || "250ms"} ease-in-out;
`;
@@ -22,6 +17,7 @@ type Props = {
*/
export const ConditionalFade = ({ animate, children }: Props) => {
const [isAnimated] = React.useState(animate);
return isAnimated ? <Fade>{children}</Fade> : <>{children}</>;
};
+1 -1
View File
@@ -88,7 +88,7 @@ export default class PasteHandler extends Extension {
// If the users selection is currently in a code block then paste
// as plain text, ignore all formatting and HTML content.
if (isInCode(state, { inclusive: true })) {
if (isInCode(state)) {
event.preventDefault();
view.dispatch(state.tr.insertText(text));
return true;
@@ -16,7 +16,6 @@ import { useDocumentContext } from "~/components/DocumentContext";
import Facepile from "~/components/Facepile";
import Fade from "~/components/Fade";
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
import useBoolean from "~/hooks/useBoolean";
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import usePersistedState from "~/hooks/usePersistedState";
@@ -64,7 +63,7 @@ function CommentThread({
const history = useHistory();
const location = useLocation();
const sidebarContext = useLocationSidebarContext();
const [autoFocus, setAutoFocusOn, setAutoFocusOff] = useBoolean(thread.isNew);
const [autoFocus, setAutoFocus] = React.useState(thread.isNew);
const can = usePolicy(document);
@@ -157,9 +156,9 @@ function CommentThread({
React.useEffect(() => {
if (!focused && autoFocus) {
setAutoFocusOff();
setAutoFocus(false);
}
}, [focused, autoFocus, setAutoFocusOff]);
}, [focused, autoFocus]);
React.useEffect(() => {
if (focused) {
@@ -274,7 +273,7 @@ function CommentThread({
)}
</ResizingHeightContainer>
{!focused && !recessed && !draft && canReply && (
<Reply onClick={setAutoFocusOn}>{t("Reply")}</Reply>
<Reply onClick={() => setAutoFocus(true)}>{t("Reply")}</Reply>
)}
</Thread>
);
-4
View File
@@ -313,10 +313,6 @@ width: 100%;
background: ${props.theme.mentionHoverBackground};
}
&[data-type="user"] {
gap: 0;
}
&.mention-user::before {
content: "@";
}
+1 -1
View File
@@ -21,7 +21,7 @@ export default class Code extends Mark {
get schema(): MarkSpec {
return {
excludes: "mention placeholder highlight",
excludes: "mention placeholder highlight em strong",
parseDOM: [{ tag: "code", preserveWhitespace: true }],
toDOM: () => ["code", { class: "inline", spellCheck: "false" }],
};
+3 -17
View File
@@ -7,8 +7,6 @@ type Options = {
onlyBlock?: boolean;
/** Only check if the selection is inside a code mark. */
onlyMark?: boolean;
/** If true then code must contain entire selection */
inclusive?: boolean;
};
/**
@@ -22,29 +20,17 @@ export function isInCode(state: EditorState, options?: Options): boolean {
const { nodes, marks } = state.schema;
if (!options?.onlyMark) {
if (
nodes.code_block &&
isNodeActive(nodes.code_block, undefined, {
inclusive: options?.inclusive,
})(state)
) {
if (nodes.code_block && isNodeActive(nodes.code_block)(state)) {
return true;
}
if (
nodes.code_fence &&
isNodeActive(nodes.code_fence, undefined, {
inclusive: options?.inclusive,
})(state)
) {
if (nodes.code_fence && isNodeActive(nodes.code_fence)(state)) {
return true;
}
}
if (!options?.onlyBlock) {
if (marks.code_inline) {
return isMarkActive(marks.code_inline, undefined, {
inclusive: options?.inclusive,
})(state);
return isMarkActive(marks.code_inline)(state);
}
}
+1 -4
View File
@@ -6,8 +6,6 @@ import { getMarksBetween } from "./getMarksBetween";
type Options = {
/** Only return match if the range and attrs is exact */
exact?: boolean;
/** If true then mark must contain entire selection */
inclusive?: boolean;
};
/**
@@ -42,8 +40,7 @@ export const isMarkActive =
Object.keys(attrs).every(
(key) => mark.attrs[key] === attrs[key]
)) &&
(!options?.exact || (start === from && end === to)) &&
(!options?.inclusive || (start <= from && end >= to))
(!options?.exact || (start === from && end === to))
);
}
+10 -34
View File
@@ -3,55 +3,31 @@ import { EditorState } from "prosemirror-state";
import { Primitive } from "utility-types";
import { findParentNode } from "./findParentNode";
type Options = {
/** Only return match if the range and attrs is exact */
exact?: boolean;
/** If true then node must contain entire selection */
inclusive?: boolean;
};
/**
* Checks if a node is active in the current selection or not.
*
* @param type The node type to check.
* @param attrs The attributes to check.
* @param options The options to use.
* @returns A function that checks if a node is active in the current selection or not.
*/
export const isNodeActive =
(type: NodeType, attrs?: Record<string, Primitive>, options?: Options) =>
(state: EditorState): boolean => {
(type: NodeType, attrs: Record<string, Primitive> = {}) =>
(state: EditorState) => {
if (!type) {
return false;
}
const { from, to } = state.selection;
const nodeWithPos = findParentNode(
(node) =>
node.type === type &&
(!attrs ||
Object.keys(attrs).every((key) => node.attrs[key] === attrs[key]))
)(state.selection);
const nodeAfter = state.selection.$from.nodeAfter;
let node = nodeAfter?.type === type ? nodeAfter : undefined;
if (!nodeWithPos) {
return false;
if (!node) {
const parent = findParentNode((n) => n.type === type)(state.selection);
node = parent?.node;
}
if (options?.inclusive) {
// Check if the node's position contains the entire selection
return (
nodeWithPos.pos <= from &&
nodeWithPos.pos + nodeWithPos.node.nodeSize >= to
);
if (!Object.keys(attrs).length || !node) {
return !!node;
}
if (options?.exact) {
// Check if node's range exactly matches selection
return (
nodeWithPos.pos === from &&
nodeWithPos.pos + nodeWithPos.node.nodeSize === to
);
}
return true;
return node.hasMarkup(type, { ...node.attrs, ...attrs });
};
+24 -16
View File
@@ -1,5 +1,16 @@
import { useState, useLayoutEffect } from "react";
const defaultRect = {
top: 0,
left: 0,
bottom: 0,
right: 0,
x: 0,
y: 0,
width: 0,
height: 0,
};
/**
* A hook that returns the size of an element or ref.
*
@@ -8,11 +19,19 @@ import { useState, useLayoutEffect } from "react";
*/
export function useComponentSize(
input: HTMLElement | null | React.RefObject<HTMLElement | null>
) {
): DOMRect | typeof defaultRect {
const element = input instanceof HTMLElement ? input : input?.current;
const [size, setSize] = useState<DOMRect | undefined>(
() => element?.getBoundingClientRect() || new DOMRect()
);
const [size, setSize] = useState(() => element?.getBoundingClientRect());
useLayoutEffect(() => {
const sizeObserver = new ResizeObserver(() => {
element?.dispatchEvent(new CustomEvent("resize"));
});
if (element) {
sizeObserver.observe(element);
}
return () => sizeObserver.disconnect();
}, [element]);
useLayoutEffect(() => {
const handleResize = () => {
@@ -36,7 +55,6 @@ export function useComponentSize(
window.addEventListener("click", handleResize);
window.addEventListener("resize", handleResize);
element?.addEventListener("resize", handleResize);
handleResize();
return () => {
window.removeEventListener("click", handleResize);
@@ -45,15 +63,5 @@ export function useComponentSize(
};
});
useLayoutEffect(() => {
const sizeObserver = new ResizeObserver(() => {
element?.dispatchEvent(new CustomEvent("resize"));
});
if (element) {
sizeObserver.observe(element);
}
return () => sizeObserver.disconnect();
}, [element]);
return size ?? new DOMRect();
return size ?? defaultRect;
}