diff --git a/ee/tabby-ui/app/chat/page.tsx b/ee/tabby-ui/app/chat/page.tsx index 3fdc0d0f2c67..347cc19f3f61 100644 --- a/ee/tabby-ui/app/chat/page.tsx +++ b/ee/tabby-ui/app/chat/page.tsx @@ -79,6 +79,10 @@ export default function ChatPage() { supportsProvideWorkspaceGitRepoInfo, setSupportsProvideWorkspaceGitRepoInfo ] = useState(false) + const [supportProvideFileAtInfo, setSupportProvideFileAtInfo] = + useState(false) + const [supportGetFileAtInfoContent, setSupportGetFileAtInfoContent] = + useState(false) const sendMessage = (message: ChatMessage) => { if (chatRef.current) { @@ -245,6 +249,12 @@ export default function ChatPage() { server ?.hasCapability('readWorkspaceGitRepositories') .then(setSupportsProvideWorkspaceGitRepoInfo) + server + ?.hasCapability('provideFileAtInfo') + .then(setSupportProvideFileAtInfo) + server + ?.hasCapability('getFileAtInfoContent') + .then(setSupportGetFileAtInfoContent) } checkCapabilities() @@ -407,6 +417,16 @@ export default function ChatPage() { ? server?.readWorkspaceGitRepositories : undefined } + provideFileAtInfo={ + isInEditor && supportProvideFileAtInfo + ? server?.provideFileAtInfo + : undefined + } + getFileAtInfoContent={ + isInEditor && supportGetFileAtInfoContent + ? server?.getFileAtInfoContent + : undefined + } /> ) diff --git a/ee/tabby-ui/components/chat/chat-panel.tsx b/ee/tabby-ui/components/chat/chat-panel.tsx index 92d5a7746e7e..937edcd3beaf 100644 --- a/ee/tabby-ui/components/chat/chat-panel.tsx +++ b/ee/tabby-ui/components/chat/chat-panel.tsx @@ -1,5 +1,6 @@ import React, { RefObject, useMemo, useState } from 'react' import slugify from '@sindresorhus/slugify' +import { Editor } from '@tiptap/core' import { useWindowSize } from '@uidotdev/usehooks' import type { UseChatHelpers } from 'ai/react' import { AnimatePresence, motion } from 'framer-motion' @@ -47,14 +48,14 @@ export interface ChatPanelProps export interface ChatPanelRef { focus: () => void + setInput: (input: string) => void + input: string } function ChatPanelRenderer( { stop, reload, - input, - setInput, className, onSubmit, chatMaxWidthClass, @@ -138,14 +139,17 @@ function ChatPanelRenderer( chatInputRef.current?.focus() }) } - React.useImperativeHandle( ref, () => { return { focus: () => { promptFormRef.current?.focus() - } + }, + setInput: str => { + promptFormRef.current?.setInput(str) + }, + input: promptFormRef.current?.input ?? '' } }, [] @@ -319,10 +323,7 @@ function ChatPanelRenderer( diff --git a/ee/tabby-ui/components/chat/chat.tsx b/ee/tabby-ui/components/chat/chat.tsx index 360afcec4bc7..eb946f775bbe 100644 --- a/ee/tabby-ui/components/chat/chat.tsx +++ b/ee/tabby-ui/components/chat/chat.tsx @@ -1,7 +1,9 @@ import React, { RefObject } from 'react' import { compact, findIndex, isEqual, some, uniqWith } from 'lodash-es' import type { + AtInputOpts, Context, + FileAtInfo, FileContext, FileLocation, GitRepository, @@ -39,6 +41,10 @@ import { cn, findClosestGitRepository, nanoid } from '@/lib/utils' import { ChatPanel, ChatPanelRef } from './chat-panel' import { ChatScrollAnchor } from './chat-scroll-anchor' import { EmptyScreen } from './empty-screen' +import { + extractAtSourceFromString, + isFileAtInfo +} from './prompt-form-editor/utils' import { QuestionAnswerList } from './question-answer' const repositoryListQuery = graphql(/* GraphQL */ ` @@ -85,6 +91,8 @@ type ChatContextValue = { setSelectedRepoId: React.Dispatch> repos: RepositorySourceListQuery['repositoryList'] | undefined fetchingRepos: boolean + provideFileAtInfo?: (opts?: AtInputOpts) => Promise + getFileAtInfoContent?: (info: FileAtInfo) => Promise } export const ChatContext = React.createContext( @@ -126,6 +134,8 @@ interface ChatProps extends React.ComponentProps<'div'> { chatInputRef: RefObject supportsOnApplyInEditorV2: boolean readWorkspaceGitRepositories?: () => Promise + provideFileAtInfo?: (opts?: AtInputOpts) => Promise + getFileAtInfoContent?: (info: FileAtInfo) => Promise } function ChatRenderer( @@ -149,7 +159,9 @@ function ChatRenderer( openInEditor, chatInputRef, supportsOnApplyInEditorV2, - readWorkspaceGitRepositories + readWorkspaceGitRepositories, + provideFileAtInfo, + getFileAtInfoContent }: ChatProps, ref: React.ForwardedRef ) { @@ -157,7 +169,6 @@ function ChatRenderer( const [threadId, setThreadId] = React.useState() const isOnLoadExecuted = React.useRef(false) const [qaPairs, setQaPairs] = React.useState(initialMessages ?? []) - const [input, setInput] = React.useState('') const [relevantContext, setRelevantContext] = React.useState([]) const [activeSelection, setActiveSelection] = React.useState( null @@ -173,6 +184,12 @@ function ChatRenderer( const chatPanelRef = React.useRef(null) + // both set/get input from prompt form + const setInput = (str: string) => { + chatPanelRef.current?.setInput(str) + } + const input = chatPanelRef.current?.input ?? '' + const [{ data: repositoryListData, fetching: fetchingRepos }] = useQuery({ query: repositoryListQuery }) @@ -484,11 +501,23 @@ function ChatRenderer( } const handleSubmit = async (value: string) => { + const { text, atInfos } = extractAtSourceFromString(value) + + // TODO: handle @{AtInfos} here into + atInfos.forEach(async atInfo => { + if (isFileAtInfo(atInfo)) { + const res = await getFileAtInfoContent?.(atInfo) + console.log('file at info content:', res) + } else { + console.log('symbol at info:', atInfo) + } + }) + if (onSubmitMessage) { - onSubmitMessage(value, relevantContext) + onSubmitMessage(text, relevantContext) } else { sendUserChat({ - message: value, + message: text, relevantContext: relevantContext }) } @@ -602,7 +631,9 @@ function ChatRenderer( setSelectedRepoId, repos, fetchingRepos, - initialized + initialized, + provideFileAtInfo, + getFileAtInfoContent }} >
diff --git a/ee/tabby-ui/components/chat/popover-mention-list.tsx b/ee/tabby-ui/components/chat/popover-mention-list.tsx new file mode 100644 index 000000000000..efe5a702160d --- /dev/null +++ b/ee/tabby-ui/components/chat/popover-mention-list.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useRef } from 'react' + +import { MentionNodeAttrs, SourceItem } from './prompt-form-editor/types' + +interface PopoverMentionListProps { + items: SourceItem[] + selectedIndex: number + onUpdateSelectedIndex: (index: number) => void + handleItemSelection: ( + item: SourceItem, + command?: (props: MentionNodeAttrs) => void + ) => void +} + +// Maximum number of items visible in the list +const MAX_VISIBLE_ITEMS = 4 +// Height of each item in pixels +const ITEM_HEIGHT = 42 + +export const PopoverMentionList: React.FC = ({ + items, + selectedIndex, + onUpdateSelectedIndex, + handleItemSelection +}) => { + const selectedItemRef = useRef(null) + const containerRef = useRef(null) + + // Dynamically calculate container height based on number of items + const containerHeight = + Math.min(items.length, MAX_VISIBLE_ITEMS) * ITEM_HEIGHT + + // Scroll into view for the currently selected item + useEffect(() => { + const container = containerRef.current + const selectedItem = selectedItemRef.current + if (container && selectedItem) { + const containerTop = container.scrollTop + const containerBottom = containerTop + container.clientHeight + const itemTop = selectedItem.offsetTop + const itemBottom = itemTop + selectedItem.offsetHeight + + if (itemTop < containerTop) { + container.scrollTop = itemTop + } else if (itemBottom > containerBottom) { + container.scrollTop = itemBottom - container.clientHeight + } + } + }, [selectedIndex]) + + // Render list content + const renderContent = () => { + if (!items.length) { + return ( +
+ No files found +
+ ) + } + + return ( +
+ {items.map((item, index) => { + const filepath = item.filepath + const isSelected = index === selectedIndex + + return ( + + ) + })} +
+ ) + } + + return <>{renderContent()} +} diff --git a/ee/tabby-ui/components/chat/prompt-form-editor/mention-component.tsx b/ee/tabby-ui/components/chat/prompt-form-editor/mention-component.tsx new file mode 100644 index 000000000000..e79cc4e6a698 --- /dev/null +++ b/ee/tabby-ui/components/chat/prompt-form-editor/mention-component.tsx @@ -0,0 +1,37 @@ +/* eslint-disable no-console */ +// mention-component.tsx +import React from 'react' +import { NodeViewWrapper } from '@tiptap/react' + +import { cn } from '@/lib/utils' + +export const PromptFormMentionComponent = ({ node }: { node: any }) => { + console.log('mention comp:' + JSON.stringify(node.attrs)) + return ( + + + {node.attrs.category === 'file' ? ( + + + + + ) : ( + + )} + {node.attrs.name} + + + ) +} diff --git a/ee/tabby-ui/components/chat/prompt-form-editor/mention-extension.ts b/ee/tabby-ui/components/chat/prompt-form-editor/mention-extension.ts new file mode 100644 index 000000000000..714fe0053961 --- /dev/null +++ b/ee/tabby-ui/components/chat/prompt-form-editor/mention-extension.ts @@ -0,0 +1,43 @@ +import Mention from '@tiptap/extension-mention' +import { ReactNodeViewRenderer } from '@tiptap/react' + +import { PromptFormMentionComponent } from './mention-component' + +export const MENTION_EXTENSION_NAME = 'mention' + +export const PromptFormMentionExtension = Mention.extend({ + addNodeView() { + return ReactNodeViewRenderer(PromptFormMentionComponent) + }, + renderText({ node }) { + return `[[atSource:${JSON.stringify(node.attrs.atInfo)}]]` + }, + addAttributes() { + return { + name: { + default: null, + parseHTML: element => element.getAttribute('data-name'), + renderHTML: attributes => ({ + 'data-name': attributes.name + }) + }, + category: { + default: 'file', + parseHTML: element => element.getAttribute('data-category'), + renderHTML: attributes => ({ + 'data-category': attributes.category + }) + }, + atInfo: { + default: null, + parseHTML: element => { + const atInfo = element.getAttribute('data-at-info') + return atInfo ? JSON.parse(atInfo) : null + }, + renderHTML: attributes => ({ + 'data-at-info': JSON.stringify(attributes.atInfo) + }) + } + } + } +}) diff --git a/ee/tabby-ui/components/chat/prompt-form-editor/types.ts b/ee/tabby-ui/components/chat/prompt-form-editor/types.ts new file mode 100644 index 000000000000..5fc94f1bfc61 --- /dev/null +++ b/ee/tabby-ui/components/chat/prompt-form-editor/types.ts @@ -0,0 +1,88 @@ +/* eslint-disable no-console */ +import { AtInfo } from 'tabby-chat-panel/index' + +/** + * Types of mention categories, such as files, symbols, or categories. + * 'categories' serves as a root-level menu before selecting 'files' or 'symbols'. + */ +export type MentionCategory = 'files' | 'symbols' | 'categories' + +/** + * The first level pop menu. + */ +export const CATEGORIES_MENU: SourceItem[] = [ + { + name: 'Files', + category: 'categories', + filepath: '' + }, + { + name: 'Symbols', + category: 'categories', + filepath: '' + } +] + +/** + * Represents an item that can be mentioned, such as a file or symbol. + */ +export interface SourceItem { + /** + * Original 'AtInfo' object, It could be optional if we are in root level + */ + atInfo?: AtInfo + + /** + * Name derived from 'atInfo.name'. + * Typically a filename or symbol name that will be shown in the mention list. + */ + name: string + + /** + * Filepath or a descriptive path to be shown in the UI. + */ + filepath: string + + /** + * The mention category, e.g. 'files', 'symbols', or 'categories'. + */ + category: MentionCategory +} + +/** + * Alias of SourceItem, used for option lists. + */ +export type OptionItem = SourceItem + +/** + * Manages the current view of the mention menu. + * For example, 'categories' or directly 'files'/'symbols'. + */ +export interface MenuState { + view: MentionCategory +} + +/** + * Attributes for a mention node used by the Tiptap editor. + * 'id' is required by Tiptap. 'atInfo' holds optional extra data. + */ +export interface MentionNodeAttrs { + id: string + name: string + category: MentionCategory + atInfo: AtInfo // TODO: Consider removing if not needed in final implementation +} + +/** + * Represents the suggestion popover state: + * - items: the current list of potential mentions + * - command: the Tiptap command function to insert the mention + * - clientRect: a function returning the current bounding box for positioning + * - selectedIndex: the index of the currently highlighted item + */ +export interface SuggestionState { + items: SourceItem[] + command: (props: MentionNodeAttrs) => void + clientRect: () => DOMRect | null + selectedIndex: number +} diff --git a/ee/tabby-ui/components/chat/prompt-form-editor/utils.ts b/ee/tabby-ui/components/chat/prompt-form-editor/utils.ts new file mode 100644 index 000000000000..645bb1b02498 --- /dev/null +++ b/ee/tabby-ui/components/chat/prompt-form-editor/utils.ts @@ -0,0 +1,85 @@ +import { AtInfo, FileAtInfo } from 'tabby-chat-panel/index' + +import { MentionNodeAttrs, SourceItem } from './types' + +/** + * A regex to detect patterns like [[atSource: {...}]] + * The JSON inside can be parsed to reconstruct AtInfo data. + */ +export const AT_SOURCE_REGEX = /\[\[atSource:(.*?)\]\]/g + +/** + * Type guard to check if the given AtInfo is a FileAtInfo. + */ +export function isFileAtInfo(atInfo: AtInfo): atInfo is FileAtInfo { + return atInfo.atKind === 'file' +} + +/** + * Convert an AtInfo object into a SourceItem for display and mention. + * @param info An AtInfo object from tabby-chat-panel + * @returns A SourceItem containing category, name, filepath, and the raw atInfo + */ +export function atInfoToSourceItem(info: AtInfo): SourceItem { + if (isFileAtInfo(info)) { + return { + category: 'files' as const, + atInfo: info, + name: info.name, + filepath: + 'uri' in info.filepath ? info.filepath.uri : info.filepath.filepath + } + } else { + return { + category: 'symbols' as const, + atInfo: info, + name: info.name, + filepath: + 'uri' in info.location.filepath + ? info.location.filepath.uri + : info.location.filepath.filepath + } + } +} + +/** + * Convert a SourceItem into a MentionNodeAttrs object to be used by Tiptap. + * Useful for inserting a mention node into the editor. + * @param item The SourceItem to convert + * @returns The mention node attributes needed by Tiptap + */ +export function sourceItemToMentionNodeAttrs( + item: SourceItem +): MentionNodeAttrs { + return { + id: `${item.name}-${item.filepath}`, + name: item.name, + category: item.category, + atInfo: item.atInfo! + } +} + +/** + * Extracts AtInfo objects from text that match the AT_SOURCE_REGEX pattern. + * Replaces the matched patterns with @ in the original text. + * @param text The text to parse + * @returns An object with updated text (after replacement) and a list of extracted AtInfo + */ +export function extractAtSourceFromString(text: string) { + const atInfos: AtInfo[] = [] + let match + + while ((match = AT_SOURCE_REGEX.exec(text))) { + const sourceData = match[1] + try { + const parsedAtInfo = JSON.parse(sourceData) + atInfos.push(parsedAtInfo) + text = text.replace(match[0], `@${parsedAtInfo.name}`) + } catch { + // If JSON parsing fails, skip this match + continue + } + } + + return { text, atInfos } +} diff --git a/ee/tabby-ui/components/chat/prompt-form.css b/ee/tabby-ui/components/chat/prompt-form.css new file mode 100644 index 000000000000..3c979015534d --- /dev/null +++ b/ee/tabby-ui/components/chat/prompt-form.css @@ -0,0 +1,22 @@ +.source-mention { + @apply inline-flex items-baseline space-x-1; +} + +.tiptap .mention { + @apply bg-muted text-muted-foreground px-1.5 py-0.5 rounded; +} + +.tiptap p.is-editor-empty:first-child::before { + @apply text-muted-foreground float-left; + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; +} +.ProseMirror { + outline: none !important; +} + +.ProseMirror-focused { + outline: none !important; +} diff --git a/ee/tabby-ui/components/chat/prompt-form.tsx b/ee/tabby-ui/components/chat/prompt-form.tsx index 98fcd56d54ec..7ca09b3fc77e 100644 --- a/ee/tabby-ui/components/chat/prompt-form.tsx +++ b/ee/tabby-ui/components/chat/prompt-form.tsx @@ -1,348 +1,457 @@ -import * as React from 'react' -import { UseChatHelpers } from 'ai/react' -import { debounce, has } from 'lodash-es' -import useSWR from 'swr' +import React, { + ForwardedRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState +} from 'react' +import { Editor, Extension } from '@tiptap/core' +import Document from '@tiptap/extension-document' +import Paragraph from '@tiptap/extension-paragraph' +import Placeholder from '@tiptap/extension-placeholder' +import Text from '@tiptap/extension-text' +import { EditorContent, useEditor } from '@tiptap/react' import { useEnterSubmit } from '@/lib/hooks/use-enter-submit' -import fetcher from '@/lib/tabby/fetcher' -import type { ISearchHit, SearchReponse } from '@/lib/types' -import { cn } from '@/lib/utils' import { Button, buttonVariants } from '@/components/ui/button' -import { - IconArrowElbow, - IconEdit, - IconSymbolFunction -} from '@/components/ui/icons' -import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover' +import { IconArrowElbow, IconEdit } from '@/components/ui/icons' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' + +import { Popover, PopoverContent } from '../ui/popover' +import { ChatContext } from './chat' +import { PopoverMentionList } from './popover-mention-list' +import { + CATEGORIES_MENU, + MentionNodeAttrs, + MenuState, + SourceItem, + SuggestionState +} from './prompt-form-editor/types' + +import './prompt-form.css' + +import { cn } from '@/lib/utils' + +import { PromptFormMentionExtension } from './prompt-form-editor/mention-extension' import { - SearchableSelect, - SearchableSelectAnchor, - SearchableSelectContent, - SearchableSelectOption, - SearchableSelectTextarea -} from '@/components/searchable-select' - -export interface PromptProps - extends Pick { + atInfoToSourceItem, + sourceItemToMentionNodeAttrs +} from './prompt-form-editor/utils' + +export interface PromptProps { onSubmit: (value: string) => Promise isLoading: boolean - chatInputRef: React.RefObject isInitializing?: boolean } export interface PromptFormRef { focus: () => void + setInput: (value: string) => void + input: string } function PromptFormRenderer( - { - onSubmit, - input, - setInput, - isLoading, - chatInputRef, - isInitializing - }: PromptProps, - ref: React.ForwardedRef + { onSubmit, isLoading, isInitializing }: PromptProps, + ref: ForwardedRef ) { - const { formRef, onKeyDown } = useEnterSubmit() - const [queryCompletionUrl, setQueryCompletionUrl] = React.useState< - string | null - >(null) - const [suggestionOpen, setSuggestionOpen] = React.useState(false) - // store the input selection for replacing inputValue - const prevInputSelectionEnd = React.useRef() - // for updating the input selection after replacing - const nextInputSelectionRange = React.useRef<[number, number]>() - const [options, setOptions] = React.useState([]) - const [selectedCompletionsMap, setSelectedCompletionsMap] = React.useState< - Record - >({}) - - const { data: completionData } = useSWR( - queryCompletionUrl, - fetcher, - { - revalidateOnFocus: false, - dedupingInterval: 0, - errorRetryCount: 0 - } + const { formRef } = useEnterSubmit() + + const { provideFileAtInfo } = React.useContext(ChatContext) + + const popoverRef = useRef(null) + const selectedItemRef = useRef(null) + + const [suggestionState, setSuggestionState] = + useState(null) + + const suggestionRef = useRef | null>(null) + + const [menuState, setMenuState] = useState({ view: 'categories' }) + const menuStateRef = useRef({ view: 'categories' }) + + useEffect(() => { + menuStateRef.current = menuState + }, [menuState]) + + const updateSelectedIndex = useCallback((index: number) => { + setSuggestionState(prev => + prev ? { ...prev, selectedIndex: index } : null + ) + }, []) + + /** + * Scroll the suggestion container so that the selected item is visible. + */ + const scrollToSelected = useCallback( + (containerEl: HTMLElement | null, selectedEl: HTMLElement | null) => { + if (!containerEl || !selectedEl) return + const containerRect = containerEl.getBoundingClientRect() + const selectedRect = selectedEl.getBoundingClientRect() + + if (selectedRect.bottom > containerRect.bottom) { + containerEl.scrollTop += selectedRect.bottom - containerRect.bottom + } else if (selectedRect.top < containerRect.top) { + containerEl.scrollTop -= containerRect.top - selectedRect.top + } + }, + [] ) - React.useEffect(() => { - const suggestions = completionData?.hits ?? [] - setOptions(suggestions) - setSuggestionOpen(!!suggestions?.length) - }, [completionData?.hits]) - - React.useImperativeHandle(ref, () => ({ - focus: () => chatInputRef.current?.focus() - })) - - React.useEffect(() => { - if ( - input && - chatInputRef.current && - chatInputRef.current !== document.activeElement - ) { - chatInputRef.current.focus() + // Whenever the selected index changes, ensure it's scrolled into view + useEffect(() => { + if (suggestionState?.selectedIndex !== undefined) { + scrollToSelected(popoverRef.current, selectedItemRef.current) } - }, [input, chatInputRef]) - - React.useLayoutEffect(() => { - if (nextInputSelectionRange.current?.length) { - chatInputRef.current?.setSelectionRange?.( - nextInputSelectionRange.current[0], - nextInputSelectionRange.current[1] - ) - nextInputSelectionRange.current = undefined + }, [suggestionState?.selectedIndex, scrollToSelected]) + + /** + * Custom Tiptap extension to handle special keyboard shortcuts. + */ + const CustomKeyboardShortcuts = Extension.create({ + addKeyboardShortcuts() { + return { + 'Shift-Enter': () => + this.editor.commands.first(({ commands }) => [ + () => commands.newlineInCode(), + () => commands.createParagraphNear(), + () => commands.liftEmptyBlock(), + () => commands.splitBlock() + ]), + Enter: ({ editor }) => { + // If there's an active suggestion or if we are not in the main menu, do not submit + if ( + suggestionRef.current || + menuStateRef.current.view !== 'categories' + ) { + return false + } + handleSubmit(undefined, editor.getText()) + editor.commands.setContent('') + return true + } + } } - }, [chatInputRef]) - - const handleSearchCompletion = React.useMemo(() => { - return debounce((e: React.ChangeEvent) => { - const value = e.target?.value ?? '' - const end = e.target?.selectionEnd ?? 0 - const queryNameMatches = getSearchCompletionQueryName(value, end) - const queryName = queryNameMatches?.[1] - if (queryName) { - const query = encodeURIComponent(`name:${queryName} AND kind:function`) - const url = `/v1beta/search?q=${query}` - setQueryCompletionUrl(url) + }) + + /** + * Handle item selection when '@' mention is triggered. + */ + const handleItemSelection = useCallback( + (item: SourceItem, command?: (props: MentionNodeAttrs) => void) => { + // If user is in the main menu (categories) and picks something like "files" + if (menuStateRef.current.view === 'categories') { + switch (item.name.toLowerCase()) { + case 'files': { + const state: MenuState = { view: 'files' } + setMenuState(state) + menuStateRef.current = state + + // If a function is provided to fetch file info, get them and update suggestions + if (provideFileAtInfo) { + provideFileAtInfo().then(files => { + if (!files) return + const items = files.map(atInfoToSourceItem) + setSuggestionState(prev => + prev + ? { + ...prev, + items, + selectedIndex: 0 + } + : null + ) + suggestionRef.current = { + items, + selectedIndex: 0, + command: command || suggestionRef.current?.command! + } + }) + } + break + } + case 'symbols': + // TODO: Implement symbol selection if needed + break + default: + break + } } else { - setOptions([]) - setSuggestionOpen(false) + const attrs = sourceItemToMentionNodeAttrs(item) + if (command) { + command(attrs) + } else if (suggestionRef.current?.command) { + suggestionRef.current.command(attrs) + } } - }, 200) - }, []) + }, + [provideFileAtInfo] + ) + + /** + * Handle up/down/enter keys in the suggestion popover. + */ + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + const currentSuggestion = suggestionRef.current + if (!currentSuggestion || !currentSuggestion.items?.length) return false + + switch (event.key) { + case 'ArrowUp': + case 'ArrowDown': { + event.preventDefault() + const direction = event.key === 'ArrowUp' ? -1 : 1 - const handleCompletionSelect = (item: ISearchHit) => { - const selectionEnd = prevInputSelectionEnd.current ?? 0 - const queryNameMatches = getSearchCompletionQueryName(input, selectionEnd) - if (queryNameMatches) { - setSelectedCompletionsMap({ - ...selectedCompletionsMap, - [`@${item.doc?.name}`]: item + setSuggestionState(prev => { + if (!prev) return null + const length = currentSuggestion.items.length + const newIndex = (prev.selectedIndex + direction + length) % length + return { ...prev, selectedIndex: newIndex } + }) + + suggestionRef.current = { + ...currentSuggestion, + selectedIndex: + (currentSuggestion.selectedIndex + + direction + + currentSuggestion.items.length) % + currentSuggestion.items.length + } + return true + } + case 'Enter': { + event.preventDefault() + const selectedItem = + currentSuggestion.items[currentSuggestion.selectedIndex] + if (selectedItem) { + handleItemSelection(selectedItem, currentSuggestion.command) + } + return true + } + default: + return false + } + }, + [handleItemSelection] + ) + + /** + * Initialize the Tiptap editor with the specified extensions and placeholder. + */ + const editor = useEditor({ + extensions: [ + Document, + Paragraph, + Text, + CustomKeyboardShortcuts, + Placeholder.configure({ + showOnlyWhenEditable: true, + placeholder: 'Ask anything...' + }), + PromptFormMentionExtension.configure({ + HTMLAttributes: { + class: 'mention' + }, + suggestion: { + char: '@', + allowSpaces: true, + items: async ({ query }): Promise => { + if (!provideFileAtInfo) return [] + + if (menuStateRef.current.view === 'categories') { + return CATEGORIES_MENU + } + + try { + const files = await provideFileAtInfo({ query }) + return files?.map(atInfoToSourceItem) || [] + } catch (error) { + // TODO: handle or log error if needed + return [] + } + }, + render: () => ({ + onStart: props => { + const newState: SuggestionState = { + items: props.items as SourceItem[], + command: props.command, + clientRect: props.clientRect!, + selectedIndex: 0 + } + suggestionRef.current = { + items: props.items as SourceItem[], + command: props.command, + selectedIndex: 0 + } + setSuggestionState(newState) + + // Ensure the editor keeps focus after mention starts + requestAnimationFrame(() => { + editor?.commands.focus() + }) + }, + onUpdate: props => { + const newState: SuggestionState = { + items: props.items as SourceItem[], + command: props.command, + clientRect: props.clientRect!, + selectedIndex: 0 + } + suggestionRef.current = { + items: props.items as SourceItem[], + command: props.command, + selectedIndex: 0 + } + setSuggestionState(newState) + + requestAnimationFrame(() => { + editor?.commands.focus() + }) + }, + onKeyDown: ({ event }) => { + if (['ArrowUp', 'ArrowDown', 'Enter'].includes(event.key)) { + return handleKeyDown(event) + } + return false + }, + onExit: () => { + const initialMenuState: MenuState = { view: 'categories' } + setMenuState(initialMenuState) + menuStateRef.current = initialMenuState + suggestionRef.current = null + setSuggestionState(null) + } + }) + } }) - const replaceString = `@${item?.doc?.name} ` - const prevInput = input - .substring(0, selectionEnd) - .replace(new RegExp(queryNameMatches[0]), '') - const nextSelectionEnd = prevInput.length + replaceString.length - nextInputSelectionRange.current = [nextSelectionEnd, nextSelectionEnd] - setInput(prevInput + replaceString + input.slice(selectionEnd)) - } - setOptions([]) - setSuggestionOpen(false) - } + ], + content: '' + }) - const handlePromptSubmit: React.FormEventHandler< - HTMLFormElement - > = async e => { - e.preventDefault() - if (!input?.trim() || isLoading || isInitializing) { - return - } + const input = editor?.getText() || '' + + useImperativeHandle( + ref, + () => ({ + focus: () => { + editor?.commands.focus('end') + }, + setInput: (str: string) => { + editor?.commands.setContent(str) + }, + input + }), + [editor, input] + ) + + /** + * Submit handler for the form. Called on Enter or via button click. + */ + const handleSubmit = async (e?: React.FormEvent, text?: string) => { + e?.preventDefault() + if (isLoading) return - let finalInput = input - Object.keys(selectedCompletionsMap).forEach(key => { - const completion = selectedCompletionsMap[key] - if (!completion?.doc) return - finalInput = finalInput.replaceAll( - key, - `\n${'```'}${completion.doc?.language ?? ''}\n${ - completion.doc.body ?? '' - }\n${'```'}\n` - ) - }) - - setInput('') - await onSubmit(finalInput) + const finalText = text ?? input + if (!finalText?.trim()) return + + await onSubmit(finalText) + editor?.commands.setContent('') } - const handleTextareaKeyDown = ( - e: React.KeyboardEvent, - isOpen: boolean - ) => { - if (e.key === 'Enter' && isOpen) { - e.preventDefault() - } else if ( - isOpen && - ['ArrowRight', 'ArrowLeft', 'Home', 'End'].includes(e.key) - ) { - setOptions([]) - setSuggestionOpen(false) - } else { - if (!isOpen) { - ;(e as any).preventDownshiftDefault = true - } - onKeyDown(e) + /** + * Calculate the vertical offset for the Popover, depending on the active menu view. + */ + const topOffset = (() => { + if (!suggestionState) return 0 + const { view } = menuStateRef.current + if (view === 'categories') { + return 70 + } else if (view === 'files' && suggestionState.items.length > 0) { + // Each item is ~42px tall, display max 4 items + return Math.min(suggestionState.items.length * 42, 4 * 42) } - } + return 70 + })() return ( -
- { - if (isOpen && options?.length) { - setSuggestionOpen(isOpen) - } else { - setSuggestionOpen(false) - setOptions([]) - } - }} - > - {({ open, highlightedIndex }) => { - const highlightedOption = options?.[highlightedIndex] - - return ( - <> - -
- - - - { - if (has(e, 'target.value')) { - prevInputSelectionEnd.current = e.target.selectionEnd - setInput(e.target.value) - // TODO: Temporarily disabling the current search function. Will be replaced with a different search functionality in the future. - // handleSearchCompletion(e) - } else { - prevInputSelectionEnd.current = undefined - } - }} - onKeyDown={e => handleTextareaKeyDown(e, open)} - /> -
- - - - - Send message - -
-
-
- e.preventDefault()} - className="w-[60vw] md:w-[430px]" - > - - -
- {open && - !!options?.length && - options.map((item, index) => ( - -
-
- -
- {item?.doc?.name}(...) -
-
-
- {item?.doc?.body} -
-
-
- ))} -
-
- e.preventDefault()} - onKeyDownCapture={e => e.preventDefault()} - className="rounded-none" - collisionPadding={{ bottom: 120 }} - > -
-
- {highlightedOption?.doc?.kind - ? `(${highlightedOption?.doc?.kind}) ` - : ''} - {highlightedOption?.doc?.name} -
-
- {highlightedOption?.doc?.body} -
-
-
-
-
- - ) - }} -
-
+ <> +
+
+ + + + +
+ +
+ +
+ + + + + Send message + +
+
+
+ + {/* Suggestion popover, triggered if mentionState is not null */} + {suggestionState && ( + + e.preventDefault()} + onPointerDownOutside={e => e.preventDefault()} + onFocusOutside={e => e.preventDefault()} + > + + handleItemSelection(item, suggestionRef.current?.command) + } + /> + + + )} + ) } +/** + * Export PromptForm with a forwarded ref to access internal methods. + */ export const PromptForm = React.forwardRef( PromptFormRenderer ) -/** - * Retrieves the name of the completion query from a given string@. - * @param {string} val - The input string to search for the completion query name. - * @param {number | undefined} selectionEnd - The index at which the selection ends in the input string. - * @return {string | undefined} - The name of the completion query if found, otherwise undefined. - */ -export function getSearchCompletionQueryName( - val: string, - selectionEnd: number | undefined -): RegExpExecArray | null { - const queryString = val.substring(0, selectionEnd) - const matches = /@(\w+)$/.exec(queryString) - return matches -} - -function IconForCompletionKind({ - kind, - ...rest -}: { kind: string | undefined } & React.ComponentProps<'svg'>) { - switch (kind) { - case 'function': - return - default: - return - } -} +export default PromptForm diff --git a/ee/tabby-ui/package.json b/ee/tabby-ui/package.json index f44474941979..4f4b1792b67d 100644 --- a/ee/tabby-ui/package.json +++ b/ee/tabby-ui/package.json @@ -48,13 +48,16 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-tooltip": "^1.0.7", "@sindresorhus/slugify": "^2.2.1", - "@tiptap/extension-document": "^2.6.6", - "@tiptap/extension-mention": "^2.6.6", - "@tiptap/extension-paragraph": "^2.6.6", - "@tiptap/extension-placeholder": "^2.6.6", - "@tiptap/extension-text": "^2.6.6", + "@tiptap/core": "^2.6.6", + "@tiptap/extension-document": "^2.10.4", + "@tiptap/extension-mention": "^2.10.4", + "@tiptap/extension-paragraph": "^2.10.4", + "@tiptap/extension-placeholder": "^2.10.4", + "@tiptap/extension-text": "^2.10.4", "@tiptap/pm": "^2.6.6", - "@tiptap/react": "^2.6.6", + "@tiptap/react": "^2.10.4", + "@tiptap/starter-kit": "^2.6.6", + "@tiptap/suggestion": "^2.10.4", "@uidotdev/usehooks": "^2.4.1", "@uiw/codemirror-extensions-langs": "^4.21.21", "@urql/core": "^4.2.3", @@ -168,4 +171,4 @@ "typescript": "^5.1.3", "vitest": "^1.5.2" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54a06d0b907a..062f2d8df0ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -360,10 +360,10 @@ importers: version: 5.2.0 esbuild-plugin-copy: specifier: ^2.1.1 - version: 2.1.1(esbuild@0.20.2) + version: 2.1.1(esbuild@0.19.12) esbuild-plugin-polyfill-node: specifier: ^0.3.0 - version: 0.3.0(esbuild@0.20.2) + version: 0.3.0(esbuild@0.19.12) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -511,27 +511,36 @@ importers: '@sindresorhus/slugify': specifier: ^2.2.1 version: 2.2.1 - '@tiptap/extension-document': + '@tiptap/core': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) + version: 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/extension-document': + specifier: ^2.10.4 + version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) '@tiptap/extension-mention': - specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(@tiptap/suggestion@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)) + specifier: ^2.10.4 + version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)(@tiptap/suggestion@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)) '@tiptap/extension-paragraph': - specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) + specifier: ^2.10.4 + version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) '@tiptap/extension-placeholder': - specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + specifier: ^2.10.4 + version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) '@tiptap/extension-text': - specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) + specifier: ^2.10.4 + version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) '@tiptap/pm': specifier: ^2.6.6 - version: 2.6.6 + version: 2.10.4 '@tiptap/react': + specifier: ^2.10.4 + version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@tiptap/starter-kit': specifier: ^2.6.6 - version: 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 2.10.4 + '@tiptap/suggestion': + specifier: ^2.10.4 + version: 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) '@uidotdev/usehooks': specifier: ^2.4.1 version: 2.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -3351,8 +3360,8 @@ packages: resolution: {integrity: sha512-kcbt7w23pcVYGLnJkh2LZpXF1OX5RDM4DLOtwPug2HvRE8ow/YfY8ZEM1YCFlA41D8rBPBVP918cYeIx4BVUbw==} engines: {node: '>=14.19.0', npm: '>=7.0.0'} - '@remirror/core-constants@2.0.2': - resolution: {integrity: sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==} + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} '@repeaterjs/repeater@3.0.5': resolution: {integrity: sha512-l3YHBLAol6d/IKnB9LhpD0cEZWAoe3eFKUyTYWmFmCO2Q/WOckxLQAUyMZWwZV2M/m3+4vgRoaolFqaII82/TA==} @@ -3790,67 +3799,155 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - '@tiptap/core@2.6.6': - resolution: {integrity: sha512-VO5qTsjt6rwworkuo0s5AqYMfDA0ZwiTiH6FHKFSu2G/6sS7HKcc/LjPq+5Legzps4QYdBDl3W28wGsGuS1GdQ==} + '@tiptap/core@2.10.4': + resolution: {integrity: sha512-fExFRTRgb6MSpg2VvR5qO2dPTQAZWuUoU4UsBCurIVcPWcyVv4FG1YzgMyoLDKy44rebFtwUGJbfU9NzX7Q/bA==} peerDependencies: - '@tiptap/pm': ^2.6.6 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-bubble-menu@2.6.6': - resolution: {integrity: sha512-IkfmlZq67aaegym5sBddBc/xXWCArxn5WJEl1oxKEayjQhybKSaqI7tk0lOx/x7fa5Ml1WlGpCFh+KKXbQTG0g==} + '@tiptap/extension-blockquote@2.10.4': + resolution: {integrity: sha512-4JSwAM3B92YWvGzu/Vd5rovPrCGwLSaSLD5rxcLyfxLSrTDQd3n7lp78pzVgGhunVECzaGF5A0ByWWpEyS0a3w==} peerDependencies: - '@tiptap/core': ^2.6.6 - '@tiptap/pm': ^2.6.6 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-document@2.6.6': - resolution: {integrity: sha512-6qlH5VWzLHHRVeeciRC6C4ZHpMsAGPNG16EF53z0GeMSaaFD/zU3B239QlmqXmLsAl8bpf8Bn93N0t2ABUvScw==} + '@tiptap/extension-bold@2.10.4': + resolution: {integrity: sha512-SdO4oFQKaERCGfwOc1CLYQRtThENam2KWfWmvpsymknokt5qYzU57ft0SE1HQV9vVYEzZ9HrWIgv2xrgu0g9kg==} peerDependencies: - '@tiptap/core': ^2.6.6 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-floating-menu@2.6.6': - resolution: {integrity: sha512-lPkESOfAUxgmXRiNqUU23WSyja5FUfSWjsW4hqe+BKNjsUt1OuFMEtYJtNc+MCGhhtPfFvM3Jg6g9jd6g5XsLQ==} + '@tiptap/extension-bubble-menu@2.10.4': + resolution: {integrity: sha512-GVtZwJaQyLBptMsmDtYl5GEobd1Uu7C9sc9Z+PdXwMuxmFfg+j07bCKCj5JJj/tjgXCSLVxWdTlDHxNrgzQHjw==} peerDependencies: - '@tiptap/core': ^2.6.6 - '@tiptap/pm': ^2.6.6 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-mention@2.6.6': - resolution: {integrity: sha512-fghNe4ZQRiZ7i3+sSrZx87zPZjaCwVtxn56/5UinoBUP/ZpCGwGtI+ErKhCBVyLW1fKyd0MmlihK/IGIeCBw1A==} + '@tiptap/extension-bullet-list@2.10.4': + resolution: {integrity: sha512-JVwDPgOBYRU2ivaadOh4IaQYXQEiSw6sB36KT/bwqJF2GnEvLiMwptdRMn9Uuh6xYR3imjIZtV6uZAoneZdd6g==} peerDependencies: - '@tiptap/core': ^2.6.6 - '@tiptap/pm': ^2.6.6 - '@tiptap/suggestion': ^2.6.6 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-paragraph@2.6.6': - resolution: {integrity: sha512-fD/onCr16UQWx+/xEmuFC2MccZZ7J5u4YaENh8LMnAnBXf78iwU7CAcmuc9rfAEO3qiLoYGXgLKiHlh2ZfD4wA==} + '@tiptap/extension-code-block@2.10.4': + resolution: {integrity: sha512-qS4jnbJqghNMT2+B+GQ807ATgqkL9OQ//NlL+ZwVSe+DPDduNA9B6IB9SrWENDfOnzekpi7kcEcm+RenELARRQ==} peerDependencies: - '@tiptap/core': ^2.6.6 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/extension-placeholder@2.6.6': - resolution: {integrity: sha512-J0ZMvF93NsRrt+R7IQ3GhxNq32vq+88g25oV/YFJiwvC48HMu1tQB6kG1I3LJpu5b8lN+LnfANNqDOEhiBfjaA==} + '@tiptap/extension-code@2.10.4': + resolution: {integrity: sha512-Vj/N0nbSQiV1o7X7pRySK9Fu72Dd266gm27TSlsts6IwJu5MklFvz7ezJUWoLjt2wmCV8/U/USmk/39ic9qjvg==} peerDependencies: - '@tiptap/core': ^2.6.6 - '@tiptap/pm': ^2.6.6 + '@tiptap/core': ^2.7.0 - '@tiptap/extension-text@2.6.6': - resolution: {integrity: sha512-e84uILnRzNzcwK1DVQNpXVmBG1Cq3BJipTOIDl1LHifOok7MBjhI/X+/NR0bd3N2t6gmDTWi63+4GuJ5EeDmsg==} + '@tiptap/extension-document@2.10.4': + resolution: {integrity: sha512-1Pqrl6Rr9bVEHJ3zO2dM7UUA0Qn/r70JQ9YLlestjW1sbMaMuY3Ifvu2uSyUE7SAGV3gvxwNVQCrv8f0VlVEaA==} peerDependencies: - '@tiptap/core': ^2.6.6 + '@tiptap/core': ^2.7.0 - '@tiptap/pm@2.6.6': - resolution: {integrity: sha512-56FGLPn3fwwUlIbLs+BO21bYfyqP9fKyZQbQyY0zWwA/AG2kOwoXaRn7FOVbjP6CylyWpFJnpRRmgn694QKHEg==} + '@tiptap/extension-dropcursor@2.10.4': + resolution: {integrity: sha512-0XEM/yNLaMc/sZlYOau7XpHyYiHT9LwXUe7kmze/L8eowIa/iLvmRbcnUd3rtlZ7x7wooE6UO9c7OtlREg4ZBw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/react@2.6.6': - resolution: {integrity: sha512-AUmdb/J1O/vCO2b8LL68ctcZr9a3931BwX4fUUZ1kCrCA5lTj2xz0rjeAtpxEdzLnR+Z7q96vB7vf7bPYOUAew==} + '@tiptap/extension-floating-menu@2.10.4': + resolution: {integrity: sha512-K2MDiu6CwQ7+Jr6g1Lh3Tuxm1L6SefSHMpQO0UW3aRGwgEV5pjlrztnBFX4K9b7MNuQ4dJGCUK9u8Cv7Xss0qg==} peerDependencies: - '@tiptap/core': ^2.6.6 - '@tiptap/pm': ^2.6.6 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.10.4': + resolution: {integrity: sha512-KbJfoaqTZePpkWAN+klpK5j0UVtELxN7H5B0J556/UCB/rnq+OsdEFHPks2Ss9TidqWzRUqcxUE50UZ7b8h7Ug==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 - '@tiptap/suggestion@2.6.6': - resolution: {integrity: sha512-jogG0QgGit9UtTznVnhQfNImZfQM89NR0is20yRQzC0HmD8B8f3jmGrotG63Why2oKbeoe3CpM5/5eDE/paqCA==} + '@tiptap/extension-hard-break@2.10.4': + resolution: {integrity: sha512-nW9wubW1A/CO2Ssn9wNMP08tR9Oarg9VUGzJ5qNuz38DDNyntE1SyDS+XStkeMq5nKqJ3YKhukyAJH/PiRq4Mg==} peerDependencies: - '@tiptap/core': ^2.6.6 - '@tiptap/pm': ^2.6.6 + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.10.4': + resolution: {integrity: sha512-7D0h0MIvE97Gx3Qwuo2xnPDK07WfCnyh4tpOPBOus4e1g6sgxVkwDwhbkYWiwvIrf4BUVJflnke/DEDCVp6/Eg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.10.4': + resolution: {integrity: sha512-fg6BNxbpMMtgKaiNI/GLcCzkxIQMwSYBhO9LA0CxLvmsWGU+My4r9W3DK6HwNoRJ9+6OleDPSLo1P73fbSTtEA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-horizontal-rule@2.10.4': + resolution: {integrity: sha512-s9ycm/BOGoW3L0Epnj541vdngHbFbMM488HoODd1CmVSw1C+wBWFgsukgqKjlyE3VGfZXuSb1ur9zinW0RiLJQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-italic@2.10.4': + resolution: {integrity: sha512-8MIQ+wsbyxNCZDCFTVTOXrS2AvFyOhtlBNgVU2+6r6xnJV4AcfEA3qclysqrjOlL117ped/nzDeoB0AeX0CI+Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-list-item@2.10.4': + resolution: {integrity: sha512-8K3WUD5fPyw2poQKnJGGm7zlfeIbpld92+SRF4M9wkp95EzvgexTlodvxlrL3i8zKXcQQVyExWA8kCcGPFb9bA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-mention@2.10.4': + resolution: {integrity: sha512-pVouKWxSVQSy4zn6HrljPIP1AG826gkm/w18Asi8QnZvR0AMqGLh9q7qd9Kc0j8NKoCzlzK8hECGlYPEaBldow==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@tiptap/suggestion': ^2.7.0 + + '@tiptap/extension-ordered-list@2.10.4': + resolution: {integrity: sha512-NaeEu+qFG2O0emc8WlwOM7DKNKOaqHWuNkuKrrmQzslgL+UQSEGlGMo6NEJ5sLLckPBDpIa0MuRm30407JE+cg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.10.4': + resolution: {integrity: sha512-SRNVhT8OXqjpZtcyuOtofbtOpXXFrQrjqqCc/yXebda//2SfUTOvB16Lss77vQOWi6xr7TF1mZuowJgSTkcczw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-placeholder@2.10.4': + resolution: {integrity: sha512-leWG4xP7cvddR6alGZS7yojOh9941bxehgAeQDLlEisaJcNa2Od5Vbap2zipjc5sXMxZakQVChL27oH1wWhHkQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-strike@2.10.4': + resolution: {integrity: sha512-OibipsomFpOJWTPVX/z4Z53HgwDA93lE/loHGa+ONJfML1dO6Zd6UTwzaVO1/g8WOwRgwkYu/6JnhxLKRlP8Lg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.10.4': + resolution: {integrity: sha512-ibq7avkcwHyUSG53Hf+P31rrwsKVbbiqbWZM4kXC7M2X3iUwFrtvaa+SWzyWQfE1jl2cCrD1+rfSkj/alcOKGg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.10.4': + resolution: {integrity: sha512-wPdVxCHrIS9S+8n08lgyyqRZPj9FBbyLlFt74/lV5yBC3LOorq1VKdjrTskmaj4jud7ImXoKDyBddAYTHdJ1xw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.10.4': + resolution: {integrity: sha512-pZ4NEkRtYoDLe0spARvXZ1N3hNv/5u6vfPdPtEbmNpoOSjSNqDC1kVM+qJY0iaCYpxbxcv7cxn3kBumcFLQpJQ==} + + '@tiptap/react@2.10.4': + resolution: {integrity: sha512-JTeqDB+xgjo46QC9ILRXe2TcSfxKVRwhZ3vDvYoemN7giRk5a/WsCF1VQIT1fax+tCl6kfv3U1f4Mkx0DkbPkA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@2.10.4': + resolution: {integrity: sha512-tu/WCs9Mkr5Nt8c3/uC4VvAbQlVX0OY7ygcqdzHGUeG9zP3twdW7o5xM3kyDKR2++sbVzqu5Ll5qNU+1JZvPGQ==} + + '@tiptap/suggestion@2.10.4': + resolution: {integrity: sha512-7Bzcn1REA7OmVRxiMF2kVK9EhosXotdLAGaEvSbn4zQtHCJG0tREuYvPy53LGzVuPkBDR6Pf6sp1QbGvSne/8g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} @@ -3992,15 +4089,24 @@ packages: '@types/katex@0.16.3': resolution: {integrity: sha512-CeVMX9EhVUW8MWnei05eIRks4D5Wscw/W9Byz1s3PA+yJvcdvq9SaDjiUKvRvEgjpdTyJMjQA43ae4KTwsvOPg==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + '@types/lodash-es@4.17.10': resolution: {integrity: sha512-YJP+w/2khSBwbUSFdGsSqmDvmnN3cCKoPOL7Zjle6s30ZtemkkqhjVfFqGwPN7ASil5VyjE2GtyU/yqYY6mC0A==} '@types/lodash@4.14.200': resolution: {integrity: sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==} + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@3.0.13': resolution: {integrity: sha512-HjiGiWedR0DVFkeNljpa6Lv4/IZU1+30VY5d747K7lBudFc3R0Ibr6yJ9lN3BE28VnZyDfLF/VB1Ql1ZIbKrmg==} + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} @@ -8842,8 +8948,8 @@ packages: prosemirror-collab@1.3.1: resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} - prosemirror-commands@1.6.0: - resolution: {integrity: sha512-xn1U/g36OqXn2tn5nGmvnnimAj/g1pUx2ypJJIe8WkVX83WyJVC5LTARaxZa2AtQRwntu9Jc5zXs9gL9svp/mg==} + prosemirror-commands@1.6.2: + resolution: {integrity: sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==} prosemirror-dropcursor@1.8.1: resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==} @@ -8860,14 +8966,14 @@ packages: prosemirror-keymap@1.2.2: resolution: {integrity: sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==} - prosemirror-markdown@1.13.0: - resolution: {integrity: sha512-UziddX3ZYSYibgx8042hfGKmukq5Aljp2qoBiJRejD/8MH70siQNz5RB1TrdTPheqLMy4aCe4GYNF10/3lQS5g==} + prosemirror-markdown@1.13.1: + resolution: {integrity: sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==} prosemirror-menu@1.2.4: resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==} - prosemirror-model@1.22.3: - resolution: {integrity: sha512-V4XCysitErI+i0rKFILGt/xClnFJaohe/wrrlT2NSZ+zk8ggQfDH4x2wNK7Gm0Hp4CIoWizvXFP7L9KMaCuI0Q==} + prosemirror-model@1.24.1: + resolution: {integrity: sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==} prosemirror-schema-basic@1.2.3: resolution: {integrity: sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==} @@ -8878,21 +8984,21 @@ packages: prosemirror-state@1.4.3: resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} - prosemirror-tables@1.5.0: - resolution: {integrity: sha512-VMx4zlYWm7aBlZ5xtfJHpqa3Xgu3b7srV54fXYnXgsAcIGRqKSrhiK3f89omzzgaAgAtDOV4ImXnLKhVfheVNQ==} + prosemirror-tables@1.6.1: + resolution: {integrity: sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==} - prosemirror-trailing-node@2.0.9: - resolution: {integrity: sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==} + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} peerDependencies: prosemirror-model: ^1.22.1 prosemirror-state: ^1.4.2 prosemirror-view: ^1.33.8 - prosemirror-transform@1.10.0: - resolution: {integrity: sha512-9UOgFSgN6Gj2ekQH5CTDJ8Rp/fnKR2IkYfGdzzp5zQMFsS4zDllLVx/+jGcX86YlACpG7UR5fwAXiWzxqWtBTg==} + prosemirror-transform@1.10.2: + resolution: {integrity: sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==} - prosemirror-view@1.34.1: - resolution: {integrity: sha512-KS2xmqrAM09h3SLu1S2pNO/ZoIP38qkTJ6KFd7+BeSfmX/ek0n5yOfGuiTZjFNTC8GOsEIUa1tHxt+2FMu3yWQ==} + prosemirror-view@1.37.1: + resolution: {integrity: sha512-MEAnjOdXU1InxEmhjgmEzQAikaS6lF3hD64MveTPpjOGNTl87iRLA1HupC/DEV6YuK7m4Q9DHFNTjwIVtqz5NA==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -13857,7 +13963,7 @@ snapshots: - encoding - supports-color - '@remirror/core-constants@2.0.2': {} + '@remirror/core-constants@3.0.0': {} '@repeaterjs/repeater@3.0.5': {} @@ -14218,81 +14324,175 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.3.3(ts-node@10.9.2(@types/node@17.0.45)(typescript@5.2.2)) - '@tiptap/core@2.6.6(@tiptap/pm@2.6.6)': + '@tiptap/core@2.10.4(@tiptap/pm@2.10.4)': dependencies: - '@tiptap/pm': 2.6.6 + '@tiptap/pm': 2.10.4 - '@tiptap/extension-bubble-menu@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/extension-blockquote@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/pm': 2.6.6 + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-bold@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-bubble-menu@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 tippy.js: 6.3.7 - '@tiptap/extension-document@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': + '@tiptap/extension-bullet-list@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-code-block@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 + + '@tiptap/extension-code@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-document@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-dropcursor@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 - '@tiptap/extension-floating-menu@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/extension-floating-menu@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/pm': 2.6.6 + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 tippy.js: 6.3.7 - '@tiptap/extension-mention@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(@tiptap/suggestion@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6))': + '@tiptap/extension-gapcursor@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/pm': 2.6.6 - '@tiptap/suggestion': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 - '@tiptap/extension-paragraph@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': + '@tiptap/extension-hard-break@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) - '@tiptap/extension-placeholder@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': + '@tiptap/extension-heading@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/pm': 2.6.6 + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) - '@tiptap/extension-text@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': + '@tiptap/extension-history@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 - '@tiptap/pm@2.6.6': + '@tiptap/extension-horizontal-rule@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 + + '@tiptap/extension-italic@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-list-item@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-mention@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)(@tiptap/suggestion@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 + '@tiptap/suggestion': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + + '@tiptap/extension-ordered-list@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-paragraph@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-placeholder@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 + + '@tiptap/extension-strike@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-text-style@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/extension-text@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + + '@tiptap/pm@2.10.4': dependencies: prosemirror-changeset: 2.2.1 prosemirror-collab: 1.3.1 - prosemirror-commands: 1.6.0 + prosemirror-commands: 1.6.2 prosemirror-dropcursor: 1.8.1 prosemirror-gapcursor: 1.3.2 prosemirror-history: 1.4.1 prosemirror-inputrules: 1.4.0 prosemirror-keymap: 1.2.2 - prosemirror-markdown: 1.13.0 + prosemirror-markdown: 1.13.1 prosemirror-menu: 1.2.4 - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-schema-basic: 1.2.3 prosemirror-schema-list: 1.4.1 prosemirror-state: 1.4.3 - prosemirror-tables: 1.5.0 - prosemirror-trailing-node: 2.0.9(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.34.1) - prosemirror-transform: 1.10.0 - prosemirror-view: 1.34.1 + prosemirror-tables: 1.6.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1) + prosemirror-transform: 1.10.2 + prosemirror-view: 1.37.1 - '@tiptap/react@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@tiptap/react@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/extension-bubble-menu': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) - '@tiptap/extension-floating-menu': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6) - '@tiptap/pm': 2.6.6 + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/extension-bubble-menu': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + '@tiptap/extension-floating-menu': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) use-sync-external-store: 1.2.2(react@18.2.0) - '@tiptap/suggestion@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))(@tiptap/pm@2.6.6)': - dependencies: - '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/pm': 2.6.6 + '@tiptap/starter-kit@2.10.4': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/extension-blockquote': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-bold': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-bullet-list': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-code': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-code-block': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + '@tiptap/extension-document': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-dropcursor': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + '@tiptap/extension-gapcursor': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + '@tiptap/extension-hard-break': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-heading': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-history': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + '@tiptap/extension-horizontal-rule': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4) + '@tiptap/extension-italic': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-list-item': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-ordered-list': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-paragraph': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-strike': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-text': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/extension-text-style': 2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4)) + '@tiptap/pm': 2.10.4 + + '@tiptap/suggestion@2.10.4(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))(@tiptap/pm@2.10.4)': + dependencies: + '@tiptap/core': 2.10.4(@tiptap/pm@2.10.4) + '@tiptap/pm': 2.10.4 '@tootallnate/once@1.1.2': {} @@ -14420,16 +14620,25 @@ snapshots: '@types/katex@0.16.3': {} + '@types/linkify-it@5.0.0': {} + '@types/lodash-es@4.17.10': dependencies: '@types/lodash': 4.14.200 '@types/lodash@4.14.200': {} + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@3.0.13': dependencies: '@types/unist': 2.0.8 + '@types/mdurl@2.0.0': {} + '@types/minimatch@5.1.2': {} '@types/mocha@10.0.6': {} @@ -16760,11 +16969,11 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - esbuild-plugin-copy@2.1.1(esbuild@0.20.2): + esbuild-plugin-copy@2.1.1(esbuild@0.19.12): dependencies: chalk: 4.1.2 chokidar: 3.6.0 - esbuild: 0.20.2 + esbuild: 0.19.12 fs-extra: 10.1.0 globby: 11.1.0 @@ -16774,12 +16983,6 @@ snapshots: esbuild: 0.19.12 import-meta-resolve: 3.1.1 - esbuild-plugin-polyfill-node@0.3.0(esbuild@0.20.2): - dependencies: - '@jspm/core': 2.0.1 - esbuild: 0.20.2 - import-meta-resolve: 3.1.1 - esbuild@0.19.11: optionalDependencies: '@esbuild/aix-ppc64': 0.19.11 @@ -20497,105 +20700,106 @@ snapshots: prosemirror-changeset@2.2.1: dependencies: - prosemirror-transform: 1.10.0 + prosemirror-transform: 1.10.2 prosemirror-collab@1.3.1: dependencies: prosemirror-state: 1.4.3 - prosemirror-commands@1.6.0: + prosemirror-commands@1.6.2: dependencies: - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.0 + prosemirror-transform: 1.10.2 prosemirror-dropcursor@1.8.1: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.0 - prosemirror-view: 1.34.1 + prosemirror-transform: 1.10.2 + prosemirror-view: 1.37.1 prosemirror-gapcursor@1.3.2: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.34.1 + prosemirror-view: 1.37.1 prosemirror-history@1.4.1: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.0 - prosemirror-view: 1.34.1 + prosemirror-transform: 1.10.2 + prosemirror-view: 1.37.1 rope-sequence: 1.3.4 prosemirror-inputrules@1.4.0: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.0 + prosemirror-transform: 1.10.2 prosemirror-keymap@1.2.2: dependencies: prosemirror-state: 1.4.3 w3c-keyname: 2.2.8 - prosemirror-markdown@1.13.0: + prosemirror-markdown@1.13.1: dependencies: + '@types/markdown-it': 14.1.2 markdown-it: 14.1.0 - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-menu@1.2.4: dependencies: crelt: 1.0.6 - prosemirror-commands: 1.6.0 + prosemirror-commands: 1.6.2 prosemirror-history: 1.4.1 prosemirror-state: 1.4.3 - prosemirror-model@1.22.3: + prosemirror-model@1.24.1: dependencies: orderedmap: 2.1.1 prosemirror-schema-basic@1.2.3: dependencies: - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-schema-list@1.4.1: dependencies: - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.0 + prosemirror-transform: 1.10.2 prosemirror-state@1.4.3: dependencies: - prosemirror-model: 1.22.3 - prosemirror-transform: 1.10.0 - prosemirror-view: 1.34.1 + prosemirror-model: 1.24.1 + prosemirror-transform: 1.10.2 + prosemirror-view: 1.37.1 - prosemirror-tables@1.5.0: + prosemirror-tables@1.6.1: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.0 - prosemirror-view: 1.34.1 + prosemirror-transform: 1.10.2 + prosemirror-view: 1.37.1 - prosemirror-trailing-node@2.0.9(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)(prosemirror-view@1.34.1): + prosemirror-trailing-node@3.0.0(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1): dependencies: - '@remirror/core-constants': 2.0.2 + '@remirror/core-constants': 3.0.0 escape-string-regexp: 4.0.0 - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.34.1 + prosemirror-view: 1.37.1 - prosemirror-transform@1.10.0: + prosemirror-transform@1.10.2: dependencies: - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 - prosemirror-view@1.34.1: + prosemirror-view@1.37.1: dependencies: - prosemirror-model: 1.22.3 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.0 + prosemirror-transform: 1.10.2 proto-list@1.2.4: {}