@liveblocks/react-ui 2.0.0 → 2.0.3-test1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/primitives/Composer/index.js +17 -13
- package/dist/primitives/Composer/index.js.map +1 -1
- package/dist/primitives/Composer/index.mjs +17 -13
- package/dist/primitives/Composer/index.mjs.map +1 -1
- package/dist/slate/plugins/auto-links.js +43 -1
- package/dist/slate/plugins/auto-links.js.map +1 -1
- package/dist/slate/plugins/auto-links.mjs +43 -1
- package/dist/slate/plugins/auto-links.mjs.map +1 -1
- package/dist/slate/utils/is-empty.js +17 -1
- package/dist/slate/utils/is-empty.js.map +1 -1
- package/dist/slate/utils/is-empty.mjs +17 -1
- package/dist/slate/utils/is-empty.mjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -568,13 +568,9 @@ const ComposerEditor = React.forwardRef(
|
|
|
568
568
|
selectedMentionSuggestionUserId
|
|
569
569
|
]
|
|
570
570
|
);
|
|
571
|
-
React.useImperativeHandle(
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return slateReact.ReactEditor.toDOMNode(editor, editor);
|
|
575
|
-
},
|
|
576
|
-
[editor]
|
|
577
|
-
);
|
|
571
|
+
React.useImperativeHandle(forwardedRef, () => {
|
|
572
|
+
return slateReact.ReactEditor.toDOMNode(editor, editor);
|
|
573
|
+
}, [editor]);
|
|
578
574
|
React.useEffect(() => {
|
|
579
575
|
if (autoFocus) {
|
|
580
576
|
focus();
|
|
@@ -634,9 +630,11 @@ const ComposerForm = React.forwardRef(
|
|
|
634
630
|
[editor]
|
|
635
631
|
);
|
|
636
632
|
const submit = React.useCallback(() => {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
633
|
+
requestAnimationFrame(() => {
|
|
634
|
+
if (ref.current) {
|
|
635
|
+
requestSubmit.requestSubmit(ref.current);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
640
638
|
}, []);
|
|
641
639
|
const clear = React.useCallback(() => {
|
|
642
640
|
slate.Transforms.delete(editor, {
|
|
@@ -684,15 +682,21 @@ const ComposerForm = React.forwardRef(
|
|
|
684
682
|
);
|
|
685
683
|
const handleSubmit = React.useCallback(
|
|
686
684
|
(event) => {
|
|
685
|
+
const isEmpty2 = isEmpty.isEmpty(editor, editor.children);
|
|
686
|
+
if (isEmpty2) {
|
|
687
|
+
event.preventDefault();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
687
690
|
onSubmit?.(event);
|
|
688
|
-
if (event.isDefaultPrevented()) {
|
|
691
|
+
if (!onComposerSubmit || event.isDefaultPrevented()) {
|
|
692
|
+
event.preventDefault();
|
|
689
693
|
return;
|
|
690
694
|
}
|
|
691
695
|
const body = utils.composerBodyToCommentBody(
|
|
692
696
|
editor.children
|
|
693
697
|
);
|
|
694
698
|
const comment = { body };
|
|
695
|
-
const promise = onComposerSubmit
|
|
699
|
+
const promise = onComposerSubmit(comment, event);
|
|
696
700
|
event.preventDefault();
|
|
697
701
|
if (promise) {
|
|
698
702
|
promise.then(onSubmitEnd);
|
|
@@ -700,7 +704,7 @@ const ComposerForm = React.forwardRef(
|
|
|
700
704
|
onSubmitEnd();
|
|
701
705
|
}
|
|
702
706
|
},
|
|
703
|
-
[editor
|
|
707
|
+
[editor, onComposerSubmit, onSubmit, onSubmitEnd]
|
|
704
708
|
);
|
|
705
709
|
return /* @__PURE__ */ React.createElement(contexts.ComposerEditorContext.Provider, {
|
|
706
710
|
value: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/primitives/Composer/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n DetectOverflowOptions,\n UseFloatingOptions,\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { CommentBody } from \"@liveblocks/core\";\nimport { useSelf } from \"@liveblocks/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport type {\n AriaAttributes,\n FocusEvent,\n FormEvent,\n KeyboardEvent,\n PointerEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Descendant as SlateDescendant,\n Element as SlateElement,\n} from \"slate\";\nimport {\n createEditor,\n Editor as SlateEditor,\n insertText as insertSlateText,\n Transforms as SlateTransforms,\n} from \"slate\";\nimport { withHistory } from \"slate-history\";\nimport type {\n RenderElementProps,\n RenderElementSpecificProps,\n RenderLeafProps,\n RenderPlaceholderProps,\n} from \"slate-react\";\nimport {\n Editable,\n ReactEditor,\n Slate,\n useSelected,\n useSlateStatic,\n withReact,\n} from \"slate-react\";\n\nimport { useLiveblocksUIConfig } from \"../../config\";\nimport { FLOATING_ELEMENT_COLLISION_PADDING } from \"../../constants\";\nimport { useMentionSuggestions } from \"../../shared\";\nimport { withAutoFormatting } from \"../../slate/plugins/auto-formatting\";\nimport { withAutoLinks } from \"../../slate/plugins/auto-links\";\nimport { withEmptyClearFormatting } from \"../../slate/plugins/empty-clear-formatting\";\nimport type { MentionDraft } from \"../../slate/plugins/mentions\";\nimport {\n getMentionDraftAtSelection,\n insertMention,\n insertMentionCharacter,\n MENTION_CHARACTER,\n withMentions,\n} from \"../../slate/plugins/mentions\";\nimport { getDOMRange } from \"../../slate/utils/get-dom-range\";\nimport { isEmpty as isEditorEmpty } from \"../../slate/utils/is-empty\";\nimport { leaveMarkEdge, toggleMark } from \"../../slate/utils/marks\";\nimport type {\n ComposerBody as ComposerBodyData,\n ComposerBodyAutoLink,\n ComposerBodyMention,\n} from \"../../types\";\nimport { isKey } from \"../../utils/is-key\";\nimport { Persist, useAnimationPersist, usePersist } from \"../../utils/Persist\";\nimport { Portal } from \"../../utils/Portal\";\nimport { requestSubmit } from \"../../utils/request-submit\";\nimport { useId } from \"../../utils/use-id\";\nimport { useIndex } from \"../../utils/use-index\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useRefs } from \"../../utils/use-refs\";\nimport { toAbsoluteUrl } from \"../Comment/utils\";\nimport {\n ComposerContext,\n ComposerEditorContext,\n ComposerSuggestionsContext,\n useComposer,\n useComposerEditorContext,\n useComposerSuggestionsContext,\n} from \"./contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorElementProps,\n ComposerEditorLinkWrapperProps,\n ComposerEditorMentionSuggestionsWrapperProps,\n ComposerEditorMentionWrapperProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerLinkProps,\n ComposerMentionProps,\n ComposerSubmitProps,\n ComposerSuggestionsListItemProps,\n ComposerSuggestionsListProps,\n ComposerSuggestionsProps,\n SuggestionsPosition,\n} from \"./types\";\nimport {\n commentBodyToComposerBody,\n composerBodyToCommentBody,\n getPlacementFromPosition,\n getSideAndAlignFromPlacement,\n} from \"./utils\";\n\nconst MENTION_SUGGESTIONS_POSITION: SuggestionsPosition = \"top\";\n\nconst COMPOSER_MENTION_NAME = \"ComposerMention\";\nconst COMPOSER_LINK_NAME = \"ComposerLink\";\nconst COMPOSER_SUGGESTIONS_NAME = \"ComposerSuggestions\";\nconst COMPOSER_SUGGESTIONS_LIST_NAME = \"ComposerSuggestionsList\";\nconst COMPOSER_SUGGESTIONS_LIST_ITEM_NAME = \"ComposerSuggestionsListItem\";\nconst COMPOSER_SUBMIT_NAME = \"ComposerSubmit\";\nconst COMPOSER_EDITOR_NAME = \"ComposerEditor\";\nconst COMPOSER_FORM_NAME = \"ComposerForm\";\n\nconst emptyCommentBody: CommentBody = {\n version: 1,\n content: [{ type: \"paragraph\", children: [{ text: \"\" }] }],\n};\n\nfunction createComposerEditor() {\n return withAutoLinks(\n withMentions(\n withEmptyClearFormatting(\n withAutoFormatting(withHistory(withReact(createEditor())))\n )\n )\n );\n}\n\nfunction ComposerEditorMentionWrapper({\n Mention,\n attributes,\n children,\n element,\n}: ComposerEditorMentionWrapperProps) {\n const isSelected = useSelected();\n\n return (\n <span {...attributes}>\n {element.id ? (\n <Mention userId={element.id} isSelected={isSelected} />\n ) : null}\n {children}\n </span>\n );\n}\n\nfunction ComposerEditorLinkWrapper({\n Link,\n attributes,\n element,\n children,\n}: ComposerEditorLinkWrapperProps) {\n const href = useMemo(\n () => toAbsoluteUrl(element.url) ?? element.url,\n [element.url]\n );\n\n return (\n <span {...attributes}>\n <Link href={href}>{children}</Link>\n </span>\n );\n}\n\nfunction ComposerEditorMentionSuggestionsWrapper({\n id,\n itemId,\n userIds,\n selectedUserId,\n setSelectedUserId,\n mentionDraft,\n onItemSelect,\n position = MENTION_SUGGESTIONS_POSITION,\n dir,\n MentionSuggestions,\n}: ComposerEditorMentionSuggestionsWrapperProps) {\n const editor = useSlateStatic();\n const { isFocused } = useComposer();\n const [content, setContent] = useState<HTMLDivElement | null>(null);\n const [contentZIndex, setContentZIndex] = useState<string>();\n const contentRef = useCallback(setContent, [setContent]);\n const { portalContainer } = useLiveblocksUIConfig();\n const floatingOptions: UseFloatingOptions = useMemo(() => {\n const detectOverflowOptions: DetectOverflowOptions = {\n padding: FLOATING_ELEMENT_COLLISION_PADDING,\n };\n\n return {\n strategy: \"fixed\",\n placement: getPlacementFromPosition(position, dir),\n middleware: [\n flip({ ...detectOverflowOptions, crossAxis: false }),\n hide(detectOverflowOptions),\n shift({\n ...detectOverflowOptions,\n limiter: limitShift(),\n }),\n size({\n ...detectOverflowOptions,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: autoUpdate,\n };\n }, [position, dir]);\n const {\n refs: { setReference, setFloating },\n strategy,\n isPositioned,\n placement,\n x,\n y,\n } = useFloating(floatingOptions);\n\n // Copy `z-index` from content to wrapper.\n // Inspired by https://github.com/radix-ui/primitives/blob/main/packages/react/popper/src/Popper.tsx\n useLayoutEffect(() => {\n if (content) {\n setContentZIndex(window.getComputedStyle(content).zIndex);\n }\n }, [content]);\n\n useLayoutEffect(() => {\n if (!mentionDraft) {\n return;\n }\n\n const domRange = getDOMRange(editor, mentionDraft.range);\n\n if (domRange) {\n setReference({\n getBoundingClientRect: () => domRange.getBoundingClientRect(),\n getClientRects: () => domRange.getClientRects(),\n });\n }\n }, [setReference, editor, mentionDraft]);\n\n return (\n <Persist>\n {mentionDraft?.range && isFocused && userIds ? (\n <ComposerSuggestionsContext.Provider\n value={{\n id,\n itemId,\n selectedValue: selectedUserId,\n setSelectedValue: setSelectedUserId,\n onItemSelect,\n placement,\n dir,\n ref: contentRef,\n }}\n >\n <Portal\n ref={setFloating}\n container={portalContainer}\n style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: isPositioned\n ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`\n : \"translate3d(0, -200%, 0)\",\n minWidth: \"max-content\",\n zIndex: contentZIndex,\n }}\n >\n <MentionSuggestions\n userIds={userIds}\n selectedUserId={selectedUserId}\n />\n </Portal>\n </ComposerSuggestionsContext.Provider>\n ) : null}\n </Persist>\n );\n}\n\nfunction ComposerEditorElement({\n Mention,\n Link,\n ...props\n}: ComposerEditorElementProps) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { attributes, children, element } = props;\n\n switch (element.type) {\n case \"mention\":\n return (\n <ComposerEditorMentionWrapper\n Mention={Mention}\n {...(props as RenderElementSpecificProps<ComposerBodyMention>)}\n />\n );\n case \"auto-link\":\n return (\n <ComposerEditorLinkWrapper\n Link={Link}\n {...(props as RenderElementSpecificProps<ComposerBodyAutoLink>)}\n />\n );\n case \"paragraph\":\n return (\n <p {...attributes} style={{ position: \"relative\" }}>\n {children}\n </p>\n );\n default:\n return null;\n }\n}\n\n// <code><s><em><strong>text</strong></s></em></code>\nfunction ComposerEditorLeaf({ attributes, children, leaf }: RenderLeafProps) {\n if (leaf.bold) {\n children = <strong>{children}</strong>;\n }\n\n if (leaf.italic) {\n children = <em>{children}</em>;\n }\n\n if (leaf.strikethrough) {\n children = <s>{children}</s>;\n }\n\n if (leaf.code) {\n children = <code>{children}</code>;\n }\n\n return <span {...attributes}>{children}</span>;\n}\n\nfunction ComposerEditorPlaceholder({\n attributes,\n children,\n}: RenderPlaceholderProps) {\n const { opacity: _opacity, ...style } = attributes.style;\n\n return (\n <span {...attributes} style={style} data-placeholder=\"\">\n {children}\n </span>\n );\n}\n\n/**\n * Displays mentions within `Composer.Editor`.\n *\n * @example\n * <Composer.Mention>@{userId}</Composer.Mention>\n */\nconst ComposerMention = forwardRef<HTMLSpanElement, ComposerMentionProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"span\";\n const isSelected = useSelected();\n\n return (\n <Component\n data-selected={isSelected || undefined}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Displays links within `Composer.Editor`.\n *\n * @example\n * <Composer.Link href={href}>{children}</Composer.Link>\n */\nconst ComposerLink = forwardRef<HTMLAnchorElement, ComposerLinkProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"a\";\n\n return (\n <Component\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Contains suggestions within `Composer.Editor`.\n */\nconst ComposerSuggestions = forwardRef<\n HTMLDivElement,\n ComposerSuggestionsProps\n>(({ children, style, asChild, ...props }, forwardedRef) => {\n const [isPresent] = usePersist();\n const ref = useRef<HTMLDivElement>(null);\n const {\n ref: contentRef,\n placement,\n dir,\n } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_NAME);\n const mergedRefs = useRefs(forwardedRef, contentRef, ref);\n const [side, align] = useMemo(\n () => getSideAndAlignFromPlacement(placement),\n [placement]\n );\n const Component = asChild ? Slot : \"div\";\n useAnimationPersist(ref);\n\n return (\n <Component\n dir={dir}\n {...props}\n data-state={isPresent ? \"open\" : \"closed\"}\n data-side={side}\n data-align={align}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n maxHeight: \"var(--lb-composer-suggestions-available-height)\",\n overflowY: \"auto\",\n ...style,\n }}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a list of suggestions within `Composer.Editor`.\n *\n * @example\n * <Composer.SuggestionsList>\n * {userIds.map((userId) => (\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n * ))}\n * </Composer.SuggestionsList>\n */\nconst ComposerSuggestionsList = forwardRef<\n HTMLUListElement,\n ComposerSuggestionsListProps\n>(({ children, asChild, ...props }, forwardedRef) => {\n const { id } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_NAME);\n const Component = asChild ? Slot : \"ul\";\n\n return (\n <Component\n role=\"listbox\"\n id={id}\n aria-label=\"Suggestions list\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a suggestion within `Composer.SuggestionsList`.\n *\n * @example\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n */\nconst ComposerSuggestionsListItem = forwardRef<\n HTMLLIElement,\n ComposerSuggestionsListItemProps\n>(\n (\n { value, children, onPointerMove, onPointerDown, asChild, ...props },\n forwardedRef\n ) => {\n const ref = useRef<HTMLLIElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const { selectedValue, setSelectedValue, itemId, onItemSelect } =\n useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_ITEM_NAME);\n const Component = asChild ? Slot : \"li\";\n const isSelected = useMemo(\n () => selectedValue === value,\n [selectedValue, value]\n );\n // TODO: Support props.id if provided, it will need to be sent up to Composer.Editor to use it in aria-activedescendant\n const id = useMemo(() => itemId(value), [itemId, value]);\n\n useEffect(() => {\n if (ref?.current && isSelected) {\n ref.current.scrollIntoView({ block: \"nearest\" });\n }\n }, [isSelected]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerMove?.(event);\n\n if (!event.isDefaultPrevented()) {\n setSelectedValue(value);\n }\n },\n [onPointerMove, setSelectedValue, value]\n );\n\n const handlePointerDown = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerDown?.(event);\n\n if (!event.isDefaultPrevented()) {\n const target = event.target as HTMLElement;\n\n if (target.hasPointerCapture(event.pointerId)) {\n target.releasePointerCapture(event.pointerId);\n }\n\n if (event.button === 0 && event.ctrlKey === false) {\n onItemSelect(value);\n\n event.preventDefault();\n }\n }\n },\n [onItemSelect, onPointerDown, value]\n );\n\n return (\n <Component\n role=\"option\"\n id={id}\n data-selected={isSelected || undefined}\n aria-selected={isSelected || undefined}\n onPointerMove={handlePointerMove}\n onPointerDown={handlePointerDown}\n {...props}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n }\n);\n\nconst defaultEditorComponents: ComposerEditorComponents = {\n Link: ({ href, children }) => {\n return <ComposerLink href={href}>{children}</ComposerLink>;\n },\n Mention: ({ userId }) => {\n return (\n <ComposerMention>\n {MENTION_CHARACTER}\n {userId}\n </ComposerMention>\n );\n },\n MentionSuggestions: ({ userIds }) => {\n return userIds.length > 0 ? (\n <ComposerSuggestions>\n <ComposerSuggestionsList>\n {userIds.map((userId) => (\n <ComposerSuggestionsListItem key={userId} value={userId}>\n {userId}\n </ComposerSuggestionsListItem>\n ))}\n </ComposerSuggestionsList>\n </ComposerSuggestions>\n ) : null;\n },\n};\n\n/**\n * Displays the composer's editor.\n *\n * @example\n * <Composer.Editor placeholder=\"Write a comment…\" />\n */\nconst ComposerEditor = forwardRef<HTMLDivElement, ComposerEditorProps>(\n (\n {\n defaultValue,\n onKeyDown,\n onFocus,\n onBlur,\n disabled,\n autoFocus,\n components,\n dir,\n ...props\n },\n forwardedRef\n ) => {\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || !self?.canComment,\n [disabled, self?.canComment]\n );\n const { editor, validate, setFocused } = useComposerEditorContext();\n const { submit, focus, select, isEmpty, isFocused } = useComposer();\n const initialBody = useInitial(defaultValue ?? emptyCommentBody);\n const initialEditorValue = useMemo(() => {\n return commentBodyToComposerBody(initialBody);\n }, [initialBody]);\n const { Link, Mention, MentionSuggestions } = useMemo(\n () => ({ ...defaultEditorComponents, ...components }),\n [components]\n );\n\n const [mentionDraft, setMentionDraft] = useState<MentionDraft>();\n const mentionSuggestions = useMentionSuggestions(mentionDraft?.text);\n const [\n selectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setNextSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n ] = useIndex(0, mentionSuggestions?.length ?? 0);\n const id = useId();\n const suggestionsListId = useMemo(\n () => `liveblocks-suggestions-list-${id}`,\n [id]\n );\n const suggestionsListItemId = useCallback(\n (userId?: string) =>\n userId ? `liveblocks-suggestions-list-item-${id}-${userId}` : undefined,\n [id]\n );\n const renderElement = useCallback(\n (props: RenderElementProps) => {\n return (\n <ComposerEditorElement Mention={Mention} Link={Link} {...props} />\n );\n },\n [Link, Mention]\n );\n\n const handleChange = useCallback(\n (value: SlateDescendant[]) => {\n validate(value as SlateElement[]);\n\n setMentionDraft(getMentionDraftAtSelection(editor));\n },\n [editor, validate]\n );\n\n const createMention = useCallback(\n (userId?: string) => {\n if (!mentionDraft || !userId) {\n return;\n }\n\n SlateTransforms.select(editor, mentionDraft.range);\n insertMention(editor, userId);\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n },\n [editor, mentionDraft, setSelectedMentionSuggestionIndex]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Allow leaving marks with ArrowLeft\n if (isKey(event, \"ArrowLeft\")) {\n leaveMarkEdge(editor, \"start\");\n }\n\n // Allow leaving marks with ArrowRight\n if (isKey(event, \"ArrowRight\")) {\n leaveMarkEdge(editor, \"end\");\n }\n\n if (mentionDraft && mentionSuggestions?.length) {\n // Select the next mention suggestion on ArrowDown\n if (isKey(event, \"ArrowDown\")) {\n event.preventDefault();\n setNextSelectedMentionSuggestionIndex();\n }\n\n // Select the previous mention suggestion on ArrowUp\n if (isKey(event, \"ArrowUp\")) {\n event.preventDefault();\n setPreviousSelectedMentionSuggestionIndex();\n }\n\n // Create a mention on Enter/Tab\n if (isKey(event, \"Enter\") || isKey(event, \"Tab\")) {\n event.preventDefault();\n\n const userId = mentionSuggestions?.[selectedMentionSuggestionIndex];\n createMention(userId);\n }\n\n // Close the suggestions on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n }\n } else {\n // Blur the editor on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n ReactEditor.blur(editor);\n }\n\n // Submit the editor on Enter\n if (isKey(event, \"Enter\", { shift: false }) && !isEmpty) {\n event.preventDefault();\n submit();\n }\n\n // Create a new line on Shift + Enter\n if (isKey(event, \"Enter\", { shift: true })) {\n event.preventDefault();\n editor.insertBreak();\n }\n\n // Toggle bold on Command/Control + B\n if (isKey(event, \"b\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"bold\");\n }\n\n // Toggle italic on Command/Control + I\n if (isKey(event, \"i\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"italic\");\n }\n\n // Toggle strikethrough on Command/Control + Shift + S\n if (isKey(event, \"s\", { mod: true, shift: true })) {\n event.preventDefault();\n toggleMark(editor, \"strikethrough\");\n }\n\n // Toggle code on Command/Control + E\n if (isKey(event, \"e\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"code\");\n }\n }\n },\n [\n createMention,\n editor,\n isEmpty,\n mentionDraft,\n mentionSuggestions,\n selectedMentionSuggestionIndex,\n onKeyDown,\n setNextSelectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n submit,\n ]\n );\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(true);\n }\n },\n [onFocus, setFocused]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(false);\n }\n },\n [onBlur, setFocused]\n );\n\n const selectedMentionSuggestionUserId = useMemo(\n () => mentionSuggestions?.[selectedMentionSuggestionIndex],\n [selectedMentionSuggestionIndex, mentionSuggestions]\n );\n const setSelectedMentionSuggestionUserId = useCallback(\n (userId: string) => {\n const index = mentionSuggestions?.indexOf(userId);\n\n if (index !== undefined && index >= 0) {\n setSelectedMentionSuggestionIndex(index);\n }\n },\n [setSelectedMentionSuggestionIndex, mentionSuggestions]\n );\n\n const propsWhileSuggesting: AriaAttributes = useMemo(\n () =>\n mentionDraft\n ? {\n role: \"combobox\",\n \"aria-autocomplete\": \"list\",\n \"aria-expanded\": true,\n \"aria-controls\": suggestionsListId,\n \"aria-activedescendant\": suggestionsListItemId(\n selectedMentionSuggestionUserId\n ),\n }\n : {},\n [\n mentionDraft,\n suggestionsListId,\n suggestionsListItemId,\n selectedMentionSuggestionUserId,\n ]\n );\n\n useImperativeHandle(\n forwardedRef,\n () => {\n return ReactEditor.toDOMNode(editor, editor) as HTMLDivElement;\n },\n [editor]\n );\n\n // Manually focus the editor when `autoFocus` is true\n useEffect(() => {\n if (autoFocus) {\n focus();\n }\n }, [autoFocus, editor, focus]);\n\n // Manually add a selection in the editor if the selection\n // is still empty after being focused\n useEffect(() => {\n if (isFocused && editor.selection === null) {\n select();\n }\n }, [editor, select, isFocused]);\n\n return (\n <Slate\n editor={editor}\n initialValue={initialEditorValue}\n onChange={handleChange}\n >\n <Editable\n dir={dir}\n enterKeyHint={mentionDraft ? \"enter\" : \"send\"}\n autoCapitalize=\"sentences\"\n aria-label=\"Composer editor\"\n data-focused={isFocused || undefined}\n data-disabled={isDisabled || undefined}\n {...propsWhileSuggesting}\n {...props}\n readOnly={isDisabled}\n disabled={isDisabled}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n renderElement={renderElement}\n renderLeaf={ComposerEditorLeaf}\n renderPlaceholder={ComposerEditorPlaceholder}\n />\n <ComposerEditorMentionSuggestionsWrapper\n dir={dir}\n mentionDraft={mentionDraft}\n selectedUserId={selectedMentionSuggestionUserId}\n setSelectedUserId={setSelectedMentionSuggestionUserId}\n userIds={mentionSuggestions}\n id={suggestionsListId}\n itemId={suggestionsListItemId}\n onItemSelect={createMention}\n MentionSuggestions={MentionSuggestions}\n />\n </Slate>\n );\n }\n);\n\n/**\n * Surrounds the composer's content and handles submissions.\n *\n * @example\n * <Composer.Form onComposerSubmit={({ body }) => {}}>\n *\t <Composer.Editor />\n * <Composer.Submit />\n * </Composer.Form>\n */\nconst ComposerForm = forwardRef<HTMLFormElement, ComposerFormProps>(\n (\n { children, onSubmit, onComposerSubmit, asChild, ...props },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"form\";\n const editor = useInitial(createComposerEditor);\n const [isEmpty, setEmpty] = useState(true);\n const [isFocused, setFocused] = useState(false);\n const ref = useRef<HTMLFormElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n\n const validate = useCallback(\n (value: SlateElement[]) => {\n setEmpty(isEditorEmpty(editor, value));\n },\n [editor]\n );\n\n const submit = useCallback(() => {\n if (ref.current) {\n requestSubmit(ref.current);\n }\n }, []);\n\n const clear = useCallback(() => {\n SlateTransforms.delete(editor, {\n at: {\n anchor: SlateEditor.start(editor, []),\n focus: SlateEditor.end(editor, []),\n },\n });\n }, [editor]);\n\n const select = useCallback(() => {\n SlateTransforms.select(editor, {\n anchor: SlateEditor.end(editor, []),\n focus: SlateEditor.end(editor, []),\n });\n }, [editor]);\n\n const focus = useCallback(\n (resetSelection = true) => {\n if (!ReactEditor.isFocused(editor)) {\n SlateTransforms.select(\n editor,\n resetSelection || !editor.selection\n ? SlateEditor.end(editor, [])\n : editor.selection\n );\n ReactEditor.focus(editor);\n }\n },\n [editor]\n );\n\n const blur = useCallback(() => {\n ReactEditor.blur(editor);\n }, [editor]);\n\n const onSubmitEnd = useCallback(() => {\n clear();\n blur();\n }, [blur, clear]);\n\n const createMention = useCallback(() => {\n focus();\n insertMentionCharacter(editor);\n }, [editor, focus]);\n\n const insertText = useCallback(\n (text: string) => {\n focus(false);\n insertSlateText(editor, text);\n },\n [editor, focus]\n );\n\n const handleSubmit = useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n onSubmit?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const body = composerBodyToCommentBody(\n editor.children as ComposerBodyData\n );\n const comment = { body };\n\n const promise = onComposerSubmit?.(comment, event);\n\n event.preventDefault();\n\n if (promise) {\n promise.then(onSubmitEnd);\n } else {\n onSubmitEnd();\n }\n },\n [editor.children, onComposerSubmit, onSubmit, onSubmitEnd]\n );\n\n return (\n <ComposerEditorContext.Provider\n value={{\n editor,\n validate,\n setFocused,\n }}\n >\n <ComposerContext.Provider\n value={{\n isFocused,\n isEmpty,\n submit,\n clear,\n select,\n focus,\n blur,\n createMention,\n insertText,\n }}\n >\n <Component {...props} onSubmit={handleSubmit} ref={mergedRefs}>\n {children}\n </Component>\n </ComposerContext.Provider>\n </ComposerEditorContext.Provider>\n );\n }\n);\n\n/**\n * A button to submit the composer.\n *\n * @example\n * <Composer.Submit>Send</Composer.Submit>\n */\nconst ComposerSubmit = forwardRef<HTMLButtonElement, ComposerSubmitProps>(\n ({ children, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { isEmpty } = useComposer();\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || isEmpty || !self?.canComment,\n [disabled, isEmpty, self?.canComment]\n );\n\n return (\n <Component\n type=\"submit\"\n {...props}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n ComposerEditor.displayName = COMPOSER_EDITOR_NAME;\n ComposerForm.displayName = COMPOSER_FORM_NAME;\n ComposerMention.displayName = COMPOSER_MENTION_NAME;\n ComposerLink.displayName = COMPOSER_LINK_NAME;\n ComposerSubmit.displayName = COMPOSER_SUBMIT_NAME;\n ComposerSuggestions.displayName = COMPOSER_SUGGESTIONS_NAME;\n ComposerSuggestionsList.displayName = COMPOSER_SUGGESTIONS_LIST_NAME;\n ComposerSuggestionsListItem.displayName = COMPOSER_SUGGESTIONS_LIST_ITEM_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as Composer.*\nexport {\n ComposerEditor as Editor,\n ComposerForm as Form,\n ComposerLink as Link,\n ComposerMention as Mention,\n ComposerSubmit as Submit,\n ComposerSuggestions as Suggestions,\n ComposerSuggestionsList as SuggestionsList,\n ComposerSuggestionsListItem as SuggestionsListItem,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2HA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAsC;AAC3B;AAEX;AAEA;AACE;AAAO;AACL;AACE;AAC2D;AAC3D;AACF;AAEJ;AAEA;AAAsC;AACpC;AACA;AACA;AAEF;AACE;AAEA;AACG;AAAS;AAEL;AAAwB;AAAI;AAKrC;AAEA;AAAmC;AACjC;AACA;AACA;AAEF;AACE;AAAa;AACiC;AAChC;AAGd;AACG;AAAS;AACP;AAAK;AAGZ;AAEA;AAAiD;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAqD;AAC1C;AAGX;AAAO;AACK;AACuC;AACrC;AACyC;AACzB;AACpB;AACD;AACiB;AACrB;AACI;AACA;AAED;AAAwB;AACtB;AACG;AAEL;AAAwB;AACtB;AACG;AACL;AACF;AACD;AACH;AACsB;AACxB;AAEF;AAAM;AAC8B;AAClC;AACA;AACA;AACA;AACA;AAKF;AACE;AACE;AAAwD;AAC1D;AAGF;AACE;AACE;AAAA;AAGF;AAEA;AACE;AAAa;AACiD;AACd;AAC/C;AACH;AAGF;AAGO;AACQ;AACL;AACA;AACe;AACG;AAClB;AACA;AACA;AACK;AACP;AAEC;AACM;AACM;AACJ;AACK;AACL;AACC;AAGF;AACM;AACF;AACV;AAEC;AACC;AACA;AAOd;AAEA;AAA+B;AAC7B;AACA;AAEF;AAEE;AAEA;AAAsB;AAElB;AACG;AACC;AACK;AACP;AAGF;AACG;AACC;AACK;AACP;AAGF;AACG;AAAM;AAA0C;AAEjD;AAGF;AAAO;AAEb;AAGA;AACE;AACE;AAA6B;AAG/B;AACE;AAAyB;AAG3B;AACE;AAAwB;AAG1B;AACE;AAA2B;AAG7B;AAAQ;AAAS;AACnB;AAEA;AAAmC;AACjC;AAEF;AACE;AAEA;AACG;AAAS;AAAY;AAA+B;AAIzD;AAQA;AAAwB;AAEpB;AACA;AAEA;AACG;AAC8B;AACzB;AACC;AAGP;AAGN;AAQA;AAAqB;AAEjB;AAEA;AACG;AACQ;AACH;AACA;AACC;AAGP;AAGN;AAKM;AAIJ;AACA;AACA;AAAM;AACC;AACL;AACA;AAEF;AACA;AAAsB;AACwB;AAClC;AAEZ;AACA;AAEA;AACG;AACC;AACI;AAC6B;AACtB;AACC;AACL;AACI;AACM;AACJ;AACA;AACR;AACL;AACK;AAKX;AAcM;AAIJ;AACA;AAEA;AACG;AACM;AACL;AACW;AACP;AACC;AAKX;AAUA;AAAoC;AAQhC;AACA;AACA;AAEA;AACA;AAAmB;AACO;AACH;AAGvB;AAEA;AACE;AACE;AAA+C;AACjD;AAGF;AAA0B;AAEtB;AAEA;AACE;AAAsB;AACxB;AACF;AACuC;AAGzC;AAA0B;AAEtB;AAEA;AACE;AAEA;AACE;AAA4C;AAG9C;AACE;AAEA;AAAqB;AACvB;AACF;AACF;AACmC;AAGrC;AACG;AACM;AACL;AAC6B;AACA;AACd;AACA;AACX;AACC;AAGP;AAGN;AAEA;AAA0D;AAEtD;AAAQ;AAAa;AAAsB;AAC7C;AAEE;AAIE;AAEJ;AAEE;AAIS;AAAiC;AAAe;AAMrD;AAER;AAQA;AAAuB;AAEnB;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AAAmB;AACQ;AACE;AAE7B;AACA;AACA;AACA;AACE;AAA4C;AAE9C;AAA8C;AACO;AACxC;AAGb;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AAEF;AACA;AAA0B;AACa;AAClC;AAEL;AAA8B;AAEoC;AAC7D;AAEL;AAAsB;AAElB;AACG;AAAsB;AAAkB;AAAgB;AAAO;AAEpE;AACc;AAGhB;AAAqB;AAEjB;AAEA;AAAkD;AACpD;AACiB;AAGnB;AAAsB;AAElB;AACE;AAAA;AAGF;AACA;AACA;AACA;AAAmC;AACrC;AACwD;AAG1D;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAA6B;AAI/B;AACE;AAA2B;AAG7B;AAEE;AACE;AACA;AAAsC;AAIxC;AACE;AACA;AAA0C;AAI5C;AACE;AAEA;AACA;AAAoB;AAItB;AACE;AACA;AACA;AAAmC;AACrC;AAGA;AACE;AACA;AAAuB;AAIzB;AACE;AACA;AAAO;AAIT;AACE;AACA;AAAmB;AAIrB;AACE;AACA;AAAyB;AAI3B;AACE;AACA;AAA2B;AAI7B;AACE;AACA;AAAkC;AAIpC;AACE;AACA;AAAyB;AAC3B;AACF;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AAAoB;AAEhB;AAEA;AACE;AAAe;AACjB;AACF;AACoB;AAGtB;AAAmB;AAEf;AAEA;AACE;AAAgB;AAClB;AACF;AACmB;AAGrB;AAAwC;AACX;AACwB;AAErD;AAA2C;AAEvC;AAEA;AACE;AAAuC;AACzC;AACF;AACsD;AAGxD;AAA6C;AAGrC;AACQ;AACe;AACJ;AACA;AACQ;AACvB;AACF;AAED;AACP;AACE;AACA;AACA;AACA;AACF;AAGF;AAAA;AACE;AAEE;AAA2C;AAC7C;AACO;AAIT;AACE;AACE;AAAM;AACR;AAKF;AACE;AACE;AAAO;AACT;AAGF;AACG;AACC;AACc;AACJ;AAET;AACC;AACuC;AACxB;AACJ;AACgB;AACE;AACzB;AACA;AACM;AACA;AACC;AACF;AACD;AACR;AACY;AACO;AAEpB;AACC;AACA;AACgB;AACG;AACV;AACL;AACI;AACM;AACd;AAEJ;AAGN;AAWA;AAAqB;AAKjB;AACA;AACA;AACA;AACA;AACA;AAEA;AAAiB;AAEb;AAAqC;AACvC;AACO;AAGT;AACE;AACE;AAAyB;AAC3B;AAGF;AACE;AAA+B;AACzB;AACkC;AACH;AACnC;AACD;AAGH;AACE;AAA+B;AACK;AACD;AAClC;AAGH;AAAc;AAEV;AACE;AAAgB;AACd;AAGW;AAEb;AAAwB;AAC1B;AACF;AACO;AAGT;AACE;AAAuB;AAGzB;AACE;AACA;AAAK;AAGP;AACE;AACA;AAA6B;AAG/B;AAAmB;AAEf;AACA;AAA4B;AAC9B;AACc;AAGhB;AAAqB;AAEjB;AAEA;AACE;AAAA;AAGF;AAAa;AACJ;AAET;AAEA;AAEA;AAEA;AACE;AAAwB;AAExB;AAAY;AACd;AACF;AACyD;AAG3D;AACG;AACQ;AACL;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAEC;AAAc;AAAiB;AAAmB;AAIvD;AAGN;AAQA;AAAuB;AAEnB;AACA;AACA;AACA;AAAmB;AACmB;AACA;AAGtC;AACG;AACM;AACD;AACC;AACK;AAGZ;AAGN;AAEA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/primitives/Composer/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n DetectOverflowOptions,\n UseFloatingOptions,\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { CommentBody } from \"@liveblocks/core\";\nimport { useSelf } from \"@liveblocks/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport type {\n AriaAttributes,\n FocusEvent,\n FormEvent,\n KeyboardEvent,\n PointerEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Descendant as SlateDescendant,\n Element as SlateElement,\n} from \"slate\";\nimport {\n createEditor,\n Editor as SlateEditor,\n insertText as insertSlateText,\n Transforms as SlateTransforms,\n} from \"slate\";\nimport { withHistory } from \"slate-history\";\nimport type {\n RenderElementProps,\n RenderElementSpecificProps,\n RenderLeafProps,\n RenderPlaceholderProps,\n} from \"slate-react\";\nimport {\n Editable,\n ReactEditor,\n Slate,\n useSelected,\n useSlateStatic,\n withReact,\n} from \"slate-react\";\n\nimport { useLiveblocksUIConfig } from \"../../config\";\nimport { FLOATING_ELEMENT_COLLISION_PADDING } from \"../../constants\";\nimport { useMentionSuggestions } from \"../../shared\";\nimport { withAutoFormatting } from \"../../slate/plugins/auto-formatting\";\nimport { withAutoLinks } from \"../../slate/plugins/auto-links\";\nimport { withEmptyClearFormatting } from \"../../slate/plugins/empty-clear-formatting\";\nimport type { MentionDraft } from \"../../slate/plugins/mentions\";\nimport {\n getMentionDraftAtSelection,\n insertMention,\n insertMentionCharacter,\n MENTION_CHARACTER,\n withMentions,\n} from \"../../slate/plugins/mentions\";\nimport { getDOMRange } from \"../../slate/utils/get-dom-range\";\nimport { isEmpty as isEditorEmpty } from \"../../slate/utils/is-empty\";\nimport { leaveMarkEdge, toggleMark } from \"../../slate/utils/marks\";\nimport type {\n ComposerBody as ComposerBodyData,\n ComposerBodyAutoLink,\n ComposerBodyMention,\n} from \"../../types\";\nimport { isKey } from \"../../utils/is-key\";\nimport { Persist, useAnimationPersist, usePersist } from \"../../utils/Persist\";\nimport { Portal } from \"../../utils/Portal\";\nimport { requestSubmit } from \"../../utils/request-submit\";\nimport { useId } from \"../../utils/use-id\";\nimport { useIndex } from \"../../utils/use-index\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useRefs } from \"../../utils/use-refs\";\nimport { toAbsoluteUrl } from \"../Comment/utils\";\nimport {\n ComposerContext,\n ComposerEditorContext,\n ComposerSuggestionsContext,\n useComposer,\n useComposerEditorContext,\n useComposerSuggestionsContext,\n} from \"./contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorElementProps,\n ComposerEditorLinkWrapperProps,\n ComposerEditorMentionSuggestionsWrapperProps,\n ComposerEditorMentionWrapperProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerLinkProps,\n ComposerMentionProps,\n ComposerSubmitProps,\n ComposerSuggestionsListItemProps,\n ComposerSuggestionsListProps,\n ComposerSuggestionsProps,\n SuggestionsPosition,\n} from \"./types\";\nimport {\n commentBodyToComposerBody,\n composerBodyToCommentBody,\n getPlacementFromPosition,\n getSideAndAlignFromPlacement,\n} from \"./utils\";\n\nconst MENTION_SUGGESTIONS_POSITION: SuggestionsPosition = \"top\";\n\nconst COMPOSER_MENTION_NAME = \"ComposerMention\";\nconst COMPOSER_LINK_NAME = \"ComposerLink\";\nconst COMPOSER_SUGGESTIONS_NAME = \"ComposerSuggestions\";\nconst COMPOSER_SUGGESTIONS_LIST_NAME = \"ComposerSuggestionsList\";\nconst COMPOSER_SUGGESTIONS_LIST_ITEM_NAME = \"ComposerSuggestionsListItem\";\nconst COMPOSER_SUBMIT_NAME = \"ComposerSubmit\";\nconst COMPOSER_EDITOR_NAME = \"ComposerEditor\";\nconst COMPOSER_FORM_NAME = \"ComposerForm\";\n\nconst emptyCommentBody: CommentBody = {\n version: 1,\n content: [{ type: \"paragraph\", children: [{ text: \"\" }] }],\n};\n\nfunction createComposerEditor() {\n return withAutoLinks(\n withMentions(\n withEmptyClearFormatting(\n withAutoFormatting(withHistory(withReact(createEditor())))\n )\n )\n );\n}\n\nfunction ComposerEditorMentionWrapper({\n Mention,\n attributes,\n children,\n element,\n}: ComposerEditorMentionWrapperProps) {\n const isSelected = useSelected();\n\n return (\n <span {...attributes}>\n {element.id ? (\n <Mention userId={element.id} isSelected={isSelected} />\n ) : null}\n {children}\n </span>\n );\n}\n\nfunction ComposerEditorLinkWrapper({\n Link,\n attributes,\n element,\n children,\n}: ComposerEditorLinkWrapperProps) {\n const href = useMemo(\n () => toAbsoluteUrl(element.url) ?? element.url,\n [element.url]\n );\n\n return (\n <span {...attributes}>\n <Link href={href}>{children}</Link>\n </span>\n );\n}\n\nfunction ComposerEditorMentionSuggestionsWrapper({\n id,\n itemId,\n userIds,\n selectedUserId,\n setSelectedUserId,\n mentionDraft,\n onItemSelect,\n position = MENTION_SUGGESTIONS_POSITION,\n dir,\n MentionSuggestions,\n}: ComposerEditorMentionSuggestionsWrapperProps) {\n const editor = useSlateStatic();\n const { isFocused } = useComposer();\n const [content, setContent] = useState<HTMLDivElement | null>(null);\n const [contentZIndex, setContentZIndex] = useState<string>();\n const contentRef = useCallback(setContent, [setContent]);\n const { portalContainer } = useLiveblocksUIConfig();\n const floatingOptions: UseFloatingOptions = useMemo(() => {\n const detectOverflowOptions: DetectOverflowOptions = {\n padding: FLOATING_ELEMENT_COLLISION_PADDING,\n };\n\n return {\n strategy: \"fixed\",\n placement: getPlacementFromPosition(position, dir),\n middleware: [\n flip({ ...detectOverflowOptions, crossAxis: false }),\n hide(detectOverflowOptions),\n shift({\n ...detectOverflowOptions,\n limiter: limitShift(),\n }),\n size({\n ...detectOverflowOptions,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: autoUpdate,\n };\n }, [position, dir]);\n const {\n refs: { setReference, setFloating },\n strategy,\n isPositioned,\n placement,\n x,\n y,\n } = useFloating(floatingOptions);\n\n // Copy `z-index` from content to wrapper.\n // Inspired by https://github.com/radix-ui/primitives/blob/main/packages/react/popper/src/Popper.tsx\n useLayoutEffect(() => {\n if (content) {\n setContentZIndex(window.getComputedStyle(content).zIndex);\n }\n }, [content]);\n\n useLayoutEffect(() => {\n if (!mentionDraft) {\n return;\n }\n\n const domRange = getDOMRange(editor, mentionDraft.range);\n\n if (domRange) {\n setReference({\n getBoundingClientRect: () => domRange.getBoundingClientRect(),\n getClientRects: () => domRange.getClientRects(),\n });\n }\n }, [setReference, editor, mentionDraft]);\n\n return (\n <Persist>\n {mentionDraft?.range && isFocused && userIds ? (\n <ComposerSuggestionsContext.Provider\n value={{\n id,\n itemId,\n selectedValue: selectedUserId,\n setSelectedValue: setSelectedUserId,\n onItemSelect,\n placement,\n dir,\n ref: contentRef,\n }}\n >\n <Portal\n ref={setFloating}\n container={portalContainer}\n style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: isPositioned\n ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`\n : \"translate3d(0, -200%, 0)\",\n minWidth: \"max-content\",\n zIndex: contentZIndex,\n }}\n >\n <MentionSuggestions\n userIds={userIds}\n selectedUserId={selectedUserId}\n />\n </Portal>\n </ComposerSuggestionsContext.Provider>\n ) : null}\n </Persist>\n );\n}\n\nfunction ComposerEditorElement({\n Mention,\n Link,\n ...props\n}: ComposerEditorElementProps) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { attributes, children, element } = props;\n\n switch (element.type) {\n case \"mention\":\n return (\n <ComposerEditorMentionWrapper\n Mention={Mention}\n {...(props as RenderElementSpecificProps<ComposerBodyMention>)}\n />\n );\n case \"auto-link\":\n return (\n <ComposerEditorLinkWrapper\n Link={Link}\n {...(props as RenderElementSpecificProps<ComposerBodyAutoLink>)}\n />\n );\n case \"paragraph\":\n return (\n <p {...attributes} style={{ position: \"relative\" }}>\n {children}\n </p>\n );\n default:\n return null;\n }\n}\n\n// <code><s><em><strong>text</strong></s></em></code>\nfunction ComposerEditorLeaf({ attributes, children, leaf }: RenderLeafProps) {\n if (leaf.bold) {\n children = <strong>{children}</strong>;\n }\n\n if (leaf.italic) {\n children = <em>{children}</em>;\n }\n\n if (leaf.strikethrough) {\n children = <s>{children}</s>;\n }\n\n if (leaf.code) {\n children = <code>{children}</code>;\n }\n\n return <span {...attributes}>{children}</span>;\n}\n\nfunction ComposerEditorPlaceholder({\n attributes,\n children,\n}: RenderPlaceholderProps) {\n const { opacity: _opacity, ...style } = attributes.style;\n\n return (\n <span {...attributes} style={style} data-placeholder=\"\">\n {children}\n </span>\n );\n}\n\n/**\n * Displays mentions within `Composer.Editor`.\n *\n * @example\n * <Composer.Mention>@{userId}</Composer.Mention>\n */\nconst ComposerMention = forwardRef<HTMLSpanElement, ComposerMentionProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"span\";\n const isSelected = useSelected();\n\n return (\n <Component\n data-selected={isSelected || undefined}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Displays links within `Composer.Editor`.\n *\n * @example\n * <Composer.Link href={href}>{children}</Composer.Link>\n */\nconst ComposerLink = forwardRef<HTMLAnchorElement, ComposerLinkProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"a\";\n\n return (\n <Component\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Contains suggestions within `Composer.Editor`.\n */\nconst ComposerSuggestions = forwardRef<\n HTMLDivElement,\n ComposerSuggestionsProps\n>(({ children, style, asChild, ...props }, forwardedRef) => {\n const [isPresent] = usePersist();\n const ref = useRef<HTMLDivElement>(null);\n const {\n ref: contentRef,\n placement,\n dir,\n } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_NAME);\n const mergedRefs = useRefs(forwardedRef, contentRef, ref);\n const [side, align] = useMemo(\n () => getSideAndAlignFromPlacement(placement),\n [placement]\n );\n const Component = asChild ? Slot : \"div\";\n useAnimationPersist(ref);\n\n return (\n <Component\n dir={dir}\n {...props}\n data-state={isPresent ? \"open\" : \"closed\"}\n data-side={side}\n data-align={align}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n maxHeight: \"var(--lb-composer-suggestions-available-height)\",\n overflowY: \"auto\",\n ...style,\n }}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a list of suggestions within `Composer.Editor`.\n *\n * @example\n * <Composer.SuggestionsList>\n * {userIds.map((userId) => (\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n * ))}\n * </Composer.SuggestionsList>\n */\nconst ComposerSuggestionsList = forwardRef<\n HTMLUListElement,\n ComposerSuggestionsListProps\n>(({ children, asChild, ...props }, forwardedRef) => {\n const { id } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_NAME);\n const Component = asChild ? Slot : \"ul\";\n\n return (\n <Component\n role=\"listbox\"\n id={id}\n aria-label=\"Suggestions list\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a suggestion within `Composer.SuggestionsList`.\n *\n * @example\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n */\nconst ComposerSuggestionsListItem = forwardRef<\n HTMLLIElement,\n ComposerSuggestionsListItemProps\n>(\n (\n { value, children, onPointerMove, onPointerDown, asChild, ...props },\n forwardedRef\n ) => {\n const ref = useRef<HTMLLIElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const { selectedValue, setSelectedValue, itemId, onItemSelect } =\n useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_ITEM_NAME);\n const Component = asChild ? Slot : \"li\";\n const isSelected = useMemo(\n () => selectedValue === value,\n [selectedValue, value]\n );\n // TODO: Support props.id if provided, it will need to be sent up to Composer.Editor to use it in aria-activedescendant\n const id = useMemo(() => itemId(value), [itemId, value]);\n\n useEffect(() => {\n if (ref?.current && isSelected) {\n ref.current.scrollIntoView({ block: \"nearest\" });\n }\n }, [isSelected]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerMove?.(event);\n\n if (!event.isDefaultPrevented()) {\n setSelectedValue(value);\n }\n },\n [onPointerMove, setSelectedValue, value]\n );\n\n const handlePointerDown = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerDown?.(event);\n\n if (!event.isDefaultPrevented()) {\n const target = event.target as HTMLElement;\n\n if (target.hasPointerCapture(event.pointerId)) {\n target.releasePointerCapture(event.pointerId);\n }\n\n if (event.button === 0 && event.ctrlKey === false) {\n onItemSelect(value);\n\n event.preventDefault();\n }\n }\n },\n [onItemSelect, onPointerDown, value]\n );\n\n return (\n <Component\n role=\"option\"\n id={id}\n data-selected={isSelected || undefined}\n aria-selected={isSelected || undefined}\n onPointerMove={handlePointerMove}\n onPointerDown={handlePointerDown}\n {...props}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n }\n);\n\nconst defaultEditorComponents: ComposerEditorComponents = {\n Link: ({ href, children }) => {\n return <ComposerLink href={href}>{children}</ComposerLink>;\n },\n Mention: ({ userId }) => {\n return (\n <ComposerMention>\n {MENTION_CHARACTER}\n {userId}\n </ComposerMention>\n );\n },\n MentionSuggestions: ({ userIds }) => {\n return userIds.length > 0 ? (\n <ComposerSuggestions>\n <ComposerSuggestionsList>\n {userIds.map((userId) => (\n <ComposerSuggestionsListItem key={userId} value={userId}>\n {userId}\n </ComposerSuggestionsListItem>\n ))}\n </ComposerSuggestionsList>\n </ComposerSuggestions>\n ) : null;\n },\n};\n\n/**\n * Displays the composer's editor.\n *\n * @example\n * <Composer.Editor placeholder=\"Write a comment…\" />\n */\nconst ComposerEditor = forwardRef<HTMLDivElement, ComposerEditorProps>(\n (\n {\n defaultValue,\n onKeyDown,\n onFocus,\n onBlur,\n disabled,\n autoFocus,\n components,\n dir,\n ...props\n },\n forwardedRef\n ) => {\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || !self?.canComment,\n [disabled, self?.canComment]\n );\n const { editor, validate, setFocused } = useComposerEditorContext();\n const { submit, focus, select, isEmpty, isFocused } = useComposer();\n const initialBody = useInitial(defaultValue ?? emptyCommentBody);\n const initialEditorValue = useMemo(() => {\n return commentBodyToComposerBody(initialBody);\n }, [initialBody]);\n const { Link, Mention, MentionSuggestions } = useMemo(\n () => ({ ...defaultEditorComponents, ...components }),\n [components]\n );\n\n const [mentionDraft, setMentionDraft] = useState<MentionDraft>();\n const mentionSuggestions = useMentionSuggestions(mentionDraft?.text);\n const [\n selectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setNextSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n ] = useIndex(0, mentionSuggestions?.length ?? 0);\n const id = useId();\n const suggestionsListId = useMemo(\n () => `liveblocks-suggestions-list-${id}`,\n [id]\n );\n const suggestionsListItemId = useCallback(\n (userId?: string) =>\n userId ? `liveblocks-suggestions-list-item-${id}-${userId}` : undefined,\n [id]\n );\n const renderElement = useCallback(\n (props: RenderElementProps) => {\n return (\n <ComposerEditorElement Mention={Mention} Link={Link} {...props} />\n );\n },\n [Link, Mention]\n );\n\n const handleChange = useCallback(\n (value: SlateDescendant[]) => {\n validate(value as SlateElement[]);\n\n setMentionDraft(getMentionDraftAtSelection(editor));\n },\n [editor, validate]\n );\n\n const createMention = useCallback(\n (userId?: string) => {\n if (!mentionDraft || !userId) {\n return;\n }\n\n SlateTransforms.select(editor, mentionDraft.range);\n insertMention(editor, userId);\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n },\n [editor, mentionDraft, setSelectedMentionSuggestionIndex]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Allow leaving marks with ArrowLeft\n if (isKey(event, \"ArrowLeft\")) {\n leaveMarkEdge(editor, \"start\");\n }\n\n // Allow leaving marks with ArrowRight\n if (isKey(event, \"ArrowRight\")) {\n leaveMarkEdge(editor, \"end\");\n }\n\n if (mentionDraft && mentionSuggestions?.length) {\n // Select the next mention suggestion on ArrowDown\n if (isKey(event, \"ArrowDown\")) {\n event.preventDefault();\n setNextSelectedMentionSuggestionIndex();\n }\n\n // Select the previous mention suggestion on ArrowUp\n if (isKey(event, \"ArrowUp\")) {\n event.preventDefault();\n setPreviousSelectedMentionSuggestionIndex();\n }\n\n // Create a mention on Enter/Tab\n if (isKey(event, \"Enter\") || isKey(event, \"Tab\")) {\n event.preventDefault();\n\n const userId = mentionSuggestions?.[selectedMentionSuggestionIndex];\n createMention(userId);\n }\n\n // Close the suggestions on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n }\n } else {\n // Blur the editor on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n ReactEditor.blur(editor);\n }\n\n // Submit the editor on Enter\n if (isKey(event, \"Enter\", { shift: false }) && !isEmpty) {\n event.preventDefault();\n submit();\n }\n\n // Create a new line on Shift + Enter\n if (isKey(event, \"Enter\", { shift: true })) {\n event.preventDefault();\n editor.insertBreak();\n }\n\n // Toggle bold on Command/Control + B\n if (isKey(event, \"b\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"bold\");\n }\n\n // Toggle italic on Command/Control + I\n if (isKey(event, \"i\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"italic\");\n }\n\n // Toggle strikethrough on Command/Control + Shift + S\n if (isKey(event, \"s\", { mod: true, shift: true })) {\n event.preventDefault();\n toggleMark(editor, \"strikethrough\");\n }\n\n // Toggle code on Command/Control + E\n if (isKey(event, \"e\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"code\");\n }\n }\n },\n [\n createMention,\n editor,\n isEmpty,\n mentionDraft,\n mentionSuggestions,\n selectedMentionSuggestionIndex,\n onKeyDown,\n setNextSelectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n submit,\n ]\n );\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(true);\n }\n },\n [onFocus, setFocused]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(false);\n }\n },\n [onBlur, setFocused]\n );\n\n const selectedMentionSuggestionUserId = useMemo(\n () => mentionSuggestions?.[selectedMentionSuggestionIndex],\n [selectedMentionSuggestionIndex, mentionSuggestions]\n );\n const setSelectedMentionSuggestionUserId = useCallback(\n (userId: string) => {\n const index = mentionSuggestions?.indexOf(userId);\n\n if (index !== undefined && index >= 0) {\n setSelectedMentionSuggestionIndex(index);\n }\n },\n [setSelectedMentionSuggestionIndex, mentionSuggestions]\n );\n\n const propsWhileSuggesting: AriaAttributes = useMemo(\n () =>\n mentionDraft\n ? {\n role: \"combobox\",\n \"aria-autocomplete\": \"list\",\n \"aria-expanded\": true,\n \"aria-controls\": suggestionsListId,\n \"aria-activedescendant\": suggestionsListItemId(\n selectedMentionSuggestionUserId\n ),\n }\n : {},\n [\n mentionDraft,\n suggestionsListId,\n suggestionsListItemId,\n selectedMentionSuggestionUserId,\n ]\n );\n\n useImperativeHandle(forwardedRef, () => {\n return ReactEditor.toDOMNode(editor, editor) as HTMLDivElement;\n }, [editor]);\n\n // Manually focus the editor when `autoFocus` is true\n useEffect(() => {\n if (autoFocus) {\n focus();\n }\n }, [autoFocus, editor, focus]);\n\n // Manually add a selection in the editor if the selection\n // is still empty after being focused\n useEffect(() => {\n if (isFocused && editor.selection === null) {\n select();\n }\n }, [editor, select, isFocused]);\n\n return (\n <Slate\n editor={editor}\n initialValue={initialEditorValue}\n onChange={handleChange}\n >\n <Editable\n dir={dir}\n enterKeyHint={mentionDraft ? \"enter\" : \"send\"}\n autoCapitalize=\"sentences\"\n aria-label=\"Composer editor\"\n data-focused={isFocused || undefined}\n data-disabled={isDisabled || undefined}\n {...propsWhileSuggesting}\n {...props}\n readOnly={isDisabled}\n disabled={isDisabled}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n renderElement={renderElement}\n renderLeaf={ComposerEditorLeaf}\n renderPlaceholder={ComposerEditorPlaceholder}\n />\n <ComposerEditorMentionSuggestionsWrapper\n dir={dir}\n mentionDraft={mentionDraft}\n selectedUserId={selectedMentionSuggestionUserId}\n setSelectedUserId={setSelectedMentionSuggestionUserId}\n userIds={mentionSuggestions}\n id={suggestionsListId}\n itemId={suggestionsListItemId}\n onItemSelect={createMention}\n MentionSuggestions={MentionSuggestions}\n />\n </Slate>\n );\n }\n);\n\n/**\n * Surrounds the composer's content and handles submissions.\n *\n * @example\n * <Composer.Form onComposerSubmit={({ body }) => {}}>\n *\t <Composer.Editor />\n * <Composer.Submit />\n * </Composer.Form>\n */\nconst ComposerForm = forwardRef<HTMLFormElement, ComposerFormProps>(\n (\n { children, onSubmit, onComposerSubmit, asChild, ...props },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"form\";\n const editor = useInitial(createComposerEditor);\n const [isEmpty, setEmpty] = useState(true);\n const [isFocused, setFocused] = useState(false);\n const ref = useRef<HTMLFormElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n\n const validate = useCallback(\n (value: SlateElement[]) => {\n setEmpty(isEditorEmpty(editor, value));\n },\n [editor]\n );\n\n const submit = useCallback(() => {\n // We need to wait for the next frame in some cases like when composing diacritics,\n // we want any native handling to be done first while still being handled on `keydown`.\n requestAnimationFrame(() => {\n if (ref.current) {\n requestSubmit(ref.current);\n }\n });\n }, []);\n\n const clear = useCallback(() => {\n SlateTransforms.delete(editor, {\n at: {\n anchor: SlateEditor.start(editor, []),\n focus: SlateEditor.end(editor, []),\n },\n });\n }, [editor]);\n\n const select = useCallback(() => {\n SlateTransforms.select(editor, {\n anchor: SlateEditor.end(editor, []),\n focus: SlateEditor.end(editor, []),\n });\n }, [editor]);\n\n const focus = useCallback(\n (resetSelection = true) => {\n if (!ReactEditor.isFocused(editor)) {\n SlateTransforms.select(\n editor,\n resetSelection || !editor.selection\n ? SlateEditor.end(editor, [])\n : editor.selection\n );\n ReactEditor.focus(editor);\n }\n },\n [editor]\n );\n\n const blur = useCallback(() => {\n ReactEditor.blur(editor);\n }, [editor]);\n\n const onSubmitEnd = useCallback(() => {\n clear();\n blur();\n }, [blur, clear]);\n\n const createMention = useCallback(() => {\n focus();\n insertMentionCharacter(editor);\n }, [editor, focus]);\n\n const insertText = useCallback(\n (text: string) => {\n focus(false);\n insertSlateText(editor, text);\n },\n [editor, focus]\n );\n\n const handleSubmit = useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n // In some situations (e.g. pressing Enter while composing diacritics), it's possible\n // for the form to be submitted as empty even though we already checked whether the\n // editor was empty when handling the key press.\n const isEmpty = isEditorEmpty(editor, editor.children);\n\n // We even prevent the user's `onSubmit` handler from being called if the editor is empty.\n if (isEmpty) {\n event.preventDefault();\n\n return;\n }\n\n onSubmit?.(event);\n\n if (!onComposerSubmit || event.isDefaultPrevented()) {\n event.preventDefault();\n\n return;\n }\n\n const body = composerBodyToCommentBody(\n editor.children as ComposerBodyData\n );\n const comment = { body };\n\n const promise = onComposerSubmit(comment, event);\n\n event.preventDefault();\n\n if (promise) {\n promise.then(onSubmitEnd);\n } else {\n onSubmitEnd();\n }\n },\n [editor, onComposerSubmit, onSubmit, onSubmitEnd]\n );\n\n return (\n <ComposerEditorContext.Provider\n value={{\n editor,\n validate,\n setFocused,\n }}\n >\n <ComposerContext.Provider\n value={{\n isFocused,\n isEmpty,\n submit,\n clear,\n select,\n focus,\n blur,\n createMention,\n insertText,\n }}\n >\n <Component {...props} onSubmit={handleSubmit} ref={mergedRefs}>\n {children}\n </Component>\n </ComposerContext.Provider>\n </ComposerEditorContext.Provider>\n );\n }\n);\n\n/**\n * A button to submit the composer.\n *\n * @example\n * <Composer.Submit>Send</Composer.Submit>\n */\nconst ComposerSubmit = forwardRef<HTMLButtonElement, ComposerSubmitProps>(\n ({ children, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { isEmpty } = useComposer();\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || isEmpty || !self?.canComment,\n [disabled, isEmpty, self?.canComment]\n );\n\n return (\n <Component\n type=\"submit\"\n {...props}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n ComposerEditor.displayName = COMPOSER_EDITOR_NAME;\n ComposerForm.displayName = COMPOSER_FORM_NAME;\n ComposerMention.displayName = COMPOSER_MENTION_NAME;\n ComposerLink.displayName = COMPOSER_LINK_NAME;\n ComposerSubmit.displayName = COMPOSER_SUBMIT_NAME;\n ComposerSuggestions.displayName = COMPOSER_SUGGESTIONS_NAME;\n ComposerSuggestionsList.displayName = COMPOSER_SUGGESTIONS_LIST_NAME;\n ComposerSuggestionsListItem.displayName = COMPOSER_SUGGESTIONS_LIST_ITEM_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as Composer.*\nexport {\n ComposerEditor as Editor,\n ComposerForm as Form,\n ComposerLink as Link,\n ComposerMention as Mention,\n ComposerSubmit as Submit,\n ComposerSuggestions as Suggestions,\n ComposerSuggestionsList as SuggestionsList,\n ComposerSuggestionsListItem as SuggestionsListItem,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2HA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAsC;AAC3B;AAEX;AAEA;AACE;AAAO;AACL;AACE;AAC2D;AAC3D;AACF;AAEJ;AAEA;AAAsC;AACpC;AACA;AACA;AAEF;AACE;AAEA;AACG;AAAS;AAEL;AAAwB;AAAI;AAKrC;AAEA;AAAmC;AACjC;AACA;AACA;AAEF;AACE;AAAa;AACiC;AAChC;AAGd;AACG;AAAS;AACP;AAAK;AAGZ;AAEA;AAAiD;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAqD;AAC1C;AAGX;AAAO;AACK;AACuC;AACrC;AACyC;AACzB;AACpB;AACD;AACiB;AACrB;AACI;AACA;AAED;AAAwB;AACtB;AACG;AAEL;AAAwB;AACtB;AACG;AACL;AACF;AACD;AACH;AACsB;AACxB;AAEF;AAAM;AAC8B;AAClC;AACA;AACA;AACA;AACA;AAKF;AACE;AACE;AAAwD;AAC1D;AAGF;AACE;AACE;AAAA;AAGF;AAEA;AACE;AAAa;AACiD;AACd;AAC/C;AACH;AAGF;AAGO;AACQ;AACL;AACA;AACe;AACG;AAClB;AACA;AACA;AACK;AACP;AAEC;AACM;AACM;AACJ;AACK;AACL;AACC;AAGF;AACM;AACF;AACV;AAEC;AACC;AACA;AAOd;AAEA;AAA+B;AAC7B;AACA;AAEF;AAEE;AAEA;AAAsB;AAElB;AACG;AACC;AACK;AACP;AAGF;AACG;AACC;AACK;AACP;AAGF;AACG;AAAM;AAA0C;AAEjD;AAGF;AAAO;AAEb;AAGA;AACE;AACE;AAA6B;AAG/B;AACE;AAAyB;AAG3B;AACE;AAAwB;AAG1B;AACE;AAA2B;AAG7B;AAAQ;AAAS;AACnB;AAEA;AAAmC;AACjC;AAEF;AACE;AAEA;AACG;AAAS;AAAY;AAA+B;AAIzD;AAQA;AAAwB;AAEpB;AACA;AAEA;AACG;AAC8B;AACzB;AACC;AAGP;AAGN;AAQA;AAAqB;AAEjB;AAEA;AACG;AACQ;AACH;AACA;AACC;AAGP;AAGN;AAKM;AAIJ;AACA;AACA;AAAM;AACC;AACL;AACA;AAEF;AACA;AAAsB;AACwB;AAClC;AAEZ;AACA;AAEA;AACG;AACC;AACI;AAC6B;AACtB;AACC;AACL;AACI;AACM;AACJ;AACA;AACR;AACL;AACK;AAKX;AAcM;AAIJ;AACA;AAEA;AACG;AACM;AACL;AACW;AACP;AACC;AAKX;AAUA;AAAoC;AAQhC;AACA;AACA;AAEA;AACA;AAAmB;AACO;AACH;AAGvB;AAEA;AACE;AACE;AAA+C;AACjD;AAGF;AAA0B;AAEtB;AAEA;AACE;AAAsB;AACxB;AACF;AACuC;AAGzC;AAA0B;AAEtB;AAEA;AACE;AAEA;AACE;AAA4C;AAG9C;AACE;AAEA;AAAqB;AACvB;AACF;AACF;AACmC;AAGrC;AACG;AACM;AACL;AAC6B;AACA;AACd;AACA;AACX;AACC;AAGP;AAGN;AAEA;AAA0D;AAEtD;AAAQ;AAAa;AAAsB;AAC7C;AAEE;AAIE;AAEJ;AAEE;AAIS;AAAiC;AAAe;AAMrD;AAER;AAQA;AAAuB;AAEnB;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AAAmB;AACQ;AACE;AAE7B;AACA;AACA;AACA;AACE;AAA4C;AAE9C;AAA8C;AACO;AACxC;AAGb;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AAEF;AACA;AAA0B;AACa;AAClC;AAEL;AAA8B;AAEoC;AAC7D;AAEL;AAAsB;AAElB;AACG;AAAsB;AAAkB;AAAgB;AAAO;AAEpE;AACc;AAGhB;AAAqB;AAEjB;AAEA;AAAkD;AACpD;AACiB;AAGnB;AAAsB;AAElB;AACE;AAAA;AAGF;AACA;AACA;AACA;AAAmC;AACrC;AACwD;AAG1D;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAA6B;AAI/B;AACE;AAA2B;AAG7B;AAEE;AACE;AACA;AAAsC;AAIxC;AACE;AACA;AAA0C;AAI5C;AACE;AAEA;AACA;AAAoB;AAItB;AACE;AACA;AACA;AAAmC;AACrC;AAGA;AACE;AACA;AAAuB;AAIzB;AACE;AACA;AAAO;AAIT;AACE;AACA;AAAmB;AAIrB;AACE;AACA;AAAyB;AAI3B;AACE;AACA;AAA2B;AAI7B;AACE;AACA;AAAkC;AAIpC;AACE;AACA;AAAyB;AAC3B;AACF;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AAAoB;AAEhB;AAEA;AACE;AAAe;AACjB;AACF;AACoB;AAGtB;AAAmB;AAEf;AAEA;AACE;AAAgB;AAClB;AACF;AACmB;AAGrB;AAAwC;AACX;AACwB;AAErD;AAA2C;AAEvC;AAEA;AACE;AAAuC;AACzC;AACF;AACsD;AAGxD;AAA6C;AAGrC;AACQ;AACe;AACJ;AACA;AACQ;AACvB;AACF;AAED;AACP;AACE;AACA;AACA;AACA;AACF;AAGF;AACE;AAA2C;AAI7C;AACE;AACE;AAAM;AACR;AAKF;AACE;AACE;AAAO;AACT;AAGF;AACG;AACC;AACc;AACJ;AAET;AACC;AACuC;AACxB;AACJ;AACgB;AACE;AACzB;AACA;AACM;AACA;AACC;AACF;AACD;AACR;AACY;AACO;AAEpB;AACC;AACA;AACgB;AACG;AACV;AACL;AACI;AACM;AACd;AAEJ;AAGN;AAWA;AAAqB;AAKjB;AACA;AACA;AACA;AACA;AACA;AAEA;AAAiB;AAEb;AAAqC;AACvC;AACO;AAGT;AAGE;AACE;AACE;AAAyB;AAC3B;AACD;AAGH;AACE;AAA+B;AACzB;AACkC;AACH;AACnC;AACD;AAGH;AACE;AAA+B;AACK;AACD;AAClC;AAGH;AAAc;AAEV;AACE;AAAgB;AACd;AAGW;AAEb;AAAwB;AAC1B;AACF;AACO;AAGT;AACE;AAAuB;AAGzB;AACE;AACA;AAAK;AAGP;AACE;AACA;AAA6B;AAG/B;AAAmB;AAEf;AACA;AAA4B;AAC9B;AACc;AAGhB;AAAqB;AAKjB;AAGA;AACE;AAEA;AAAA;AAGF;AAEA;AACE;AAEA;AAAA;AAGF;AAAa;AACJ;AAET;AAEA;AAEA;AAEA;AACE;AAAwB;AAExB;AAAY;AACd;AACF;AACgD;AAGlD;AACG;AACQ;AACL;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAEC;AAAc;AAAiB;AAAmB;AAIvD;AAGN;AAQA;AAAuB;AAEnB;AACA;AACA;AACA;AAAmB;AACmB;AACA;AAGtC;AACG;AACM;AACD;AACC;AACK;AAGZ;AAGN;AAEA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;;;;;;;;;"}
|
|
@@ -566,13 +566,9 @@ const ComposerEditor = forwardRef(
|
|
|
566
566
|
selectedMentionSuggestionUserId
|
|
567
567
|
]
|
|
568
568
|
);
|
|
569
|
-
useImperativeHandle(
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return ReactEditor.toDOMNode(editor, editor);
|
|
573
|
-
},
|
|
574
|
-
[editor]
|
|
575
|
-
);
|
|
569
|
+
useImperativeHandle(forwardedRef, () => {
|
|
570
|
+
return ReactEditor.toDOMNode(editor, editor);
|
|
571
|
+
}, [editor]);
|
|
576
572
|
useEffect(() => {
|
|
577
573
|
if (autoFocus) {
|
|
578
574
|
focus();
|
|
@@ -632,9 +628,11 @@ const ComposerForm = forwardRef(
|
|
|
632
628
|
[editor]
|
|
633
629
|
);
|
|
634
630
|
const submit = useCallback(() => {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
631
|
+
requestAnimationFrame(() => {
|
|
632
|
+
if (ref.current) {
|
|
633
|
+
requestSubmit(ref.current);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
638
636
|
}, []);
|
|
639
637
|
const clear = useCallback(() => {
|
|
640
638
|
Transforms.delete(editor, {
|
|
@@ -682,15 +680,21 @@ const ComposerForm = forwardRef(
|
|
|
682
680
|
);
|
|
683
681
|
const handleSubmit = useCallback(
|
|
684
682
|
(event) => {
|
|
683
|
+
const isEmpty2 = isEmpty(editor, editor.children);
|
|
684
|
+
if (isEmpty2) {
|
|
685
|
+
event.preventDefault();
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
685
688
|
onSubmit?.(event);
|
|
686
|
-
if (event.isDefaultPrevented()) {
|
|
689
|
+
if (!onComposerSubmit || event.isDefaultPrevented()) {
|
|
690
|
+
event.preventDefault();
|
|
687
691
|
return;
|
|
688
692
|
}
|
|
689
693
|
const body = composerBodyToCommentBody(
|
|
690
694
|
editor.children
|
|
691
695
|
);
|
|
692
696
|
const comment = { body };
|
|
693
|
-
const promise = onComposerSubmit
|
|
697
|
+
const promise = onComposerSubmit(comment, event);
|
|
694
698
|
event.preventDefault();
|
|
695
699
|
if (promise) {
|
|
696
700
|
promise.then(onSubmitEnd);
|
|
@@ -698,7 +702,7 @@ const ComposerForm = forwardRef(
|
|
|
698
702
|
onSubmitEnd();
|
|
699
703
|
}
|
|
700
704
|
},
|
|
701
|
-
[editor
|
|
705
|
+
[editor, onComposerSubmit, onSubmit, onSubmitEnd]
|
|
702
706
|
);
|
|
703
707
|
return /* @__PURE__ */ React__default.createElement(ComposerEditorContext.Provider, {
|
|
704
708
|
value: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../../src/primitives/Composer/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n DetectOverflowOptions,\n UseFloatingOptions,\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { CommentBody } from \"@liveblocks/core\";\nimport { useSelf } from \"@liveblocks/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport type {\n AriaAttributes,\n FocusEvent,\n FormEvent,\n KeyboardEvent,\n PointerEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Descendant as SlateDescendant,\n Element as SlateElement,\n} from \"slate\";\nimport {\n createEditor,\n Editor as SlateEditor,\n insertText as insertSlateText,\n Transforms as SlateTransforms,\n} from \"slate\";\nimport { withHistory } from \"slate-history\";\nimport type {\n RenderElementProps,\n RenderElementSpecificProps,\n RenderLeafProps,\n RenderPlaceholderProps,\n} from \"slate-react\";\nimport {\n Editable,\n ReactEditor,\n Slate,\n useSelected,\n useSlateStatic,\n withReact,\n} from \"slate-react\";\n\nimport { useLiveblocksUIConfig } from \"../../config\";\nimport { FLOATING_ELEMENT_COLLISION_PADDING } from \"../../constants\";\nimport { useMentionSuggestions } from \"../../shared\";\nimport { withAutoFormatting } from \"../../slate/plugins/auto-formatting\";\nimport { withAutoLinks } from \"../../slate/plugins/auto-links\";\nimport { withEmptyClearFormatting } from \"../../slate/plugins/empty-clear-formatting\";\nimport type { MentionDraft } from \"../../slate/plugins/mentions\";\nimport {\n getMentionDraftAtSelection,\n insertMention,\n insertMentionCharacter,\n MENTION_CHARACTER,\n withMentions,\n} from \"../../slate/plugins/mentions\";\nimport { getDOMRange } from \"../../slate/utils/get-dom-range\";\nimport { isEmpty as isEditorEmpty } from \"../../slate/utils/is-empty\";\nimport { leaveMarkEdge, toggleMark } from \"../../slate/utils/marks\";\nimport type {\n ComposerBody as ComposerBodyData,\n ComposerBodyAutoLink,\n ComposerBodyMention,\n} from \"../../types\";\nimport { isKey } from \"../../utils/is-key\";\nimport { Persist, useAnimationPersist, usePersist } from \"../../utils/Persist\";\nimport { Portal } from \"../../utils/Portal\";\nimport { requestSubmit } from \"../../utils/request-submit\";\nimport { useId } from \"../../utils/use-id\";\nimport { useIndex } from \"../../utils/use-index\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useRefs } from \"../../utils/use-refs\";\nimport { toAbsoluteUrl } from \"../Comment/utils\";\nimport {\n ComposerContext,\n ComposerEditorContext,\n ComposerSuggestionsContext,\n useComposer,\n useComposerEditorContext,\n useComposerSuggestionsContext,\n} from \"./contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorElementProps,\n ComposerEditorLinkWrapperProps,\n ComposerEditorMentionSuggestionsWrapperProps,\n ComposerEditorMentionWrapperProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerLinkProps,\n ComposerMentionProps,\n ComposerSubmitProps,\n ComposerSuggestionsListItemProps,\n ComposerSuggestionsListProps,\n ComposerSuggestionsProps,\n SuggestionsPosition,\n} from \"./types\";\nimport {\n commentBodyToComposerBody,\n composerBodyToCommentBody,\n getPlacementFromPosition,\n getSideAndAlignFromPlacement,\n} from \"./utils\";\n\nconst MENTION_SUGGESTIONS_POSITION: SuggestionsPosition = \"top\";\n\nconst COMPOSER_MENTION_NAME = \"ComposerMention\";\nconst COMPOSER_LINK_NAME = \"ComposerLink\";\nconst COMPOSER_SUGGESTIONS_NAME = \"ComposerSuggestions\";\nconst COMPOSER_SUGGESTIONS_LIST_NAME = \"ComposerSuggestionsList\";\nconst COMPOSER_SUGGESTIONS_LIST_ITEM_NAME = \"ComposerSuggestionsListItem\";\nconst COMPOSER_SUBMIT_NAME = \"ComposerSubmit\";\nconst COMPOSER_EDITOR_NAME = \"ComposerEditor\";\nconst COMPOSER_FORM_NAME = \"ComposerForm\";\n\nconst emptyCommentBody: CommentBody = {\n version: 1,\n content: [{ type: \"paragraph\", children: [{ text: \"\" }] }],\n};\n\nfunction createComposerEditor() {\n return withAutoLinks(\n withMentions(\n withEmptyClearFormatting(\n withAutoFormatting(withHistory(withReact(createEditor())))\n )\n )\n );\n}\n\nfunction ComposerEditorMentionWrapper({\n Mention,\n attributes,\n children,\n element,\n}: ComposerEditorMentionWrapperProps) {\n const isSelected = useSelected();\n\n return (\n <span {...attributes}>\n {element.id ? (\n <Mention userId={element.id} isSelected={isSelected} />\n ) : null}\n {children}\n </span>\n );\n}\n\nfunction ComposerEditorLinkWrapper({\n Link,\n attributes,\n element,\n children,\n}: ComposerEditorLinkWrapperProps) {\n const href = useMemo(\n () => toAbsoluteUrl(element.url) ?? element.url,\n [element.url]\n );\n\n return (\n <span {...attributes}>\n <Link href={href}>{children}</Link>\n </span>\n );\n}\n\nfunction ComposerEditorMentionSuggestionsWrapper({\n id,\n itemId,\n userIds,\n selectedUserId,\n setSelectedUserId,\n mentionDraft,\n onItemSelect,\n position = MENTION_SUGGESTIONS_POSITION,\n dir,\n MentionSuggestions,\n}: ComposerEditorMentionSuggestionsWrapperProps) {\n const editor = useSlateStatic();\n const { isFocused } = useComposer();\n const [content, setContent] = useState<HTMLDivElement | null>(null);\n const [contentZIndex, setContentZIndex] = useState<string>();\n const contentRef = useCallback(setContent, [setContent]);\n const { portalContainer } = useLiveblocksUIConfig();\n const floatingOptions: UseFloatingOptions = useMemo(() => {\n const detectOverflowOptions: DetectOverflowOptions = {\n padding: FLOATING_ELEMENT_COLLISION_PADDING,\n };\n\n return {\n strategy: \"fixed\",\n placement: getPlacementFromPosition(position, dir),\n middleware: [\n flip({ ...detectOverflowOptions, crossAxis: false }),\n hide(detectOverflowOptions),\n shift({\n ...detectOverflowOptions,\n limiter: limitShift(),\n }),\n size({\n ...detectOverflowOptions,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: autoUpdate,\n };\n }, [position, dir]);\n const {\n refs: { setReference, setFloating },\n strategy,\n isPositioned,\n placement,\n x,\n y,\n } = useFloating(floatingOptions);\n\n // Copy `z-index` from content to wrapper.\n // Inspired by https://github.com/radix-ui/primitives/blob/main/packages/react/popper/src/Popper.tsx\n useLayoutEffect(() => {\n if (content) {\n setContentZIndex(window.getComputedStyle(content).zIndex);\n }\n }, [content]);\n\n useLayoutEffect(() => {\n if (!mentionDraft) {\n return;\n }\n\n const domRange = getDOMRange(editor, mentionDraft.range);\n\n if (domRange) {\n setReference({\n getBoundingClientRect: () => domRange.getBoundingClientRect(),\n getClientRects: () => domRange.getClientRects(),\n });\n }\n }, [setReference, editor, mentionDraft]);\n\n return (\n <Persist>\n {mentionDraft?.range && isFocused && userIds ? (\n <ComposerSuggestionsContext.Provider\n value={{\n id,\n itemId,\n selectedValue: selectedUserId,\n setSelectedValue: setSelectedUserId,\n onItemSelect,\n placement,\n dir,\n ref: contentRef,\n }}\n >\n <Portal\n ref={setFloating}\n container={portalContainer}\n style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: isPositioned\n ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`\n : \"translate3d(0, -200%, 0)\",\n minWidth: \"max-content\",\n zIndex: contentZIndex,\n }}\n >\n <MentionSuggestions\n userIds={userIds}\n selectedUserId={selectedUserId}\n />\n </Portal>\n </ComposerSuggestionsContext.Provider>\n ) : null}\n </Persist>\n );\n}\n\nfunction ComposerEditorElement({\n Mention,\n Link,\n ...props\n}: ComposerEditorElementProps) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { attributes, children, element } = props;\n\n switch (element.type) {\n case \"mention\":\n return (\n <ComposerEditorMentionWrapper\n Mention={Mention}\n {...(props as RenderElementSpecificProps<ComposerBodyMention>)}\n />\n );\n case \"auto-link\":\n return (\n <ComposerEditorLinkWrapper\n Link={Link}\n {...(props as RenderElementSpecificProps<ComposerBodyAutoLink>)}\n />\n );\n case \"paragraph\":\n return (\n <p {...attributes} style={{ position: \"relative\" }}>\n {children}\n </p>\n );\n default:\n return null;\n }\n}\n\n// <code><s><em><strong>text</strong></s></em></code>\nfunction ComposerEditorLeaf({ attributes, children, leaf }: RenderLeafProps) {\n if (leaf.bold) {\n children = <strong>{children}</strong>;\n }\n\n if (leaf.italic) {\n children = <em>{children}</em>;\n }\n\n if (leaf.strikethrough) {\n children = <s>{children}</s>;\n }\n\n if (leaf.code) {\n children = <code>{children}</code>;\n }\n\n return <span {...attributes}>{children}</span>;\n}\n\nfunction ComposerEditorPlaceholder({\n attributes,\n children,\n}: RenderPlaceholderProps) {\n const { opacity: _opacity, ...style } = attributes.style;\n\n return (\n <span {...attributes} style={style} data-placeholder=\"\">\n {children}\n </span>\n );\n}\n\n/**\n * Displays mentions within `Composer.Editor`.\n *\n * @example\n * <Composer.Mention>@{userId}</Composer.Mention>\n */\nconst ComposerMention = forwardRef<HTMLSpanElement, ComposerMentionProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"span\";\n const isSelected = useSelected();\n\n return (\n <Component\n data-selected={isSelected || undefined}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Displays links within `Composer.Editor`.\n *\n * @example\n * <Composer.Link href={href}>{children}</Composer.Link>\n */\nconst ComposerLink = forwardRef<HTMLAnchorElement, ComposerLinkProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"a\";\n\n return (\n <Component\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Contains suggestions within `Composer.Editor`.\n */\nconst ComposerSuggestions = forwardRef<\n HTMLDivElement,\n ComposerSuggestionsProps\n>(({ children, style, asChild, ...props }, forwardedRef) => {\n const [isPresent] = usePersist();\n const ref = useRef<HTMLDivElement>(null);\n const {\n ref: contentRef,\n placement,\n dir,\n } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_NAME);\n const mergedRefs = useRefs(forwardedRef, contentRef, ref);\n const [side, align] = useMemo(\n () => getSideAndAlignFromPlacement(placement),\n [placement]\n );\n const Component = asChild ? Slot : \"div\";\n useAnimationPersist(ref);\n\n return (\n <Component\n dir={dir}\n {...props}\n data-state={isPresent ? \"open\" : \"closed\"}\n data-side={side}\n data-align={align}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n maxHeight: \"var(--lb-composer-suggestions-available-height)\",\n overflowY: \"auto\",\n ...style,\n }}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a list of suggestions within `Composer.Editor`.\n *\n * @example\n * <Composer.SuggestionsList>\n * {userIds.map((userId) => (\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n * ))}\n * </Composer.SuggestionsList>\n */\nconst ComposerSuggestionsList = forwardRef<\n HTMLUListElement,\n ComposerSuggestionsListProps\n>(({ children, asChild, ...props }, forwardedRef) => {\n const { id } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_NAME);\n const Component = asChild ? Slot : \"ul\";\n\n return (\n <Component\n role=\"listbox\"\n id={id}\n aria-label=\"Suggestions list\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a suggestion within `Composer.SuggestionsList`.\n *\n * @example\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n */\nconst ComposerSuggestionsListItem = forwardRef<\n HTMLLIElement,\n ComposerSuggestionsListItemProps\n>(\n (\n { value, children, onPointerMove, onPointerDown, asChild, ...props },\n forwardedRef\n ) => {\n const ref = useRef<HTMLLIElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const { selectedValue, setSelectedValue, itemId, onItemSelect } =\n useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_ITEM_NAME);\n const Component = asChild ? Slot : \"li\";\n const isSelected = useMemo(\n () => selectedValue === value,\n [selectedValue, value]\n );\n // TODO: Support props.id if provided, it will need to be sent up to Composer.Editor to use it in aria-activedescendant\n const id = useMemo(() => itemId(value), [itemId, value]);\n\n useEffect(() => {\n if (ref?.current && isSelected) {\n ref.current.scrollIntoView({ block: \"nearest\" });\n }\n }, [isSelected]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerMove?.(event);\n\n if (!event.isDefaultPrevented()) {\n setSelectedValue(value);\n }\n },\n [onPointerMove, setSelectedValue, value]\n );\n\n const handlePointerDown = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerDown?.(event);\n\n if (!event.isDefaultPrevented()) {\n const target = event.target as HTMLElement;\n\n if (target.hasPointerCapture(event.pointerId)) {\n target.releasePointerCapture(event.pointerId);\n }\n\n if (event.button === 0 && event.ctrlKey === false) {\n onItemSelect(value);\n\n event.preventDefault();\n }\n }\n },\n [onItemSelect, onPointerDown, value]\n );\n\n return (\n <Component\n role=\"option\"\n id={id}\n data-selected={isSelected || undefined}\n aria-selected={isSelected || undefined}\n onPointerMove={handlePointerMove}\n onPointerDown={handlePointerDown}\n {...props}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n }\n);\n\nconst defaultEditorComponents: ComposerEditorComponents = {\n Link: ({ href, children }) => {\n return <ComposerLink href={href}>{children}</ComposerLink>;\n },\n Mention: ({ userId }) => {\n return (\n <ComposerMention>\n {MENTION_CHARACTER}\n {userId}\n </ComposerMention>\n );\n },\n MentionSuggestions: ({ userIds }) => {\n return userIds.length > 0 ? (\n <ComposerSuggestions>\n <ComposerSuggestionsList>\n {userIds.map((userId) => (\n <ComposerSuggestionsListItem key={userId} value={userId}>\n {userId}\n </ComposerSuggestionsListItem>\n ))}\n </ComposerSuggestionsList>\n </ComposerSuggestions>\n ) : null;\n },\n};\n\n/**\n * Displays the composer's editor.\n *\n * @example\n * <Composer.Editor placeholder=\"Write a comment…\" />\n */\nconst ComposerEditor = forwardRef<HTMLDivElement, ComposerEditorProps>(\n (\n {\n defaultValue,\n onKeyDown,\n onFocus,\n onBlur,\n disabled,\n autoFocus,\n components,\n dir,\n ...props\n },\n forwardedRef\n ) => {\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || !self?.canComment,\n [disabled, self?.canComment]\n );\n const { editor, validate, setFocused } = useComposerEditorContext();\n const { submit, focus, select, isEmpty, isFocused } = useComposer();\n const initialBody = useInitial(defaultValue ?? emptyCommentBody);\n const initialEditorValue = useMemo(() => {\n return commentBodyToComposerBody(initialBody);\n }, [initialBody]);\n const { Link, Mention, MentionSuggestions } = useMemo(\n () => ({ ...defaultEditorComponents, ...components }),\n [components]\n );\n\n const [mentionDraft, setMentionDraft] = useState<MentionDraft>();\n const mentionSuggestions = useMentionSuggestions(mentionDraft?.text);\n const [\n selectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setNextSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n ] = useIndex(0, mentionSuggestions?.length ?? 0);\n const id = useId();\n const suggestionsListId = useMemo(\n () => `liveblocks-suggestions-list-${id}`,\n [id]\n );\n const suggestionsListItemId = useCallback(\n (userId?: string) =>\n userId ? `liveblocks-suggestions-list-item-${id}-${userId}` : undefined,\n [id]\n );\n const renderElement = useCallback(\n (props: RenderElementProps) => {\n return (\n <ComposerEditorElement Mention={Mention} Link={Link} {...props} />\n );\n },\n [Link, Mention]\n );\n\n const handleChange = useCallback(\n (value: SlateDescendant[]) => {\n validate(value as SlateElement[]);\n\n setMentionDraft(getMentionDraftAtSelection(editor));\n },\n [editor, validate]\n );\n\n const createMention = useCallback(\n (userId?: string) => {\n if (!mentionDraft || !userId) {\n return;\n }\n\n SlateTransforms.select(editor, mentionDraft.range);\n insertMention(editor, userId);\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n },\n [editor, mentionDraft, setSelectedMentionSuggestionIndex]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Allow leaving marks with ArrowLeft\n if (isKey(event, \"ArrowLeft\")) {\n leaveMarkEdge(editor, \"start\");\n }\n\n // Allow leaving marks with ArrowRight\n if (isKey(event, \"ArrowRight\")) {\n leaveMarkEdge(editor, \"end\");\n }\n\n if (mentionDraft && mentionSuggestions?.length) {\n // Select the next mention suggestion on ArrowDown\n if (isKey(event, \"ArrowDown\")) {\n event.preventDefault();\n setNextSelectedMentionSuggestionIndex();\n }\n\n // Select the previous mention suggestion on ArrowUp\n if (isKey(event, \"ArrowUp\")) {\n event.preventDefault();\n setPreviousSelectedMentionSuggestionIndex();\n }\n\n // Create a mention on Enter/Tab\n if (isKey(event, \"Enter\") || isKey(event, \"Tab\")) {\n event.preventDefault();\n\n const userId = mentionSuggestions?.[selectedMentionSuggestionIndex];\n createMention(userId);\n }\n\n // Close the suggestions on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n }\n } else {\n // Blur the editor on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n ReactEditor.blur(editor);\n }\n\n // Submit the editor on Enter\n if (isKey(event, \"Enter\", { shift: false }) && !isEmpty) {\n event.preventDefault();\n submit();\n }\n\n // Create a new line on Shift + Enter\n if (isKey(event, \"Enter\", { shift: true })) {\n event.preventDefault();\n editor.insertBreak();\n }\n\n // Toggle bold on Command/Control + B\n if (isKey(event, \"b\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"bold\");\n }\n\n // Toggle italic on Command/Control + I\n if (isKey(event, \"i\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"italic\");\n }\n\n // Toggle strikethrough on Command/Control + Shift + S\n if (isKey(event, \"s\", { mod: true, shift: true })) {\n event.preventDefault();\n toggleMark(editor, \"strikethrough\");\n }\n\n // Toggle code on Command/Control + E\n if (isKey(event, \"e\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"code\");\n }\n }\n },\n [\n createMention,\n editor,\n isEmpty,\n mentionDraft,\n mentionSuggestions,\n selectedMentionSuggestionIndex,\n onKeyDown,\n setNextSelectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n submit,\n ]\n );\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(true);\n }\n },\n [onFocus, setFocused]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(false);\n }\n },\n [onBlur, setFocused]\n );\n\n const selectedMentionSuggestionUserId = useMemo(\n () => mentionSuggestions?.[selectedMentionSuggestionIndex],\n [selectedMentionSuggestionIndex, mentionSuggestions]\n );\n const setSelectedMentionSuggestionUserId = useCallback(\n (userId: string) => {\n const index = mentionSuggestions?.indexOf(userId);\n\n if (index !== undefined && index >= 0) {\n setSelectedMentionSuggestionIndex(index);\n }\n },\n [setSelectedMentionSuggestionIndex, mentionSuggestions]\n );\n\n const propsWhileSuggesting: AriaAttributes = useMemo(\n () =>\n mentionDraft\n ? {\n role: \"combobox\",\n \"aria-autocomplete\": \"list\",\n \"aria-expanded\": true,\n \"aria-controls\": suggestionsListId,\n \"aria-activedescendant\": suggestionsListItemId(\n selectedMentionSuggestionUserId\n ),\n }\n : {},\n [\n mentionDraft,\n suggestionsListId,\n suggestionsListItemId,\n selectedMentionSuggestionUserId,\n ]\n );\n\n useImperativeHandle(\n forwardedRef,\n () => {\n return ReactEditor.toDOMNode(editor, editor) as HTMLDivElement;\n },\n [editor]\n );\n\n // Manually focus the editor when `autoFocus` is true\n useEffect(() => {\n if (autoFocus) {\n focus();\n }\n }, [autoFocus, editor, focus]);\n\n // Manually add a selection in the editor if the selection\n // is still empty after being focused\n useEffect(() => {\n if (isFocused && editor.selection === null) {\n select();\n }\n }, [editor, select, isFocused]);\n\n return (\n <Slate\n editor={editor}\n initialValue={initialEditorValue}\n onChange={handleChange}\n >\n <Editable\n dir={dir}\n enterKeyHint={mentionDraft ? \"enter\" : \"send\"}\n autoCapitalize=\"sentences\"\n aria-label=\"Composer editor\"\n data-focused={isFocused || undefined}\n data-disabled={isDisabled || undefined}\n {...propsWhileSuggesting}\n {...props}\n readOnly={isDisabled}\n disabled={isDisabled}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n renderElement={renderElement}\n renderLeaf={ComposerEditorLeaf}\n renderPlaceholder={ComposerEditorPlaceholder}\n />\n <ComposerEditorMentionSuggestionsWrapper\n dir={dir}\n mentionDraft={mentionDraft}\n selectedUserId={selectedMentionSuggestionUserId}\n setSelectedUserId={setSelectedMentionSuggestionUserId}\n userIds={mentionSuggestions}\n id={suggestionsListId}\n itemId={suggestionsListItemId}\n onItemSelect={createMention}\n MentionSuggestions={MentionSuggestions}\n />\n </Slate>\n );\n }\n);\n\n/**\n * Surrounds the composer's content and handles submissions.\n *\n * @example\n * <Composer.Form onComposerSubmit={({ body }) => {}}>\n *\t <Composer.Editor />\n * <Composer.Submit />\n * </Composer.Form>\n */\nconst ComposerForm = forwardRef<HTMLFormElement, ComposerFormProps>(\n (\n { children, onSubmit, onComposerSubmit, asChild, ...props },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"form\";\n const editor = useInitial(createComposerEditor);\n const [isEmpty, setEmpty] = useState(true);\n const [isFocused, setFocused] = useState(false);\n const ref = useRef<HTMLFormElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n\n const validate = useCallback(\n (value: SlateElement[]) => {\n setEmpty(isEditorEmpty(editor, value));\n },\n [editor]\n );\n\n const submit = useCallback(() => {\n if (ref.current) {\n requestSubmit(ref.current);\n }\n }, []);\n\n const clear = useCallback(() => {\n SlateTransforms.delete(editor, {\n at: {\n anchor: SlateEditor.start(editor, []),\n focus: SlateEditor.end(editor, []),\n },\n });\n }, [editor]);\n\n const select = useCallback(() => {\n SlateTransforms.select(editor, {\n anchor: SlateEditor.end(editor, []),\n focus: SlateEditor.end(editor, []),\n });\n }, [editor]);\n\n const focus = useCallback(\n (resetSelection = true) => {\n if (!ReactEditor.isFocused(editor)) {\n SlateTransforms.select(\n editor,\n resetSelection || !editor.selection\n ? SlateEditor.end(editor, [])\n : editor.selection\n );\n ReactEditor.focus(editor);\n }\n },\n [editor]\n );\n\n const blur = useCallback(() => {\n ReactEditor.blur(editor);\n }, [editor]);\n\n const onSubmitEnd = useCallback(() => {\n clear();\n blur();\n }, [blur, clear]);\n\n const createMention = useCallback(() => {\n focus();\n insertMentionCharacter(editor);\n }, [editor, focus]);\n\n const insertText = useCallback(\n (text: string) => {\n focus(false);\n insertSlateText(editor, text);\n },\n [editor, focus]\n );\n\n const handleSubmit = useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n onSubmit?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n const body = composerBodyToCommentBody(\n editor.children as ComposerBodyData\n );\n const comment = { body };\n\n const promise = onComposerSubmit?.(comment, event);\n\n event.preventDefault();\n\n if (promise) {\n promise.then(onSubmitEnd);\n } else {\n onSubmitEnd();\n }\n },\n [editor.children, onComposerSubmit, onSubmit, onSubmitEnd]\n );\n\n return (\n <ComposerEditorContext.Provider\n value={{\n editor,\n validate,\n setFocused,\n }}\n >\n <ComposerContext.Provider\n value={{\n isFocused,\n isEmpty,\n submit,\n clear,\n select,\n focus,\n blur,\n createMention,\n insertText,\n }}\n >\n <Component {...props} onSubmit={handleSubmit} ref={mergedRefs}>\n {children}\n </Component>\n </ComposerContext.Provider>\n </ComposerEditorContext.Provider>\n );\n }\n);\n\n/**\n * A button to submit the composer.\n *\n * @example\n * <Composer.Submit>Send</Composer.Submit>\n */\nconst ComposerSubmit = forwardRef<HTMLButtonElement, ComposerSubmitProps>(\n ({ children, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { isEmpty } = useComposer();\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || isEmpty || !self?.canComment,\n [disabled, isEmpty, self?.canComment]\n );\n\n return (\n <Component\n type=\"submit\"\n {...props}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n ComposerEditor.displayName = COMPOSER_EDITOR_NAME;\n ComposerForm.displayName = COMPOSER_FORM_NAME;\n ComposerMention.displayName = COMPOSER_MENTION_NAME;\n ComposerLink.displayName = COMPOSER_LINK_NAME;\n ComposerSubmit.displayName = COMPOSER_SUBMIT_NAME;\n ComposerSuggestions.displayName = COMPOSER_SUGGESTIONS_NAME;\n ComposerSuggestionsList.displayName = COMPOSER_SUGGESTIONS_LIST_NAME;\n ComposerSuggestionsListItem.displayName = COMPOSER_SUGGESTIONS_LIST_ITEM_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as Composer.*\nexport {\n ComposerEditor as Editor,\n ComposerForm as Form,\n ComposerLink as Link,\n ComposerMention as Mention,\n ComposerSubmit as Submit,\n ComposerSuggestions as Suggestions,\n ComposerSuggestionsList as SuggestionsList,\n ComposerSuggestionsListItem as SuggestionsListItem,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2HA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAsC;AAC3B;AAEX;AAEA;AACE;AAAO;AACL;AACE;AAC2D;AAC3D;AACF;AAEJ;AAEA;AAAsC;AACpC;AACA;AACA;AAEF;AACE;AAEA;AACG;AAAS;AAEL;AAAwB;AAAI;AAKrC;AAEA;AAAmC;AACjC;AACA;AACA;AAEF;AACE;AAAa;AACiC;AAChC;AAGd;AACG;AAAS;AACP;AAAK;AAGZ;AAEA;AAAiD;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAqD;AAC1C;AAGX;AAAO;AACK;AACuC;AACrC;AACyC;AACzB;AACpB;AACD;AACiB;AACrB;AACI;AACA;AAED;AAAwB;AACtB;AACG;AAEL;AAAwB;AACtB;AACG;AACL;AACF;AACD;AACH;AACsB;AACxB;AAEF;AAAM;AAC8B;AAClC;AACA;AACA;AACA;AACA;AAKF;AACE;AACE;AAAwD;AAC1D;AAGF;AACE;AACE;AAAA;AAGF;AAEA;AACE;AAAa;AACiD;AACd;AAC/C;AACH;AAGF;AAGO;AACQ;AACL;AACA;AACe;AACG;AAClB;AACA;AACA;AACK;AACP;AAEC;AACM;AACM;AACJ;AACK;AACL;AACC;AAGF;AACM;AACF;AACV;AAEC;AACC;AACA;AAOd;AAEA;AAA+B;AAC7B;AACA;AAEF;AAEE;AAEA;AAAsB;AAElB;AACG;AACC;AACK;AACP;AAGF;AACG;AACC;AACK;AACP;AAGF;AACG;AAAM;AAA0C;AAEjD;AAGF;AAAO;AAEb;AAGA;AACE;AACE;AAA6B;AAG/B;AACE;AAAyB;AAG3B;AACE;AAAwB;AAG1B;AACE;AAA2B;AAG7B;AAAQ;AAAS;AACnB;AAEA;AAAmC;AACjC;AAEF;AACE;AAEA;AACG;AAAS;AAAY;AAA+B;AAIzD;AAQA;AAAwB;AAEpB;AACA;AAEA;AACG;AAC8B;AACzB;AACC;AAGP;AAGN;AAQA;AAAqB;AAEjB;AAEA;AACG;AACQ;AACH;AACA;AACC;AAGP;AAGN;AAKM;AAIJ;AACA;AACA;AAAM;AACC;AACL;AACA;AAEF;AACA;AAAsB;AACwB;AAClC;AAEZ;AACA;AAEA;AACG;AACC;AACI;AAC6B;AACtB;AACC;AACL;AACI;AACM;AACJ;AACA;AACR;AACL;AACK;AAKX;AAcM;AAIJ;AACA;AAEA;AACG;AACM;AACL;AACW;AACP;AACC;AAKX;AAUA;AAAoC;AAQhC;AACA;AACA;AAEA;AACA;AAAmB;AACO;AACH;AAGvB;AAEA;AACE;AACE;AAA+C;AACjD;AAGF;AAA0B;AAEtB;AAEA;AACE;AAAsB;AACxB;AACF;AACuC;AAGzC;AAA0B;AAEtB;AAEA;AACE;AAEA;AACE;AAA4C;AAG9C;AACE;AAEA;AAAqB;AACvB;AACF;AACF;AACmC;AAGrC;AACG;AACM;AACL;AAC6B;AACA;AACd;AACA;AACX;AACC;AAGP;AAGN;AAEA;AAA0D;AAEtD;AAAQ;AAAa;AAAsB;AAC7C;AAEE;AAIE;AAEJ;AAEE;AAIS;AAAiC;AAAe;AAMrD;AAER;AAQA;AAAuB;AAEnB;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AAAmB;AACQ;AACE;AAE7B;AACA;AACA;AACA;AACE;AAA4C;AAE9C;AAA8C;AACO;AACxC;AAGb;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AAEF;AACA;AAA0B;AACa;AAClC;AAEL;AAA8B;AAEoC;AAC7D;AAEL;AAAsB;AAElB;AACG;AAAsB;AAAkB;AAAgB;AAAO;AAEpE;AACc;AAGhB;AAAqB;AAEjB;AAEA;AAAkD;AACpD;AACiB;AAGnB;AAAsB;AAElB;AACE;AAAA;AAGF;AACA;AACA;AACA;AAAmC;AACrC;AACwD;AAG1D;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAA6B;AAI/B;AACE;AAA2B;AAG7B;AAEE;AACE;AACA;AAAsC;AAIxC;AACE;AACA;AAA0C;AAI5C;AACE;AAEA;AACA;AAAoB;AAItB;AACE;AACA;AACA;AAAmC;AACrC;AAGA;AACE;AACA;AAAuB;AAIzB;AACE;AACA;AAAO;AAIT;AACE;AACA;AAAmB;AAIrB;AACE;AACA;AAAyB;AAI3B;AACE;AACA;AAA2B;AAI7B;AACE;AACA;AAAkC;AAIpC;AACE;AACA;AAAyB;AAC3B;AACF;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AAAoB;AAEhB;AAEA;AACE;AAAe;AACjB;AACF;AACoB;AAGtB;AAAmB;AAEf;AAEA;AACE;AAAgB;AAClB;AACF;AACmB;AAGrB;AAAwC;AACX;AACwB;AAErD;AAA2C;AAEvC;AAEA;AACE;AAAuC;AACzC;AACF;AACsD;AAGxD;AAA6C;AAGrC;AACQ;AACe;AACJ;AACA;AACQ;AACvB;AACF;AAED;AACP;AACE;AACA;AACA;AACA;AACF;AAGF;AAAA;AACE;AAEE;AAA2C;AAC7C;AACO;AAIT;AACE;AACE;AAAM;AACR;AAKF;AACE;AACE;AAAO;AACT;AAGF;AACG;AACC;AACc;AACJ;AAET;AACC;AACuC;AACxB;AACJ;AACgB;AACE;AACzB;AACA;AACM;AACA;AACC;AACF;AACD;AACR;AACY;AACO;AAEpB;AACC;AACA;AACgB;AACG;AACV;AACL;AACI;AACM;AACd;AAEJ;AAGN;AAWA;AAAqB;AAKjB;AACA;AACA;AACA;AACA;AACA;AAEA;AAAiB;AAEb;AAAqC;AACvC;AACO;AAGT;AACE;AACE;AAAyB;AAC3B;AAGF;AACE;AAA+B;AACzB;AACkC;AACH;AACnC;AACD;AAGH;AACE;AAA+B;AACK;AACD;AAClC;AAGH;AAAc;AAEV;AACE;AAAgB;AACd;AAGW;AAEb;AAAwB;AAC1B;AACF;AACO;AAGT;AACE;AAAuB;AAGzB;AACE;AACA;AAAK;AAGP;AACE;AACA;AAA6B;AAG/B;AAAmB;AAEf;AACA;AAA4B;AAC9B;AACc;AAGhB;AAAqB;AAEjB;AAEA;AACE;AAAA;AAGF;AAAa;AACJ;AAET;AAEA;AAEA;AAEA;AACE;AAAwB;AAExB;AAAY;AACd;AACF;AACyD;AAG3D;AACG;AACQ;AACL;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAEC;AAAc;AAAiB;AAAmB;AAIvD;AAGN;AAQA;AAAuB;AAEnB;AACA;AACA;AACA;AAAmB;AACmB;AACA;AAGtC;AACG;AACM;AACD;AACC;AACK;AAGZ;AAGN;AAEA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;;"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../../src/primitives/Composer/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n DetectOverflowOptions,\n UseFloatingOptions,\n} from \"@floating-ui/react-dom\";\nimport {\n autoUpdate,\n flip,\n hide,\n limitShift,\n shift,\n size,\n useFloating,\n} from \"@floating-ui/react-dom\";\nimport type { CommentBody } from \"@liveblocks/core\";\nimport { useSelf } from \"@liveblocks/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport type {\n AriaAttributes,\n FocusEvent,\n FormEvent,\n KeyboardEvent,\n PointerEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Descendant as SlateDescendant,\n Element as SlateElement,\n} from \"slate\";\nimport {\n createEditor,\n Editor as SlateEditor,\n insertText as insertSlateText,\n Transforms as SlateTransforms,\n} from \"slate\";\nimport { withHistory } from \"slate-history\";\nimport type {\n RenderElementProps,\n RenderElementSpecificProps,\n RenderLeafProps,\n RenderPlaceholderProps,\n} from \"slate-react\";\nimport {\n Editable,\n ReactEditor,\n Slate,\n useSelected,\n useSlateStatic,\n withReact,\n} from \"slate-react\";\n\nimport { useLiveblocksUIConfig } from \"../../config\";\nimport { FLOATING_ELEMENT_COLLISION_PADDING } from \"../../constants\";\nimport { useMentionSuggestions } from \"../../shared\";\nimport { withAutoFormatting } from \"../../slate/plugins/auto-formatting\";\nimport { withAutoLinks } from \"../../slate/plugins/auto-links\";\nimport { withEmptyClearFormatting } from \"../../slate/plugins/empty-clear-formatting\";\nimport type { MentionDraft } from \"../../slate/plugins/mentions\";\nimport {\n getMentionDraftAtSelection,\n insertMention,\n insertMentionCharacter,\n MENTION_CHARACTER,\n withMentions,\n} from \"../../slate/plugins/mentions\";\nimport { getDOMRange } from \"../../slate/utils/get-dom-range\";\nimport { isEmpty as isEditorEmpty } from \"../../slate/utils/is-empty\";\nimport { leaveMarkEdge, toggleMark } from \"../../slate/utils/marks\";\nimport type {\n ComposerBody as ComposerBodyData,\n ComposerBodyAutoLink,\n ComposerBodyMention,\n} from \"../../types\";\nimport { isKey } from \"../../utils/is-key\";\nimport { Persist, useAnimationPersist, usePersist } from \"../../utils/Persist\";\nimport { Portal } from \"../../utils/Portal\";\nimport { requestSubmit } from \"../../utils/request-submit\";\nimport { useId } from \"../../utils/use-id\";\nimport { useIndex } from \"../../utils/use-index\";\nimport { useInitial } from \"../../utils/use-initial\";\nimport { useLayoutEffect } from \"../../utils/use-layout-effect\";\nimport { useRefs } from \"../../utils/use-refs\";\nimport { toAbsoluteUrl } from \"../Comment/utils\";\nimport {\n ComposerContext,\n ComposerEditorContext,\n ComposerSuggestionsContext,\n useComposer,\n useComposerEditorContext,\n useComposerSuggestionsContext,\n} from \"./contexts\";\nimport type {\n ComposerEditorComponents,\n ComposerEditorElementProps,\n ComposerEditorLinkWrapperProps,\n ComposerEditorMentionSuggestionsWrapperProps,\n ComposerEditorMentionWrapperProps,\n ComposerEditorProps,\n ComposerFormProps,\n ComposerLinkProps,\n ComposerMentionProps,\n ComposerSubmitProps,\n ComposerSuggestionsListItemProps,\n ComposerSuggestionsListProps,\n ComposerSuggestionsProps,\n SuggestionsPosition,\n} from \"./types\";\nimport {\n commentBodyToComposerBody,\n composerBodyToCommentBody,\n getPlacementFromPosition,\n getSideAndAlignFromPlacement,\n} from \"./utils\";\n\nconst MENTION_SUGGESTIONS_POSITION: SuggestionsPosition = \"top\";\n\nconst COMPOSER_MENTION_NAME = \"ComposerMention\";\nconst COMPOSER_LINK_NAME = \"ComposerLink\";\nconst COMPOSER_SUGGESTIONS_NAME = \"ComposerSuggestions\";\nconst COMPOSER_SUGGESTIONS_LIST_NAME = \"ComposerSuggestionsList\";\nconst COMPOSER_SUGGESTIONS_LIST_ITEM_NAME = \"ComposerSuggestionsListItem\";\nconst COMPOSER_SUBMIT_NAME = \"ComposerSubmit\";\nconst COMPOSER_EDITOR_NAME = \"ComposerEditor\";\nconst COMPOSER_FORM_NAME = \"ComposerForm\";\n\nconst emptyCommentBody: CommentBody = {\n version: 1,\n content: [{ type: \"paragraph\", children: [{ text: \"\" }] }],\n};\n\nfunction createComposerEditor() {\n return withAutoLinks(\n withMentions(\n withEmptyClearFormatting(\n withAutoFormatting(withHistory(withReact(createEditor())))\n )\n )\n );\n}\n\nfunction ComposerEditorMentionWrapper({\n Mention,\n attributes,\n children,\n element,\n}: ComposerEditorMentionWrapperProps) {\n const isSelected = useSelected();\n\n return (\n <span {...attributes}>\n {element.id ? (\n <Mention userId={element.id} isSelected={isSelected} />\n ) : null}\n {children}\n </span>\n );\n}\n\nfunction ComposerEditorLinkWrapper({\n Link,\n attributes,\n element,\n children,\n}: ComposerEditorLinkWrapperProps) {\n const href = useMemo(\n () => toAbsoluteUrl(element.url) ?? element.url,\n [element.url]\n );\n\n return (\n <span {...attributes}>\n <Link href={href}>{children}</Link>\n </span>\n );\n}\n\nfunction ComposerEditorMentionSuggestionsWrapper({\n id,\n itemId,\n userIds,\n selectedUserId,\n setSelectedUserId,\n mentionDraft,\n onItemSelect,\n position = MENTION_SUGGESTIONS_POSITION,\n dir,\n MentionSuggestions,\n}: ComposerEditorMentionSuggestionsWrapperProps) {\n const editor = useSlateStatic();\n const { isFocused } = useComposer();\n const [content, setContent] = useState<HTMLDivElement | null>(null);\n const [contentZIndex, setContentZIndex] = useState<string>();\n const contentRef = useCallback(setContent, [setContent]);\n const { portalContainer } = useLiveblocksUIConfig();\n const floatingOptions: UseFloatingOptions = useMemo(() => {\n const detectOverflowOptions: DetectOverflowOptions = {\n padding: FLOATING_ELEMENT_COLLISION_PADDING,\n };\n\n return {\n strategy: \"fixed\",\n placement: getPlacementFromPosition(position, dir),\n middleware: [\n flip({ ...detectOverflowOptions, crossAxis: false }),\n hide(detectOverflowOptions),\n shift({\n ...detectOverflowOptions,\n limiter: limitShift(),\n }),\n size({\n ...detectOverflowOptions,\n apply({ availableWidth, availableHeight, elements }) {\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-width\",\n `${availableWidth}px`\n );\n elements.floating.style.setProperty(\n \"--lb-composer-suggestions-available-height\",\n `${availableHeight}px`\n );\n },\n }),\n ],\n whileElementsMounted: autoUpdate,\n };\n }, [position, dir]);\n const {\n refs: { setReference, setFloating },\n strategy,\n isPositioned,\n placement,\n x,\n y,\n } = useFloating(floatingOptions);\n\n // Copy `z-index` from content to wrapper.\n // Inspired by https://github.com/radix-ui/primitives/blob/main/packages/react/popper/src/Popper.tsx\n useLayoutEffect(() => {\n if (content) {\n setContentZIndex(window.getComputedStyle(content).zIndex);\n }\n }, [content]);\n\n useLayoutEffect(() => {\n if (!mentionDraft) {\n return;\n }\n\n const domRange = getDOMRange(editor, mentionDraft.range);\n\n if (domRange) {\n setReference({\n getBoundingClientRect: () => domRange.getBoundingClientRect(),\n getClientRects: () => domRange.getClientRects(),\n });\n }\n }, [setReference, editor, mentionDraft]);\n\n return (\n <Persist>\n {mentionDraft?.range && isFocused && userIds ? (\n <ComposerSuggestionsContext.Provider\n value={{\n id,\n itemId,\n selectedValue: selectedUserId,\n setSelectedValue: setSelectedUserId,\n onItemSelect,\n placement,\n dir,\n ref: contentRef,\n }}\n >\n <Portal\n ref={setFloating}\n container={portalContainer}\n style={{\n position: strategy,\n top: 0,\n left: 0,\n transform: isPositioned\n ? `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`\n : \"translate3d(0, -200%, 0)\",\n minWidth: \"max-content\",\n zIndex: contentZIndex,\n }}\n >\n <MentionSuggestions\n userIds={userIds}\n selectedUserId={selectedUserId}\n />\n </Portal>\n </ComposerSuggestionsContext.Provider>\n ) : null}\n </Persist>\n );\n}\n\nfunction ComposerEditorElement({\n Mention,\n Link,\n ...props\n}: ComposerEditorElementProps) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { attributes, children, element } = props;\n\n switch (element.type) {\n case \"mention\":\n return (\n <ComposerEditorMentionWrapper\n Mention={Mention}\n {...(props as RenderElementSpecificProps<ComposerBodyMention>)}\n />\n );\n case \"auto-link\":\n return (\n <ComposerEditorLinkWrapper\n Link={Link}\n {...(props as RenderElementSpecificProps<ComposerBodyAutoLink>)}\n />\n );\n case \"paragraph\":\n return (\n <p {...attributes} style={{ position: \"relative\" }}>\n {children}\n </p>\n );\n default:\n return null;\n }\n}\n\n// <code><s><em><strong>text</strong></s></em></code>\nfunction ComposerEditorLeaf({ attributes, children, leaf }: RenderLeafProps) {\n if (leaf.bold) {\n children = <strong>{children}</strong>;\n }\n\n if (leaf.italic) {\n children = <em>{children}</em>;\n }\n\n if (leaf.strikethrough) {\n children = <s>{children}</s>;\n }\n\n if (leaf.code) {\n children = <code>{children}</code>;\n }\n\n return <span {...attributes}>{children}</span>;\n}\n\nfunction ComposerEditorPlaceholder({\n attributes,\n children,\n}: RenderPlaceholderProps) {\n const { opacity: _opacity, ...style } = attributes.style;\n\n return (\n <span {...attributes} style={style} data-placeholder=\"\">\n {children}\n </span>\n );\n}\n\n/**\n * Displays mentions within `Composer.Editor`.\n *\n * @example\n * <Composer.Mention>@{userId}</Composer.Mention>\n */\nconst ComposerMention = forwardRef<HTMLSpanElement, ComposerMentionProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"span\";\n const isSelected = useSelected();\n\n return (\n <Component\n data-selected={isSelected || undefined}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Displays links within `Composer.Editor`.\n *\n * @example\n * <Composer.Link href={href}>{children}</Composer.Link>\n */\nconst ComposerLink = forwardRef<HTMLAnchorElement, ComposerLinkProps>(\n ({ children, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"a\";\n\n return (\n <Component\n target=\"_blank\"\n rel=\"noopener noreferrer nofollow\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n }\n);\n\n/**\n * Contains suggestions within `Composer.Editor`.\n */\nconst ComposerSuggestions = forwardRef<\n HTMLDivElement,\n ComposerSuggestionsProps\n>(({ children, style, asChild, ...props }, forwardedRef) => {\n const [isPresent] = usePersist();\n const ref = useRef<HTMLDivElement>(null);\n const {\n ref: contentRef,\n placement,\n dir,\n } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_NAME);\n const mergedRefs = useRefs(forwardedRef, contentRef, ref);\n const [side, align] = useMemo(\n () => getSideAndAlignFromPlacement(placement),\n [placement]\n );\n const Component = asChild ? Slot : \"div\";\n useAnimationPersist(ref);\n\n return (\n <Component\n dir={dir}\n {...props}\n data-state={isPresent ? \"open\" : \"closed\"}\n data-side={side}\n data-align={align}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n maxHeight: \"var(--lb-composer-suggestions-available-height)\",\n overflowY: \"auto\",\n ...style,\n }}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a list of suggestions within `Composer.Editor`.\n *\n * @example\n * <Composer.SuggestionsList>\n * {userIds.map((userId) => (\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n * ))}\n * </Composer.SuggestionsList>\n */\nconst ComposerSuggestionsList = forwardRef<\n HTMLUListElement,\n ComposerSuggestionsListProps\n>(({ children, asChild, ...props }, forwardedRef) => {\n const { id } = useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_NAME);\n const Component = asChild ? Slot : \"ul\";\n\n return (\n <Component\n role=\"listbox\"\n id={id}\n aria-label=\"Suggestions list\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Component>\n );\n});\n\n/**\n * Displays a suggestion within `Composer.SuggestionsList`.\n *\n * @example\n * <Composer.SuggestionsListItem key={userId} value={userId}>\n * @{userId}\n * </Composer.SuggestionsListItem>\n */\nconst ComposerSuggestionsListItem = forwardRef<\n HTMLLIElement,\n ComposerSuggestionsListItemProps\n>(\n (\n { value, children, onPointerMove, onPointerDown, asChild, ...props },\n forwardedRef\n ) => {\n const ref = useRef<HTMLLIElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const { selectedValue, setSelectedValue, itemId, onItemSelect } =\n useComposerSuggestionsContext(COMPOSER_SUGGESTIONS_LIST_ITEM_NAME);\n const Component = asChild ? Slot : \"li\";\n const isSelected = useMemo(\n () => selectedValue === value,\n [selectedValue, value]\n );\n // TODO: Support props.id if provided, it will need to be sent up to Composer.Editor to use it in aria-activedescendant\n const id = useMemo(() => itemId(value), [itemId, value]);\n\n useEffect(() => {\n if (ref?.current && isSelected) {\n ref.current.scrollIntoView({ block: \"nearest\" });\n }\n }, [isSelected]);\n\n const handlePointerMove = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerMove?.(event);\n\n if (!event.isDefaultPrevented()) {\n setSelectedValue(value);\n }\n },\n [onPointerMove, setSelectedValue, value]\n );\n\n const handlePointerDown = useCallback(\n (event: PointerEvent<HTMLLIElement>) => {\n onPointerDown?.(event);\n\n if (!event.isDefaultPrevented()) {\n const target = event.target as HTMLElement;\n\n if (target.hasPointerCapture(event.pointerId)) {\n target.releasePointerCapture(event.pointerId);\n }\n\n if (event.button === 0 && event.ctrlKey === false) {\n onItemSelect(value);\n\n event.preventDefault();\n }\n }\n },\n [onItemSelect, onPointerDown, value]\n );\n\n return (\n <Component\n role=\"option\"\n id={id}\n data-selected={isSelected || undefined}\n aria-selected={isSelected || undefined}\n onPointerMove={handlePointerMove}\n onPointerDown={handlePointerDown}\n {...props}\n ref={mergedRefs}\n >\n {children}\n </Component>\n );\n }\n);\n\nconst defaultEditorComponents: ComposerEditorComponents = {\n Link: ({ href, children }) => {\n return <ComposerLink href={href}>{children}</ComposerLink>;\n },\n Mention: ({ userId }) => {\n return (\n <ComposerMention>\n {MENTION_CHARACTER}\n {userId}\n </ComposerMention>\n );\n },\n MentionSuggestions: ({ userIds }) => {\n return userIds.length > 0 ? (\n <ComposerSuggestions>\n <ComposerSuggestionsList>\n {userIds.map((userId) => (\n <ComposerSuggestionsListItem key={userId} value={userId}>\n {userId}\n </ComposerSuggestionsListItem>\n ))}\n </ComposerSuggestionsList>\n </ComposerSuggestions>\n ) : null;\n },\n};\n\n/**\n * Displays the composer's editor.\n *\n * @example\n * <Composer.Editor placeholder=\"Write a comment…\" />\n */\nconst ComposerEditor = forwardRef<HTMLDivElement, ComposerEditorProps>(\n (\n {\n defaultValue,\n onKeyDown,\n onFocus,\n onBlur,\n disabled,\n autoFocus,\n components,\n dir,\n ...props\n },\n forwardedRef\n ) => {\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || !self?.canComment,\n [disabled, self?.canComment]\n );\n const { editor, validate, setFocused } = useComposerEditorContext();\n const { submit, focus, select, isEmpty, isFocused } = useComposer();\n const initialBody = useInitial(defaultValue ?? emptyCommentBody);\n const initialEditorValue = useMemo(() => {\n return commentBodyToComposerBody(initialBody);\n }, [initialBody]);\n const { Link, Mention, MentionSuggestions } = useMemo(\n () => ({ ...defaultEditorComponents, ...components }),\n [components]\n );\n\n const [mentionDraft, setMentionDraft] = useState<MentionDraft>();\n const mentionSuggestions = useMentionSuggestions(mentionDraft?.text);\n const [\n selectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setNextSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n ] = useIndex(0, mentionSuggestions?.length ?? 0);\n const id = useId();\n const suggestionsListId = useMemo(\n () => `liveblocks-suggestions-list-${id}`,\n [id]\n );\n const suggestionsListItemId = useCallback(\n (userId?: string) =>\n userId ? `liveblocks-suggestions-list-item-${id}-${userId}` : undefined,\n [id]\n );\n const renderElement = useCallback(\n (props: RenderElementProps) => {\n return (\n <ComposerEditorElement Mention={Mention} Link={Link} {...props} />\n );\n },\n [Link, Mention]\n );\n\n const handleChange = useCallback(\n (value: SlateDescendant[]) => {\n validate(value as SlateElement[]);\n\n setMentionDraft(getMentionDraftAtSelection(editor));\n },\n [editor, validate]\n );\n\n const createMention = useCallback(\n (userId?: string) => {\n if (!mentionDraft || !userId) {\n return;\n }\n\n SlateTransforms.select(editor, mentionDraft.range);\n insertMention(editor, userId);\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n },\n [editor, mentionDraft, setSelectedMentionSuggestionIndex]\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Allow leaving marks with ArrowLeft\n if (isKey(event, \"ArrowLeft\")) {\n leaveMarkEdge(editor, \"start\");\n }\n\n // Allow leaving marks with ArrowRight\n if (isKey(event, \"ArrowRight\")) {\n leaveMarkEdge(editor, \"end\");\n }\n\n if (mentionDraft && mentionSuggestions?.length) {\n // Select the next mention suggestion on ArrowDown\n if (isKey(event, \"ArrowDown\")) {\n event.preventDefault();\n setNextSelectedMentionSuggestionIndex();\n }\n\n // Select the previous mention suggestion on ArrowUp\n if (isKey(event, \"ArrowUp\")) {\n event.preventDefault();\n setPreviousSelectedMentionSuggestionIndex();\n }\n\n // Create a mention on Enter/Tab\n if (isKey(event, \"Enter\") || isKey(event, \"Tab\")) {\n event.preventDefault();\n\n const userId = mentionSuggestions?.[selectedMentionSuggestionIndex];\n createMention(userId);\n }\n\n // Close the suggestions on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n setMentionDraft(undefined);\n setSelectedMentionSuggestionIndex(0);\n }\n } else {\n // Blur the editor on Escape\n if (isKey(event, \"Escape\")) {\n event.preventDefault();\n ReactEditor.blur(editor);\n }\n\n // Submit the editor on Enter\n if (isKey(event, \"Enter\", { shift: false }) && !isEmpty) {\n event.preventDefault();\n submit();\n }\n\n // Create a new line on Shift + Enter\n if (isKey(event, \"Enter\", { shift: true })) {\n event.preventDefault();\n editor.insertBreak();\n }\n\n // Toggle bold on Command/Control + B\n if (isKey(event, \"b\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"bold\");\n }\n\n // Toggle italic on Command/Control + I\n if (isKey(event, \"i\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"italic\");\n }\n\n // Toggle strikethrough on Command/Control + Shift + S\n if (isKey(event, \"s\", { mod: true, shift: true })) {\n event.preventDefault();\n toggleMark(editor, \"strikethrough\");\n }\n\n // Toggle code on Command/Control + E\n if (isKey(event, \"e\", { mod: true })) {\n event.preventDefault();\n toggleMark(editor, \"code\");\n }\n }\n },\n [\n createMention,\n editor,\n isEmpty,\n mentionDraft,\n mentionSuggestions,\n selectedMentionSuggestionIndex,\n onKeyDown,\n setNextSelectedMentionSuggestionIndex,\n setPreviousSelectedMentionSuggestionIndex,\n setSelectedMentionSuggestionIndex,\n submit,\n ]\n );\n\n const handleFocus = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(true);\n }\n },\n [onFocus, setFocused]\n );\n\n const handleBlur = useCallback(\n (event: FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n\n if (!event.isDefaultPrevented()) {\n setFocused(false);\n }\n },\n [onBlur, setFocused]\n );\n\n const selectedMentionSuggestionUserId = useMemo(\n () => mentionSuggestions?.[selectedMentionSuggestionIndex],\n [selectedMentionSuggestionIndex, mentionSuggestions]\n );\n const setSelectedMentionSuggestionUserId = useCallback(\n (userId: string) => {\n const index = mentionSuggestions?.indexOf(userId);\n\n if (index !== undefined && index >= 0) {\n setSelectedMentionSuggestionIndex(index);\n }\n },\n [setSelectedMentionSuggestionIndex, mentionSuggestions]\n );\n\n const propsWhileSuggesting: AriaAttributes = useMemo(\n () =>\n mentionDraft\n ? {\n role: \"combobox\",\n \"aria-autocomplete\": \"list\",\n \"aria-expanded\": true,\n \"aria-controls\": suggestionsListId,\n \"aria-activedescendant\": suggestionsListItemId(\n selectedMentionSuggestionUserId\n ),\n }\n : {},\n [\n mentionDraft,\n suggestionsListId,\n suggestionsListItemId,\n selectedMentionSuggestionUserId,\n ]\n );\n\n useImperativeHandle(forwardedRef, () => {\n return ReactEditor.toDOMNode(editor, editor) as HTMLDivElement;\n }, [editor]);\n\n // Manually focus the editor when `autoFocus` is true\n useEffect(() => {\n if (autoFocus) {\n focus();\n }\n }, [autoFocus, editor, focus]);\n\n // Manually add a selection in the editor if the selection\n // is still empty after being focused\n useEffect(() => {\n if (isFocused && editor.selection === null) {\n select();\n }\n }, [editor, select, isFocused]);\n\n return (\n <Slate\n editor={editor}\n initialValue={initialEditorValue}\n onChange={handleChange}\n >\n <Editable\n dir={dir}\n enterKeyHint={mentionDraft ? \"enter\" : \"send\"}\n autoCapitalize=\"sentences\"\n aria-label=\"Composer editor\"\n data-focused={isFocused || undefined}\n data-disabled={isDisabled || undefined}\n {...propsWhileSuggesting}\n {...props}\n readOnly={isDisabled}\n disabled={isDisabled}\n onKeyDown={handleKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n renderElement={renderElement}\n renderLeaf={ComposerEditorLeaf}\n renderPlaceholder={ComposerEditorPlaceholder}\n />\n <ComposerEditorMentionSuggestionsWrapper\n dir={dir}\n mentionDraft={mentionDraft}\n selectedUserId={selectedMentionSuggestionUserId}\n setSelectedUserId={setSelectedMentionSuggestionUserId}\n userIds={mentionSuggestions}\n id={suggestionsListId}\n itemId={suggestionsListItemId}\n onItemSelect={createMention}\n MentionSuggestions={MentionSuggestions}\n />\n </Slate>\n );\n }\n);\n\n/**\n * Surrounds the composer's content and handles submissions.\n *\n * @example\n * <Composer.Form onComposerSubmit={({ body }) => {}}>\n *\t <Composer.Editor />\n * <Composer.Submit />\n * </Composer.Form>\n */\nconst ComposerForm = forwardRef<HTMLFormElement, ComposerFormProps>(\n (\n { children, onSubmit, onComposerSubmit, asChild, ...props },\n forwardedRef\n ) => {\n const Component = asChild ? Slot : \"form\";\n const editor = useInitial(createComposerEditor);\n const [isEmpty, setEmpty] = useState(true);\n const [isFocused, setFocused] = useState(false);\n const ref = useRef<HTMLFormElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n\n const validate = useCallback(\n (value: SlateElement[]) => {\n setEmpty(isEditorEmpty(editor, value));\n },\n [editor]\n );\n\n const submit = useCallback(() => {\n // We need to wait for the next frame in some cases like when composing diacritics,\n // we want any native handling to be done first while still being handled on `keydown`.\n requestAnimationFrame(() => {\n if (ref.current) {\n requestSubmit(ref.current);\n }\n });\n }, []);\n\n const clear = useCallback(() => {\n SlateTransforms.delete(editor, {\n at: {\n anchor: SlateEditor.start(editor, []),\n focus: SlateEditor.end(editor, []),\n },\n });\n }, [editor]);\n\n const select = useCallback(() => {\n SlateTransforms.select(editor, {\n anchor: SlateEditor.end(editor, []),\n focus: SlateEditor.end(editor, []),\n });\n }, [editor]);\n\n const focus = useCallback(\n (resetSelection = true) => {\n if (!ReactEditor.isFocused(editor)) {\n SlateTransforms.select(\n editor,\n resetSelection || !editor.selection\n ? SlateEditor.end(editor, [])\n : editor.selection\n );\n ReactEditor.focus(editor);\n }\n },\n [editor]\n );\n\n const blur = useCallback(() => {\n ReactEditor.blur(editor);\n }, [editor]);\n\n const onSubmitEnd = useCallback(() => {\n clear();\n blur();\n }, [blur, clear]);\n\n const createMention = useCallback(() => {\n focus();\n insertMentionCharacter(editor);\n }, [editor, focus]);\n\n const insertText = useCallback(\n (text: string) => {\n focus(false);\n insertSlateText(editor, text);\n },\n [editor, focus]\n );\n\n const handleSubmit = useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n // In some situations (e.g. pressing Enter while composing diacritics), it's possible\n // for the form to be submitted as empty even though we already checked whether the\n // editor was empty when handling the key press.\n const isEmpty = isEditorEmpty(editor, editor.children);\n\n // We even prevent the user's `onSubmit` handler from being called if the editor is empty.\n if (isEmpty) {\n event.preventDefault();\n\n return;\n }\n\n onSubmit?.(event);\n\n if (!onComposerSubmit || event.isDefaultPrevented()) {\n event.preventDefault();\n\n return;\n }\n\n const body = composerBodyToCommentBody(\n editor.children as ComposerBodyData\n );\n const comment = { body };\n\n const promise = onComposerSubmit(comment, event);\n\n event.preventDefault();\n\n if (promise) {\n promise.then(onSubmitEnd);\n } else {\n onSubmitEnd();\n }\n },\n [editor, onComposerSubmit, onSubmit, onSubmitEnd]\n );\n\n return (\n <ComposerEditorContext.Provider\n value={{\n editor,\n validate,\n setFocused,\n }}\n >\n <ComposerContext.Provider\n value={{\n isFocused,\n isEmpty,\n submit,\n clear,\n select,\n focus,\n blur,\n createMention,\n insertText,\n }}\n >\n <Component {...props} onSubmit={handleSubmit} ref={mergedRefs}>\n {children}\n </Component>\n </ComposerContext.Provider>\n </ComposerEditorContext.Provider>\n );\n }\n);\n\n/**\n * A button to submit the composer.\n *\n * @example\n * <Composer.Submit>Send</Composer.Submit>\n */\nconst ComposerSubmit = forwardRef<HTMLButtonElement, ComposerSubmitProps>(\n ({ children, disabled, asChild, ...props }, forwardedRef) => {\n const Component = asChild ? Slot : \"button\";\n const { isEmpty } = useComposer();\n const self = useSelf();\n const isDisabled = useMemo(\n () => disabled || isEmpty || !self?.canComment,\n [disabled, isEmpty, self?.canComment]\n );\n\n return (\n <Component\n type=\"submit\"\n {...props}\n ref={forwardedRef}\n disabled={isDisabled}\n >\n {children}\n </Component>\n );\n }\n);\n\nif (process.env.NODE_ENV !== \"production\") {\n ComposerEditor.displayName = COMPOSER_EDITOR_NAME;\n ComposerForm.displayName = COMPOSER_FORM_NAME;\n ComposerMention.displayName = COMPOSER_MENTION_NAME;\n ComposerLink.displayName = COMPOSER_LINK_NAME;\n ComposerSubmit.displayName = COMPOSER_SUBMIT_NAME;\n ComposerSuggestions.displayName = COMPOSER_SUGGESTIONS_NAME;\n ComposerSuggestionsList.displayName = COMPOSER_SUGGESTIONS_LIST_NAME;\n ComposerSuggestionsListItem.displayName = COMPOSER_SUGGESTIONS_LIST_ITEM_NAME;\n}\n\n// NOTE: Every export from this file will be available publicly as Composer.*\nexport {\n ComposerEditor as Editor,\n ComposerForm as Form,\n ComposerLink as Link,\n ComposerMention as Mention,\n ComposerSubmit as Submit,\n ComposerSuggestions as Suggestions,\n ComposerSuggestionsList as SuggestionsList,\n ComposerSuggestionsListItem as SuggestionsListItem,\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2HA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAsC;AAC3B;AAEX;AAEA;AACE;AAAO;AACL;AACE;AAC2D;AAC3D;AACF;AAEJ;AAEA;AAAsC;AACpC;AACA;AACA;AAEF;AACE;AAEA;AACG;AAAS;AAEL;AAAwB;AAAI;AAKrC;AAEA;AAAmC;AACjC;AACA;AACA;AAEF;AACE;AAAa;AACiC;AAChC;AAGd;AACG;AAAS;AACP;AAAK;AAGZ;AAEA;AAAiD;AAC/C;AACA;AACA;AACA;AACA;AACA;AACA;AACW;AACX;AAEF;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAqD;AAC1C;AAGX;AAAO;AACK;AACuC;AACrC;AACyC;AACzB;AACpB;AACD;AACiB;AACrB;AACI;AACA;AAED;AAAwB;AACtB;AACG;AAEL;AAAwB;AACtB;AACG;AACL;AACF;AACD;AACH;AACsB;AACxB;AAEF;AAAM;AAC8B;AAClC;AACA;AACA;AACA;AACA;AAKF;AACE;AACE;AAAwD;AAC1D;AAGF;AACE;AACE;AAAA;AAGF;AAEA;AACE;AAAa;AACiD;AACd;AAC/C;AACH;AAGF;AAGO;AACQ;AACL;AACA;AACe;AACG;AAClB;AACA;AACA;AACK;AACP;AAEC;AACM;AACM;AACJ;AACK;AACL;AACC;AAGF;AACM;AACF;AACV;AAEC;AACC;AACA;AAOd;AAEA;AAA+B;AAC7B;AACA;AAEF;AAEE;AAEA;AAAsB;AAElB;AACG;AACC;AACK;AACP;AAGF;AACG;AACC;AACK;AACP;AAGF;AACG;AAAM;AAA0C;AAEjD;AAGF;AAAO;AAEb;AAGA;AACE;AACE;AAA6B;AAG/B;AACE;AAAyB;AAG3B;AACE;AAAwB;AAG1B;AACE;AAA2B;AAG7B;AAAQ;AAAS;AACnB;AAEA;AAAmC;AACjC;AAEF;AACE;AAEA;AACG;AAAS;AAAY;AAA+B;AAIzD;AAQA;AAAwB;AAEpB;AACA;AAEA;AACG;AAC8B;AACzB;AACC;AAGP;AAGN;AAQA;AAAqB;AAEjB;AAEA;AACG;AACQ;AACH;AACA;AACC;AAGP;AAGN;AAKM;AAIJ;AACA;AACA;AAAM;AACC;AACL;AACA;AAEF;AACA;AAAsB;AACwB;AAClC;AAEZ;AACA;AAEA;AACG;AACC;AACI;AAC6B;AACtB;AACC;AACL;AACI;AACM;AACJ;AACA;AACR;AACL;AACK;AAKX;AAcM;AAIJ;AACA;AAEA;AACG;AACM;AACL;AACW;AACP;AACC;AAKX;AAUA;AAAoC;AAQhC;AACA;AACA;AAEA;AACA;AAAmB;AACO;AACH;AAGvB;AAEA;AACE;AACE;AAA+C;AACjD;AAGF;AAA0B;AAEtB;AAEA;AACE;AAAsB;AACxB;AACF;AACuC;AAGzC;AAA0B;AAEtB;AAEA;AACE;AAEA;AACE;AAA4C;AAG9C;AACE;AAEA;AAAqB;AACvB;AACF;AACF;AACmC;AAGrC;AACG;AACM;AACL;AAC6B;AACA;AACd;AACA;AACX;AACC;AAGP;AAGN;AAEA;AAA0D;AAEtD;AAAQ;AAAa;AAAsB;AAC7C;AAEE;AAIE;AAEJ;AAEE;AAIS;AAAiC;AAAe;AAMrD;AAER;AAQA;AAAuB;AAEnB;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AAAmB;AACQ;AACE;AAE7B;AACA;AACA;AACA;AACE;AAA4C;AAE9C;AAA8C;AACO;AACxC;AAGb;AACA;AACA;AAAM;AACJ;AACA;AACA;AACA;AAEF;AACA;AAA0B;AACa;AAClC;AAEL;AAA8B;AAEoC;AAC7D;AAEL;AAAsB;AAElB;AACG;AAAsB;AAAkB;AAAgB;AAAO;AAEpE;AACc;AAGhB;AAAqB;AAEjB;AAEA;AAAkD;AACpD;AACiB;AAGnB;AAAsB;AAElB;AACE;AAAA;AAGF;AACA;AACA;AACA;AAAmC;AACrC;AACwD;AAG1D;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAA6B;AAI/B;AACE;AAA2B;AAG7B;AAEE;AACE;AACA;AAAsC;AAIxC;AACE;AACA;AAA0C;AAI5C;AACE;AAEA;AACA;AAAoB;AAItB;AACE;AACA;AACA;AAAmC;AACrC;AAGA;AACE;AACA;AAAuB;AAIzB;AACE;AACA;AAAO;AAIT;AACE;AACA;AAAmB;AAIrB;AACE;AACA;AAAyB;AAI3B;AACE;AACA;AAA2B;AAI7B;AACE;AACA;AAAkC;AAIpC;AACE;AACA;AAAyB;AAC3B;AACF;AACF;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAGF;AAAoB;AAEhB;AAEA;AACE;AAAe;AACjB;AACF;AACoB;AAGtB;AAAmB;AAEf;AAEA;AACE;AAAgB;AAClB;AACF;AACmB;AAGrB;AAAwC;AACX;AACwB;AAErD;AAA2C;AAEvC;AAEA;AACE;AAAuC;AACzC;AACF;AACsD;AAGxD;AAA6C;AAGrC;AACQ;AACe;AACJ;AACA;AACQ;AACvB;AACF;AAED;AACP;AACE;AACA;AACA;AACA;AACF;AAGF;AACE;AAA2C;AAI7C;AACE;AACE;AAAM;AACR;AAKF;AACE;AACE;AAAO;AACT;AAGF;AACG;AACC;AACc;AACJ;AAET;AACC;AACuC;AACxB;AACJ;AACgB;AACE;AACzB;AACA;AACM;AACA;AACC;AACF;AACD;AACR;AACY;AACO;AAEpB;AACC;AACA;AACgB;AACG;AACV;AACL;AACI;AACM;AACd;AAEJ;AAGN;AAWA;AAAqB;AAKjB;AACA;AACA;AACA;AACA;AACA;AAEA;AAAiB;AAEb;AAAqC;AACvC;AACO;AAGT;AAGE;AACE;AACE;AAAyB;AAC3B;AACD;AAGH;AACE;AAA+B;AACzB;AACkC;AACH;AACnC;AACD;AAGH;AACE;AAA+B;AACK;AACD;AAClC;AAGH;AAAc;AAEV;AACE;AAAgB;AACd;AAGW;AAEb;AAAwB;AAC1B;AACF;AACO;AAGT;AACE;AAAuB;AAGzB;AACE;AACA;AAAK;AAGP;AACE;AACA;AAA6B;AAG/B;AAAmB;AAEf;AACA;AAA4B;AAC9B;AACc;AAGhB;AAAqB;AAKjB;AAGA;AACE;AAEA;AAAA;AAGF;AAEA;AACE;AAEA;AAAA;AAGF;AAAa;AACJ;AAET;AAEA;AAEA;AAEA;AACE;AAAwB;AAExB;AAAY;AACd;AACF;AACgD;AAGlD;AACG;AACQ;AACL;AACA;AACA;AACF;AAEC;AACQ;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;AAEC;AAAc;AAAiB;AAAmB;AAIvD;AAGN;AAQA;AAAuB;AAEnB;AACA;AACA;AACA;AAAmB;AACmB;AACA;AAGtC;AACG;AACM;AACD;AACC;AACK;AAGZ;AAGN;AAEA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF;;"}
|
|
@@ -45,8 +45,9 @@ function isComposerBodyAutoLink(node) {
|
|
|
45
45
|
return slate.Element.isElement(node) && node.type === "auto-link";
|
|
46
46
|
}
|
|
47
47
|
const URL_REGEX = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;
|
|
48
|
-
const PUNCTUATION_OR_SPACE = /[.,;!?\s]/;
|
|
48
|
+
const PUNCTUATION_OR_SPACE = /[.,;!?\s()]/;
|
|
49
49
|
const PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;
|
|
50
|
+
const PARENTHESES = /[()]/;
|
|
50
51
|
function isSeparator(char) {
|
|
51
52
|
return PUNCTUATION_OR_SPACE.test(char);
|
|
52
53
|
}
|
|
@@ -59,6 +60,26 @@ function startsWithSeparator(textContent) {
|
|
|
59
60
|
function endsWithPeriodOrQuestionMark(textContent) {
|
|
60
61
|
return textContent[textContent.length - 1] === "." || textContent[textContent.length - 1] === "?";
|
|
61
62
|
}
|
|
63
|
+
function getUrlLogicalLength(url) {
|
|
64
|
+
if (!PARENTHESES.test(url)) {
|
|
65
|
+
return url.length;
|
|
66
|
+
}
|
|
67
|
+
let logicalLength = 0;
|
|
68
|
+
let parenthesesCount = 0;
|
|
69
|
+
for (const character of url) {
|
|
70
|
+
if (character === "(") {
|
|
71
|
+
parenthesesCount++;
|
|
72
|
+
}
|
|
73
|
+
if (character === ")") {
|
|
74
|
+
parenthesesCount--;
|
|
75
|
+
if (parenthesesCount < 0) {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
logicalLength++;
|
|
80
|
+
}
|
|
81
|
+
return logicalLength;
|
|
82
|
+
}
|
|
62
83
|
function isPreviousNodeValid(editor, path) {
|
|
63
84
|
const entry = slate.Editor.previous(editor, { at: path });
|
|
64
85
|
if (!entry)
|
|
@@ -113,6 +134,27 @@ const handleLinkEdit = (editor, entry) => {
|
|
|
113
134
|
);
|
|
114
135
|
return;
|
|
115
136
|
}
|
|
137
|
+
const logicalLength = getUrlLogicalLength(text);
|
|
138
|
+
if (logicalLength < text.length) {
|
|
139
|
+
slate.Transforms.unwrapNodes(editor, { at: path });
|
|
140
|
+
const logicalText = text.slice(0, logicalLength);
|
|
141
|
+
slate.Transforms.wrapNodes(
|
|
142
|
+
editor,
|
|
143
|
+
{
|
|
144
|
+
type: "auto-link",
|
|
145
|
+
url: logicalText,
|
|
146
|
+
children: []
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
at: {
|
|
150
|
+
anchor: { path, offset: 0 },
|
|
151
|
+
focus: { path, offset: logicalText.length }
|
|
152
|
+
},
|
|
153
|
+
split: true
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
116
158
|
if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {
|
|
117
159
|
slate.Transforms.unwrapNodes(editor, { at: path });
|
|
118
160
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-links.js","sources":["../../../src/slate/plugins/auto-links.ts"],"sourcesContent":["import type { NodeEntry } from \"slate\";\nimport { Editor, Element, Node, Path, Range, Text, Transforms } from \"slate\";\n\nimport type { ComposerBodyAutoLink } from \"../../types\";\n\n/**\n * This implementation is inspired by Lexical's AutoLink plugin.\n * Additional modifications and features were added to adapt it to our specific needs.\n *\n * Original Lexical AutoLink plugin can be found at [Lexical's Github Repository](https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/LexicalAutoLinkPlugin.ts)\n */\nexport function withAutoLinks(editor: Editor): Editor {\n const { isInline, normalizeNode, deleteBackward } = editor;\n\n editor.isInline = (element) => {\n return element.type === \"auto-link\" ? true : isInline(element);\n };\n\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n if (Text.isText(node)) {\n const parentNode = Node.parent(editor, path);\n\n if (isComposerBodyAutoLink(parentNode)) {\n const parentPath = Path.parent(path);\n handleLinkEdit(editor, [parentNode, parentPath]);\n } else {\n handleLinkCreate(editor, [node, path]);\n handleNeighbours(editor, [node, path]);\n }\n }\n\n normalizeNode(entry);\n };\n\n editor.deleteBackward = (unit) => {\n deleteBackward(unit);\n const { selection } = editor;\n if (!selection) return;\n\n if (!Range.isCollapsed(selection)) return;\n\n const [match] = Editor.nodes(editor, {\n at: selection,\n match: isComposerBodyAutoLink,\n mode: \"lowest\",\n });\n\n if (!match) return;\n\n Transforms.unwrapNodes(editor, {\n match: isComposerBodyAutoLink,\n });\n };\n\n return editor;\n}\n\nexport function isComposerBodyAutoLink(\n node: Node\n): node is ComposerBodyAutoLink {\n return Element.isElement(node) && node.type === \"auto-link\";\n}\n\n/**\n * 1. ((https?:\\/\\/(www\\.)?)|(www\\.))\n * - Matches 'http://' or 'https://' optionally followed by 'www.', or just 'www.'\n *\n * 2. [-a-zA-Z0-9@:%._+~#=]{1,256}\n * - Matches any character in the set [-a-zA-Z0-9@:%._+~#=] between 1 and 256 times, often found in the domain and subdomain part of the URL\n *\n * 3. \\.[a-zA-Z0-9()]{1,6}\n * - Matches a period followed by any character in the set [a-zA-Z0-9()] between 1 and 6 times, usually indicating the domain extension like .com, .org, etc.\n *\n * 4. \\b\n * - Represents a word boundary, ensuring that the characters following cannot be part of a different word\n *\n * 5. ([-a-zA-Z0-9().@:%_+~#?&//=]*)\n * - Matches any character in the set [-a-zA-Z0-9().@:%_+~#?&//=] between 0 and unlimited times, often found in the path, query parameters, or anchor part of the URL\n *\n * Matching URLs:\n * - http://www.example.com\n * - https://www.example.com\n * - www.example.com\n * - https://example.com/path?query=param#anchor\n *\n * Non-Matching URLs:\n * - http:/example.com (malformed scheme)\n * - example (missing scheme and domain extension)\n * - ftp://example.com (ftp scheme is not supported)\n * - example.com (missing scheme)\n */\nconst URL_REGEX =\n /((https?:\\/\\/(www\\.)?)|(www\\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;\n\nconst PUNCTUATION_OR_SPACE = /[.,;!?\\s]/;\n\nconst PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;\n\n/**\n * Helper function to check if a character is a separator (punctuation or space)\n * @param char The character to check\n * @returns Whether the character is a separator or not\n */\nfunction isSeparator(char: string): boolean {\n return PUNCTUATION_OR_SPACE.test(char);\n}\n\n/**\n * Helper function to check if a text content ends with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content ends with a separator or not\n */\nfunction endsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[textContent.length - 1]);\n}\n\n/**\n * Helper function to check if a text content starts with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content starts with a separator or not\n */\nfunction startsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[0]);\n}\n\n/**\n * Helper function to check if a text content ends with a period or question mark\n * @param textContent The text content to check\n * @returns Whether the text content ends with a period or not\n */\nfunction endsWithPeriodOrQuestionMark(textContent: string): boolean {\n return (\n textContent[textContent.length - 1] === \".\" ||\n textContent[textContent.length - 1] === \"?\"\n );\n}\n\n/**\n * Helper function to check if the previous node is valid (text node that ends with a separator or is empty)\n */\nfunction isPreviousNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.previous(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (endsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the next node is valid (text node that starts with a separator or is empty)\n */\nfunction isNextNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.next(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (startsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the content around a text node is valid.\n * @param editor\n * @param entry\n * @param start\n * @param end\n * @returns\n */\nfunction isContentAroundValid(\n editor: Editor,\n entry: NodeEntry<Text>,\n start: number,\n end: number\n): boolean {\n const [node, path] = entry;\n const text = node.text;\n\n const contentBeforeIsValid =\n start > 0\n ? isSeparator(text[start - 1])\n : isPreviousNodeValid(editor, path);\n\n const contentAfterIsValid =\n end < text.length ? isSeparator(text[end]) : isNextNodeValid(editor, path);\n\n return contentBeforeIsValid && contentAfterIsValid;\n}\n\nconst handleLinkEdit = (\n editor: Editor,\n entry: NodeEntry<ComposerBodyAutoLink>\n) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the Link node only contains text nodes as children\n const children = Node.children(editor, path);\n for (const [child] of children) {\n if (Text.isText(child)) continue;\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n // Attempt to match the text content (of the Link node) against the URL regex\n const text = Node.string(node);\n const match = URL_REGEX.exec(text);\n\n // Step 2: Ensure that the text content of the Link node matches the URL regex and is identical to the match\n if (!match || match[0] !== text) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 3: Ensure that if the text content of the Link node ends with a period, we unwrap the Link node and wrap the text before the period in a new Link node\n if (endsWithPeriodOrQuestionMark(text)) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const textBeforePeriod = text.slice(0, text.length - 1);\n\n // Remove the last character from the link text and wrap the remaining text in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: textBeforePeriod,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: textBeforePeriod.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 4: Ensure that the text content of the Link node is surrounded by separators or the start/end of the text content\n if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 5: Ensure that the url attribute of the Link node is identical to its text content\n if (node.url !== text) {\n Transforms.setNodes(editor, { url: match[0] }, { at: path });\n return;\n }\n};\n\nconst handleLinkCreate = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the text content of the node matches the URL regex\n const match = URL_REGEX.exec(node.text);\n if (!match) return;\n\n const start = match.index;\n const end = start + match[0].length;\n\n // Step 2: Ensure that the content around the node is valid\n if (!isContentAroundValid(editor, entry, start, end)) return;\n\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: match[0],\n children: [],\n },\n {\n at: {\n anchor: { path, offset: start },\n focus: { path, offset: end },\n },\n split: true,\n }\n );\n return;\n};\n\nconst handleNeighbours = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n const text = node.text;\n\n const previousSibling = Editor.previous(editor, { at: path });\n\n if (previousSibling && isComposerBodyAutoLink(previousSibling[0])) {\n if (PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC.test(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n Transforms.mergeNodes(editor, { at: path });\n return;\n }\n\n if (!startsWithSeparator(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n return;\n }\n }\n\n const nextSibling = Editor.next(editor, { at: path });\n if (\n nextSibling &&\n isComposerBodyAutoLink(nextSibling[0]) &&\n !endsWithSeparator(text)\n ) {\n Transforms.unwrapNodes(editor, { at: nextSibling[1] });\n return;\n }\n};\n"],"names":["Text","Node","Path","Range","Editor","Transforms","Element"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auto-links.js","sources":["../../../src/slate/plugins/auto-links.ts"],"sourcesContent":["import type { NodeEntry } from \"slate\";\nimport { Editor, Element, Node, Path, Range, Text, Transforms } from \"slate\";\n\nimport type { ComposerBodyAutoLink } from \"../../types\";\n\n/**\n * This implementation is inspired by Lexical's AutoLink plugin.\n * Additional modifications and features were added to adapt it to our specific needs.\n *\n * Original Lexical AutoLink plugin can be found at [Lexical's Github Repository](https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/LexicalAutoLinkPlugin.ts)\n */\nexport function withAutoLinks(editor: Editor): Editor {\n const { isInline, normalizeNode, deleteBackward } = editor;\n\n editor.isInline = (element) => {\n return element.type === \"auto-link\" ? true : isInline(element);\n };\n\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n if (Text.isText(node)) {\n const parentNode = Node.parent(editor, path);\n\n if (isComposerBodyAutoLink(parentNode)) {\n const parentPath = Path.parent(path);\n handleLinkEdit(editor, [parentNode, parentPath]);\n } else {\n handleLinkCreate(editor, [node, path]);\n handleNeighbours(editor, [node, path]);\n }\n }\n\n normalizeNode(entry);\n };\n\n editor.deleteBackward = (unit) => {\n deleteBackward(unit);\n const { selection } = editor;\n if (!selection) return;\n\n if (!Range.isCollapsed(selection)) return;\n\n const [match] = Editor.nodes(editor, {\n at: selection,\n match: isComposerBodyAutoLink,\n mode: \"lowest\",\n });\n\n if (!match) return;\n\n Transforms.unwrapNodes(editor, {\n match: isComposerBodyAutoLink,\n });\n };\n\n return editor;\n}\n\nexport function isComposerBodyAutoLink(\n node: Node\n): node is ComposerBodyAutoLink {\n return Element.isElement(node) && node.type === \"auto-link\";\n}\n\n/**\n * 1. ((https?:\\/\\/(www\\.)?)|(www\\.))\n * - Matches 'http://' or 'https://' optionally followed by 'www.', or just 'www.'\n *\n * 2. [-a-zA-Z0-9@:%._+~#=]{1,256}\n * - Matches any character in the set [-a-zA-Z0-9@:%._+~#=] between 1 and 256 times, often found in the domain and subdomain part of the URL\n *\n * 3. \\.[a-zA-Z0-9()]{1,6}\n * - Matches a period followed by any character in the set [a-zA-Z0-9()] between 1 and 6 times, usually indicating the domain extension like .com, .org, etc.\n *\n * 4. \\b\n * - Represents a word boundary, ensuring that the characters following cannot be part of a different word\n *\n * 5. ([-a-zA-Z0-9().@:%_+~#?&//=]*)\n * - Matches any character in the set [-a-zA-Z0-9().@:%_+~#?&//=] between 0 and unlimited times, often found in the path, query parameters, or anchor part of the URL\n *\n * Matching URLs:\n * - http://www.example.com\n * - https://www.example.com\n * - www.example.com\n * - https://example.com/path?query=param#anchor\n *\n * Non-Matching URLs:\n * - http:/example.com (malformed scheme)\n * - example (missing scheme and domain extension)\n * - ftp://example.com (ftp scheme is not supported)\n * - example.com (missing scheme)\n */\nconst URL_REGEX =\n /((https?:\\/\\/(www\\.)?)|(www\\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;\n\nconst PUNCTUATION_OR_SPACE = /[.,;!?\\s()]/;\n\nconst PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;\n\nconst PARENTHESES = /[()]/;\n\n/**\n * Helper function to check if a character is a separator (punctuation or space)\n * @param char The character to check\n * @returns Whether the character is a separator or not\n */\nfunction isSeparator(char: string): boolean {\n return PUNCTUATION_OR_SPACE.test(char);\n}\n\n/**\n * Helper function to check if a text content ends with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content ends with a separator or not\n */\nfunction endsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[textContent.length - 1]);\n}\n\n/**\n * Helper function to check if a text content starts with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content starts with a separator or not\n */\nfunction startsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[0]);\n}\n\n/**\n * Helper function to check if a text content ends with a period or question mark\n * @param textContent The text content to check\n * @returns Whether the text content ends with a period or not\n */\nfunction endsWithPeriodOrQuestionMark(textContent: string): boolean {\n return (\n textContent[textContent.length - 1] === \".\" ||\n textContent[textContent.length - 1] === \"?\"\n );\n}\n\n/**\n * Helper function to get the \"logical length\" of a URL, taking into account things like opening/closing parentheses\n * @param url The URL to check\n * @returns The \"logical length\" of the URL\n */\nfunction getUrlLogicalLength(url: string): number {\n if (!PARENTHESES.test(url)) {\n return url.length;\n }\n\n let logicalLength = 0;\n let parenthesesCount = 0;\n\n for (const character of url) {\n if (character === \"(\") {\n parenthesesCount++;\n }\n\n if (character === \")\") {\n parenthesesCount--;\n\n if (parenthesesCount < 0) {\n break;\n }\n }\n\n logicalLength++;\n }\n\n return logicalLength;\n}\n\n/**\n * Helper function to check if the previous node is valid (text node that ends with a separator or is empty)\n */\nfunction isPreviousNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.previous(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (endsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the next node is valid (text node that starts with a separator or is empty)\n */\nfunction isNextNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.next(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (startsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the content around a text node is valid.\n * @param editor\n * @param entry\n * @param start\n * @param end\n * @returns\n */\nfunction isContentAroundValid(\n editor: Editor,\n entry: NodeEntry<Text>,\n start: number,\n end: number\n): boolean {\n const [node, path] = entry;\n const text = node.text;\n\n const contentBeforeIsValid =\n start > 0\n ? isSeparator(text[start - 1])\n : isPreviousNodeValid(editor, path);\n\n const contentAfterIsValid =\n end < text.length ? isSeparator(text[end]) : isNextNodeValid(editor, path);\n\n return contentBeforeIsValid && contentAfterIsValid;\n}\n\nconst handleLinkEdit = (\n editor: Editor,\n entry: NodeEntry<ComposerBodyAutoLink>\n) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the Link node only contains text nodes as children\n const children = Node.children(editor, path);\n for (const [child] of children) {\n if (Text.isText(child)) continue;\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n // Attempt to match the text content (of the Link node) against the URL regex\n const text = Node.string(node);\n const match = URL_REGEX.exec(text);\n\n // Step 2: Ensure that the text content of the Link node matches the URL regex and is identical to the match\n if (!match || match[0] !== text) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 3: Ensure that if the text content of the Link node ends with a period, we unwrap the Link node and wrap the text before the period in a new Link node\n if (endsWithPeriodOrQuestionMark(text)) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const textBeforePeriod = text.slice(0, text.length - 1);\n\n // Remove the last character from the link text and wrap the remaining text in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: textBeforePeriod,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: textBeforePeriod.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 4: Allow some conditions to shorten the URL (e.g. supporting parentheses but only if they are balanced)\n const logicalLength = getUrlLogicalLength(text);\n\n if (logicalLength < text.length) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const logicalText = text.slice(0, logicalLength);\n\n // Keep the \"logical\" text and wrap it in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: logicalText,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: logicalText.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 5: Ensure that the text content of the Link node is surrounded by separators or the start/end of the text content\n if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 6: Ensure that the url attribute of the Link node is identical to its text content\n if (node.url !== text) {\n Transforms.setNodes(editor, { url: match[0] }, { at: path });\n return;\n }\n};\n\nconst handleLinkCreate = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the text content of the node matches the URL regex\n const match = URL_REGEX.exec(node.text);\n if (!match) return;\n\n const start = match.index;\n const end = start + match[0].length;\n\n // Step 2: Ensure that the content around the node is valid\n if (!isContentAroundValid(editor, entry, start, end)) return;\n\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: match[0],\n children: [],\n },\n {\n at: {\n anchor: { path, offset: start },\n focus: { path, offset: end },\n },\n split: true,\n }\n );\n return;\n};\n\nconst handleNeighbours = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n const text = node.text;\n\n const previousSibling = Editor.previous(editor, { at: path });\n\n if (previousSibling && isComposerBodyAutoLink(previousSibling[0])) {\n if (PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC.test(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n Transforms.mergeNodes(editor, { at: path });\n return;\n }\n\n if (!startsWithSeparator(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n return;\n }\n }\n\n const nextSibling = Editor.next(editor, { at: path });\n if (\n nextSibling &&\n isComposerBodyAutoLink(nextSibling[0]) &&\n !endsWithSeparator(text)\n ) {\n Transforms.unwrapNodes(editor, { at: nextSibling[1] });\n return;\n }\n};\n"],"names":["Text","Node","Path","Range","Editor","Transforms","Element"],"mappings":"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"}
|
|
@@ -43,8 +43,9 @@ function isComposerBodyAutoLink(node) {
|
|
|
43
43
|
return Element.isElement(node) && node.type === "auto-link";
|
|
44
44
|
}
|
|
45
45
|
const URL_REGEX = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;
|
|
46
|
-
const PUNCTUATION_OR_SPACE = /[.,;!?\s]/;
|
|
46
|
+
const PUNCTUATION_OR_SPACE = /[.,;!?\s()]/;
|
|
47
47
|
const PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;
|
|
48
|
+
const PARENTHESES = /[()]/;
|
|
48
49
|
function isSeparator(char) {
|
|
49
50
|
return PUNCTUATION_OR_SPACE.test(char);
|
|
50
51
|
}
|
|
@@ -57,6 +58,26 @@ function startsWithSeparator(textContent) {
|
|
|
57
58
|
function endsWithPeriodOrQuestionMark(textContent) {
|
|
58
59
|
return textContent[textContent.length - 1] === "." || textContent[textContent.length - 1] === "?";
|
|
59
60
|
}
|
|
61
|
+
function getUrlLogicalLength(url) {
|
|
62
|
+
if (!PARENTHESES.test(url)) {
|
|
63
|
+
return url.length;
|
|
64
|
+
}
|
|
65
|
+
let logicalLength = 0;
|
|
66
|
+
let parenthesesCount = 0;
|
|
67
|
+
for (const character of url) {
|
|
68
|
+
if (character === "(") {
|
|
69
|
+
parenthesesCount++;
|
|
70
|
+
}
|
|
71
|
+
if (character === ")") {
|
|
72
|
+
parenthesesCount--;
|
|
73
|
+
if (parenthesesCount < 0) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
logicalLength++;
|
|
78
|
+
}
|
|
79
|
+
return logicalLength;
|
|
80
|
+
}
|
|
60
81
|
function isPreviousNodeValid(editor, path) {
|
|
61
82
|
const entry = Editor.previous(editor, { at: path });
|
|
62
83
|
if (!entry)
|
|
@@ -111,6 +132,27 @@ const handleLinkEdit = (editor, entry) => {
|
|
|
111
132
|
);
|
|
112
133
|
return;
|
|
113
134
|
}
|
|
135
|
+
const logicalLength = getUrlLogicalLength(text);
|
|
136
|
+
if (logicalLength < text.length) {
|
|
137
|
+
Transforms.unwrapNodes(editor, { at: path });
|
|
138
|
+
const logicalText = text.slice(0, logicalLength);
|
|
139
|
+
Transforms.wrapNodes(
|
|
140
|
+
editor,
|
|
141
|
+
{
|
|
142
|
+
type: "auto-link",
|
|
143
|
+
url: logicalText,
|
|
144
|
+
children: []
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
at: {
|
|
148
|
+
anchor: { path, offset: 0 },
|
|
149
|
+
focus: { path, offset: logicalText.length }
|
|
150
|
+
},
|
|
151
|
+
split: true
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
114
156
|
if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {
|
|
115
157
|
Transforms.unwrapNodes(editor, { at: path });
|
|
116
158
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-links.mjs","sources":["../../../src/slate/plugins/auto-links.ts"],"sourcesContent":["import type { NodeEntry } from \"slate\";\nimport { Editor, Element, Node, Path, Range, Text, Transforms } from \"slate\";\n\nimport type { ComposerBodyAutoLink } from \"../../types\";\n\n/**\n * This implementation is inspired by Lexical's AutoLink plugin.\n * Additional modifications and features were added to adapt it to our specific needs.\n *\n * Original Lexical AutoLink plugin can be found at [Lexical's Github Repository](https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/LexicalAutoLinkPlugin.ts)\n */\nexport function withAutoLinks(editor: Editor): Editor {\n const { isInline, normalizeNode, deleteBackward } = editor;\n\n editor.isInline = (element) => {\n return element.type === \"auto-link\" ? true : isInline(element);\n };\n\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n if (Text.isText(node)) {\n const parentNode = Node.parent(editor, path);\n\n if (isComposerBodyAutoLink(parentNode)) {\n const parentPath = Path.parent(path);\n handleLinkEdit(editor, [parentNode, parentPath]);\n } else {\n handleLinkCreate(editor, [node, path]);\n handleNeighbours(editor, [node, path]);\n }\n }\n\n normalizeNode(entry);\n };\n\n editor.deleteBackward = (unit) => {\n deleteBackward(unit);\n const { selection } = editor;\n if (!selection) return;\n\n if (!Range.isCollapsed(selection)) return;\n\n const [match] = Editor.nodes(editor, {\n at: selection,\n match: isComposerBodyAutoLink,\n mode: \"lowest\",\n });\n\n if (!match) return;\n\n Transforms.unwrapNodes(editor, {\n match: isComposerBodyAutoLink,\n });\n };\n\n return editor;\n}\n\nexport function isComposerBodyAutoLink(\n node: Node\n): node is ComposerBodyAutoLink {\n return Element.isElement(node) && node.type === \"auto-link\";\n}\n\n/**\n * 1. ((https?:\\/\\/(www\\.)?)|(www\\.))\n * - Matches 'http://' or 'https://' optionally followed by 'www.', or just 'www.'\n *\n * 2. [-a-zA-Z0-9@:%._+~#=]{1,256}\n * - Matches any character in the set [-a-zA-Z0-9@:%._+~#=] between 1 and 256 times, often found in the domain and subdomain part of the URL\n *\n * 3. \\.[a-zA-Z0-9()]{1,6}\n * - Matches a period followed by any character in the set [a-zA-Z0-9()] between 1 and 6 times, usually indicating the domain extension like .com, .org, etc.\n *\n * 4. \\b\n * - Represents a word boundary, ensuring that the characters following cannot be part of a different word\n *\n * 5. ([-a-zA-Z0-9().@:%_+~#?&//=]*)\n * - Matches any character in the set [-a-zA-Z0-9().@:%_+~#?&//=] between 0 and unlimited times, often found in the path, query parameters, or anchor part of the URL\n *\n * Matching URLs:\n * - http://www.example.com\n * - https://www.example.com\n * - www.example.com\n * - https://example.com/path?query=param#anchor\n *\n * Non-Matching URLs:\n * - http:/example.com (malformed scheme)\n * - example (missing scheme and domain extension)\n * - ftp://example.com (ftp scheme is not supported)\n * - example.com (missing scheme)\n */\nconst URL_REGEX =\n /((https?:\\/\\/(www\\.)?)|(www\\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;\n\nconst PUNCTUATION_OR_SPACE = /[.,;!?\\s]/;\n\nconst PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;\n\n/**\n * Helper function to check if a character is a separator (punctuation or space)\n * @param char The character to check\n * @returns Whether the character is a separator or not\n */\nfunction isSeparator(char: string): boolean {\n return PUNCTUATION_OR_SPACE.test(char);\n}\n\n/**\n * Helper function to check if a text content ends with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content ends with a separator or not\n */\nfunction endsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[textContent.length - 1]);\n}\n\n/**\n * Helper function to check if a text content starts with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content starts with a separator or not\n */\nfunction startsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[0]);\n}\n\n/**\n * Helper function to check if a text content ends with a period or question mark\n * @param textContent The text content to check\n * @returns Whether the text content ends with a period or not\n */\nfunction endsWithPeriodOrQuestionMark(textContent: string): boolean {\n return (\n textContent[textContent.length - 1] === \".\" ||\n textContent[textContent.length - 1] === \"?\"\n );\n}\n\n/**\n * Helper function to check if the previous node is valid (text node that ends with a separator or is empty)\n */\nfunction isPreviousNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.previous(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (endsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the next node is valid (text node that starts with a separator or is empty)\n */\nfunction isNextNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.next(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (startsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the content around a text node is valid.\n * @param editor\n * @param entry\n * @param start\n * @param end\n * @returns\n */\nfunction isContentAroundValid(\n editor: Editor,\n entry: NodeEntry<Text>,\n start: number,\n end: number\n): boolean {\n const [node, path] = entry;\n const text = node.text;\n\n const contentBeforeIsValid =\n start > 0\n ? isSeparator(text[start - 1])\n : isPreviousNodeValid(editor, path);\n\n const contentAfterIsValid =\n end < text.length ? isSeparator(text[end]) : isNextNodeValid(editor, path);\n\n return contentBeforeIsValid && contentAfterIsValid;\n}\n\nconst handleLinkEdit = (\n editor: Editor,\n entry: NodeEntry<ComposerBodyAutoLink>\n) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the Link node only contains text nodes as children\n const children = Node.children(editor, path);\n for (const [child] of children) {\n if (Text.isText(child)) continue;\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n // Attempt to match the text content (of the Link node) against the URL regex\n const text = Node.string(node);\n const match = URL_REGEX.exec(text);\n\n // Step 2: Ensure that the text content of the Link node matches the URL regex and is identical to the match\n if (!match || match[0] !== text) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 3: Ensure that if the text content of the Link node ends with a period, we unwrap the Link node and wrap the text before the period in a new Link node\n if (endsWithPeriodOrQuestionMark(text)) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const textBeforePeriod = text.slice(0, text.length - 1);\n\n // Remove the last character from the link text and wrap the remaining text in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: textBeforePeriod,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: textBeforePeriod.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 4: Ensure that the text content of the Link node is surrounded by separators or the start/end of the text content\n if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 5: Ensure that the url attribute of the Link node is identical to its text content\n if (node.url !== text) {\n Transforms.setNodes(editor, { url: match[0] }, { at: path });\n return;\n }\n};\n\nconst handleLinkCreate = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the text content of the node matches the URL regex\n const match = URL_REGEX.exec(node.text);\n if (!match) return;\n\n const start = match.index;\n const end = start + match[0].length;\n\n // Step 2: Ensure that the content around the node is valid\n if (!isContentAroundValid(editor, entry, start, end)) return;\n\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: match[0],\n children: [],\n },\n {\n at: {\n anchor: { path, offset: start },\n focus: { path, offset: end },\n },\n split: true,\n }\n );\n return;\n};\n\nconst handleNeighbours = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n const text = node.text;\n\n const previousSibling = Editor.previous(editor, { at: path });\n\n if (previousSibling && isComposerBodyAutoLink(previousSibling[0])) {\n if (PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC.test(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n Transforms.mergeNodes(editor, { at: path });\n return;\n }\n\n if (!startsWithSeparator(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n return;\n }\n }\n\n const nextSibling = Editor.next(editor, { at: path });\n if (\n nextSibling &&\n isComposerBodyAutoLink(nextSibling[0]) &&\n !endsWithSeparator(text)\n ) {\n Transforms.unwrapNodes(editor, { at: nextSibling[1] });\n return;\n }\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auto-links.mjs","sources":["../../../src/slate/plugins/auto-links.ts"],"sourcesContent":["import type { NodeEntry } from \"slate\";\nimport { Editor, Element, Node, Path, Range, Text, Transforms } from \"slate\";\n\nimport type { ComposerBodyAutoLink } from \"../../types\";\n\n/**\n * This implementation is inspired by Lexical's AutoLink plugin.\n * Additional modifications and features were added to adapt it to our specific needs.\n *\n * Original Lexical AutoLink plugin can be found at [Lexical's Github Repository](https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/LexicalAutoLinkPlugin.ts)\n */\nexport function withAutoLinks(editor: Editor): Editor {\n const { isInline, normalizeNode, deleteBackward } = editor;\n\n editor.isInline = (element) => {\n return element.type === \"auto-link\" ? true : isInline(element);\n };\n\n editor.normalizeNode = (entry) => {\n const [node, path] = entry;\n\n if (Text.isText(node)) {\n const parentNode = Node.parent(editor, path);\n\n if (isComposerBodyAutoLink(parentNode)) {\n const parentPath = Path.parent(path);\n handleLinkEdit(editor, [parentNode, parentPath]);\n } else {\n handleLinkCreate(editor, [node, path]);\n handleNeighbours(editor, [node, path]);\n }\n }\n\n normalizeNode(entry);\n };\n\n editor.deleteBackward = (unit) => {\n deleteBackward(unit);\n const { selection } = editor;\n if (!selection) return;\n\n if (!Range.isCollapsed(selection)) return;\n\n const [match] = Editor.nodes(editor, {\n at: selection,\n match: isComposerBodyAutoLink,\n mode: \"lowest\",\n });\n\n if (!match) return;\n\n Transforms.unwrapNodes(editor, {\n match: isComposerBodyAutoLink,\n });\n };\n\n return editor;\n}\n\nexport function isComposerBodyAutoLink(\n node: Node\n): node is ComposerBodyAutoLink {\n return Element.isElement(node) && node.type === \"auto-link\";\n}\n\n/**\n * 1. ((https?:\\/\\/(www\\.)?)|(www\\.))\n * - Matches 'http://' or 'https://' optionally followed by 'www.', or just 'www.'\n *\n * 2. [-a-zA-Z0-9@:%._+~#=]{1,256}\n * - Matches any character in the set [-a-zA-Z0-9@:%._+~#=] between 1 and 256 times, often found in the domain and subdomain part of the URL\n *\n * 3. \\.[a-zA-Z0-9()]{1,6}\n * - Matches a period followed by any character in the set [a-zA-Z0-9()] between 1 and 6 times, usually indicating the domain extension like .com, .org, etc.\n *\n * 4. \\b\n * - Represents a word boundary, ensuring that the characters following cannot be part of a different word\n *\n * 5. ([-a-zA-Z0-9().@:%_+~#?&//=]*)\n * - Matches any character in the set [-a-zA-Z0-9().@:%_+~#?&//=] between 0 and unlimited times, often found in the path, query parameters, or anchor part of the URL\n *\n * Matching URLs:\n * - http://www.example.com\n * - https://www.example.com\n * - www.example.com\n * - https://example.com/path?query=param#anchor\n *\n * Non-Matching URLs:\n * - http:/example.com (malformed scheme)\n * - example (missing scheme and domain extension)\n * - ftp://example.com (ftp scheme is not supported)\n * - example.com (missing scheme)\n */\nconst URL_REGEX =\n /((https?:\\/\\/(www\\.)?)|(www\\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9().@:%_+~#?&//=]*)/;\n\nconst PUNCTUATION_OR_SPACE = /[.,;!?\\s()]/;\n\nconst PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC = /^[.?][a-zA-Z0-9]+/;\n\nconst PARENTHESES = /[()]/;\n\n/**\n * Helper function to check if a character is a separator (punctuation or space)\n * @param char The character to check\n * @returns Whether the character is a separator or not\n */\nfunction isSeparator(char: string): boolean {\n return PUNCTUATION_OR_SPACE.test(char);\n}\n\n/**\n * Helper function to check if a text content ends with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content ends with a separator or not\n */\nfunction endsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[textContent.length - 1]);\n}\n\n/**\n * Helper function to check if a text content starts with a separator (punctuation or space)\n * @param textContent The text content to check\n * @returns Whether the text content starts with a separator or not\n */\nfunction startsWithSeparator(textContent: string): boolean {\n return isSeparator(textContent[0]);\n}\n\n/**\n * Helper function to check if a text content ends with a period or question mark\n * @param textContent The text content to check\n * @returns Whether the text content ends with a period or not\n */\nfunction endsWithPeriodOrQuestionMark(textContent: string): boolean {\n return (\n textContent[textContent.length - 1] === \".\" ||\n textContent[textContent.length - 1] === \"?\"\n );\n}\n\n/**\n * Helper function to get the \"logical length\" of a URL, taking into account things like opening/closing parentheses\n * @param url The URL to check\n * @returns The \"logical length\" of the URL\n */\nfunction getUrlLogicalLength(url: string): number {\n if (!PARENTHESES.test(url)) {\n return url.length;\n }\n\n let logicalLength = 0;\n let parenthesesCount = 0;\n\n for (const character of url) {\n if (character === \"(\") {\n parenthesesCount++;\n }\n\n if (character === \")\") {\n parenthesesCount--;\n\n if (parenthesesCount < 0) {\n break;\n }\n }\n\n logicalLength++;\n }\n\n return logicalLength;\n}\n\n/**\n * Helper function to check if the previous node is valid (text node that ends with a separator or is empty)\n */\nfunction isPreviousNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.previous(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (endsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the next node is valid (text node that starts with a separator or is empty)\n */\nfunction isNextNodeValid(editor: Editor, path: Path): boolean {\n const entry = Editor.next(editor, { at: path });\n if (!entry) return true;\n\n return (\n Text.isText(entry[0]) &&\n (startsWithSeparator(entry[0].text) || entry[0].text === \"\")\n );\n}\n\n/**\n * Helper function to check if the content around a text node is valid.\n * @param editor\n * @param entry\n * @param start\n * @param end\n * @returns\n */\nfunction isContentAroundValid(\n editor: Editor,\n entry: NodeEntry<Text>,\n start: number,\n end: number\n): boolean {\n const [node, path] = entry;\n const text = node.text;\n\n const contentBeforeIsValid =\n start > 0\n ? isSeparator(text[start - 1])\n : isPreviousNodeValid(editor, path);\n\n const contentAfterIsValid =\n end < text.length ? isSeparator(text[end]) : isNextNodeValid(editor, path);\n\n return contentBeforeIsValid && contentAfterIsValid;\n}\n\nconst handleLinkEdit = (\n editor: Editor,\n entry: NodeEntry<ComposerBodyAutoLink>\n) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the Link node only contains text nodes as children\n const children = Node.children(editor, path);\n for (const [child] of children) {\n if (Text.isText(child)) continue;\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n // Attempt to match the text content (of the Link node) against the URL regex\n const text = Node.string(node);\n const match = URL_REGEX.exec(text);\n\n // Step 2: Ensure that the text content of the Link node matches the URL regex and is identical to the match\n if (!match || match[0] !== text) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 3: Ensure that if the text content of the Link node ends with a period, we unwrap the Link node and wrap the text before the period in a new Link node\n if (endsWithPeriodOrQuestionMark(text)) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const textBeforePeriod = text.slice(0, text.length - 1);\n\n // Remove the last character from the link text and wrap the remaining text in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: textBeforePeriod,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: textBeforePeriod.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 4: Allow some conditions to shorten the URL (e.g. supporting parentheses but only if they are balanced)\n const logicalLength = getUrlLogicalLength(text);\n\n if (logicalLength < text.length) {\n Transforms.unwrapNodes(editor, { at: path });\n\n const logicalText = text.slice(0, logicalLength);\n\n // Keep the \"logical\" text and wrap it in a new link node\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: logicalText,\n children: [],\n },\n {\n at: {\n anchor: { path, offset: 0 },\n focus: { path, offset: logicalText.length },\n },\n split: true,\n }\n );\n return;\n }\n\n // Step 5: Ensure that the text content of the Link node is surrounded by separators or the start/end of the text content\n if (!isPreviousNodeValid(editor, path) || !isNextNodeValid(editor, path)) {\n Transforms.unwrapNodes(editor, { at: path });\n return;\n }\n\n // Step 6: Ensure that the url attribute of the Link node is identical to its text content\n if (node.url !== text) {\n Transforms.setNodes(editor, { url: match[0] }, { at: path });\n return;\n }\n};\n\nconst handleLinkCreate = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n\n // Step 1: Ensure that the text content of the node matches the URL regex\n const match = URL_REGEX.exec(node.text);\n if (!match) return;\n\n const start = match.index;\n const end = start + match[0].length;\n\n // Step 2: Ensure that the content around the node is valid\n if (!isContentAroundValid(editor, entry, start, end)) return;\n\n Transforms.wrapNodes<ComposerBodyAutoLink>(\n editor,\n {\n type: \"auto-link\",\n url: match[0],\n children: [],\n },\n {\n at: {\n anchor: { path, offset: start },\n focus: { path, offset: end },\n },\n split: true,\n }\n );\n return;\n};\n\nconst handleNeighbours = (editor: Editor, entry: NodeEntry<Text>) => {\n const [node, path] = entry;\n const text = node.text;\n\n const previousSibling = Editor.previous(editor, { at: path });\n\n if (previousSibling && isComposerBodyAutoLink(previousSibling[0])) {\n if (PERIOD_OR_QUESTION_MARK_FOLLOWED_BY_ALPHANUMERIC.test(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n Transforms.mergeNodes(editor, { at: path });\n return;\n }\n\n if (!startsWithSeparator(text)) {\n Transforms.unwrapNodes(editor, { at: previousSibling[1] });\n return;\n }\n }\n\n const nextSibling = Editor.next(editor, { at: path });\n if (\n nextSibling &&\n isComposerBodyAutoLink(nextSibling[0]) &&\n !endsWithSeparator(text)\n ) {\n Transforms.unwrapNodes(editor, { at: nextSibling[1] });\n return;\n }\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var slate = require('slate');
|
|
4
|
+
var isEmptyString = require('./is-empty-string.js');
|
|
4
5
|
|
|
5
6
|
function isEmpty(editor, children) {
|
|
6
|
-
|
|
7
|
+
for (const child of children) {
|
|
8
|
+
if (slate.Text.isText(child)) {
|
|
9
|
+
if (!isEmptyString.isEmptyString(child.text)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
} else if (child.type === "paragraph") {
|
|
13
|
+
if (child.children.length > 1 || !(slate.Text.isText(child.children[0]) && isEmptyString.isEmptyString(child.children[0].text))) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
if (!slate.Editor.isEmpty(editor, child)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
7
23
|
}
|
|
8
24
|
|
|
9
25
|
exports.isEmpty = isEmpty;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-empty.js","sources":["../../../src/slate/utils/is-empty.ts"],"sourcesContent":["import type { Descendant } from \"slate\";\nimport { Editor as SlateEditor, Text as SlateText } from \"slate\";\n\nexport function isEmpty(editor: SlateEditor, children: Descendant[]) {\n return (\n
|
|
1
|
+
{"version":3,"file":"is-empty.js","sources":["../../../src/slate/utils/is-empty.ts"],"sourcesContent":["import type { Descendant } from \"slate\";\nimport { Editor as SlateEditor, Text as SlateText } from \"slate\";\n\nimport { isEmptyString } from \"./is-empty-string\";\n\nexport function isEmpty(editor: SlateEditor, children: Descendant[]) {\n // Check if all blocks are empty, stopping at the first non-empty block\n for (const child of children) {\n if (SlateText.isText(child)) {\n // Non-empty text\n if (!isEmptyString(child.text)) {\n return false;\n }\n } else if (child.type === \"paragraph\") {\n // Non-empty paragraph\n if (\n child.children.length > 1 ||\n !(\n SlateText.isText(child.children[0]) &&\n isEmptyString(child.children[0].text)\n )\n ) {\n return false;\n }\n } else {\n // Non-empty other block\n if (!SlateEditor.isEmpty(editor, child)) {\n return false;\n }\n }\n }\n\n return true;\n}\n"],"names":["SlateText","isEmptyString","SlateEditor"],"mappings":";;;;;AAKgB,SAAA,OAAA,CAAQ,QAAqB,QAAwB,EAAA;AAEnE,EAAA,KAAA,MAAW,SAAS,QAAU,EAAA;AAC5B,IAAI,IAAAA,UAAA,CAAU,MAAO,CAAA,KAAK,CAAG,EAAA;AAE3B,MAAA,IAAI,CAACC,2BAAA,CAAc,KAAM,CAAA,IAAI,CAAG,EAAA;AAC9B,QAAO,OAAA,KAAA,CAAA;AAAA,OACT;AAAA,KACF,MAAA,IAAW,KAAM,CAAA,IAAA,KAAS,WAAa,EAAA;AAErC,MAAA,IACE,MAAM,QAAS,CAAA,MAAA,GAAS,CACxB,IAAA,EACED,WAAU,MAAO,CAAA,KAAA,CAAM,QAAS,CAAA,CAAA,CAAE,KAClCC,2BAAc,CAAA,KAAA,CAAM,QAAS,CAAA,CAAA,CAAA,CAAG,IAAI,CAEtC,CAAA,EAAA;AACA,QAAO,OAAA,KAAA,CAAA;AAAA,OACT;AAAA,KACK,MAAA;AAEL,MAAA,IAAI,CAACC,YAAA,CAAY,OAAQ,CAAA,MAAA,EAAQ,KAAK,CAAG,EAAA;AACvC,QAAO,OAAA,KAAA,CAAA;AAAA,OACT;AAAA,KACF;AAAA,GACF;AAEA,EAAO,OAAA,IAAA,CAAA;AACT;;;;"}
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { Text, Editor } from 'slate';
|
|
2
|
+
import { isEmptyString } from './is-empty-string.mjs';
|
|
2
3
|
|
|
3
4
|
function isEmpty(editor, children) {
|
|
4
|
-
|
|
5
|
+
for (const child of children) {
|
|
6
|
+
if (Text.isText(child)) {
|
|
7
|
+
if (!isEmptyString(child.text)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
} else if (child.type === "paragraph") {
|
|
11
|
+
if (child.children.length > 1 || !(Text.isText(child.children[0]) && isEmptyString(child.children[0].text))) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
if (!Editor.isEmpty(editor, child)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
5
21
|
}
|
|
6
22
|
|
|
7
23
|
export { isEmpty };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-empty.mjs","sources":["../../../src/slate/utils/is-empty.ts"],"sourcesContent":["import type { Descendant } from \"slate\";\nimport { Editor as SlateEditor, Text as SlateText } from \"slate\";\n\nexport function isEmpty(editor: SlateEditor, children: Descendant[]) {\n return (\n
|
|
1
|
+
{"version":3,"file":"is-empty.mjs","sources":["../../../src/slate/utils/is-empty.ts"],"sourcesContent":["import type { Descendant } from \"slate\";\nimport { Editor as SlateEditor, Text as SlateText } from \"slate\";\n\nimport { isEmptyString } from \"./is-empty-string\";\n\nexport function isEmpty(editor: SlateEditor, children: Descendant[]) {\n // Check if all blocks are empty, stopping at the first non-empty block\n for (const child of children) {\n if (SlateText.isText(child)) {\n // Non-empty text\n if (!isEmptyString(child.text)) {\n return false;\n }\n } else if (child.type === \"paragraph\") {\n // Non-empty paragraph\n if (\n child.children.length > 1 ||\n !(\n SlateText.isText(child.children[0]) &&\n isEmptyString(child.children[0].text)\n )\n ) {\n return false;\n }\n } else {\n // Non-empty other block\n if (!SlateEditor.isEmpty(editor, child)) {\n return false;\n }\n }\n }\n\n return true;\n}\n"],"names":["SlateText","SlateEditor"],"mappings":";;;AAKgB,SAAA,OAAA,CAAQ,QAAqB,QAAwB,EAAA;AAEnE,EAAA,KAAA,MAAW,SAAS,QAAU,EAAA;AAC5B,IAAI,IAAAA,IAAA,CAAU,MAAO,CAAA,KAAK,CAAG,EAAA;AAE3B,MAAA,IAAI,CAAC,aAAA,CAAc,KAAM,CAAA,IAAI,CAAG,EAAA;AAC9B,QAAO,OAAA,KAAA,CAAA;AAAA,OACT;AAAA,KACF,MAAA,IAAW,KAAM,CAAA,IAAA,KAAS,WAAa,EAAA;AAErC,MAAA,IACE,MAAM,QAAS,CAAA,MAAA,GAAS,CACxB,IAAA,EACEA,KAAU,MAAO,CAAA,KAAA,CAAM,QAAS,CAAA,CAAA,CAAE,KAClC,aAAc,CAAA,KAAA,CAAM,QAAS,CAAA,CAAA,CAAA,CAAG,IAAI,CAEtC,CAAA,EAAA;AACA,QAAO,OAAA,KAAA,CAAA;AAAA,OACT;AAAA,KACK,MAAA;AAEL,MAAA,IAAI,CAACC,MAAA,CAAY,OAAQ,CAAA,MAAA,EAAQ,KAAK,CAAG,EAAA;AACvC,QAAO,OAAA,KAAA,CAAA;AAAA,OACT;AAAA,KACF;AAAA,GACF;AAEA,EAAO,OAAA,IAAA,CAAA;AACT;;;;"}
|
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/react-ui\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n"],"names":[],"mappings":";;AAGO,MAAM,QAAW,GAAA,uBAAA;AACX,MAAA,WAAA,GAAiD,
|
|
1
|
+
{"version":3,"file":"version.js","sources":["../src/version.ts"],"sourcesContent":["declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/react-ui\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n"],"names":[],"mappings":";;AAGO,MAAM,QAAW,GAAA,uBAAA;AACX,MAAA,WAAA,GAAiD,cAAA;AACjD,MAAA,UAAA,GAAgD;;;;;;"}
|
package/dist/version.mjs
CHANGED
package/dist/version.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.mjs","sources":["../src/version.ts"],"sourcesContent":["declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/react-ui\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n"],"names":[],"mappings":"AAGO,MAAM,QAAW,GAAA,uBAAA;AACX,MAAA,WAAA,GAAiD,
|
|
1
|
+
{"version":3,"file":"version.mjs","sources":["../src/version.ts"],"sourcesContent":["declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/react-ui\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n"],"names":[],"mappings":"AAGO,MAAM,QAAW,GAAA,uBAAA;AACX,MAAA,WAAA,GAAiD,cAAA;AACjD,MAAA,UAAA,GAAgD;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liveblocks/react-ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3-test1",
|
|
4
4
|
"description": "A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@floating-ui/react-dom": "^2.0.8",
|
|
66
|
-
"@liveblocks/client": "2.0.
|
|
67
|
-
"@liveblocks/core": "2.0.
|
|
68
|
-
"@liveblocks/react": "2.0.
|
|
66
|
+
"@liveblocks/client": "2.0.3-test1",
|
|
67
|
+
"@liveblocks/core": "2.0.3-test1",
|
|
68
|
+
"@liveblocks/react": "2.0.3-test1",
|
|
69
69
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
|
70
70
|
"@radix-ui/react-popover": "^1.0.7",
|
|
71
71
|
"@radix-ui/react-slot": "^1.0.2",
|