diff --git a/apps/web/package.json b/apps/web/package.json index 2e24759f2..002ef4586 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -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", diff --git a/apps/web/src/components/workspace/common/editor/MonacoCodeEditor.tsx b/apps/web/src/components/workspace/common/editor/MonacoCodeEditor.tsx index 7f8542ecc..bb882459e 100644 --- a/apps/web/src/components/workspace/common/editor/MonacoCodeEditor.tsx +++ b/apps/web/src/components/workspace/common/editor/MonacoCodeEditor.tsx @@ -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['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(null); const editorContainerRef = useRef(null); + const vimStatusBarRef = useRef(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 ( -
- - } - 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); - }} + <> +
-
+
+ + } + 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); + }} + /> +
+ ); } diff --git a/apps/web/src/components/workspace/common/editor/hooks/useMonacoEditorVimMode.ts b/apps/web/src/components/workspace/common/editor/hooks/useMonacoEditorVimMode.ts new file mode 100644 index 000000000..3a2b825ea --- /dev/null +++ b/apps/web/src/components/workspace/common/editor/hooks/useMonacoEditorVimMode.ts @@ -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(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]); +} diff --git a/apps/web/src/components/workspace/common/editor/hooks/useVimMode.ts b/apps/web/src/components/workspace/common/editor/hooks/useVimMode.ts new file mode 100644 index 000000000..397ed5f40 --- /dev/null +++ b/apps/web/src/components/workspace/common/editor/hooks/useVimMode.ts @@ -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( + VIM_MODE_LOCAL_STORAGE_KEY, + false, + ); + + const toggleVimMode = useCallback(() => { + setIsVimModeEnabled((prev) => !prev); + }, [setIsVimModeEnabled]); + + return { isVimModeEnabled, toggleVimMode }; +} diff --git a/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceBottomBar.tsx b/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceBottomBar.tsx index 029c22f73..55fb8da5c 100644 --- a/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceBottomBar.tsx +++ b/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceBottomBar.tsx @@ -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({ diff --git a/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCodeEditor.tsx b/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCodeEditor.tsx index 68dbf8002..59625e792 100644 --- a/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCodeEditor.tsx +++ b/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCodeEditor.tsx @@ -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({ )} { updateFile(filePath, val ?? ''); diff --git a/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCommunitySolutionCreateTab.tsx b/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCommunitySolutionCreateTab.tsx index 0e24e812c..a6087c886 100644 --- a/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCommunitySolutionCreateTab.tsx +++ b/apps/web/src/components/workspace/javascript/JavaScriptCodingWorkspaceCommunitySolutionCreateTab.tsx @@ -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 } }) => (
- + {formState.isDirty && formState.errors.code?.message && ( {formState.errors.code?.message} diff --git a/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceBottomBar.tsx b/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceBottomBar.tsx index 9ca1024e7..dbe10fa7f 100644 --- a/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceBottomBar.tsx +++ b/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceBottomBar.tsx @@ -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 = (
@@ -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({ diff --git a/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceCodeEditor.tsx b/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceCodeEditor.tsx index a0e4fee4e..017d6215a 100644 --- a/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceCodeEditor.tsx +++ b/apps/web/src/components/workspace/user-interface/UserInterfaceCodingWorkspaceCodeEditor.tsx @@ -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({ )} { updateFile(filePath, val ?? ''); diff --git a/apps/web/src/locales/compiled/en-US.json b/apps/web/src/locales/compiled/en-US.json index 11a897f57..9c378eb8d 100644 --- a/apps/web/src/locales/compiled/en-US.json +++ b/apps/web/src/locales/compiled/en-US.json @@ -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, diff --git a/apps/web/src/locales/compiled/zh-CN.json b/apps/web/src/locales/compiled/zh-CN.json index b6749a6a1..1b577e315 100644 --- a/apps/web/src/locales/compiled/zh-CN.json +++ b/apps/web/src/locales/compiled/zh-CN.json @@ -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, diff --git a/apps/web/src/locales/raw/en-US.json b/apps/web/src/locales/raw/en-US.json index 860bc918b..ec3417d10 100644 --- a/apps/web/src/locales/raw/en-US.json +++ b/apps/web/src/locales/raw/en-US.json @@ -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 — 1 week, 1 month, 3 months.", "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" diff --git a/apps/web/src/locales/raw/zh-CN.json b/apps/web/src/locales/raw/zh-CN.json index 0a5e5125a..16eb19578 100644 --- a/apps/web/src/locales/raw/zh-CN.json +++ b/apps/web/src/locales/raw/zh-CN.json @@ -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": "各种准备时间线的准备计划——1 周1 个月3 个月。", "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" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 124445d33..c948e839e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: