[web] workspace/editor: vim mode (#1384)

This commit is contained in:
Harshit Pant 2025-04-21 09:10:50 +05:30 committed by Yangshun
parent 791f23a81f
commit fe4288d7f7
14 changed files with 384 additions and 156 deletions

View File

@ -108,6 +108,7 @@
"mitt": "^3.0.0",
"monaco-editor": "^0.40.0",
"monaco-themes": "^0.4.3",
"monaco-vim": "^0.4.2",
"negotiator": "^1.0.0",
"next": "14.2.15",
"next-contentlayer": "^0.3.4",

View File

@ -12,6 +12,7 @@ import { cdnUrl } from '~/utils/cdnUrl';
import { getErrorMessage } from '~/utils/getErrorMessage';
import getLanguageFromFilePath from './getLanguageFromFilePath';
import useMonacoEditorVimMode from './hooks/useMonacoEditorVimMode';
import useMonacoEditorAddActions from './useMonacoEditorAddActions';
import useMonacoEditorAddFormatter from './useMonacoEditorAddFormatter';
import useMonacoEditorOnShown from './useMonacoEditorOnShown';
@ -37,6 +38,7 @@ type Props = Readonly<
className?: string;
filePath?: string;
height?: React.ComponentProps<typeof MonacoEditor>['height'];
isVimModeEnabled?: boolean;
keepCurrentModel?: boolean;
language?: string;
onFocus?: () => void;
@ -93,12 +95,14 @@ export default function MonacoCodeEditor({
wordWrap = false,
readOnly = false,
keepCurrentModel = true,
isVimModeEnabled = false,
options,
}: Props) {
const intl = useIntl();
const monaco = useMonaco();
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const editorContainerRef = useRef<HTMLDivElement | null>(null);
const vimStatusBarRef = useRef<HTMLDivElement | null>(null);
const themeKey = useMonacoEditorTheme();
useEffect(() => {
@ -115,46 +119,57 @@ export default function MonacoCodeEditor({
useMonacoEditorAddActions(monaco, editorRef.current);
useMonacoEditorAddFormatter(monaco, editorRef.current, languageExt?.ext);
useMonacoEditorOnShown(editorContainerRef.current, onFocus);
useMonacoEditorVimMode(
editorRef.current,
isVimModeEnabled,
vimStatusBarRef.current,
);
return (
<div
ref={editorContainerRef}
className={clsx(className, 'size-full')}
onFocus={onFocus}>
<MonacoEditor
height={height}
keepCurrentModel={keepCurrentModel}
language={language ?? languageExt?.language ?? undefined}
loading={
<EmptyState
iconClassName="animate-bounce"
size="sm"
title={intl.formatMessage({
defaultMessage: 'Loading editor',
description: 'Loading code editor',
id: 'AFsv0q',
})}
variant="editor_loading"
/>
}
options={{
fixedOverflowWidgets: true,
minimap: {
enabled: false,
},
readOnly,
wordWrap: wordWrap ? 'on' : 'off',
...options,
}}
path={filePath}
theme={themeKey}
value={value ?? ''}
onChange={(val) => onChange?.(val ?? '')}
onMount={(editorInstance) => {
editorRef.current = editorInstance;
onMount?.(editorInstance);
}}
<>
<div
ref={vimStatusBarRef}
className="h-6 w-full px-3 font-mono text-sm/6 [&_input:focus]:outline-0 [&_input:focus]:ring-0 [&_input]:h-full [&_input]:border-0 [&_input]:bg-transparent [&_input]:text-sm/6"
/>
</div>
<div
ref={editorContainerRef}
className={clsx(className, 'size-full')}
onFocus={onFocus}>
<MonacoEditor
height={height}
keepCurrentModel={keepCurrentModel}
language={language ?? languageExt?.language ?? undefined}
loading={
<EmptyState
iconClassName="animate-bounce"
size="sm"
title={intl.formatMessage({
defaultMessage: 'Loading editor',
description: 'Loading code editor',
id: 'AFsv0q',
})}
variant="editor_loading"
/>
}
options={{
fixedOverflowWidgets: true,
minimap: {
enabled: false,
},
readOnly,
wordWrap: wordWrap ? 'on' : 'off',
...options,
}}
path={filePath}
theme={themeKey}
value={value ?? ''}
onChange={(val) => onChange?.(val ?? '')}
onMount={(editorInstance) => {
editorRef.current = editorInstance;
onMount?.(editorInstance);
}}
/>
</div>
</>
);
}

View File

@ -0,0 +1,33 @@
import type { editor } from 'monaco-editor';
// @ts-expect-error monaco-vim is not typed.
import { initVimMode } from 'monaco-vim';
import { useEffect, useRef } from 'react';
export default function useMonacoEditorVimMode(
editorInstance: editor.IStandaloneCodeEditor | null,
enabled: boolean,
vimStatusBar: HTMLDivElement | null,
) {
// AnyIntentional as monaco-vim is not typed.
const vimModeRef = useRef<AnyIntentional>(null);
useEffect(() => {
if (!editorInstance) {
return;
}
if (enabled && !vimModeRef.current) {
vimModeRef.current = initVimMode(editorInstance, vimStatusBar);
} else if (!enabled && vimModeRef.current) {
vimModeRef.current.dispose();
vimModeRef.current = null;
}
return () => {
if (vimModeRef.current) {
vimModeRef.current.dispose();
vimModeRef.current = null;
}
};
}, [editorInstance, enabled, vimStatusBar]);
}

View File

@ -0,0 +1,17 @@
import { useCallback } from 'react';
import { useLocalStorage } from 'usehooks-ts';
const VIM_MODE_LOCAL_STORAGE_KEY = 'gfe-vim-mode-enabled';
export function useVimMode() {
const [isVimModeEnabled, setIsVimModeEnabled] = useLocalStorage<boolean>(
VIM_MODE_LOCAL_STORAGE_KEY,
false,
);
const toggleVimMode = useCallback(() => {
setIsVimModeEnabled((prev) => !prev);
}, [setIsVimModeEnabled]);
return { isVimModeEnabled, toggleVimMode };
}

View File

@ -2,7 +2,7 @@
import { useState } from 'react';
import { RiArrowGoBackLine, RiPlayLine, RiSettings2Line } from 'react-icons/ri';
import { VscLayout } from 'react-icons/vsc';
import { VscLayout, VscTerminal } from 'react-icons/vsc';
import { useAuthActiveEngagementPoints } from '~/components/auth/auth-points';
import QuestionProgressAction from '~/components/interviews/questions/common/QuestionProgressAction';
@ -11,6 +11,7 @@ import type { QuestionMetadata } from '~/components/interviews/questions/common/
import { useIntl } from '~/components/intl';
import Button from '~/components/ui/Button';
import DropdownMenu from '~/components/ui/DropdownMenu';
import { useVimMode } from '~/components/workspace/common/editor/hooks/useVimMode';
import logEvent from '~/logging/logEvent';
@ -36,6 +37,7 @@ export default function JavaScriptCodingWorkspaceBottomBar({
const { status, runTests, submit, resetToDefaultCode } =
useCodingWorkspaceContext();
const [isLayoutDialogOpen, setIsLayoutDialogOpen] = useState(false);
const { isVimModeEnabled, toggleVimMode } = useVimMode();
useAuthActiveEngagementPoints({
entityId: metadata.slug,
@ -159,6 +161,24 @@ export default function JavaScriptCodingWorkspaceBottomBar({
},
value: 'layout',
},
{
icon: VscTerminal,
label: isVimModeEnabled
? intl.formatMessage({
defaultMessage: 'Disable Vim mode',
description:
'Button label to disable vim mode in editor',
id: 'cnL7HI',
})
: intl.formatMessage({
defaultMessage: 'Enable Vim mode',
description:
'Button label to enable vim mode in editor',
id: 'YeHWje',
}),
onClick: toggleVimMode,
value: 'vim-mode',
},
{
icon: RiArrowGoBackLine,
label: intl.formatMessage({

View File

@ -3,11 +3,13 @@ import type { editor } from 'monaco-editor';
import { useIsMounted } from 'usehooks-ts';
import { useIntl } from '~/components/intl';
import Banner from '~/components/ui/Banner';
import Button from '~/components/ui/Button';
import { themeBorderColor } from '~/components/ui/theme';
import CodingWorkspaceEditorShortcutsButton from '~/components/workspace/common/editor/CodingWorkspaceEditorShortcutsButton';
import CodingWorkspaceResetButton from '~/components/workspace/common/editor/CodingWorkspaceResetButton';
import CodingWorkspaceThemeSelect from '~/components/workspace/common/editor/CodingWorkspaceThemeSelect';
import { useVimMode } from '~/components/workspace/common/editor/hooks/useVimMode';
import JavaScriptCodingWorkspaceWorkingLanguageSelect from '~/components/workspace/javascript/JavaScriptCodingWorkspaceWorkingLanguageSelect';
import { useJavaScriptCodingWorkspaceContext } from './JavaScriptCodingWorkspaceContext';
@ -36,6 +38,7 @@ export default function JavaScriptCodingWorkspaceCodeEditor({
useJavaScriptCodingWorkspaceContext();
const { sandpack } = useSandpack();
const intl = useIntl();
const { isVimModeEnabled } = useVimMode();
const isMounted = useIsMounted();
const { files, updateFile } = sandpack;
@ -118,6 +121,7 @@ export default function JavaScriptCodingWorkspaceCodeEditor({
)}
<MonacoCodeEditor
filePath={filePath}
isVimModeEnabled={isVimModeEnabled}
value={files[filePath].code}
onChange={(val) => {
updateFile(filePath, val ?? '');

View File

@ -18,6 +18,7 @@ import TextArea from '~/components/ui/TextArea';
import TextInput from '~/components/ui/TextInput';
import JavaScriptCodingWorkspaceWorkingLanguageSelect from './JavaScriptCodingWorkspaceWorkingLanguageSelect';
import { useVimMode } from '../common/editor/hooks/useVimMode';
import MonacoCodeEditor from '../common/editor/MonacoCodeEditor';
type Props = Readonly<{
@ -37,6 +38,8 @@ function JavaScriptCodingWorkspaceCommunitySolutionCreateTabImpl({
const intl = useIntl();
const trpcUtils = trpc.useUtils();
const { isVimModeEnabled } = useVimMode();
const { isLoading, mutateAsync: addSolution } =
trpc.questionCommunitySolution.javaScriptAdd.useMutation({
onSuccess: () => {
@ -158,7 +161,11 @@ function JavaScriptCodingWorkspaceCommunitySolutionCreateTabImpl({
name="code"
render={({ field: { ref: _, ...field } }) => (
<div className="flex flex-1 flex-col">
<MonacoCodeEditor filePath="community-solution.ts" {...field} />
<MonacoCodeEditor
filePath="community-solution.ts"
isVimModeEnabled={isVimModeEnabled}
{...field}
/>
{formState.isDirty && formState.errors.code?.message && (
<Text color="error" size="body2" weight="medium">
{formState.errors.code?.message}

View File

@ -2,7 +2,7 @@
import { useState } from 'react';
import { RiArrowGoBackLine, RiSettings2Line } from 'react-icons/ri';
import { VscLayout } from 'react-icons/vsc';
import { VscLayout, VscTerminal } from 'react-icons/vsc';
import { useAuthActiveEngagementPoints } from '~/components/auth/auth-points';
import QuestionProgressAction from '~/components/interviews/questions/common/QuestionProgressAction';
@ -15,6 +15,7 @@ import type {
import type { QuestionUserInterfaceMode } from '~/components/interviews/questions/common/QuestionUserInterfacePath';
import { useIntl } from '~/components/intl';
import DropdownMenu from '~/components/ui/DropdownMenu';
import { useVimMode } from '~/components/workspace/common/editor/hooks/useVimMode';
import UserInterfaceCodingWorkspaceLayoutDialog from './UserInterfaceCodingWorkspaceLayoutDialog';
import UserInterfaceCodingWorkspaceSaveButton from './UserInterfaceCodingWorkspaceSaveButton';
@ -52,6 +53,7 @@ export default function UserInterfaceCodingWorkspaceBottomBar({
});
const [isLayoutDialogOpen, setIsLayoutDialogOpen] = useState(false);
const { isVimModeEnabled, toggleVimMode } = useVimMode();
const leftElements = (
<div className="hidden flex-1 items-center gap-x-2 sm:inline-flex">
@ -84,6 +86,22 @@ export default function UserInterfaceCodingWorkspaceBottomBar({
},
value: 'layout',
},
{
icon: VscTerminal,
label: isVimModeEnabled
? intl.formatMessage({
defaultMessage: 'Disable Vim mode',
description: 'Button label to disable vim mode in editor',
id: 'cnL7HI',
})
: intl.formatMessage({
defaultMessage: 'Enable Vim mode',
description: 'Button label to enable vim mode in editor',
id: 'YeHWje',
}),
onClick: toggleVimMode,
value: 'vim-mode',
},
{
icon: RiArrowGoBackLine,
label: intl.formatMessage({

View File

@ -11,6 +11,7 @@ import { themeBorderColor } from '~/components/ui/theme';
import CodingWorkspaceEditorShortcutsButton from '~/components/workspace/common/editor/CodingWorkspaceEditorShortcutsButton';
import CodingWorkspaceResetButton from '~/components/workspace/common/editor/CodingWorkspaceResetButton';
import CodingWorkspaceThemeSelect from '~/components/workspace/common/editor/CodingWorkspaceThemeSelect';
import { useVimMode } from '~/components/workspace/common/editor/hooks/useVimMode';
import MonacoCodeEditor from '~/components/workspace/common/editor/MonacoCodeEditor';
import { useUserInterfaceCodingWorkspaceSavesContext } from './UserInterfaceCodingWorkspaceSaveContext';
@ -41,6 +42,7 @@ export default function UserInterfaceCodingWorkspaceCodeEditor({
setShowLoadedFilesFromLocalStorageMessage,
resetToDefaultCode,
} = useCodingWorkspaceContext();
const { isVimModeEnabled } = useVimMode();
const isMounted = useIsMounted();
const onFocus = useCallback(() => {
@ -210,6 +212,7 @@ export default function UserInterfaceCodingWorkspaceCodeEditor({
)}
<MonacoCodeEditor
filePath={filePath}
isVimModeEnabled={isVimModeEnabled}
value={files[filePath].code}
onChange={(val) => {
updateFile(filePath, val ?? '');

View File

@ -8748,12 +8748,6 @@
"value": "John Doe"
}
],
"I7qnG/": [
{
"type": 0,
"value": "Disable Vim Mode"
}
],
"I872jM": [
{
"type": 0,
@ -8876,12 +8870,6 @@
"value": "Features"
}
],
"ILCBgB": [
{
"type": 0,
"value": "Enable Vim Mode"
}
],
"INrnLA": [
{
"type": 0,
@ -16308,6 +16296,12 @@
"value": "."
}
],
"YeHWje": [
{
"type": 0,
"value": "Enable Vim mode"
}
],
"YeWPOG": [
{
"type": 0,
@ -18074,6 +18068,12 @@
"value": "questionFramework"
}
],
"cnL7HI": [
{
"type": 0,
"value": "Disable Vim mode"
}
],
"cotxax": [
{
"type": 0,

View File

@ -7976,6 +7976,12 @@
"value": "获得完全访问权限"
}
],
"GPM7SF": [
{
"type": 0,
"value": "新建文件"
}
],
"GPp0wf": [
{
"type": 0,
@ -8816,6 +8822,24 @@
"value": "加入社区"
}
],
"IIE89R": [
{
"type": 0,
"value": "您确定要删除 "
},
{
"type": 1,
"value": "displayName"
},
{
"type": 0,
"value": " 吗? "
},
{
"type": 1,
"value": "directoryWarning"
}
],
"IIctLn": [
{
"type": 0,
@ -10669,6 +10693,12 @@
"value": "%佣金,无上限"
}
],
"M9GFCh": [
{
"type": 0,
"value": "此文件已被删除或重命名。"
}
],
"M9z02W": [
{
"type": 0,
@ -16138,6 +16168,12 @@
"value": "查看现金返还条款和条件"
}
],
"YRi3wG": [
{
"type": 0,
"value": "新建文件夹"
}
],
"YS1B1R": [
{
"type": 0,
@ -16234,6 +16270,12 @@
"value": "。"
}
],
"YeHWje": [
{
"type": 0,
"value": "启用 Vim 模式"
}
],
"YeWPOG": [
{
"type": 0,
@ -17450,6 +17492,12 @@
"value": "级别"
}
],
"bOVGLU": [
{
"type": 0,
"value": "请登录或创建帐户以继续"
}
],
"bQ3XUx": [
{
"type": 0,
@ -18006,6 +18054,12 @@
"value": "questionTitle"
}
],
"cnL7HI": [
{
"type": 0,
"value": "禁用 Vim 模式"
}
],
"cotxax": [
{
"type": 0,
@ -19512,12 +19566,6 @@
"value": "您还获得了专属促销代码:"
}
],
"fxQRHJ": [
{
"type": 0,
"value": "目前,无法创建或重命名文件。在面试期间,将多个组件写入单个文件中是可以接受的。"
}
],
"fxZ0zk": [
{
"type": 0,
@ -23783,6 +23831,12 @@
"value": "前端系统设计"
}
],
"oxAf2V": [
{
"type": 0,
"value": "这将删除其所有内容。"
}
],
"oxD4TD": [
{
"type": 0,

View File

@ -4191,10 +4191,6 @@
"defaultMessage": "John Doe",
"description": "Placeholder for name field"
},
"I7qnG/": {
"defaultMessage": "Disable Vim Mode",
"description": "Button label to disable vim mode in editor"
},
"I872jM": {
"defaultMessage": "Not completed",
"description": "Incomplete status for a question"
@ -4263,10 +4259,6 @@
"defaultMessage": "Features",
"description": "Resume review features"
},
"ILCBgB": {
"defaultMessage": "Enable Vim Mode",
"description": "Button label to enable vim mode in editor"
},
"INrnLA": {
"defaultMessage": "Search over {questionCount} JavaScript interview questions",
"description": "Search placeholder for JavaScript questions list page"
@ -7939,6 +7931,10 @@
"defaultMessage": "Preparation plans for all kinds of preparation timelines — <owlink>1 week</owlink>, <omlink>1 month</omlink>, <tmlink>3 months</tmlink>.",
"description": "Subtitle for proven preparation plans feature"
},
"YeHWje": {
"defaultMessage": "Enable Vim mode",
"description": "Button label to enable vim mode in editor"
},
"YeWPOG": {
"defaultMessage": "Invalid URL",
"description": "Invalid URL error"
@ -8859,6 +8855,10 @@
"defaultMessage": "Read how to solve {questionTitle} using {questionFramework}",
"description": "Description of Front End Interview UI Coding Questions solution page"
},
"cnL7HI": {
"defaultMessage": "Disable Vim mode",
"description": "Button label to disable vim mode in editor"
},
"cotxax": {
"defaultMessage": "Component track",
"description": "Label for Component Track filter for submissions list"

View File

@ -5519,6 +5519,10 @@
"defaultMessage": "获得完全访问权限",
"description": "Button CTA to encourage upgrading"
},
"GPM7SF": {
"defaultMessage": "新建文件",
"description": "Button tooltip for creating a new file"
},
"GPp0wf": {
"defaultMessage": "欢迎来到 GreatFrontEnd Projects",
"description": "Title for Projects onboarding page"
@ -5951,6 +5955,10 @@
"defaultMessage": "加入社区",
"description": "Title for marketing page section"
},
"IIE89R": {
"defaultMessage": "您确定要删除 {displayName} 吗? {directoryWarning}",
"description": "Confirmation message shown when deleting a file or directory"
},
"IIctLn": {
"defaultMessage": "完成此技能推荐技能计划中的所有挑战可获得的声誉",
"description": "Tooltip for reputation label"
@ -6923,6 +6931,10 @@
"defaultMessage": "简单的{percent}%佣金,无上限",
"description": "Affiliate program commission section title"
},
"M9GFCh": {
"defaultMessage": "此文件已被删除或重命名。",
"description": "Message shown when a file in the code editor no longer exists"
},
"M9z02W": {
"defaultMessage": "爱彼迎",
"description": "Company name for Airbnb"
@ -9595,6 +9607,10 @@
"defaultMessage": "查看现金返还条款和条件",
"description": "Title of legal page"
},
"YRi3wG": {
"defaultMessage": "新建文件夹",
"description": "Button tooltip for creating a new folder"
},
"YS1B1R": {
"defaultMessage": "根据您已完成的挑战、技术技能和经验,查找建议您审核的提交。",
"description": "Page subtitle"
@ -9631,6 +9647,10 @@
"defaultMessage": "各种准备时间线的准备计划——<owlink>1 周</owlink><omlink>1 个月</omlink><tmlink>3 个月</tmlink>。",
"description": "Subtitle for proven preparation plans feature"
},
"YeHWje": {
"defaultMessage": "启用 Vim 模式",
"description": "Button label to enable vim mode in editor"
},
"YeWPOG": {
"defaultMessage": "无效的 URL",
"description": "Invalid URL error"
@ -10263,6 +10283,10 @@
"defaultMessage": "级别",
"description": "Blog level label"
},
"bOVGLU": {
"defaultMessage": "请登录或创建帐户以继续",
"description": "Subtitle of auth sign up dialog"
},
"bQ3XUx": {
"defaultMessage": "暂无测试",
"description": "Label indicating that no test cases are available for this question"
@ -10547,6 +10571,10 @@
"defaultMessage": "阅读如何使用{questionFramework}解决{questionTitle}",
"description": "Description of Front End Interview UI Coding Questions solution page"
},
"cnL7HI": {
"defaultMessage": "禁用 Vim 模式",
"description": "Button label to disable vim mode in editor"
},
"cotxax": {
"defaultMessage": "组件跟踪",
"description": "Label for Component Track filter for submissions list"
@ -11279,10 +11307,6 @@
"defaultMessage": "您还获得了专属促销代码:",
"description": "Marketing rewards message"
},
"fxQRHJ": {
"defaultMessage": "目前,无法创建或重命名文件。在面试期间,将多个组件写入单个文件中是可以接受的。",
"description": "File creation or removal warning message for coding workspace"
},
"fxZ0zk": {
"defaultMessage": "一些常见原因包括:",
"description": "Error message description"
@ -13175,6 +13199,10 @@
"defaultMessage": "前端系统设计",
"description": "Short title of front end system design playbook"
},
"oxAf2V": {
"defaultMessage": "这将删除其所有内容。",
"description": "Warning message shown when deleting a directory"
},
"oxD4TD": {
"defaultMessage": "应届毕业生",
"description": "Label for \"Fresh Grad\" option in YOE replacement"

View File

@ -469,6 +469,9 @@ importers:
monaco-themes:
specifier: ^0.4.3
version: 0.4.4
monaco-vim:
specifier: ^0.4.2
version: 0.4.2(monaco-editor@0.40.0)
negotiator:
specifier: ^1.0.0
version: 1.0.0
@ -539,8 +542,8 @@ importers:
specifier: 5.4.0
version: 5.4.0(react@18.2.0)
react-intl:
specifier: ^7.1.11
version: 7.1.11(react@18.2.0)(typescript@5.7.2)
specifier: ^6.2.8
version: 6.8.9(react@18.2.0)(typescript@5.7.2)
react-merge-refs:
specifier: ^2.1.1
version: 2.1.1
@ -826,7 +829,7 @@ importers:
version: 0.9.1
ai:
specifier: ^4.2.5
version: 4.2.5(react@19.1.0)(zod@3.24.2)
version: 4.2.5(react@18.2.0)(zod@3.24.2)
chalk:
specifier: ^5.2.0
version: 5.3.0
@ -1048,7 +1051,7 @@ packages:
zod: 3.22.4
dev: false
/@ai-sdk/react@1.2.2(react@19.1.0)(zod@3.24.2):
/@ai-sdk/react@1.2.2(react@18.2.0)(zod@3.24.2):
resolution: {integrity: sha512-rxyNTFjUd3IilVOJFuUJV5ytZBYAIyRi50kFS2gNmSEiG4NHMBBm31ddrxI/i86VpY8gzZVp1/igtljnWBihUA==}
engines: {node: '>=18'}
peerDependencies:
@ -1060,8 +1063,8 @@ packages:
dependencies:
'@ai-sdk/provider-utils': 2.2.1(zod@3.24.2)
'@ai-sdk/ui-utils': 1.2.1(zod@3.24.2)
react: 19.1.0
swr: 2.3.3(react@19.1.0)
react: 18.2.0
swr: 2.3.3(react@18.2.0)
throttleit: 2.1.0
zod: 3.24.2
dev: false
@ -1831,15 +1834,6 @@ packages:
- supports-color
dev: false
/@babel/helper-module-imports@7.24.7:
resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/traverse': 7.24.7
'@babel/types': 7.24.7
transitivePeerDependencies:
- supports-color
/@babel/helper-module-imports@7.25.9:
resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
engines: {node: '>=6.9.0'}
@ -1897,11 +1891,6 @@ packages:
- supports-color
dev: false
/@babel/helper-string-parser@7.24.7:
resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-string-parser@7.25.9:
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
engines: {node: '>=6.9.0'}
@ -2319,15 +2308,6 @@ packages:
transitivePeerDependencies:
- supports-color
/@babel/types@7.24.7:
resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.24.7
'@babel/helper-validator-identifier': 7.25.9
to-fast-properties: 2.0.0
dev: false
/@babel/types@7.26.7:
resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==}
engines: {node: '>=6.9.0'}
@ -4350,6 +4330,14 @@ packages:
tslib: 2.8.1
dev: false
/@formatjs/ecma402-abstract@2.2.4:
resolution: {integrity: sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==}
dependencies:
'@formatjs/fast-memoize': 2.2.3
'@formatjs/intl-localematcher': 0.5.8
tslib: 2.8.1
dev: false
/@formatjs/ecma402-abstract@2.3.4:
resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==}
dependencies:
@ -4359,6 +4347,12 @@ packages:
tslib: 2.8.1
dev: false
/@formatjs/fast-memoize@2.2.3:
resolution: {integrity: sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==}
dependencies:
tslib: 2.8.1
dev: false
/@formatjs/fast-memoize@2.2.7:
resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
dependencies:
@ -4381,6 +4375,14 @@ packages:
tslib: 2.8.1
dev: false
/@formatjs/icu-messageformat-parser@2.9.4:
resolution: {integrity: sha512-Tbvp5a9IWuxUcpWNIW6GlMQYEc4rwNHR259uUFoKWNN1jM9obf9Ul0e+7r7MvFOBNcN+13K7NuKCKqQiAn1QEg==}
dependencies:
'@formatjs/ecma402-abstract': 2.2.4
'@formatjs/icu-skeleton-parser': 1.8.8
tslib: 2.8.1
dev: false
/@formatjs/icu-skeleton-parser@1.7.0:
resolution: {integrity: sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A==}
dependencies:
@ -4395,18 +4397,65 @@ packages:
tslib: 2.8.1
dev: false
/@formatjs/icu-skeleton-parser@1.8.8:
resolution: {integrity: sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==}
dependencies:
'@formatjs/ecma402-abstract': 2.2.4
tslib: 2.8.1
dev: false
/@formatjs/intl-displaynames@6.8.5:
resolution: {integrity: sha512-85b+GdAKCsleS6cqVxf/Aw/uBd+20EM0wDpgaxzHo3RIR3bxF4xCJqH/Grbzx8CXurTgDDZHPdPdwJC+May41w==}
dependencies:
'@formatjs/ecma402-abstract': 2.2.4
'@formatjs/intl-localematcher': 0.5.8
tslib: 2.8.1
dev: false
/@formatjs/intl-listformat@7.7.5:
resolution: {integrity: sha512-Wzes10SMNeYgnxYiKsda4rnHP3Q3II4XT2tZyOgnH5fWuHDtIkceuWlRQNsvrI3uiwP4hLqp2XdQTCsfkhXulg==}
dependencies:
'@formatjs/ecma402-abstract': 2.2.4
'@formatjs/intl-localematcher': 0.5.8
tslib: 2.8.1
dev: false
/@formatjs/intl-localematcher@0.5.2:
resolution: {integrity: sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw==}
dependencies:
tslib: 2.8.1
dev: false
/@formatjs/intl-localematcher@0.5.8:
resolution: {integrity: sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==}
dependencies:
tslib: 2.8.1
dev: false
/@formatjs/intl-localematcher@0.6.1:
resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==}
dependencies:
tslib: 2.8.1
dev: false
/@formatjs/intl@2.10.15(typescript@5.7.2):
resolution: {integrity: sha512-i6+xVqT+6KCz7nBfk4ybMXmbKO36tKvbMKtgFz9KV+8idYFyFbfwKooYk8kGjyA5+T5f1kEPQM5IDLXucTAQ9g==}
peerDependencies:
typescript: ^4.7 || 5
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@formatjs/ecma402-abstract': 2.2.4
'@formatjs/fast-memoize': 2.2.3
'@formatjs/icu-messageformat-parser': 2.9.4
'@formatjs/intl-displaynames': 6.8.5
'@formatjs/intl-listformat': 7.7.5
intl-messageformat: 10.7.7
tslib: 2.8.1
typescript: 5.7.2
dev: false
/@formatjs/intl@3.1.6(typescript@5.7.2):
resolution: {integrity: sha512-tDkXnA4qpIFcDWac8CyVJq6oW8DR7W44QDUBsfXWIIJD/FYYen0QoH46W7XsVMFfPOVKkvbufjboZrrWbEfmww==}
peerDependencies:
@ -6058,20 +6107,6 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.0.28)(react@18.2.0):
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.24.7
'@types/react': 18.0.28
react: 18.2.0
dev: false
/@radix-ui/react-compose-refs@1.1.2(@types/react@18.0.28)(react@18.2.0):
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
peerDependencies:
@ -9443,7 +9478,7 @@ packages:
- vue
dev: false
/ai@4.2.5(react@19.1.0)(zod@3.24.2):
/ai@4.2.5(react@18.2.0)(zod@3.24.2):
resolution: {integrity: sha512-URJEslI3cgF/atdTJHtz+Sj0W1JTmiGmD3znw9KensL3qV605odktDim+GTazNJFPR4QaIu1lUio5b8RymvOjA==}
engines: {node: '>=18'}
peerDependencies:
@ -9455,11 +9490,11 @@ packages:
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.1(zod@3.24.2)
'@ai-sdk/react': 1.2.2(react@19.1.0)(zod@3.24.2)
'@ai-sdk/react': 1.2.2(react@18.2.0)(zod@3.24.2)
'@ai-sdk/ui-utils': 1.2.1(zod@3.24.2)
'@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0
react: 19.1.0
react: 18.2.0
zod: 3.24.2
dev: false
@ -11695,7 +11730,7 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.34.0)(typescript@5.7.2)
'@typescript-eslint/parser': 5.62.0(eslint@8.34.0)(typescript@5.3.3)
debug: 3.2.7
eslint: 8.34.0
eslint-import-resolver-node: 0.3.9
@ -11736,7 +11771,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.62.0(eslint@8.34.0)(typescript@5.7.2)
'@typescript-eslint/parser': 5.62.0(eslint@8.34.0)(typescript@5.3.3)
array-includes: 3.1.7
array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2
@ -13143,6 +13178,15 @@ packages:
tslib: 2.8.1
dev: false
/intl-messageformat@10.7.7:
resolution: {integrity: sha512-F134jIoeYMro/3I0h08D0Yt4N9o9pjddU/4IIxMMURqbAtI2wu70X8hvG1V48W49zXHXv3RKSF/po+0fDfsGjA==}
dependencies:
'@formatjs/ecma402-abstract': 2.2.4
'@formatjs/fast-memoize': 2.2.3
'@formatjs/icu-messageformat-parser': 2.9.4
tslib: 2.8.1
dev: false
/invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
dependencies:
@ -15637,6 +15681,14 @@ packages:
fast-plist: 0.1.3
dev: false
/monaco-vim@0.4.2(monaco-editor@0.40.0):
resolution: {integrity: sha512-rdbQC3O2rmpwX2Orzig/6gZjZfH7q7TIeB+uEl49sa+QyNm3jCKJOw5mwxBdFzTqbrPD+URfg6A2lEkuL5kymw==}
peerDependencies:
monaco-editor: '*'
dependencies:
monaco-editor: 0.40.0
dev: false
/mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@ -17468,14 +17520,6 @@ packages:
react: 18.2.0
dev: false
/react-icons@5.3.0(react@19.1.0):
resolution: {integrity: sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==}
peerDependencies:
react: '*'
dependencies:
react: 19.1.0
dev: false
/react-inline-center@1.0.1(react@18.2.0):
resolution: {integrity: sha512-nMxG933OWuZET/CkvQTPBVEPNRI2zhfeeeiRqXKKzSdvIPHnHnDXZmh9KkRO2DDCjJFvZQ3KIe50lXaaxvnoNw==}
peerDependencies:
@ -17495,22 +17539,24 @@ packages:
react: 18.2.0
dev: false
/react-intl@7.1.11(react@18.2.0)(typescript@5.7.2):
resolution: {integrity: sha512-tnVoRCWvW5Ie2ikYSdPF7z3+880yCe/9xPmitFeRPw3RYDcCfR4m8ZYa4MBq19W4adt9Z+PQA4FaMBCJ7E+HCQ==}
/react-intl@6.8.9(react@18.2.0)(typescript@5.7.2):
resolution: {integrity: sha512-TUfj5E7lyUDvz/GtovC9OMh441kBr08rtIbgh3p0R8iF3hVY+V2W9Am7rb8BpJ/29BH1utJOqOOhmvEVh3GfZg==}
peerDependencies:
react: 16 || 17 || 18 || 19
typescript: ^5.6.0
react: ^16.6.0 || 17 || 18
typescript: ^4.7 || 5
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@formatjs/ecma402-abstract': 2.3.4
'@formatjs/icu-messageformat-parser': 2.11.2
'@formatjs/intl': 3.1.6(typescript@5.7.2)
'@formatjs/ecma402-abstract': 2.2.4
'@formatjs/icu-messageformat-parser': 2.9.4
'@formatjs/intl': 2.10.15(typescript@5.7.2)
'@formatjs/intl-displaynames': 6.8.5
'@formatjs/intl-listformat': 7.7.5
'@types/hoist-non-react-statics': 3.3.5
'@types/react': 18.0.28
hoist-non-react-statics: 3.3.2
intl-messageformat: 10.7.16
intl-messageformat: 10.7.7
react: 18.2.0
tslib: 2.8.1
typescript: 5.7.2
@ -17756,11 +17802,6 @@ packages:
dependencies:
loose-envify: 1.4.0
/react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'}
dev: false
/read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
dependencies:
@ -18990,14 +19031,14 @@ packages:
use-sync-external-store: 1.4.0(react@18.2.0)
dev: false
/swr@2.3.3(react@19.1.0):
/swr@2.3.3(react@18.2.0):
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
dependencies:
dequal: 2.0.3
react: 19.1.0
use-sync-external-store: 1.4.0(react@19.1.0)
react: 18.2.0
use-sync-external-store: 1.4.0(react@18.2.0)
dev: false
/swrev@4.0.0:
@ -19314,11 +19355,6 @@ packages:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
dev: false
/to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
dev: false
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@ -20168,14 +20204,6 @@ packages:
react: 18.2.0
dev: false
/use-sync-external-store@1.4.0(react@19.1.0):
resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
dependencies:
react: 19.1.0
dev: false
/use-sync-external-store@1.5.0(react@18.2.0):
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
peerDependencies: