diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 3207678..88b63b1 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,8 +1,7 @@ +import '../src/index.scss'; import type { Preview } from '@storybook/react-vite'; import { withThemeByClassName } from '@storybook/addon-themes'; -import '../src/index.scss'; - const preview: Preview = { parameters: { controls: { diff --git a/package.json b/package.json index 503d3a9..d652a0b 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@chromatic-com/storybook": "4.1.3", "@internxt/eslint-config-internxt": "2.0.1", "@internxt/prettier-config": "internxt/prettier-config#v1.0.2", + "@storybook/addon-docs": "^10.1.4", "@storybook/addon-links": "^10.1.4", "@storybook/addon-onboarding": "^10.1.4", "@storybook/addon-themes": "^10.1.4", @@ -66,8 +67,7 @@ "vite-plugin-dts": "^4.5.4", "vite-plugin-svgr": "^4.5.0", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.2.4", - "@storybook/addon-docs": "^10.1.4" + "vitest": "^3.2.4" }, "scripts": { "build:tsc": "tsc", @@ -90,7 +90,17 @@ "@internxt/css-config": "1.1.0", "@phosphor-icons/react": "^2.1.10", "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/themes": "^3.2.1" + "@radix-ui/themes": "^3.2.1", + "@tiptap/extension-color": "^3.20.0", + "@tiptap/extension-font-family": "^3.20.0", + "@tiptap/extension-image": "^3.20.0", + "@tiptap/extension-link": "^3.20.0", + "@tiptap/extension-text-align": "^3.20.0", + "@tiptap/extension-text-style": "^3.20.0", + "@tiptap/extension-underline": "^3.20.0", + "@tiptap/pm": "^3.20.0", + "@tiptap/react": "^3.20.0", + "@tiptap/starter-kit": "^3.20.0" }, "lint-staged": { "*.{ts,tsx}": [ diff --git a/src/components/mail/ComposeMessageDialog/ComposeMessageDialog.tsx b/src/components/mail/ComposeMessageDialog/ComposeMessageDialog.tsx new file mode 100644 index 0000000..2790340 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/ComposeMessageDialog.tsx @@ -0,0 +1,192 @@ +import { PaperclipIcon, XIcon } from '@phosphor-icons/react'; +import { useState, useCallback } from 'react'; +import { Editor } from '@tiptap/react'; + +import Input from '../../input/Input'; +import { ActionBar } from './components/actionBar/ActionBar'; +import { RichTextEditor } from './components/RichTextEditor'; +import { RecipientInput } from './components/RecipientInput'; +import { Button } from '@/components/button'; +import { DefaultAttachmentItem } from './components/DefaultAttachmentItem'; +import { Attachment, Recipient } from './types'; + +export interface ComposeMessageDialogProps { + isOpen: boolean; + title: string; + mailValue: string; + subject?: string; + isLoading?: boolean; + primaryActionColor?: string; + attachments?: Attachment[]; + toRecipients?: Recipient[]; + ccRecipients?: Recipient[]; + bccRecipients?: Recipient[]; + text: { + to: string; + cc: string; + bcc: string; + subject: string; + send: string; + }; + onClose: () => void; + onPrimaryAction: (html: string) => void; + onMailChange?: (html: string) => void; + onSecondaryAction?: () => void; + onRemoveAttachment?: (id: string) => void; + onAddToRecipient?: (email: string) => void; + onRemoveToRecipient?: (id: string) => void; + onAddCcRecipient?: (email: string) => void; + onRemoveCcRecipient?: (id: string) => void; + onAddBccRecipient?: (email: string) => void; + onRemoveBccRecipient?: (id: string) => void; + onSubjectChange?: (value: string) => void; +} + +export const ComposeMessageDialog = ({ + isOpen, + title, + isLoading, + primaryActionColor = 'primary', + mailValue, + attachments = [], + toRecipients = [], + ccRecipients = [], + bccRecipients = [], + subject = '', + onClose, + onMailChange, + onPrimaryAction, + onSecondaryAction, + onRemoveAttachment, + onAddToRecipient, + onRemoveToRecipient, + onAddCcRecipient, + onRemoveCcRecipient, + onAddBccRecipient, + onRemoveBccRecipient, + onSubjectChange, + text, +}: ComposeMessageDialogProps) => { + const [editor, setEditor] = useState(null); + const [showCc, setShowCc] = useState(ccRecipients.length > 0); + const [showBcc, setShowBcc] = useState(bccRecipients.length > 0); + + const handleEditorReady = useCallback((editorInstance: Editor) => { + setEditor(editorInstance); + }, []); + + const handlePrimaryAction = useCallback(() => { + if (editor) { + const html = editor.getHTML(); + onPrimaryAction(html); + } + }, [editor, onPrimaryAction]); + + if (!isOpen) { + return null; + } + + return ( +
+
+ +
+
+
+

{title}

+ +
+ onAddToRecipient?.(email)} + onRemoveRecipient={(id) => onRemoveToRecipient?.(id)} + showCcBcc + onCcClick={() => setShowCc(true)} + onBccClick={() => setShowBcc(true)} + showCcButton={!showCc} + showBccButton={!showBcc} + ccButtonText={text.cc} + bccButtonText={text.bcc} + disabled={isLoading} + /> + {showCc && ( + onAddCcRecipient?.(email)} + onRemoveRecipient={(id) => onRemoveCcRecipient?.(id)} + disabled={isLoading} + /> + )} + {showBcc && ( + onAddBccRecipient?.(email)} + onRemoveRecipient={(id) => onRemoveBccRecipient?.(id)} + disabled={isLoading} + /> + )} +
+

{text.subject}

