diff --git a/package.json b/package.json index 9a3db604b..9e4c096f2 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@box/item-icon": "^3.2.0", "@box/languages": "^1.1.0", "@box/readable-time": "^2.1.32", - "@box/threaded-annotations": "^4.0.6", + "@box/threaded-annotations": "^4.11.0", "@box/types": "^2.1.8", "@box/user-selector": "^2.1.35", "@cfaester/enzyme-adapter-react-18": "^0.8.0", @@ -138,7 +138,7 @@ "@box/combobox-with-api": "^1.42.22", "@box/item-icon": "^3.2.0", "@box/readable-time": "^2.1.32", - "@box/threaded-annotations": "^4.0.6", + "@box/threaded-annotations": "^4.11.0", "@box/types": "^2.1.8", "@box/user-selector": "^2.1.35" }, diff --git a/src/BoxAnnotations.ts b/src/BoxAnnotations.ts index 0cfe8e792..4053a310d 100644 --- a/src/BoxAnnotations.ts +++ b/src/BoxAnnotations.ts @@ -20,6 +20,7 @@ type AnnotationsOptions = { }; export type Features = { + isRichTextEnabled?: boolean; isThreadedAnnotation?: boolean; [key: string]: boolean | undefined; }; diff --git a/src/adapters/__tests__/threadedAnnotationsAdapters-test.ts b/src/adapters/__tests__/threadedAnnotationsAdapters-test.ts index 7811d7602..55abfff82 100644 --- a/src/adapters/__tests__/threadedAnnotationsAdapters-test.ts +++ b/src/adapters/__tests__/threadedAnnotationsAdapters-test.ts @@ -1,3 +1,5 @@ +import { parseMessageMarkdown } from '@box/threaded-annotations'; + import { annotationToMessages, collaboratorToUserContact, @@ -7,6 +9,12 @@ import { import type { Annotation, Collaborator, Reply } from '../../@types'; import { TARGET_TYPE } from '../../constants'; +jest.mock('@box/threaded-annotations', () => ({ + parseMessageMarkdown: jest.fn((text: string | null | undefined) => + text ? { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text }] }] } : { type: 'doc', content: [] }, + ), +})); + describe('threadedAnnotationsAdapters', () => { describe('deserializeMentionMarkup', () => { test('should return empty doc for empty string', () => { @@ -109,6 +117,10 @@ describe('threadedAnnotationsAdapters', () => { type: 'reply', }; + beforeEach(() => { + (parseMessageMarkdown as jest.Mock).mockClear(); + }); + test('should map reply fields to TextMessageType', () => { const result = replyToTextMessage(mockReply); @@ -126,6 +138,7 @@ describe('threadedAnnotationsAdapters', () => { }; const result = replyToTextMessage(reply); + expect(parseMessageMarkdown).not.toHaveBeenCalled(); expect(result.message.content[0].content).toHaveLength(3); expect(result.message.content[0].content?.[1]).toMatchObject({ type: 'mention', @@ -174,6 +187,18 @@ describe('threadedAnnotationsAdapters', () => { }); }); + test('should parse markdown when isRichTextEnabled is true', () => { + const reply: Reply = { ...mockReply, message: '**bold**' }; + + const result = replyToTextMessage(reply, true); + + expect(parseMessageMarkdown).toHaveBeenCalledWith('**bold**'); + expect(result.message).toEqual({ + type: 'doc', + content: [{ type: 'paragraph', content: [{ type: 'text', text: '**bold**' }] }], + }); + }); + test('should leave updatedAt undefined when reply has no modified_at', () => { const result = replyToTextMessage(mockReply); @@ -207,6 +232,10 @@ describe('threadedAnnotationsAdapters', () => { type: 'annotation', }; + beforeEach(() => { + (parseMessageMarkdown as jest.Mock).mockClear(); + }); + test('should return empty array when no description or replies', () => { const result = annotationToMessages(baseAnnotation); expect(result).toEqual([]); @@ -268,6 +297,29 @@ describe('threadedAnnotationsAdapters', () => { expect(result[0].author.name).toBe('User'); expect(result[1].author.name).toBe('Other'); }); + + test('should parse description and replies with parseMessageMarkdown when enabled', () => { + const annotation: Annotation = { + ...baseAnnotation, + description: { message: 'Root' } as unknown as Reply, + replies: [ + { + created_at: '2026-01-02T00:00:00Z', + created_by: { id: '2', login: 'other@box.com', name: 'Other', type: 'user' }, + id: 'reply-1', + message: 'First reply', + parent: { id: 'ann-1', type: 'annotation' }, + type: 'reply', + }, + ], + }; + + annotationToMessages(annotation, true); + + expect(parseMessageMarkdown).toHaveBeenCalledWith('Root'); + expect(parseMessageMarkdown).toHaveBeenCalledWith('First reply'); + expect(parseMessageMarkdown).toHaveBeenCalledTimes(2); + }); }); describe('collaboratorToUserContact', () => { diff --git a/src/adapters/threadedAnnotationsAdapters.ts b/src/adapters/threadedAnnotationsAdapters.ts index 7157fcb08..8be7f287e 100644 --- a/src/adapters/threadedAnnotationsAdapters.ts +++ b/src/adapters/threadedAnnotationsAdapters.ts @@ -1,4 +1,5 @@ import type { DocumentNodeV2, MentionNodeV2, ParagraphNodeV2, TextMessageTypeV2, TextNodeV2 } from '@box/threaded-annotations'; +import { parseMessageMarkdown } from '@box/threaded-annotations'; import type { Annotation, Collaborator, Reply, UserMini } from '../@types'; @@ -69,6 +70,9 @@ export const deserializeMentionMarkup = (text: string): DocumentNodeV2 => { return { type: 'doc', content }; }; +const toDocumentNode = (text: string, isRichTextEnabled = false): DocumentNodeV2 => + isRichTextEnabled ? (parseMessageMarkdown(text) as DocumentNodeV2) : deserializeMentionMarkup(text); + /** * Returns the edit timestamp consumers use to render an edited indicator. * Compares parsed instants, not raw strings, so equivalent ISO formats @@ -86,7 +90,7 @@ const toUpdatedAt = (createdAt: string, modifiedAt: string | undefined): number /** * Converts a box-annotations Reply to a threaded-annotations TextMessageType. */ -export const replyToTextMessage = (reply: Reply): TextMessageTypeV2 => ({ +export const replyToTextMessage = (reply: Reply, isRichTextEnabled = false): TextMessageTypeV2 => ({ author: { email: reply.created_by?.login ?? '', id: parseInt(reply.created_by?.id ?? '0', 10), @@ -94,7 +98,7 @@ export const replyToTextMessage = (reply: Reply): TextMessageTypeV2 => ({ }, createdAt: new Date(reply.created_at).getTime(), id: reply.id, - message: deserializeMentionMarkup(reply.message), + message: toDocumentNode(reply.message, isRichTextEnabled), permissions: { canDelete: reply.permissions?.can_delete ?? false, canEdit: reply.permissions?.can_edit ?? false, @@ -106,7 +110,7 @@ export const replyToTextMessage = (reply: Reply): TextMessageTypeV2 => ({ // The root message shares the annotation's author and permissions; description // comes back sparse ({ message } only) from the list endpoint. -const descriptionToTextMessage = (annotation: Annotation): TextMessageTypeV2 => ({ +const descriptionToTextMessage = (annotation: Annotation, isRichTextEnabled = false): TextMessageTypeV2 => ({ author: { email: annotation.created_by?.login ?? '', id: parseInt(annotation.created_by?.id ?? '0', 10), @@ -114,7 +118,7 @@ const descriptionToTextMessage = (annotation: Annotation): TextMessageTypeV2 => }, createdAt: new Date(annotation.created_at).getTime(), id: annotation.id, - message: deserializeMentionMarkup(annotation.description?.message ?? ''), + message: toDocumentNode(annotation.description?.message ?? '', isRichTextEnabled), permissions: { canDelete: annotation.permissions?.can_delete ?? false, canEdit: annotation.permissions?.can_edit ?? false, @@ -128,16 +132,16 @@ const descriptionToTextMessage = (annotation: Annotation): TextMessageTypeV2 => * Maps a full Annotation (description + replies) to an array of TextMessageType * suitable for ThreadedAnnotationsV2. */ -export const annotationToMessages = (annotation: Annotation): TextMessageTypeV2[] => { +export const annotationToMessages = (annotation: Annotation, isRichTextEnabled = false): TextMessageTypeV2[] => { const messages: TextMessageTypeV2[] = []; if (annotation.description) { - messages.push(descriptionToTextMessage(annotation)); + messages.push(descriptionToTextMessage(annotation, isRichTextEnabled)); } if (annotation.replies) { annotation.replies.forEach(reply => { - messages.push(replyToTextMessage(reply)); + messages.push(replyToTextMessage(reply, isRichTextEnabled)); }); } diff --git a/src/components/Popups/PopupV2.tsx b/src/components/Popups/PopupV2.tsx index cf182233f..54358006b 100644 --- a/src/components/Popups/PopupV2.tsx +++ b/src/components/Popups/PopupV2.tsx @@ -10,6 +10,7 @@ import { MessageEditorV2, ThreadedAnnotationsV2, serializeMentionMarkup, + serializeMessageToMarkdown, } from '@box/threaded-annotations'; import type { DocumentNodeV2, TextMessageTypeV2 } from '@box/threaded-annotations'; import type { FetchedAvatarUrls, UserContactType } from '@box/user-selector'; @@ -27,7 +28,7 @@ import { updateReplyAction, } from '../../store/annotations/actions'; import { getAnnotation } from '../../store/annotations/selectors'; -import { getApiHost, getFileId, getFileVersionId, getToken } from '../../store/options'; +import { getApiHost, getFileId, getFileVersionId, getToken, isFeatureEnabled } from '../../store/options'; import { fetchCollaboratorsAction } from '../../store/users/actions'; import type { Token, TokenLiteral, TokenMap } from '../../@types'; @@ -66,6 +67,9 @@ const createDocumentNode = (content: JSONContent | null): DocumentNodeV2 => { return { type: 'doc', content: [content] } as DocumentNodeV2; }; +const serializePopupMessage = (doc: DocumentNodeV2, isRichTextEnabled: boolean): string => + isRichTextEnabled ? serializeMessageToMarkdown(doc) : serializeMentionMarkup(doc).text; + const literalToString = (literal: TokenLiteral): string | null => { if (!literal) return null; if (typeof literal === 'string') return literal; @@ -112,6 +116,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J const apiHost = useSelector(getApiHost); const fileId = useSelector(getFileId); const fileVersionId = useSelector(getFileVersionId); + const isRichTextEnabled = useSelector((state: AppState) => isFeatureEnabled(state, 'isRichTextEnabled')); const token = useSelector(getToken); const onCopyLink = React.useMemo( () => @@ -187,7 +192,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J if (!annotation) return undefined; const userIds = Array.from( - new Set(annotationToMessages(annotation).map(msg => String(msg.author.id))), + new Set(annotationToMessages(annotation, isRichTextEnabled).map(msg => String(msg.author.id))), ); let cancelled = false; @@ -205,18 +210,18 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J return () => { cancelled = true; }; - }, [annotation, getOrFetchAvatarBlob]); + }, [annotation, getOrFetchAvatarBlob, isRichTextEnabled]); const threadMessages: TextMessageTypeV2[] = React.useMemo(() => { if (!annotation) return []; - return annotationToMessages(annotation).map(msg => ({ + return annotationToMessages(annotation, isRichTextEnabled).map(msg => ({ ...msg, author: { ...msg.author, avatarUrl: avatarBlobs[String(msg.author.id)], }, })); - }, [annotation, avatarBlobs]); + }, [annotation, avatarBlobs, isRichTextEnabled]); const isResolved = annotation?.status === 'resolved'; const resolvedBy = isResolved @@ -259,28 +264,28 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J const handlePost = React.useCallback( async (content: JSONContent | null): Promise => { const doc = createDocumentNode(content); - const { text } = serializeMentionMarkup(doc); + const text = serializePopupMessage(doc, isRichTextEnabled); if (annotationId) { await dispatch(createReplyAction({ annotationId, message: text })); } else { onSubmit(text); } }, - [annotationId, dispatch, onSubmit], + [annotationId, dispatch, isRichTextEnabled, onSubmit], ); const handleEdit = React.useCallback( async (id: string, content: JSONContent | null): Promise => { if (!annotationId) return; const doc = createDocumentNode(content); - const { text } = serializeMentionMarkup(doc); + const text = serializePopupMessage(doc, isRichTextEnabled); if (id === annotationId) { await dispatch(updateAnnotationAction({ annotationId, payload: { message: text } })); return; } await dispatch(updateReplyAction({ annotationId, replyId: id, payload: { message: text } })); }, - [annotationId, dispatch], + [annotationId, dispatch, isRichTextEnabled], ); const handleThreadDelete = React.useCallback( @@ -349,6 +354,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J diff --git a/src/components/Popups/__tests__/PopupV2-test.tsx b/src/components/Popups/__tests__/PopupV2-test.tsx index 9c0bb0a97..51e963fc1 100644 --- a/src/components/Popups/__tests__/PopupV2-test.tsx +++ b/src/components/Popups/__tests__/PopupV2-test.tsx @@ -1,10 +1,15 @@ import React from 'react'; import { act, render, screen } from '@testing-library/react'; import { useDispatch, useSelector } from 'react-redux'; +import { + serializeMentionMarkup, + serializeMessageToMarkdown, +} from '@box/threaded-annotations'; import type { MentionContextData, ThreadedAnnotationsPropsV2 } from '@box/threaded-annotations'; import AnnotationCallbacksContext from '../../../common/AnnotationCallbacksContext'; import PopupV2, { Props } from '../PopupV2'; import { + createReplyAction, deleteReplyAction, updateAnnotationAction, updateReplyAction, @@ -35,6 +40,11 @@ jest.mock('@box/blueprint-web', () => ({ })); let lastMentionContextValue: MentionContextData = {}; +let lastMessageEditorProps: { + isFirstAnnotation?: boolean; + isRichTextEnabled?: boolean; + onPost?: (content: unknown) => Promise; +} = {}; let lastThreadedAnnotationsProps: Partial = {}; jest.mock('@box/threaded-annotations', () => { @@ -44,16 +54,24 @@ jest.mock('@box/threaded-annotations', () => { lastMentionContextValue = value; return ReactMock.createElement('div', { 'data-testid': 'mention-context' }, children); }, - MessageEditorV2: (props: Record) => - ReactMock.createElement('div', { + MessageEditorV2: (props: { + isFirstAnnotation?: boolean; + isRichTextEnabled?: boolean; + onPost?: (content: unknown) => Promise; + }) => { + lastMessageEditorProps = props; + return ReactMock.createElement('div', { 'data-testid': 'message-editor-v2', 'data-is-first-annotation': String(props.isFirstAnnotation), - }), + 'data-is-rich-text-enabled': String(props.isRichTextEnabled), + }); + }, ThreadedAnnotationsV2: (props: Partial) => { lastThreadedAnnotationsProps = props; return ReactMock.createElement('div', { 'data-testid': 'threaded-annotations-v2', 'data-is-annotations': String(props.isAnnotations), + 'data-is-rich-text-enabled': String(props.isRichTextEnabled), 'data-messages-count': String(props.messages?.length ?? 0), 'data-has-on-edit': String(typeof props.onEdit === 'function'), 'data-has-on-post': String(typeof props.onPost === 'function'), @@ -63,6 +81,8 @@ jest.mock('@box/threaded-annotations', () => { }); }, serializeMentionMarkup: jest.fn().mockReturnValue({ hasMention: false, text: 'serialized text' }), + serializeMessageToMarkdown: jest.fn().mockReturnValue('markdown text'), + parseMessageMarkdown: jest.fn().mockReturnValue({ type: 'doc', content: [] }), }; }); @@ -91,6 +111,7 @@ type SelectorOverrides = { apiHost?: string; fileId?: string | null; fileVersionId?: string | null; + isRichTextEnabled?: boolean; token?: unknown; }; @@ -99,6 +120,7 @@ const mockSelectorValues = ({ apiHost = 'https://api.box.com', fileId = '12345', fileVersionId = 'fv-1', + isRichTextEnabled = false, token = 'test-token', }: SelectorOverrides = {}): void => { mockUseSelector.mockImplementation(selector => { @@ -106,7 +128,11 @@ const mockSelectorValues = ({ if (selector === getFileId) return fileId; if (selector === getFileVersionId) return fileVersionId; if (selector === getToken) return token; - return annotation; + const result = selector({ + annotations: { byId: {} }, + options: { features: { isRichTextEnabled } }, + }); + return typeof result === 'boolean' ? result : annotation; }); }; @@ -153,6 +179,7 @@ describe('PopupV2', () => { beforeEach(() => { lastMentionContextValue = {}; + lastMessageEditorProps = {}; lastThreadedAnnotationsProps = {}; mockUseDispatch.mockReturnValue(mockDispatch); mockFetch.mockResolvedValue({ @@ -195,6 +222,35 @@ describe('PopupV2', () => { render(); expect(screen.getByTestId('message-editor-v2').getAttribute('data-is-first-annotation')).toBe('true'); + expect(screen.getByTestId('message-editor-v2').getAttribute('data-is-rich-text-enabled')).toBe('false'); + }); + + test('should pass isRichTextEnabled to MessageEditorV2 when the feature is on', () => { + mockSelectorValues({ isRichTextEnabled: true }); + render(); + + expect(screen.getByTestId('message-editor-v2').getAttribute('data-is-rich-text-enabled')).toBe('true'); + }); + + test('should serialize mention markup when posting a new annotation with rich text disabled', async () => { + render(); + + await lastMessageEditorProps.onPost?.({ type: 'doc', content: [] }); + + expect(serializeMentionMarkup).toHaveBeenCalled(); + expect(serializeMessageToMarkdown).not.toHaveBeenCalled(); + expect(defaults.onSubmit).toHaveBeenCalledWith('serialized text'); + }); + + test('should serialize markdown when posting a new annotation with rich text enabled', async () => { + mockSelectorValues({ isRichTextEnabled: true }); + render(); + + await lastMessageEditorProps.onPost?.({ type: 'doc', content: [] }); + + expect(serializeMessageToMarkdown).toHaveBeenCalled(); + expect(serializeMentionMarkup).not.toHaveBeenCalled(); + expect(defaults.onSubmit).toHaveBeenCalledWith('markdown text'); }); test('should set popupReplyV2 as resin component', () => { @@ -248,9 +304,20 @@ describe('PopupV2', () => { const thread = screen.getByTestId('threaded-annotations-v2'); expect(thread.getAttribute('data-is-annotations')).toBe('true'); + expect(thread.getAttribute('data-is-rich-text-enabled')).toBe('false'); expect(thread.getAttribute('data-messages-count')).toBe('1'); }); + test('should pass isRichTextEnabled to ThreadedAnnotationsV2 when the feature is on', async () => { + mockSelectorValues({ annotation: mockAnnotation, isRichTextEnabled: true }); + render(); + await flushPromises(); + + expect(screen.getByTestId('threaded-annotations-v2').getAttribute('data-is-rich-text-enabled')).toBe( + 'true', + ); + }); + test('should render empty messages when annotation is not found', async () => { mockSelectorValues(); render(); @@ -297,6 +364,50 @@ describe('PopupV2', () => { }); }); + test('should serialize markdown when editing the root message with rich text enabled', async () => { + mockSelectorValues({ annotation: mockAnnotation, isRichTextEnabled: true }); + render(); + await flushPromises(); + + await lastThreadedAnnotationsProps.onEdit?.('annotation-1', { type: 'doc', content: [] }); + + expect(serializeMessageToMarkdown).toHaveBeenCalled(); + expect(serializeMentionMarkup).not.toHaveBeenCalled(); + expect(updateAnnotationAction).toHaveBeenCalledWith({ + annotationId: 'annotation-1', + payload: { message: 'markdown text' }, + }); + }); + + test('should dispatch createReplyAction with mention markup when posting a reply', async () => { + render(); + await flushPromises(); + + await lastThreadedAnnotationsProps.onPost?.({ type: 'doc', content: [] }); + + expect(serializeMentionMarkup).toHaveBeenCalled(); + expect(serializeMessageToMarkdown).not.toHaveBeenCalled(); + expect(createReplyAction).toHaveBeenCalledWith({ + annotationId: 'annotation-1', + message: 'serialized text', + }); + }); + + test('should serialize markdown when posting a reply with rich text enabled', async () => { + mockSelectorValues({ annotation: mockAnnotation, isRichTextEnabled: true }); + render(); + await flushPromises(); + + await lastThreadedAnnotationsProps.onPost?.({ type: 'doc', content: [] }); + + expect(serializeMessageToMarkdown).toHaveBeenCalled(); + expect(serializeMentionMarkup).not.toHaveBeenCalled(); + expect(createReplyAction).toHaveBeenCalledWith({ + annotationId: 'annotation-1', + message: 'markdown text', + }); + }); + test('should dispatch deleteReplyAction when a reply is deleted', async () => { render(); await flushPromises(); diff --git a/yarn.lock b/yarn.lock index efed851b0..64551ba1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,14 +35,6 @@ react-transition-group "^4.4.5" use-sync-external-store "^1.6.0" -"@ampproject/remapping@^2.2.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" - integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - "@ariakit/core@0.4.18": version "0.4.18" resolved "https://registry.yarnpkg.com/@ariakit/core/-/core-0.4.18.tgz#e1a629e942c98b9371fd81b14f63b793f878c5eb" @@ -80,7 +72,7 @@ "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" chokidar "^3.6.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.29.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== @@ -89,12 +81,12 @@ js-tokens "^4.0.0" picocolors "^1.1.1" -"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.9", "@babel/compat-data@^7.26.0", "@babel/compat-data@^7.29.7": +"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.0", "@babel/compat-data@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629" integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== -"@babel/core@>=7.2.2", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.25.9", "@babel/core@^7.7.2", "@babel/core@^7.7.5": +"@babel/core@>=7.2.2", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.25.9", "@babel/core@^7.7.2": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== @@ -124,7 +116,7 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.25.9", "@babel/generator@^7.26.0", "@babel/generator@^7.29.7", "@babel/generator@^7.29.8", "@babel/generator@^7.7.2": +"@babel/generator@^7.29.7", "@babel/generator@^7.29.8", "@babel/generator@^7.7.2": version "7.29.8" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979" integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg== @@ -215,7 +207,7 @@ "@babel/traverse" "^7.29.7" "@babel/types" "^7.29.7" -"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0", "@babel/helper-module-transforms@^7.29.7": +"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae" integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg== @@ -270,12 +262,12 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/helper-string-parser@^7.25.9", "@babel/helper-string-parser@^7.29.7": +"@babel/helper-string-parser@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== -"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.28.5", "@babel/helper-validator-identifier@^7.29.7": +"@babel/helper-validator-identifier@^7.25.9", "@babel/helper-validator-identifier@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== @@ -294,7 +286,7 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/helpers@^7.26.0", "@babel/helpers@^7.29.7": +"@babel/helpers@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607" integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg== @@ -302,7 +294,7 @@ "@babel/template" "^7.29.7" "@babel/types" "^7.29.7" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2", "@babel/parser@^7.29.7", "@babel/parser@^7.29.8", "@babel/parser@^7.7.0", "@babel/parser@^7.7.5": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.29.7", "@babel/parser@^7.29.8", "@babel/parser@^7.7.0": version "7.29.8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.8.tgz#9653716a2f10c677b98fbc63d4bfb000c302cf17" integrity sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA== @@ -1093,7 +1085,7 @@ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.2.tgz#9a6e2d05f4b6692e1801cd4fb176ad823930ed5e" integrity sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== -"@babel/template@^7.25.9", "@babel/template@^7.29.7", "@babel/template@^7.3.3", "@babel/template@^7.7.4": +"@babel/template@^7.25.9", "@babel/template@^7.29.7", "@babel/template@^7.3.3": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700" integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== @@ -1102,7 +1094,7 @@ "@babel/parser" "^7.29.7" "@babel/types" "^7.29.7" -"@babel/traverse@^7.25.9", "@babel/traverse@^7.29.7", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.4": +"@babel/traverse@^7.25.9", "@babel/traverse@^7.29.7", "@babel/traverse@^7.7.0": version "7.29.8" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.8.tgz#4111014cdc71a0f95d9471907590baa0b8a6b28a" integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg== @@ -1115,7 +1107,7 @@ "@babel/types" "^7.29.8" debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.29.7", "@babel/types@^7.29.8", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.9.0": +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.29.7", "@babel/types@^7.29.8", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.9.0": version "7.29.8" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863" integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg== @@ -1205,22 +1197,27 @@ resolved "https://registry.yarnpkg.com/@box/readable-time/-/readable-time-2.2.45.tgz#d883e1f10883a03b24ebbeb99b8eb92820411d48" integrity sha512-cbfSozx9yD6m3IctYsmB3fJsVDOBW4urJhOqxGB1KbAQhU/DCu+ikvNoGA1TowD3mzxq1XG/Q6Pm39D20GWFfA== -"@box/threaded-annotations@^4.0.6": - version "4.1.7" - resolved "https://registry.yarnpkg.com/@box/threaded-annotations/-/threaded-annotations-4.1.7.tgz#cc87194a4fbb44d5f0bd024f088111de33fa37a7" - integrity sha512-19Ds/Ulbc+SnPtsbDSr9+/VHkTouFPISxUMci3hvP0DRbDiPWKaDZRy4eFvZwFw049e0jqEws3YuZ2W9b6xnhQ== +"@box/threaded-annotations@^4.11.0": + version "4.11.3" + resolved "https://registry.yarnpkg.com/@box/threaded-annotations/-/threaded-annotations-4.11.3.tgz#a31421a57b87100810afdbbb269579c30093b948" + integrity sha512-dlv51XHTzDfrh+S13bvgUCbHd9wuxQNAPSef+qNLv6VBuZWlmZMMzmeJOOqTJ/vOVr0jcTEJ/py4znesHmvwEg== dependencies: "@floating-ui/dom" "^1.6.0" "@tanstack/react-virtual" "^3.10.8" "@tiptap/core" "3.26.1" + "@tiptap/extension-bold" "3.26.1" "@tiptap/extension-document" "3.26.1" + "@tiptap/extension-italic" "3.26.1" + "@tiptap/extension-list" "3.26.1" "@tiptap/extension-mention" "3.26.1" "@tiptap/extension-paragraph" "3.26.1" "@tiptap/extension-text" "3.26.1" + "@tiptap/extension-underline" "3.26.1" "@tiptap/extensions" "3.26.1" "@tiptap/pm" "3.26.1" "@tiptap/react" "3.26.1" "@tiptap/suggestion" "3.26.1" + markdown-it "^14.2.0" uuid "^9.0.1" "@box/tree@^1.42.13": @@ -1999,11 +1996,6 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - "@jridgewell/source-map@^0.3.3": version "0.3.6" resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" @@ -2012,7 +2004,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -3666,6 +3658,11 @@ resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.26.1.tgz#a4d3222248af7af01aede146806a5757465cc654" integrity sha512-TX9PyPqBoix0qDLjtok/bddtdSy54QhzLVha405C07V+WySOpH3s/pWYkywehZQY0SQtcrcY4MNSCeQjCbA28A== +"@tiptap/extension-bold@3.26.1": + version "3.26.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.26.1.tgz#5e6b1caca15e90a45324e960a597e6bedef15491" + integrity sha512-VIlF2sAiV6K009pcIDotfY8mvsPaq90dxeG9Q0ZIqfMD958TUCqjHw4MGYZf0/FgP12xksBfmcR7W312xgUf9Q== + "@tiptap/extension-bubble-menu@^3.26.1": version "3.28.0" resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.28.0.tgz#51fc147b0f35d57f802204dd063831f67e538122" @@ -3683,6 +3680,16 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.28.0.tgz#30b9c5d4c52687f53375f5396d92701ce3b75bde" integrity sha512-59SECvJq3pQfeJBuydEdhQqrpl0oDlk8N1Ovs1Si3/fJa6rEQAJB2zIvcpBHky9Z+5JodI2eueDnv8eimiyGbg== +"@tiptap/extension-italic@3.26.1": + version "3.26.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.26.1.tgz#656ff4d6cac83c8cdd1fd030cd1c0fc368803283" + integrity sha512-cLKYvOLToWEkJkAPspgIZ/PYDzAxacLm1VWcAq1tO1QDQCDe2Kw+y/zsGlyYEq/aKsAgpp4JNopBwAXRXxt2/A== + +"@tiptap/extension-list@3.26.1": + version "3.26.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.26.1.tgz#532804fda623a25ed10bcacad55ccf5ab347969e" + integrity sha512-06nOjnyXpzMO8Ys5k3IbYsDsKib1mv2OtaxBYX1/1uvRyOKwUX5tqDLb/qigic0LIANNL73lkNC8Z8XPeG4Tkg== + "@tiptap/extension-mention@3.26.1": version "3.26.1" resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-3.26.1.tgz#9ac3147291d343b8b4edccfb4ac2753d0d503216" @@ -3698,6 +3705,11 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.26.1.tgz#4b693434477a8fed89f964974634dbe5ae23d742" integrity sha512-Gocui5WvcCCJJIX17gdOVCSdYi5H4fDwaR0qkMAUZPq5kJCdrfl+vNpt8BTt53Bk+/QumiUW21fhQ184w7RoeQ== +"@tiptap/extension-underline@3.26.1": + version "3.26.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.26.1.tgz#bf83cdc3119422d1401416438497ecfb1f0bc3e3" + integrity sha512-HUHtQ+DRWDM0opW7Nk3YQwrLzw876hMU7cr1X/ZTG+8Bp+AKHihlwU+bqrPgG5St0mqASyUEhHQ/vK5PlnUYOQ== + "@tiptap/extensions@3.26.1": version "3.26.1" resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.26.1.tgz#9f5d9ea5eb64d5888b35d4ea91907610e1d51844" @@ -3809,11 +3821,6 @@ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - "@types/connect-history-api-fallback@^1.5.4": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" @@ -6986,7 +6993,7 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -entities@^4.2.0: +entities@^4.2.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -9513,19 +9520,6 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== -istanbul-lib-instrument@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.0.tgz#53321a7970f076262fd3292c8f9b2e4ac544aae1" - integrity sha512-Nm4wVHdo7ZXSG30KjZ2Wl5SU/Bw7bDx1PdaiIFzEStdjs0H12mOTncn1GVYuqQSaZxpg87VGBRsVRPGD2cD1AQ== - dependencies: - "@babel/core" "^7.7.5" - "@babel/parser" "^7.7.5" - "@babel/template" "^7.7.4" - "@babel/traverse" "^7.7.4" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - istanbul-lib-instrument@^5.0.4: version "5.2.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" @@ -10341,6 +10335,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +linkify-it@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.2.tgz#d3be0a693af3da9df3883f1e346a0e97461a8c19" + integrity sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q== + dependencies: + uc.micro "^2.0.0" + lint-staged@^9.5.0: version "9.5.0" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-9.5.0.tgz#290ec605252af646d9b74d73a0fa118362b05a33" @@ -10729,6 +10730,18 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122" integrity sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA== +markdown-it@^14.2.0: + version "14.3.1" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.3.1.tgz#8974e2473779363ed5682ea377f31564ee51e292" + integrity sha512-4Ej49aYTDFIQ+uBkfX8GBvJGccoARxxPep+7aWTs55ozbjQJpW9M26Fe53vnGgvLeVzva/amzjQQaQu9w0vMhA== + dependencies: + argparse "^2.0.1" + entities "^4.5.0" + linkify-it "^5.0.2" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + markdown-table@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.2.tgz#c78db948fa879903a41bce522e3b96f801c63786" @@ -10761,6 +10774,11 @@ mdn-data@2.27.1: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.27.1.tgz#e37b9c50880b75366c4d40ac63d9bbcacdb61f0e" integrity sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ== +mdurl@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.1.0.tgz#d711d3f7bce7f22c487c91be78545f356fa96573" + integrity sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -12406,6 +12424,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +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.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -14786,6 +14809,11 @@ ua-parser-js@^0.7.18: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== +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== + uglify-js@^3.1.4: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"