+ +
+
+ +
+
+ +
+ {attachments.length > 0 && ( +
+ {attachments.map((attachment) => { + const handleRemove = () => onRemoveAttachment?.(attachment.id); + return ; + })} +
+ )} +
+ + +
+
+
+ ); +}; diff --git a/src/components/mail/ComposeMessageDialog/components/DefaultAttachmentItem.tsx b/src/components/mail/ComposeMessageDialog/components/DefaultAttachmentItem.tsx new file mode 100644 index 0000000..acd6d38 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/DefaultAttachmentItem.tsx @@ -0,0 +1,20 @@ +import { PaperclipIcon, XIcon } from '@phosphor-icons/react'; +import { Attachment } from '../types'; + +export const DefaultAttachmentItem = ({ attachment, onRemove }: { attachment: Attachment; onRemove: () => void }) => ( +
+
+ + {attachment.name} + ({attachment.size}) +
+ +
+); diff --git a/src/components/mail/ComposeMessageDialog/components/RecipientInput.tsx b/src/components/mail/ComposeMessageDialog/components/RecipientInput.tsx new file mode 100644 index 0000000..8725535 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/RecipientInput.tsx @@ -0,0 +1,100 @@ +import { useState, KeyboardEvent } from 'react'; +import { RecipientChip } from '../../chips/RecipientChip'; +import { Recipient } from '../types'; + +interface RecipientInputProps { + label: string; + recipients: Recipient[]; + onAddRecipient: (email: string) => void; + onRemoveRecipient: (id: string) => void; + showCcBcc?: boolean; + onCcClick?: () => void; + onBccClick?: () => void; + showCcButton?: boolean; + showBccButton?: boolean; + ccButtonText?: string; + bccButtonText?: string; + disabled?: boolean; +} + +export const RecipientInput = ({ + label, + recipients, + onAddRecipient, + onRemoveRecipient, + showCcBcc = false, + onCcClick, + onBccClick, + showCcButton = true, + showBccButton = true, + ccButtonText = 'CC', + bccButtonText = 'BCC', + disabled, +}: RecipientInputProps) => { + const [inputValue, setInputValue] = useState(''); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ',') { + e.preventDefault(); + const email = inputValue.trim().replace(/,$/, ''); + if (email) { + onAddRecipient(email); + setInputValue(''); + } + } else if (e.key === 'Backspace' && inputValue === '' && recipients.length > 0) { + onRemoveRecipient(recipients[recipients.length - 1].id); + } + }; + + const handleBlur = () => { + const email = inputValue.trim(); + if (email) { + onAddRecipient(email); + setInputValue(''); + } + }; + + return ( +
+

{label}

+
+ {recipients.map((recipient) => ( + onRemoveRecipient(recipient.id)} /> + ))} + setInputValue(e.target.value)} + onKeyDown={handleKeyDown} + onBlur={handleBlur} + disabled={disabled} + className={`flex-1 min-w-[120px] bg-transparent text-sm text-gray-100 placeholder:text-gray-40 focus:outline-none py-0.5 ${disabled ? 'cursor-not-allowed' : ''}`} + /> + {showCcBcc && (showCcButton || showBccButton) && ( +
+ {showCcButton && ( + + )} + {showBccButton && ( + + )} +
+ )} +
+
+ ); +}; diff --git a/src/components/mail/ComposeMessageDialog/components/RichTextEditor.tsx b/src/components/mail/ComposeMessageDialog/components/RichTextEditor.tsx new file mode 100644 index 0000000..c50b6ba --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/RichTextEditor.tsx @@ -0,0 +1,121 @@ +import { useEditor, EditorContent, Editor } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import { Underline } from '@tiptap/extension-underline'; +import { TextAlign } from '@tiptap/extension-text-align'; +import { Link } from '@tiptap/extension-link'; +import { Image } from '@tiptap/extension-image'; +import { TextStyle } from '@tiptap/extension-text-style'; +import { Color } from '@tiptap/extension-color'; +import { FontFamily } from '@tiptap/extension-font-family'; +import { FontSize } from './fontSizeExtension'; +import { useEffect } from 'react'; + +export interface RichTextEditorProps { + value?: string; + onChange?: (html: string) => void; + onEditorReady?: (editor: Editor) => void; + className?: string; + disabled?: boolean; +} + +export const RichTextEditor = ({ value = '', onChange, onEditorReady, className = '', disabled }: RichTextEditorProps) => { + const editor = useEditor({ + editable: !disabled, + extensions: [ + StarterKit.configure({ + bulletList: { + keepMarks: true, + keepAttributes: true, + HTMLAttributes: { + class: 'list-disc ml-4', + }, + }, + orderedList: { + keepMarks: true, + keepAttributes: true, + HTMLAttributes: { + class: 'list-decimal ml-4', + }, + }, + listItem: { + HTMLAttributes: { + class: 'ml-2', + }, + }, + }), + Underline, + TextAlign.configure({ + types: ['heading', 'paragraph'], + }), + Link.configure({ + openOnClick: false, + HTMLAttributes: { + class: 'text-primary underline', + }, + }), + Image.configure({ + HTMLAttributes: { + class: 'max-w-full h-auto', + }, + }), + TextStyle, + Color, + FontFamily, + FontSize, + ], + content: value, + onUpdate: ({ editor }) => { + onChange?.(editor.getHTML()); + }, + editorProps: { + attributes: { + class: `focus:outline-none h-full ${className}`, + }, + handlePaste: (view, event) => { + const text = event.clipboardData?.getData('text/plain'); + const { from, to } = view.state.selection; + const hasSelection = from !== to; + + if (text && hasSelection) { + const urlPattern = /^(https?:\/\/|www\.)[^\s]+$/i; + if (urlPattern.test(text)) { + const url = text.startsWith('www.') ? `https://${text}` : text; + view.dispatch(view.state.tr.addMark(from, to, view.state.schema.marks.link.create({ href: url }))); + return true; + } + } + return false; + }, + }, + }); + + useEffect(() => { + if (editor && onEditorReady) { + onEditorReady(editor); + } + }, [editor, onEditorReady]); + + useEffect(() => { + if (editor && value !== editor.getHTML()) { + editor.commands.setContent(value); + } + }, [value, editor]); + + useEffect(() => { + if (editor) { + editor.setEditable(!disabled); + } + }, [editor, disabled]); + + if (!editor) { + return null; + } + + return ( +
+ +
+ ); +}; + +export type { Editor }; diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/ActionBar.tsx b/src/components/mail/ComposeMessageDialog/components/actionBar/ActionBar.tsx new file mode 100644 index 0000000..d50e975 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/ActionBar.tsx @@ -0,0 +1,356 @@ +import { + TextBIcon, + TextItalicIcon, + TextUnderlineIcon, + TextStrikethroughIcon, + ListBulletsIcon, + ListNumbersIcon, + TextAlignLeftIcon, + TextAlignCenterIcon, + TextAlignRightIcon, + LinkIcon, + EraserIcon, + ImageIcon, + CaretDownIcon, + PaintBucketIcon, +} from '@phosphor-icons/react'; +import { Editor } from '@tiptap/react'; +import { useCallback, useState, useRef, useEffect, useReducer } from 'react'; +import { ToolbarButton } from './ToolbarButton'; +import { ToolbarGroup, ToolbarItem } from './ToolbarGroup'; + +export interface ActionBarProps { + editor: Editor | null; + disabled?: boolean; +} + +const FONTS = [ + { label: 'Arial', value: 'Arial, sans-serif' }, + { label: 'Times New Roman', value: 'Times New Roman, serif' }, + { label: 'Georgia', value: 'Georgia, serif' }, + { label: 'Verdana', value: 'Verdana, sans-serif' }, + { label: 'Courier New', value: 'Courier New, monospace' }, +]; + +const FONT_SIZES = ['10', '12', '14', '16', '18', '20', '24', '28', '32']; + +const COLORS = [ + '#000000', + '#434343', + '#666666', + '#999999', + '#CCCCCC', + '#EFEFEF', + '#F3F3F3', + '#FFFFFF', + '#FF0000', + '#FF9900', + '#FFFF00', + '#00FF00', + '#00FFFF', + '#0000FF', + '#9900FF', + '#FF00FF', +]; + +export const ActionBar = ({ editor, disabled }: ActionBarProps) => { + const [showColorPicker, setShowColorPicker] = useState(false); + const [showFontPicker, setShowFontPicker] = useState(false); + const [showSizePicker, setShowSizePicker] = useState(false); + const [currentFont, setCurrentFont] = useState('Arial'); + const [currentSize, setCurrentSize] = useState('14'); + + const colorPickerRef = useRef(null); + const fontPickerRef = useRef(null); + const sizePickerRef = useRef(null); + const [, forceUpdate] = useReducer((x) => x + 1, 0); + + useEffect(() => { + if (!editor) return; + + editor.on('selectionUpdate', forceUpdate); + editor.on('transaction', forceUpdate); + + return () => { + editor.off('selectionUpdate', forceUpdate); + editor.off('transaction', forceUpdate); + }; + }, [editor]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (colorPickerRef.current && !colorPickerRef.current.contains(event.target as Node)) { + setShowColorPicker(false); + } + if (fontPickerRef.current && !fontPickerRef.current.contains(event.target as Node)) { + setShowFontPicker(false); + } + if (sizePickerRef.current && !sizePickerRef.current.contains(event.target as Node)) { + setShowSizePicker(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // TODO: Update this to use the Modal component instead + const setLink = useCallback(() => { + if (!editor) return; + + const previousUrl = editor.getAttributes('link').href; + const url = window.prompt('URL', previousUrl); + + if (url === null) return; + + if (url === '') { + editor.chain().focus().extendMarkRange('link').unsetLink().run(); + return; + } + + editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); + }, [editor]); + + // TODO: Update this to use the Modal component instead + const addImage = useCallback(() => { + if (!editor) return; + + const url = window.prompt('Image URL'); + + if (url) { + editor.chain().focus().setImage({ src: url }).run(); + } + }, [editor]); + + const setColor = useCallback( + (color: string) => { + if (!editor) return; + editor.chain().focus().setColor(color).run(); + setShowColorPicker(false); + }, + [editor], + ); + + const setFont = useCallback( + (font: { label: string; value: string }) => { + if (!editor) return; + editor.chain().focus().setFontFamily(font.value).run(); + setCurrentFont(font.label); + setShowFontPicker(false); + }, + [editor], + ); + + const setFontSize = useCallback( + (size: string) => { + if (!editor) return; + editor.chain().focus().setFontSize(`${size}px`).run(); + setCurrentSize(size); + setShowSizePicker(false); + }, + [editor], + ); + + if (!editor) { + return null; + } + + return ( +
+ {/* Font selector */} +
+ + {showFontPicker && ( +
+ {FONTS.map((font) => ( + + ))} +
+ )} +
+
+ {/* Size selector */} +
+ + {showSizePicker && ( +
+ {FONT_SIZES.map((size) => ( + + ))} +
+ )} +
+
+ {/* Color picker */} +
+ setShowColorPicker(!showColorPicker)} disabled={disabled}> + + + {showColorPicker && ( +
+
+ {COLORS.map((color) => ( +
+
+ )} +
+
+ {/* Text styles */} + editor.chain().focus().toggleBold().run(), + isActive: editor.isActive('bold'), + }, + { + id: 'italic', + icon: TextItalicIcon, + onClick: () => editor.chain().focus().toggleItalic().run(), + isActive: editor.isActive('italic'), + }, + { + id: 'underline', + icon: TextUnderlineIcon, + onClick: () => editor.chain().focus().toggleUnderline().run(), + isActive: editor.isActive('underline'), + }, + { + id: 'strike', + icon: TextStrikethroughIcon, + onClick: () => editor.chain().focus().toggleStrike().run(), + isActive: editor.isActive('strike'), + }, + ] satisfies ToolbarItem[] + } + /> +
+ {/* Lists */} + editor.chain().focus().toggleBulletList().run(), + isActive: editor.isActive('bulletList'), + }, + { + id: 'orderedList', + icon: ListNumbersIcon, + onClick: () => editor.chain().focus().toggleOrderedList().run(), + isActive: editor.isActive('orderedList'), + }, + ] satisfies ToolbarItem[] + } + /> +
+ {/* Text alignment */} + editor.chain().focus().setTextAlign('left').run(), + isActive: editor.isActive({ textAlign: 'left' }), + }, + { + id: 'alignCenter', + icon: TextAlignCenterIcon, + onClick: () => editor.chain().focus().setTextAlign('center').run(), + isActive: editor.isActive({ textAlign: 'center' }), + }, + { + id: 'alignRight', + icon: TextAlignRightIcon, + onClick: () => editor.chain().focus().setTextAlign('right').run(), + isActive: editor.isActive({ textAlign: 'right' }), + }, + ] satisfies ToolbarItem[] + } + /> +
+ {/* Link, clear, image */} + editor.chain().focus().unsetAllMarks().clearNodes().run() }, + ] satisfies ToolbarItem[] + } + /> +
+ {/* Image */} + + + +
+ ); +}; diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/ToolbarButton.tsx b/src/components/mail/ComposeMessageDialog/components/actionBar/ToolbarButton.tsx new file mode 100644 index 0000000..90c6a6e --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/ToolbarButton.tsx @@ -0,0 +1,17 @@ +export interface ToolbarButtonProps { + onClick: () => void; + isActive?: boolean; + disabled?: boolean; + children: React.ReactNode; +} + +export const ToolbarButton = ({ onClick, isActive, disabled, children }: ToolbarButtonProps) => ( + +); diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/ToolbarGroup.tsx b/src/components/mail/ComposeMessageDialog/components/actionBar/ToolbarGroup.tsx new file mode 100644 index 0000000..15d1681 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/ToolbarGroup.tsx @@ -0,0 +1,24 @@ +import { Icon } from '@phosphor-icons/react'; +import { ToolbarButton } from './ToolbarButton'; + +export interface ToolbarItem { + id: string; + icon: Icon; + onClick: () => void; + isActive?: boolean; +} + +export interface ToolbarGroupProps { + items: ToolbarItem[]; + disabled?: boolean; +} + +export const ToolbarGroup = ({ items, disabled }: ToolbarGroupProps) => ( +
+ {items.map((item) => ( + + + + ))} +
+); diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ActionBar.test.tsx b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ActionBar.test.tsx new file mode 100644 index 0000000..a75b1cb --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ActionBar.test.tsx @@ -0,0 +1,79 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { ActionBar } from '../'; + +const createMockEditor = (overrides = {}) => ({ + chain: vi.fn(() => ({ + focus: vi.fn(() => ({ + toggleBold: vi.fn(() => ({ run: vi.fn() })), + toggleItalic: vi.fn(() => ({ run: vi.fn() })), + toggleUnderline: vi.fn(() => ({ run: vi.fn() })), + toggleStrike: vi.fn(() => ({ run: vi.fn() })), + toggleBulletList: vi.fn(() => ({ run: vi.fn() })), + toggleOrderedList: vi.fn(() => ({ run: vi.fn() })), + setTextAlign: vi.fn(() => ({ run: vi.fn() })), + setColor: vi.fn(() => ({ run: vi.fn() })), + setFontFamily: vi.fn(() => ({ run: vi.fn() })), + setFontSize: vi.fn(() => ({ run: vi.fn() })), + extendMarkRange: vi.fn(() => ({ + setLink: vi.fn(() => ({ run: vi.fn() })), + unsetLink: vi.fn(() => ({ run: vi.fn() })), + })), + setImage: vi.fn(() => ({ run: vi.fn() })), + unsetAllMarks: vi.fn(() => ({ + clearNodes: vi.fn(() => ({ run: vi.fn() })), + })), + })), + })), + isActive: vi.fn(() => false), + getAttributes: vi.fn(() => ({ href: '' })), + on: vi.fn(), + off: vi.fn(), + ...overrides, +}); + +describe('ActionBar component', () => { + it('ActionBar should return null when editor is null', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('ActionBar should render all toolbar groups', () => { + const mockEditor = createMockEditor(); + render(); + + // Should have multiple buttons for all toolbar items + const buttons = screen.getAllByRole('button'); + expect(buttons.length).toBeGreaterThan(10); + }); + + it('ActionBar default state should render correctly', () => { + const mockEditor = createMockEditor(); + const actionBar = render(); + expect(actionBar).toMatchSnapshot(); + }); + + it('ActionBar disabled state should render correctly', () => { + const mockEditor = createMockEditor(); + const actionBar = render(); + expect(actionBar).toMatchSnapshot(); + }); + + it('ActionBar with active bold should render correctly', () => { + const mockEditor = createMockEditor({ + isActive: vi.fn((type) => type === 'bold'), + }); + const actionBar = render(); + expect(actionBar).toMatchSnapshot(); + }); + + it('ActionBar should disable all buttons when disabled prop is true', () => { + const mockEditor = createMockEditor(); + render(); + + const buttons = screen.getAllByRole('button'); + const disabledButtons = buttons.filter((button) => button.hasAttribute('disabled')); + expect(disabledButtons.length).toBeGreaterThan(0); + }); +}); diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ToolbarButton.test.tsx b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ToolbarButton.test.tsx new file mode 100644 index 0000000..8fe4731 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ToolbarButton.test.tsx @@ -0,0 +1,66 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { ToolbarButton } from '../'; + +describe('ToolbarButton component', () => { + it('ToolbarButton onClick should be called correctly', () => { + const buttonClick = vi.fn(); + render( + + Icon + , + ); + const button = screen.getByRole('button'); + button.click(); + expect(buttonClick).toHaveBeenCalledOnce(); + }); + + it('ToolbarButton should not call onClick when disabled', () => { + const buttonClick = vi.fn(); + render( + + Icon + , + ); + const button = screen.getByRole('button'); + button.click(); + expect(buttonClick).not.toHaveBeenCalled(); + }); + + it('ToolbarButton default state should render correctly', () => { + const button = render( + {}}> + Icon + , + ); + expect(button).toMatchSnapshot(); + }); + + it('ToolbarButton active state should render correctly', () => { + const button = render( + {}} isActive> + Icon + , + ); + expect(button).toMatchSnapshot(); + }); + + it('ToolbarButton disabled state should render correctly', () => { + const button = render( + {}} disabled> + Icon + , + ); + expect(button).toMatchSnapshot(); + }); + + it('ToolbarButton active and disabled state should render correctly', () => { + const button = render( + {}} isActive disabled> + Icon + , + ); + expect(button).toMatchSnapshot(); + }); +}); diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ToolbarGroup.test.tsx b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ToolbarGroup.test.tsx new file mode 100644 index 0000000..fa51fca --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/ToolbarGroup.test.tsx @@ -0,0 +1,70 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { ToolbarGroup, ToolbarItem } from '../'; +import { TextBIcon, TextItalicIcon } from '@phosphor-icons/react'; + +describe('ToolbarGroup component', () => { + const mockItems: ToolbarItem[] = [ + { id: 'bold', icon: TextBIcon, onClick: vi.fn(), isActive: false }, + { id: 'italic', icon: TextItalicIcon, onClick: vi.fn(), isActive: true }, + ]; + + it('ToolbarGroup should render all items', () => { + render(); + const buttons = screen.getAllByRole('button'); + expect(buttons).toHaveLength(2); + }); + + it('ToolbarGroup item onClick should be called correctly', () => { + const onClickBold = vi.fn(); + const items: ToolbarItem[] = [{ id: 'bold', icon: TextBIcon, onClick: onClickBold, isActive: false }]; + + render(); + const button = screen.getByRole('button'); + button.click(); + expect(onClickBold).toHaveBeenCalledOnce(); + }); + + it('ToolbarGroup should not call onClick when disabled', () => { + const onClickBold = vi.fn(); + const items: ToolbarItem[] = [{ id: 'bold', icon: TextBIcon, onClick: onClickBold, isActive: false }]; + + render(); + const button = screen.getByRole('button'); + button.click(); + expect(onClickBold).not.toHaveBeenCalled(); + }); + + it('ToolbarGroup default state should render correctly', () => { + const items: ToolbarItem[] = [ + { id: 'bold', icon: TextBIcon, onClick: () => {}, isActive: false }, + { id: 'italic', icon: TextItalicIcon, onClick: () => {}, isActive: false }, + ]; + const group = render(); + expect(group).toMatchSnapshot(); + }); + + it('ToolbarGroup with active items should render correctly', () => { + const items: ToolbarItem[] = [ + { id: 'bold', icon: TextBIcon, onClick: () => {}, isActive: true }, + { id: 'italic', icon: TextItalicIcon, onClick: () => {}, isActive: false }, + ]; + const group = render(); + expect(group).toMatchSnapshot(); + }); + + it('ToolbarGroup disabled state should render correctly', () => { + const items: ToolbarItem[] = [ + { id: 'bold', icon: TextBIcon, onClick: () => {}, isActive: false }, + { id: 'italic', icon: TextItalicIcon, onClick: () => {}, isActive: false }, + ]; + const group = render(); + expect(group).toMatchSnapshot(); + }); + + it('ToolbarGroup empty items should render correctly', () => { + const group = render(); + expect(group).toMatchSnapshot(); + }); +}); diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ActionBar.test.tsx.snap b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ActionBar.test.tsx.snap new file mode 100644 index 0000000..38e40c1 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ActionBar.test.tsx.snap @@ -0,0 +1,2038 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ActionBar component > ActionBar default state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+ +
+
+ , + "container":
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+ +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ActionBar component > ActionBar disabled state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+ +
+
+ , + "container":
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+ +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ActionBar component > ActionBar with active bold should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+ +
+
+ , + "container":
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+ +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ToolbarButton.test.tsx.snap b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ToolbarButton.test.tsx.snap new file mode 100644 index 0000000..f231d1a --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ToolbarButton.test.tsx.snap @@ -0,0 +1,321 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ToolbarButton component > ToolbarButton active and disabled state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ToolbarButton component > ToolbarButton active state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ToolbarButton component > ToolbarButton default state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ToolbarButton component > ToolbarButton disabled state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ToolbarGroup.test.tsx.snap b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ToolbarGroup.test.tsx.snap new file mode 100644 index 0000000..a80cb12 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/__test__/__snapshots__/ToolbarGroup.test.tsx.snap @@ -0,0 +1,479 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ToolbarGroup component > ToolbarGroup default state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+ + +
+
+ , + "container":
+
+ + +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ToolbarGroup component > ToolbarGroup disabled state should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+ + +
+
+ , + "container":
+
+ + +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ToolbarGroup component > ToolbarGroup empty items should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+ , + "container":
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`ToolbarGroup component > ToolbarGroup with active items should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+ + +
+
+ , + "container":
+
+ + +
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/components/mail/ComposeMessageDialog/components/actionBar/index.ts b/src/components/mail/ComposeMessageDialog/components/actionBar/index.ts new file mode 100644 index 0000000..45472d8 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/actionBar/index.ts @@ -0,0 +1,8 @@ +export { ActionBar } from './ActionBar'; +export type { ActionBarProps } from './ActionBar'; + +export { ToolbarButton } from './ToolbarButton'; +export type { ToolbarButtonProps } from './ToolbarButton'; + +export { ToolbarGroup } from './ToolbarGroup'; +export type { ToolbarGroupProps, ToolbarItem } from './ToolbarGroup'; diff --git a/src/components/mail/ComposeMessageDialog/components/fontSizeExtension.ts b/src/components/mail/ComposeMessageDialog/components/fontSizeExtension.ts new file mode 100644 index 0000000..924f3f9 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/components/fontSizeExtension.ts @@ -0,0 +1,57 @@ +import { Extension } from '@tiptap/core'; + +declare module '@tiptap/core' { + interface Commands { + fontSize: { + setFontSize: (size: string) => ReturnType; + unsetFontSize: () => ReturnType; + }; + } +} + +export const FontSize = Extension.create({ + name: 'fontSize', + + addOptions() { + return { + types: ['textStyle'], + }; + }, + + addGlobalAttributes() { + return [ + { + types: this.options.types, + attributes: { + fontSize: { + default: null, + parseHTML: (element) => element.style.fontSize?.replace(/['"]+/g, ''), + renderHTML: (attributes) => { + if (!attributes.fontSize) { + return {}; + } + return { + style: `font-size: ${attributes.fontSize}`, + }; + }, + }, + }, + }, + ]; + }, + + addCommands() { + return { + setFontSize: + (fontSize: string) => + ({ chain }) => { + return chain().setMark('textStyle', { fontSize }).run(); + }, + unsetFontSize: + () => + ({ chain }) => { + return chain().setMark('textStyle', { fontSize: null }).removeEmptyTextStyle().run(); + }, + }; + }, +}); diff --git a/src/components/mail/ComposeMessageDialog/index.ts b/src/components/mail/ComposeMessageDialog/index.ts new file mode 100644 index 0000000..937b63d --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/index.ts @@ -0,0 +1,7 @@ +export { ComposeMessageDialog } from './ComposeMessageDialog'; +export type { ComposeMessageDialogProps } from './ComposeMessageDialog'; + +export { ActionBar, ToolbarButton, ToolbarGroup } from './components/actionBar'; +export type { ActionBarProps, ToolbarButtonProps, ToolbarGroupProps, ToolbarItem } from './components/actionBar'; + +export type { Attachment, Recipient } from './types'; diff --git a/src/components/mail/ComposeMessageDialog/types/index.ts b/src/components/mail/ComposeMessageDialog/types/index.ts new file mode 100644 index 0000000..fee4e36 --- /dev/null +++ b/src/components/mail/ComposeMessageDialog/types/index.ts @@ -0,0 +1,11 @@ +export interface Attachment { + id: string; + name: string; + size: number; +} + +export interface Recipient { + id: string; + email: string; + displayName?: string; +} diff --git a/src/components/mail/chips/RecipientChip.tsx b/src/components/mail/chips/RecipientChip.tsx new file mode 100644 index 0000000..7bde066 --- /dev/null +++ b/src/components/mail/chips/RecipientChip.tsx @@ -0,0 +1,16 @@ +import { XIcon } from '@phosphor-icons/react'; +import { Recipient } from '../ComposeMessageDialog/types'; + +export const RecipientChip = ({ recipient, onRemove }: { recipient: Recipient; onRemove: () => void }) => ( + + {recipient.displayName || recipient.email} + + +); diff --git a/src/components/textArea/TextArea.tsx b/src/components/textArea/TextArea.tsx index d099047..740e6bb 100644 --- a/src/components/textArea/TextArea.tsx +++ b/src/components/textArea/TextArea.tsx @@ -3,6 +3,7 @@ export interface TextAreaComponentProps { accentColor?: 'red'; placeholder?: string; value?: string; + className?: string; onChange?: (e: React.ChangeEvent) => void; name?: string; } @@ -30,6 +31,9 @@ export interface TextAreaComponentProps { * @property {string} [name] * - Optional name attribute for the text area, typically used for form submissions. * + * @property {string} [className] + * - Optional custom class name for styling the text area. + * * @returns {JSX.Element} * - The rendered TextArea component. */ @@ -40,6 +44,7 @@ const TextArea = ({ placeholder = '', value = '', onChange, + className, name, }: TextAreaComponentProps): JSX.Element => { return ( @@ -49,6 +54,7 @@ const TextArea = ({ className={` w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + ${className} ${!disabled ? 'border-gray-20 text-gray-100' : 'border-gray-5 text-gray-40'} ${!accentColor && 'border-gray-20 focus:border-primary focus:ring focus:ring-primary/10'} ${accentColor === 'red' && 'border-red focus:ring focus:ring-red/10'} diff --git a/src/components/textArea/__test__/__snapshots__/TextArea.test.tsx.snap b/src/components/textArea/__test__/__snapshots__/TextArea.test.tsx.snap index 34de2cf..c75c428 100644 --- a/src/components/textArea/__test__/__snapshots__/TextArea.test.tsx.snap +++ b/src/components/textArea/__test__/__snapshots__/TextArea.test.tsx.snap @@ -9,6 +9,7 @@ exports[`Text area component > TextArea disabled should render correctly 1`] = ` class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-5 text-gray-40 border-gray-20 focus:border-primary focus:ring focus:ring-primary/10 false @@ -23,6 +24,7 @@ exports[`Text area component > TextArea disabled should render correctly 1`] = ` class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-5 text-gray-40 border-gray-20 focus:border-primary focus:ring focus:ring-primary/10 false @@ -94,6 +96,7 @@ exports[`Text area component > TextArea enabled should render correctly 1`] = ` class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-20 text-gray-100 border-gray-20 focus:border-primary focus:ring focus:ring-primary/10 false @@ -107,6 +110,7 @@ exports[`Text area component > TextArea enabled should render correctly 1`] = ` class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-20 text-gray-100 border-gray-20 focus:border-primary focus:ring focus:ring-primary/10 false @@ -177,6 +181,7 @@ exports[`Text area component > TextArea enabled with accent color red should ren class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-20 text-gray-100 false border-red focus:ring focus:ring-red/10 @@ -190,6 +195,7 @@ exports[`Text area component > TextArea enabled with accent color red should ren class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-20 text-gray-100 false border-red focus:ring focus:ring-red/10 @@ -260,6 +266,7 @@ exports[`Text area component > TextArea enabled with placeholder should render c class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-20 text-gray-100 border-gray-20 focus:border-primary focus:ring focus:ring-primary/10 false @@ -273,6 +280,7 @@ exports[`Text area component > TextArea enabled with placeholder should render c class=" w-full h-full py-4 px-3.5 bg-transparent border rounded-md outline-none text-lg font-regular resize-none placeholder:text-gray-30 + undefined border-gray-20 text-gray-100 border-gray-20 focus:border-primary focus:ring focus:ring-primary/10 false diff --git a/src/stories/components/mail/composeMessageDialog/ComposeMessageDialog.stories.tsx b/src/stories/components/mail/composeMessageDialog/ComposeMessageDialog.stories.tsx new file mode 100644 index 0000000..f95507c --- /dev/null +++ b/src/stories/components/mail/composeMessageDialog/ComposeMessageDialog.stories.tsx @@ -0,0 +1,141 @@ +import type { Decorator, Meta, StoryObj } from '@storybook/react-vite'; +import { useArgs } from 'storybook/preview-api'; +import { useState } from 'react'; +import { ComposeMessageDialog } from '../../../../components/mail/ComposeMessageDialog/ComposeMessageDialog'; +import { Button } from '@/components/button'; +import { Attachment, Recipient } from '@/components/mail/ComposeMessageDialog/types'; + +const interactiveDecorator: Decorator = (Story, context) => { + const [{ isOpen }, setArgs] = useArgs(); + const [mailValue, setMailValue] = useState(''); + const [toRecipients, setToRecipients] = useState([]); + const [ccRecipients, setCcRecipients] = useState([]); + const [bccRecipients, setBccRecipients] = useState([]); + const [subject, setSubject] = useState(''); + + const addRecipient = (setter: React.Dispatch>, email: string) => { + setter((prev) => [...prev, { id: crypto.randomUUID(), email, displayName: email.split('@')[0] }]); + }; + + const removeRecipient = (setter: React.Dispatch>, id: string) => { + setter((prev) => prev.filter((r) => r.id !== id)); + }; + + return ( +
+ + {Story({ + ...context, + args: { + ...context.allArgs, + isOpen, + mailValue, + toRecipients, + ccRecipients, + bccRecipients, + subject, + onClose: () => setArgs({ isOpen: false }), + onMailChange: (html: string) => setMailValue(html), + onAddToRecipient: (email: string) => addRecipient(setToRecipients, email), + onRemoveToRecipient: (id: string) => removeRecipient(setToRecipients, id), + onAddCcRecipient: (email: string) => addRecipient(setCcRecipients, email), + onRemoveCcRecipient: (id: string) => removeRecipient(setCcRecipients, id), + onAddBccRecipient: (email: string) => addRecipient(setBccRecipients, email), + onRemoveBccRecipient: (id: string) => removeRecipient(setBccRecipients, id), + onSubjectChange: setSubject, + onPrimaryAction: () => setArgs({ isOpen: false }), + }, + })} +
+ ); +}; + +const defaultText = { + to: 'To', + cc: 'CC', + bcc: 'BCC', + subject: 'Subject', + send: 'Send', +}; + +const meta: Meta = { + title: 'Components - Mail/Compose Message Dialog', + component: ComposeMessageDialog, + parameters: { + layout: 'fullscreen', + }, + decorators: [interactiveDecorator], + tags: ['autodocs'], + argTypes: { + isOpen: { control: 'boolean' }, + title: { control: 'text' }, + isLoading: { control: 'boolean' }, + primaryActionColor: { control: 'select', options: ['primary', 'danger'] }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'New message', + isOpen: false, + isLoading: false, + primaryActionColor: 'primary', + text: defaultText, + onSecondaryAction: () => console.log('attach'), + }, +}; + +export const Loading: Story = { + args: { + title: 'Sending...', + isOpen: false, + isLoading: true, + primaryActionColor: 'primary', + text: defaultText, + onPrimaryAction: () => console.log('send'), + }, +}; + +const sampleAttachments: Attachment[] = [ + { id: '1', name: 'document.pdf', size: 1024 * 1024 * 2.5 }, + { id: '2', name: 'image.png', size: 1024 * 512 }, + { id: '3', name: 'very-long-filename-that-should-be-truncated-in-the-ui.xlsx', size: 1024 * 1024 * 10 }, + { id: '4', name: 'presentation.pptx', size: 1024 * 1024 * 5 }, + { id: '5', name: 'data.csv', size: 1024 * 100 }, +]; + +export const WithAttachments: Story = { + args: { + title: 'New message', + isOpen: false, + isLoading: false, + primaryActionColor: 'primary', + text: defaultText, + attachments: sampleAttachments, + onPrimaryAction: () => console.log('send'), + onSecondaryAction: () => console.log('attach'), + onRemoveAttachment: (id: string) => console.log('remove attachment:', id), + }, +}; + +const sampleRecipients: Recipient[] = [ + { id: '1', email: 'bea@internxt.com', displayName: 'Bea D.' }, + { id: '2', email: 'anna@internxt.com', displayName: 'Anna M.' }, +]; + +export const WithRecipients: Story = { + args: { + title: 'New message', + isOpen: false, + isLoading: false, + primaryActionColor: 'primary', + text: defaultText, + toRecipients: sampleRecipients, + subject: 'Trip to Korea 🇰🇷', + onPrimaryAction: () => console.log('send'), + onSecondaryAction: () => console.log('attach'), + }, +}; diff --git a/tailwind.config.ts b/tailwind.config.ts index 459c576..fddd3e0 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -2,4 +2,5 @@ import config from '@internxt/css-config'; export default { ...config, -} \ No newline at end of file + content: ['./src/**/*.{js,ts,jsx,tsx}', './.storybook/**/*.{js,ts,jsx,tsx}'], +}; diff --git a/yarn.lock b/yarn.lock index 22d1c18..0e0d186 100644 --- a/yarn.lock +++ b/yarn.lock @@ -686,6 +686,21 @@ dependencies: "@floating-ui/utils" "^0.2.10" +"@floating-ui/core@^1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.4.tgz#4a006a6e01565c0f87ba222c317b056a2cffd2f4" + integrity sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg== + dependencies: + "@floating-ui/utils" "^0.2.10" + +"@floating-ui/dom@^1.0.0": + version "1.7.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.5.tgz#60bfc83a4d1275b2a90db76bf42ca2a5f2c231c2" + integrity sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg== + dependencies: + "@floating-ui/core" "^1.7.4" + "@floating-ui/utils" "^0.2.10" + "@floating-ui/dom@^1.7.2": version "1.7.2" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.2.tgz#3540b051cf5ce0d4f4db5fb2507a76e8ea5b4a45" @@ -1720,6 +1735,11 @@ resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== +"@remirror/core-constants@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" + integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== + "@rollup/pluginutils@^5.0.2", "@rollup/pluginutils@^5.1.4": version "5.2.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.2.0.tgz#eac25ca5b0bdda4ba735ddaca5fbf26bd435f602" @@ -2276,6 +2296,226 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== +"@tiptap/core@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.20.0.tgz#dac72894d83829f2fbbabee2e90a748d7c1479ee" + integrity sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ== + +"@tiptap/extension-blockquote@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.20.0.tgz#92e4a8ed00cf4fcab056766b848fc0a551847e5b" + integrity sha512-LQzn6aGtL4WXz2+rYshl/7/VnP2qJTpD7fWL96GXAzhqviPEY1bJES7poqJb3MU/gzl8VJUVzVzU1VoVfUKlbA== + +"@tiptap/extension-bold@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.20.0.tgz#4298d24cb6c7759f6233eec5e24e9fd7c7efdf38" + integrity sha512-sQklEWiyf58yDjiHtm5vmkVjfIc/cBuSusmCsQ0q9vGYnEF1iOHKhGpvnCeEXNeqF3fiJQRlquzt/6ymle3Iwg== + +"@tiptap/extension-bubble-menu@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.20.0.tgz#cd670c66e3583417e7a1e6fa058f82659e9dc98b" + integrity sha512-MDosUfs8Tj+nwg8RC+wTMWGkLJORXmbR6YZgbiX4hrc7G90Gopdd6kj6ht5/T8t7dLLaX7N0+DEHdUEPGED7dw== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@tiptap/extension-bullet-list@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.20.0.tgz#57bcba5988990f39cb71c7901173da9b4979523b" + integrity sha512-OcKMeopBbqWzhSi6o8nNz0aayogg1sfOAhto3NxJu3Ya32dwBFqmHXSYM6uW4jOphNvVPyjiq9aNRh3qTdd1dw== + +"@tiptap/extension-code-block@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.20.0.tgz#5c00e8ae017c32ff4dd629447635a25fcb9d0f52" + integrity sha512-lBbmNek14aCjrHcBcq3PRqWfNLvC6bcRa2Osc6e/LtmXlcpype4f6n+Yx+WZ+f2uUh0UmDRCz7BEyUETEsDmlQ== + +"@tiptap/extension-code@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.20.0.tgz#6aa18bc21ed8a3a6a899653c48add1cecc64783d" + integrity sha512-TYDWFeSQ9umiyrqsT6VecbuhL8XIHkUhO+gEk0sVvH67ZLwjFDhAIIgWIr1/dbIGPcvMZM19E7xUUhAdIaXaOQ== + +"@tiptap/extension-color@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-color/-/extension-color-3.20.0.tgz#a9e501fb280b3fd43420a9954376576412ca4b70" + integrity sha512-KGXQ3I18uLHQ61FZpXDu0gH6+0jqmDkVwkPNXmM3oPNDSH80SG5UeZlrXi/PwtlusePJ3dFHtoQ1g6j2bJUssg== + +"@tiptap/extension-document@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.20.0.tgz#e10f92139c97354ab917f3095d4011d6703528f6" + integrity sha512-oJfLIG3vAtZo/wg29WiBcyWt22KUgddpP8wqtCE+kY5Dw8znLR9ehNmVWlSWJA5OJUMO0ntAHx4bBT+I2MBd5w== + +"@tiptap/extension-dropcursor@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.20.0.tgz#459d6c5d7e5f4dc1152246901387e000596fbb40" + integrity sha512-d+cxplRlktVgZPwatnc34IArlppM0IFKS1J5wLk+ba1jidizsbMVh45tP/BTK2flhyfRqcNoB5R0TArhUpbkNQ== + +"@tiptap/extension-floating-menu@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.20.0.tgz#c131a13a87b8edac05bef6f9716908f47b3a2a27" + integrity sha512-rYs4Bv5pVjqZ/2vvR6oe7ammZapkAwN51As/WDbemvYDjfOGRqK58qGauUjYZiDzPOEIzI2mxGwsZ4eJhPW4Ig== + +"@tiptap/extension-font-family@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-font-family/-/extension-font-family-3.20.0.tgz#9f6bfbb9593bfdf8fd3126bd7922e83644b94ad6" + integrity sha512-XYR2vXYdeWA/YSr1nVlRw00h524a7Jjp529sti4EnKrQfGmB4LsYzihhcXsbzCxa2ffusWXeA3hbfO9NoTBltg== + +"@tiptap/extension-gapcursor@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.20.0.tgz#df89dd19417c020c6e9529e2db5077d08353e4a1" + integrity sha512-P/LasfvG9/qFq43ZAlNbAnPnXC+/RJf49buTrhtFvI9Zg0+Lbpjx1oh6oMHB19T88Y28KtrckfFZ8aTSUWDq6w== + +"@tiptap/extension-hard-break@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.20.0.tgz#fff1553d3b41ad32d3979d618f0f894cee926f46" + integrity sha512-rqvhMOw4f+XQmEthncbvDjgLH6fz8L9splnKZC7OeS0eX8b0qd7+xI1u5kyxF3KA2Z0BnigES++jjWuecqV6mA== + +"@tiptap/extension-heading@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.20.0.tgz#a16dbb625d91556399912fa110b04b2ad2dfd2e3" + integrity sha512-JgJhurnCe3eN6a0lEsNQM/46R1bcwzwWWZEFDSb1P9dR8+t1/5v7cMZWsSInpD7R4/74iJn0+M5hcXLwCmBmYA== + +"@tiptap/extension-horizontal-rule@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.0.tgz#53ffe2f9b9627f27f85b02d75878583dfb044107" + integrity sha512-6uvcutFMv+9wPZgptDkbRDjAm3YVxlibmkhWD5GuaWwS9L/yUtobpI3GycujRSUZ8D3q6Q9J7LqpmQtQRTalWA== + +"@tiptap/extension-image@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-3.20.0.tgz#1788d8d3d0965c820c274e96886db6f36971ba08" + integrity sha512-0t7HYncV0kYEQS79NFczxdlZoZ8zu8X4VavDqt+mbSAUKRq3gCvgtZ5Zyd778sNmtmbz3arxkEYMIVou2swD0g== + +"@tiptap/extension-italic@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.20.0.tgz#176c2080a75082d296797618c2ed84e9defc7ef9" + integrity sha512-/DhnKQF8yN8RxtuL8abZ28wd5281EaGoE2Oha35zXSOF1vNYnbyt8Ymkv/7u1BcWEWTvRPgaju0YCGXisPRLYw== + +"@tiptap/extension-link@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.20.0.tgz#b3f2d89aabb88ed1eb66925e59fe82d1d2b866e9" + integrity sha512-qI/5A+R0ZWBxo/8HxSn1uOyr7odr3xHBZ/gzOR1GUJaZqjlJxkWFX0RtXMbLKEGEvT25o345cF7b0wFznEh8qA== + dependencies: + linkifyjs "^4.3.2" + +"@tiptap/extension-list-item@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.20.0.tgz#60ec36a3326d3bf28982b653b63f6cab5c7d9d9f" + integrity sha512-qEtjaaGPuqaFB4VpLrGDoIe9RHnckxPfu6d3rc22ap6TAHCDyRv05CEyJogqccnFceG/v5WN4znUBER8RWnWHA== + +"@tiptap/extension-list-keymap@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.20.0.tgz#3e49292b0cdf0a7b6d8f08ae5acdd7014c15cdc1" + integrity sha512-Z4GvKy04Ms4cLFN+CY6wXswd36xYsT2p/YL0V89LYFMZTerOeTjFYlndzn6svqL8NV1PRT5Diw4WTTxJSmcJPA== + +"@tiptap/extension-list@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.20.0.tgz#17d379fe34e09a9b42ab620f7ff571826485c7d5" + integrity sha512-+V0/gsVWAv+7vcY0MAe6D52LYTIicMSHw00wz3ISZgprSb2yQhJ4+4gurOnUrQ4Du3AnRQvxPROaofwxIQ66WQ== + +"@tiptap/extension-ordered-list@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.20.0.tgz#ec449716854d496ef7c1ea9243c2c467fa5d3cb1" + integrity sha512-jVKnJvrizLk7etwBMfyoj6H2GE4M+PD4k7Bwp6Bh1ohBWtfIA1TlngdS842Mx5i1VB2e3UWIwr8ZH46gl6cwMA== + +"@tiptap/extension-paragraph@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.20.0.tgz#b1778746021ac38894287d62d9429ac309a632ef" + integrity sha512-mM99zK4+RnEXIMCv6akfNATAs0Iija6FgyFA9J9NZ6N4o8y9QiNLLa6HjLpAC+W+VoCgQIekyoF/Q9ftxmAYDQ== + +"@tiptap/extension-strike@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.20.0.tgz#a1ec09a1a56aad98d6e7dc493d77547cad4403ee" + integrity sha512-0vcTZRRAiDfon3VM1mHBr9EFmTkkUXMhm0Xtdtn0bGe+sIqufyi+hUYTEw93EQOD9XNsPkrud6jzQNYpX2H3AQ== + +"@tiptap/extension-text-align@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text-align/-/extension-text-align-3.20.0.tgz#57d36e466bda8fc2ec1ca81a3ca6d2aaba3221ac" + integrity sha512-4s0r+bovtH6yeGDUD+Ui8j5WOV5koB5P6AuzOMqoLwaFGRSkKf64ly6DXjjmjIgnYCLZN/XO6llaQKVVdvad2g== + +"@tiptap/extension-text-style@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text-style/-/extension-text-style-3.20.0.tgz#747d0b0bb12c9676d638a2fb605801e34c15ed55" + integrity sha512-zyWW1a6W+kaXAn3wv2svJ1XuVMapujftvH7Xn2Q3QmKKiDkO+NiFkrGe8BhMopu8Im51nO3NylIgVA0X1mS1rQ== + +"@tiptap/extension-text@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.20.0.tgz#bdf0ac6e0c638c9dbb99a5a2b5a07db2b8cba1de" + integrity sha512-tf8bE8tSaOEWabCzPm71xwiUhyMFKqY9jkP5af3Kr1/F45jzZFIQAYZooHI/+zCHRrgJ99MQHKHe1ZNvODrKHQ== + +"@tiptap/extension-underline@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.20.0.tgz#535aebafc9e30da51df3be2c2065c49539535672" + integrity sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ== + +"@tiptap/extensions@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.20.0.tgz#22bad09a1d861446e17e0d9732439940d80ed808" + integrity sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg== + +"@tiptap/pm@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.20.0.tgz#d9a1b92a1cb061059977952e6ec2afe8dff67857" + integrity sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A== + dependencies: + prosemirror-changeset "^2.3.0" + prosemirror-collab "^1.3.1" + 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.1" + prosemirror-menu "^1.2.4" + prosemirror-model "^1.24.1" + prosemirror-schema-basic "^1.2.3" + prosemirror-schema-list "^1.5.0" + prosemirror-state "^1.4.3" + prosemirror-tables "^1.6.4" + prosemirror-trailing-node "^3.0.0" + prosemirror-transform "^1.10.2" + prosemirror-view "^1.38.1" + +"@tiptap/react@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-3.20.0.tgz#ea9d4cc7c4c28dfdd500929e4a66954d272b7196" + integrity sha512-jFLNzkmn18zqefJwPje0PPd9VhZ7Oy28YHiSvSc7YpBnQIbuN/HIxZ2lrOsKyEHta0WjRZjfU5X1pGxlbcGwOA== + dependencies: + "@types/use-sync-external-store" "^0.0.6" + fast-equals "^5.3.3" + use-sync-external-store "^1.4.0" + optionalDependencies: + "@tiptap/extension-bubble-menu" "^3.20.0" + "@tiptap/extension-floating-menu" "^3.20.0" + +"@tiptap/starter-kit@^3.20.0": + version "3.20.0" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.20.0.tgz#d356f15632c52f90e8eea8b5c912cab3b01b0866" + integrity sha512-W4+1re35pDNY/7rpXVg+OKo/Fa4Gfrn08Bq3E3fzlJw6gjE3tYU8dY9x9vC2rK9pd9NOp7Af11qCFDaWpohXkw== + dependencies: + "@tiptap/core" "^3.20.0" + "@tiptap/extension-blockquote" "^3.20.0" + "@tiptap/extension-bold" "^3.20.0" + "@tiptap/extension-bullet-list" "^3.20.0" + "@tiptap/extension-code" "^3.20.0" + "@tiptap/extension-code-block" "^3.20.0" + "@tiptap/extension-document" "^3.20.0" + "@tiptap/extension-dropcursor" "^3.20.0" + "@tiptap/extension-gapcursor" "^3.20.0" + "@tiptap/extension-hard-break" "^3.20.0" + "@tiptap/extension-heading" "^3.20.0" + "@tiptap/extension-horizontal-rule" "^3.20.0" + "@tiptap/extension-italic" "^3.20.0" + "@tiptap/extension-link" "^3.20.0" + "@tiptap/extension-list" "^3.20.0" + "@tiptap/extension-list-item" "^3.20.0" + "@tiptap/extension-list-keymap" "^3.20.0" + "@tiptap/extension-ordered-list" "^3.20.0" + "@tiptap/extension-paragraph" "^3.20.0" + "@tiptap/extension-strike" "^3.20.0" + "@tiptap/extension-text" "^3.20.0" + "@tiptap/extension-underline" "^3.20.0" + "@tiptap/extensions" "^3.20.0" + "@tiptap/pm" "^3.20.0" + "@tybys/wasm-util@^0.10.1": version "0.10.1" resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" @@ -2360,11 +2600,29 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + "@types/lodash@^4.17.21": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.21.tgz#b806831543d696b14f8112db600ea9d3a1df6ea4" integrity sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ== +"@types/markdown-it@^14.0.0": + version "14.1.2" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61" + integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + "@types/mdx@^2.0.0": version "2.0.13" resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd" @@ -2393,6 +2651,11 @@ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.6.tgz#e6e60dad29c2c8c206c026e6dd8d6d1bdda850b8" integrity sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ== +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== + "@typescript-eslint/eslint-plugin@8.48.1": version "8.48.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz#c772d1dbdd97cfddf85f5a161a97783233643631" @@ -3119,6 +3382,11 @@ cosmiconfig@^8.1.3: parse-json "^5.2.0" path-type "^4.0.0" +crelt@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" + integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== + cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -3558,6 +3826,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-equals@^5.3.3: + version "5.4.0" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.4.0.tgz#b60073b8764f27029598447f05773c7534ba7f1e" + integrity sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw== + fast-glob@^3.2.9, fast-glob@^3.3.2: version "3.3.3" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" @@ -4214,6 +4487,18 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421" + integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ== + dependencies: + uc.micro "^2.0.0" + +linkifyjs@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.3.2.tgz#d97eb45419aabf97ceb4b05a7adeb7b8c8ade2b1" + integrity sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA== + lint-staged@^16.2.7: version "16.2.7" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-16.2.7.tgz#c4a635960c17b52fe774f1f40aee8ce1bd86531f" @@ -4368,11 +4653,28 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" +markdown-it@^14.0.0: + version "14.1.1" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.1.tgz#856f90b66fc39ae70affd25c1b18b581d7deee1f" + integrity sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA== + dependencies: + argparse "^2.0.1" + entities "^4.4.0" + linkify-it "^5.0.0" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + mdn-data@2.12.2: version "2.12.2" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.12.2.tgz#9ae6c41a9e65adf61318b32bff7b64fbfb13f8cf" integrity sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA== +mdurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0" + integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -4514,6 +4816,11 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" +orderedmap@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-2.1.1.tgz#61481269c44031c449915497bf5a4ad273c512d2" + integrity sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -4692,6 +4999,165 @@ pretty-format@^27.0.2: ansi-styles "^5.0.0" react-is "^17.0.1" +prosemirror-changeset@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz#8d8ea0290cb9545c298ec427ac3a8f298c39170f" + integrity sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng== + dependencies: + prosemirror-transform "^1.0.0" + +prosemirror-collab@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz#0e8c91e76e009b53457eb3b3051fb68dad029a33" + integrity sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ== + dependencies: + prosemirror-state "^1.0.0" + +prosemirror-commands@^1.0.0, prosemirror-commands@^1.6.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz#d101fef85618b1be53d5b99ea17bee5600781b38" + integrity sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.10.2" + +prosemirror-dropcursor@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz#2ed30c4796109ddeb1cf7282372b3850528b7228" + integrity sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + prosemirror-view "^1.1.0" + +prosemirror-gapcursor@^1.3.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz#e1144a83b79db7ed0ec32cd0e915a0364220af43" + integrity sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ== + dependencies: + prosemirror-keymap "^1.0.0" + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-view "^1.0.0" + +prosemirror-history@^1.0.0, prosemirror-history@^1.4.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/prosemirror-history/-/prosemirror-history-1.5.0.tgz#ee21fc5de85a1473e3e3752015ffd6d649a06859" + integrity sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg== + dependencies: + prosemirror-state "^1.2.2" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.31.0" + rope-sequence "^1.3.0" + +prosemirror-inputrules@^1.4.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz#d2e935f6086e3801486b09222638f61dae89a570" + integrity sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw== + dependencies: + prosemirror-state "^1.0.0" + prosemirror-transform "^1.0.0" + +prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.2.2, prosemirror-keymap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz#c0f6ab95f75c0b82c97e44eb6aaf29cbfc150472" + integrity sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw== + dependencies: + prosemirror-state "^1.0.0" + w3c-keyname "^2.2.0" + +prosemirror-markdown@^1.13.1: + version "1.13.4" + resolved "https://registry.yarnpkg.com/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz#4620e6a0580cd52b5fc8e352c7e04830cd4b3048" + integrity sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw== + dependencies: + "@types/markdown-it" "^14.0.0" + markdown-it "^14.0.0" + prosemirror-model "^1.25.0" + +prosemirror-menu@^1.2.4: + version "1.3.0" + resolved "https://registry.yarnpkg.com/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz#f51e25259b91d7c35ad7b65fc0c92d838404e177" + integrity sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg== + dependencies: + crelt "^1.0.0" + prosemirror-commands "^1.0.0" + prosemirror-history "^1.0.0" + prosemirror-state "^1.0.0" + +prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0, prosemirror-model@^1.24.1, prosemirror-model@^1.25.0, prosemirror-model@^1.25.4: + version "1.25.4" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.25.4.tgz#8ebfbe29ecbee9e5e2e4048c4fe8e363fcd56e7c" + integrity sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA== + dependencies: + orderedmap "^2.0.0" + +prosemirror-schema-basic@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz#389ce1ec09b8a30ea9bbb92c58569cb690c2d695" + integrity sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ== + dependencies: + prosemirror-model "^1.25.0" + +prosemirror-schema-list@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz#5869c8f749e8745c394548bb11820b0feb1e32f5" + integrity sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.7.3" + +prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3, prosemirror-state@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/prosemirror-state/-/prosemirror-state-1.4.4.tgz#72b5e926f9e92dcee12b62a05fcc8a2de3bf5b39" + integrity sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-transform "^1.0.0" + prosemirror-view "^1.27.0" + +prosemirror-tables@^1.6.4: + version "1.8.5" + resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz#104427012e5a5da1d2a38c122efee8d66bdd5104" + integrity sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw== + dependencies: + prosemirror-keymap "^1.2.3" + prosemirror-model "^1.25.4" + prosemirror-state "^1.4.4" + prosemirror-transform "^1.10.5" + prosemirror-view "^1.41.4" + +prosemirror-trailing-node@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz#5bc223d4fc1e8d9145e4079ec77a932b54e19e04" + integrity sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ== + dependencies: + "@remirror/core-constants" "3.0.0" + escape-string-regexp "^4.0.0" + +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.10.5, prosemirror-transform@^1.7.3: + version "1.11.0" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz#f5c5050354423dc83c6b083f6f1959ec86a3f9ba" + integrity sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw== + dependencies: + prosemirror-model "^1.21.0" + +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.38.1, prosemirror-view@^1.41.4: + version "1.41.6" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.6.tgz#949d0407a91e36f6024db2191b8d3058dfd18838" + integrity sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg== + dependencies: + prosemirror-model "^1.20.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -4970,6 +5436,11 @@ rollup@^4.43.0: "@rollup/rollup-win32-x64-msvc" "4.53.3" fsevents "~2.3.2" +rope-sequence@^1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.4.tgz#df85711aaecd32f1e756f76e43a415171235d425" + integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ== + run-applescript@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.1.0.tgz#2e9e54c4664ec3106c5b5630e249d3d6595c4911" @@ -5442,6 +5913,11 @@ typescript@^5.9.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + ufo@^1.5.4: version "1.6.1" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.1.tgz#ac2db1d54614d1b22c1d603e3aef44a85d8f146b" @@ -5492,6 +5968,11 @@ use-sidecar@^1.1.3: detect-node-es "^1.1.0" tslib "^2.0.0" +use-sync-external-store@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + use-sync-external-store@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" @@ -5594,6 +6075,11 @@ vscode-uri@^3.0.8: resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c" integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ== +w3c-keyname@^2.2.0: + version "2.2.8" + resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" + integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== + w3c-xmlserializer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c"