├── .husky └── pre-commit ├── vitest.setup.ts ├── .npmrc ├── .prettierignore ├── public ├── qwen.png ├── kunkun.gif ├── dashboard.png ├── images │ ├── home.png │ ├── preview.png │ ├── step-1.png │ ├── step-2.png │ ├── step-3.png │ └── fileIcon │ │ ├── vue.svg │ │ ├── image.svg │ │ ├── FileDir.svg │ │ ├── css.svg │ │ ├── yaml.svg │ │ ├── html.svg │ │ ├── git.svg │ │ ├── json.svg │ │ ├── ts.svg │ │ ├── markdown.svg │ │ ├── eslint.svg │ │ ├── JavaScript.svg │ │ ├── scss.svg │ │ ├── jsx.svg │ │ └── prettier.svg ├── vue.svg ├── preact.svg ├── arrow.svg ├── project.svg └── node.svg ├── src ├── app │ ├── favicon.ico │ ├── edit │ │ └── [projectId] │ │ │ └── (main) │ │ │ └── (router) │ │ │ ├── plugins │ │ │ └── page.tsx │ │ │ ├── ai │ │ │ └── page.tsx │ │ │ └── file │ │ │ └── page.tsx │ ├── cooperation │ │ ├── (router) │ │ │ └── [room] │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── layout.tsx │ ├── community │ │ └── page.tsx │ ├── globals.css │ └── (main) │ │ └── layout.tsx ├── stories │ ├── assets │ │ ├── docs.png │ │ ├── assets.png │ │ ├── context.png │ │ ├── share.png │ │ ├── styling.png │ │ ├── testing.png │ │ ├── theming.png │ │ ├── figma-plugin.png │ │ ├── accessibility.png │ │ ├── addon-library.png │ │ ├── avif-test-image.avif │ │ ├── youtube.svg │ │ ├── tutorials.svg │ │ ├── accessibility.svg │ │ ├── discord.svg │ │ └── github.svg │ ├── header.css │ ├── button.css │ ├── Header.stories.ts │ ├── Page.stories.ts │ ├── Button.tsx │ ├── page.css │ ├── Button.stories.ts │ ├── Header.tsx │ └── Page.tsx ├── components │ ├── ai │ │ └── chat │ │ │ ├── utils.ts │ │ │ ├── chat-layout.tsx │ │ │ ├── data.tsx │ │ │ ├── textarea.tsx │ │ │ ├── chat-topbar.tsx │ │ │ ├── chat.tsx │ │ │ ├── chat-bottombar.tsx │ │ │ └── chat-list.tsx │ ├── nextTopLoader │ │ └── index.tsx │ ├── common │ │ └── Avatar │ │ │ └── index.tsx │ ├── webContainerProvider │ │ └── index.tsx │ ├── ui │ │ ├── scroll-area │ │ │ ├── index.stories.tsx │ │ │ └── index.tsx │ │ ├── input │ │ │ ├── index.stories.tsx │ │ │ └── index.tsx │ │ ├── accordion │ │ │ ├── index.stories.tsx │ │ │ └── index.tsx │ │ ├── label │ │ │ └── index.tsx │ │ ├── textarea │ │ │ └── index.tsx │ │ ├── badge │ │ │ ├── index.stories.tsx │ │ │ └── index.tsx │ │ ├── avatar │ │ │ ├── index.stories.tsx │ │ │ └── index.tsx │ │ ├── tooltip │ │ │ └── index.tsx │ │ ├── button │ │ │ ├── index.stories.tsx │ │ │ └── index.tsx │ │ ├── dialog │ │ │ └── index.stories.tsx │ │ ├── tabs │ │ │ └── index.tsx │ │ └── dropdown-menu │ │ │ └── index.stories.tsx │ ├── resize-handle │ │ └── index.tsx │ ├── main │ │ ├── header │ │ │ └── index.tsx │ │ └── sider │ │ │ └── index.tsx │ ├── file │ │ ├── dragIcon │ │ │ └── index.tsx │ │ ├── pendingFileItem │ │ │ └── index.tsx │ │ └── fileTree │ │ │ └── index.tsx │ ├── menu │ │ └── index.tsx │ ├── cooperation │ │ ├── cooperationEditor │ │ │ └── cooperationEditorHeader │ │ │ │ └── index.tsx │ │ ├── collaboratorAvatarList │ │ │ └── index.tsx │ │ ├── cursor │ │ │ └── index.tsx │ │ ├── header │ │ │ └── index.tsx │ │ ├── avatarList │ │ │ └── index.tsx │ │ ├── shareDocumentButton │ │ │ └── index.tsx │ │ ├── taskList │ │ │ └── index.tsx │ │ └── taskDescription │ │ │ └── index.tsx │ ├── edit │ │ ├── edit-loading │ │ │ └── index.tsx │ │ └── header │ │ │ └── index.tsx │ ├── fileSearch │ │ ├── input │ │ │ └── index.tsx │ │ ├── ToolBtn │ │ │ └── index.tsx │ │ ├── search │ │ │ └── index.tsx │ │ └── includeAndExclude │ │ │ └── index.tsx │ ├── home │ │ ├── header │ │ │ └── index.tsx │ │ ├── useSteps │ │ │ └── index.tsx │ │ ├── container-scroll-animation │ │ │ └── index.tsx │ │ └── card-hover-effect │ │ │ └── index.tsx │ ├── preview │ │ ├── booting │ │ │ └── index.tsx │ │ └── index.tsx │ └── modals │ │ └── create-coopertaion-modal │ │ └── index.tsx ├── utils │ ├── cn.ts │ ├── time.ts │ ├── hexToRgba.ts │ ├── index.ts │ ├── createColorFromId.ts │ ├── debounce.ts │ ├── apis.ts │ ├── throttle.ts │ ├── route.tsx │ ├── fileIconMap.ts │ ├── editor.ts │ └── zip.ts ├── assets │ └── image │ │ └── fileIcon │ │ ├── vue.svg │ │ ├── image.svg │ │ ├── FileDir.svg │ │ ├── css.svg │ │ ├── yaml.svg │ │ ├── html.svg │ │ ├── git.svg │ │ ├── json.svg │ │ ├── ts.svg │ │ ├── markdown.svg │ │ ├── eslint.svg │ │ ├── JavaScript.svg │ │ ├── scss.svg │ │ ├── jsx.svg │ │ └── prettier.svg ├── store │ ├── dragIconStore.tsx │ ├── taskStore.tsx │ ├── cooperationPersonStore.tsx │ ├── authStore.tsx │ └── webContainerStore.tsx ├── hooks │ ├── usePerfectCursor.ts │ └── useModal.ts ├── __tests__ │ └── Hello.test.tsx └── types │ └── index.ts ├── postcss.config.mjs ├── .env.development ├── .env.production ├── .gitattributes ├── .prettierrc ├── ecosystem.config.cjs ├── .editorconfig ├── .vscode └── setting.json ├── .storybook ├── preview.ts └── main.ts ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── TASK.md │ ├── DISCUSS.yml │ ├── FEATURE.yml │ ├── Regression.yml │ └── BUGS.yml ├── workflows │ └── lint.yml └── PULL_REQUEST_TEMPLATE.md ├── vitest.config.ts ├── .gitignore ├── vercel.json ├── tsconfig.json ├── LICENSE ├── next.config.mjs ├── CONTRIBUTING-zh.md └── eslint.config.cjs /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged -------------------------------------------------------------------------------- /vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | package-manager=pnpm@9.4.0 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | .next/ 4 | pnpm-lock.yaml -------------------------------------------------------------------------------- /public/qwen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/qwen.png -------------------------------------------------------------------------------- /public/kunkun.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/kunkun.gif -------------------------------------------------------------------------------- /public/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/dashboard.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /public/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/images/home.png -------------------------------------------------------------------------------- /public/images/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/images/preview.png -------------------------------------------------------------------------------- /public/images/step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/images/step-1.png -------------------------------------------------------------------------------- /public/images/step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/images/step-2.png -------------------------------------------------------------------------------- /public/images/step-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/public/images/step-3.png -------------------------------------------------------------------------------- /src/stories/assets/docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/docs.png -------------------------------------------------------------------------------- /src/stories/assets/assets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/assets.png -------------------------------------------------------------------------------- /src/stories/assets/context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/context.png -------------------------------------------------------------------------------- /src/stories/assets/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/share.png -------------------------------------------------------------------------------- /src/stories/assets/styling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/styling.png -------------------------------------------------------------------------------- /src/stories/assets/testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/testing.png -------------------------------------------------------------------------------- /src/stories/assets/theming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/theming.png -------------------------------------------------------------------------------- /src/stories/assets/figma-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/figma-plugin.png -------------------------------------------------------------------------------- /src/stories/assets/accessibility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/accessibility.png -------------------------------------------------------------------------------- /src/stories/assets/addon-library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/addon-library.png -------------------------------------------------------------------------------- /src/stories/assets/avif-test-image.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xun082/online-edit-web/HEAD/src/stories/assets/avif-test-image.avif -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SERVER_URL=http://1.117.152.40:8080 2 | NEXT_PUBLIC_WS_URL=ws://1.117.152.40:8080 3 | 4 | MODEL_API_BASE_URL=https://dashscope.aliyuncs.com/api/v1/apps/ 5 | 6 | QWEN_APP_ID= 7 | QWEN_AUTH= 8 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SERVER_URL=http://1.117.152.40:8080 2 | NEXT_PUBLIC_WS_URL=ws://1.117.152.40:8080 3 | 4 | MODEL_API_BASE_URL=https://dashscope.aliyuncs.com/api/v1/apps/ 5 | 6 | QWEN_APP_ID= 7 | QWEN_AUTH= 8 | -------------------------------------------------------------------------------- /src/app/edit/[projectId]/(main)/(router)/plugins/page.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | 3 | const Plugins: FC = () => { 4 | return
Plugins
; 5 | }; 6 | 7 | export default Plugins; 8 | -------------------------------------------------------------------------------- /src/components/ai/chat/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | import type { ClassValue } from 'clsx'; 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | -------------------------------------------------------------------------------- /src/app/cooperation/(router)/[room]/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function CooperationLayout({ 2 | children, 3 | }: Readonly<{ 4 | children: React.ReactNode; 5 | }>) { 6 | return
{children}
; 7 | } 8 | -------------------------------------------------------------------------------- /public/images/fileIcon/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 强制使用 LF 作为行尾符 2 | * text=auto eol=lf 3 | 4 | # 针对特定文件类型的设置 5 | *.md text eol=lf 6 | *.sh text eol=lf 7 | *.html text eol=lf 8 | *.css text eol=lf 9 | *.js text eol=lf 10 | *.ts text eol=lf 11 | 12 | # Makefile 使用 tab 缩进,不需要特定的 eol 设置 13 | Makefile -text -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "arrowParens": "always", 5 | "bracketSpacing": true, 6 | "proseWrap": "preserve", 7 | "trailingComma": "all", 8 | "jsxSingleQuote": false, 9 | "printWidth": 100, 10 | "endOfLine": "auto" 11 | } 12 | -------------------------------------------------------------------------------- /public/images/fileIcon/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecosystem.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'online_editor_web', 5 | script: './node_modules/next/dist/bin/next', 6 | args: 'start -p 3000', 7 | instances: 1, 8 | autorestart: true, 9 | watch: false, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /public/images/fileIcon/FileDir.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/nextTopLoader/index.tsx: -------------------------------------------------------------------------------- 1 | import NextTopLoader from 'nextjs-toploader'; 2 | 3 | interface nextTopLoaderProps {} 4 | 5 | const NextProcessLoader: React.FC = () => { 6 | return ; 7 | }; 8 | 9 | export default NextProcessLoader; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab -------------------------------------------------------------------------------- /src/assets/image/fileIcon/FileDir.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/edit/[projectId]/(main)/(router)/ai/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { ChatLayout } from '@/components/ai/chat/chat-layout'; 4 | 5 | const AI = () => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default AI; 14 | -------------------------------------------------------------------------------- /.vscode/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll": true, 6 | "source.fixAll.eslint": true 7 | }, 8 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"] 9 | } 10 | -------------------------------------------------------------------------------- /public/images/fileIcon/css.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/css.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/fileIcon/yaml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from '@storybook/react'; 2 | import '../src/app/globals.css'; 3 | 4 | const preview: Preview = { 5 | parameters: { 6 | controls: { 7 | matchers: { 8 | color: /(background|color)$/i, 9 | date: /Date$/i, 10 | }, 11 | }, 12 | }, 13 | }; 14 | 15 | export default preview; 16 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/yaml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ai/chat/chat-layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | 5 | import { userData } from './data'; 6 | import { Chat } from './chat'; 7 | 8 | export function ChatLayout() { 9 | const [selectedUser] = React.useState(userData[0]); 10 | 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/time.ts: -------------------------------------------------------------------------------- 1 | export function isoDateStringFormat(isoDateString: string) { 2 | var date = new Date(isoDateString); 3 | 4 | var month: string | number = date.getMonth() + 1; 5 | var day: string | number = date.getDate(); 6 | 7 | month = month < 10 ? '0' + month : month; 8 | day = day < 10 ? '0' + day : day; 9 | 10 | return `${month}月${day}日`; 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | ## To encourage contributors to use issue templates, we don't allow blank issues 2 | blank_issues_enabled: false 3 | 4 | contact_links: 5 | - name: "\u2753 Discord Community of Create-Neat" 6 | url: 'https://raw.githubusercontent.com/xun082/md/main/blogs.images20240307173700.png' 7 | about: 'Please ask support questions or discuss suggestions/enhancements here.' 8 | -------------------------------------------------------------------------------- /public/images/fileIcon/html.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | globals: true, 7 | environment: 'jsdom', 8 | setupFiles: './vitest.setup.ts', 9 | }, 10 | resolve: { 11 | alias: { 12 | '@': '/src', // 添加别名以便更好地管理导入 13 | }, 14 | }, 15 | plugins: [react()], 16 | }); 17 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/html.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/hexToRgba.ts: -------------------------------------------------------------------------------- 1 | export function hexToRgba(hex: string, alpha: number) { 2 | // 去掉开头的#号 3 | let cleanedHex = hex.replace('#', ''); 4 | 5 | // 解析16进制颜色值 6 | let r = parseInt(cleanedHex.substring(0, 2), 16); 7 | let g = parseInt(cleanedHex.substring(2, 4), 16); 8 | let b = parseInt(cleanedHex.substring(4, 6), 16); 9 | 10 | // 返回rgba格式 11 | return `rgba(${r}, ${g}, ${b}, ${alpha})`; 12 | } 13 | -------------------------------------------------------------------------------- /src/store/dragIconStore.tsx: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | interface DragIconState { 4 | dragIconRef: any; 5 | } 6 | 7 | interface DragIconActions { 8 | setDragIconRef: (ref: any) => void; 9 | } 10 | 11 | export const useDragIconStore = create((set) => ({ 12 | dragIconRef: null, 13 | setDragIconRef: (dragIconRef: any) => set(() => ({ dragIconRef })), 14 | })); 15 | -------------------------------------------------------------------------------- /public/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/fileIcon/git.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/git.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cn'; 2 | export * from './constants'; 3 | export * from './editor'; 4 | export * from './getLocalDirectory'; 5 | export * from './route'; 6 | export * from './webcontainer'; 7 | export * from './file'; 8 | export * from './fileIconMap'; 9 | export * from './time'; 10 | export * from './zip'; 11 | export * from './template'; 12 | export * from './match'; 13 | export * from './matchHelper'; 14 | export * from './hexToRgba'; 15 | export * from './throttle'; 16 | export * from './createColorFromId'; 17 | -------------------------------------------------------------------------------- /src/hooks/usePerfectCursor.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PerfectCursor } from 'perfect-cursors'; 3 | 4 | export default function usePerfectCursor(cb: (point: number[]) => void, point?: number[]) { 5 | const [pc] = React.useState(() => new PerfectCursor(cb)); 6 | 7 | React.useLayoutEffect(() => { 8 | if (point) pc.addPoint(point); 9 | 10 | return () => pc.dispose(); 11 | }, [pc]); 12 | 13 | const onPointChange = React.useCallback((point: number[]) => pc.addPoint(point), [pc]); 14 | 15 | return onPointChange; 16 | } 17 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/nextjs'; 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-onboarding', 7 | '@storybook/addon-links', 8 | '@storybook/addon-essentials', 9 | '@chromatic-com/storybook', 10 | '@storybook/addon-interactions', 11 | ], 12 | framework: { 13 | name: '@storybook/nextjs', 14 | options: {}, 15 | }, 16 | staticDirs: ['../public'], 17 | }; 18 | export default config; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | *storybook.log 39 | -------------------------------------------------------------------------------- /public/images/fileIcon/json.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/fileIcon/ts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/__tests__/Hello.test.tsx: -------------------------------------------------------------------------------- 1 | // __tests__/Hello.test.tsx 2 | import { render, screen } from '@testing-library/react'; 3 | 4 | import Hello from '../components/Hello'; 5 | 6 | describe('Hello Component', () => { 7 | it('renders the correct greeting', () => { 8 | render(); 9 | expect(screen.getByText('Hello, World!')).toBeInTheDocument(); 10 | }); 11 | 12 | it('renders the correct greeting with a different name', () => { 13 | render(); 14 | expect(screen.getByText('Hello, Vitest!')).toBeInTheDocument(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/json.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/ts.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/fileIcon/markdown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/markdown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Inter } from 'next/font/google'; 4 | 5 | import NextProcessLoader from '@/components/nextTopLoader'; 6 | 7 | import './globals.css'; 8 | import './xterm.css'; 9 | 10 | const inter = Inter({ subsets: ['latin'] }); 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | 20 | 21 | {children} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/common/Avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar as ShadcnAvatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; 2 | 3 | interface AvatarProps { 4 | src?: string; 5 | alt?: string; 6 | fallBackNode?: React.ReactNode; 7 | className?: string; 8 | } 9 | 10 | export function Avatar({ 11 | src = 'https://github.com/shadcn.png', 12 | alt, 13 | fallBackNode, 14 | className, 15 | }: AvatarProps) { 16 | return ( 17 | 18 | 19 | {fallBackNode} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/TASK.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🛎 TODO LIST 3 | about: Use this template to post new development tasks 4 | title: '[Task] Task Title' 5 | labels: 'To Be Claimed' 6 | assignees: '' 7 | --- 8 | 9 | ### Task Description 10 | 11 | Briefly describe the goal that this task aims to achieve. 12 | 13 | ### Specific Task Requirements 14 | 15 | - [ ] Requirement 1 16 | - [ ] Requirement 2 17 | - [ ] Requirement 3 18 | 19 | ### Claiming Process 20 | 21 | If you would like to claim this task, please reply with “I want to claim this task” in the comments. 22 | 23 | ### Task Remarks 24 | 25 | Any additional information or remarks about the task. 26 | -------------------------------------------------------------------------------- /src/components/webContainerProvider/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect } from 'react'; 2 | 3 | import { useWebContainerStore } from '@/store/webContainerStore'; 4 | 5 | interface WebContainerProviderProps { 6 | projectId: string; 7 | } 8 | 9 | const WebContainerProvider: React.FC = ({ projectId }) => { 10 | const { initWebContainer, setInitialized } = useWebContainerStore(); 11 | useEffect(() => { 12 | initWebContainer(projectId); 13 | 14 | return () => { 15 | setInitialized(false); 16 | }; 17 | }, [projectId]); 18 | 19 | return ; 20 | }; 21 | 22 | export default WebContainerProvider; 23 | -------------------------------------------------------------------------------- /src/store/taskStore.tsx: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | import { TASKS } from '@/utils'; 4 | 5 | export type Task = { 6 | id: number; 7 | title: string; 8 | desc: string; 9 | }; 10 | 11 | // 定义 store 的状态接口 12 | interface TaskState { 13 | totalTask: Task[]; 14 | curTask: Task | null; 15 | } 16 | 17 | // 定义操作 store 的动作接口 18 | interface TaskActions { 19 | setCurTask: (task: Task) => void; 20 | } 21 | 22 | // 创建 store 23 | export const useTaskStore = create((set) => ({ 24 | totalTask: [...TASKS], // 初始化为一个空数组 25 | curTask: TASKS[0], // 初始化为 null 26 | setCurTask: (task: Task) => set((state) => ({ ...state, curTask: task })), 27 | })); 28 | -------------------------------------------------------------------------------- /src/hooks/useModal.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | export type ModalType = 'createProject' | 'createFile' | 'createDirector' | 'createCooperation'; 4 | 5 | interface ModalData { 6 | [key: string]: any; 7 | } 8 | 9 | interface ModalStore { 10 | type: ModalType | null; 11 | data: ModalData; 12 | isOpen: boolean; 13 | onOpen: (type: ModalType, data?: ModalData) => void; 14 | onClose: () => void; 15 | } 16 | 17 | export const useModal = create((set) => ({ 18 | type: null, 19 | data: {}, 20 | isOpen: false, 21 | onOpen: (type, data = {}) => set({ isOpen: true, type, data }), 22 | onClose: () => set({ type: null, isOpen: false, data: {} }), 23 | })); 24 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "env": { 4 | "NODE_VERSION": "18.x", 5 | "PNPM_VERSION": "9.4.0" 6 | } 7 | }, 8 | "headers": [ 9 | { 10 | "source": "/(.*)", 11 | "headers": [ 12 | { 13 | "key": "Cross-Origin-Embedder-Policy", 14 | "value": "require-corp" 15 | }, 16 | { 17 | "key": "Cross-Origin-Opener-Policy", 18 | "value": "same-origin" 19 | } 20 | ] 21 | }, 22 | { 23 | "source": "/cooperation/:slug*", 24 | "headers": [ 25 | { 26 | "key": "Cross-Origin-Embedder-Policy", 27 | "value": "unsafe-none" 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/stories/assets/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Commit Message Check on PR 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [20] 13 | steps: 14 | - name: Checkout 🛎️ 15 | uses: actions/checkout@v3 16 | with: 17 | persist-credentials: false 18 | 19 | - name: Install PNPM 20 | uses: pnpm/action-setup@v2 21 | with: 22 | version: 9.4.0 23 | 24 | - name: Install Deps 25 | run: pnpm i --no-frozen-lockfile 26 | 27 | - name: Format 28 | run: | 29 | pnpm run format:ci 30 | 31 | - name: Lint 32 | run: pnpm run lint:ci 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/DISCUSS.yml: -------------------------------------------------------------------------------- 1 | name: '💬 Discussion' 2 | description: Start a discussion 3 | title: '[Discuss]: Discussion Title' 4 | labels: [discussion] 5 | assignees: [] 6 | body: 7 | - type: textarea 8 | id: topic 9 | attributes: 10 | label: Discussion Topic 11 | description: 'Please provide a clear and concise topic for discussion.' 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: screenshots 16 | attributes: 17 | label: Screenshots 18 | description: 'If applicable, add screenshots to help explain your topic.' 19 | - type: textarea 20 | id: links 21 | attributes: 22 | label: Links 23 | description: 'Include any relevant links that could provide more context for the discussion.' 24 | -------------------------------------------------------------------------------- /src/stories/header.css: -------------------------------------------------------------------------------- 1 | .storybook-header { 2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 4 | padding: 15px 20px; 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | } 9 | 10 | .storybook-header svg { 11 | display: inline-block; 12 | vertical-align: top; 13 | } 14 | 15 | .storybook-header h1 { 16 | font-weight: 700; 17 | font-size: 20px; 18 | line-height: 1; 19 | margin: 6px 0 6px 10px; 20 | display: inline-block; 21 | vertical-align: top; 22 | } 23 | 24 | .storybook-header button + button { 25 | margin-left: 10px; 26 | } 27 | 28 | .storybook-header .welcome { 29 | color: #333; 30 | font-size: 14px; 31 | margin-right: 10px; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react'; 3 | 4 | import { ScrollArea, ScrollBar } from './index'; 5 | 6 | export default { 7 | title: 'Components/ScrollArea', 8 | component: ScrollArea, 9 | subcomponents: { ScrollBar }, 10 | } as Meta; 11 | 12 | const Template: StoryFn = (args) => ( 13 |
14 | 15 |
16 | This is a scrollable content area. Add more content here to see the scrollbars in action. 17 |
18 |
19 |
20 | ); 21 | 22 | export const Default = Template.bind({}); 23 | Default.args = {}; 24 | -------------------------------------------------------------------------------- /src/stories/button.css: -------------------------------------------------------------------------------- 1 | .storybook-button { 2 | font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | font-weight: 700; 4 | border: 0; 5 | border-radius: 3em; 6 | cursor: pointer; 7 | display: inline-block; 8 | line-height: 1; 9 | } 10 | .storybook-button--primary { 11 | color: white; 12 | background-color: #1ea7fd; 13 | } 14 | .storybook-button--secondary { 15 | color: #333; 16 | background-color: transparent; 17 | box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; 18 | } 19 | .storybook-button--small { 20 | font-size: 12px; 21 | padding: 10px 16px; 22 | } 23 | .storybook-button--medium { 24 | font-size: 14px; 25 | padding: 11px 20px; 26 | } 27 | .storybook-button--large { 28 | font-size: 16px; 29 | padding: 12px 24px; 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/createColorFromId.ts: -------------------------------------------------------------------------------- 1 | export function createColorFromId(value: any) { 2 | // 确保传入的值总是字符串 3 | const userId = String(value); 4 | // 将userId字符串转换为一个整数值 5 | let hash = 0; 6 | 7 | for (let i = 0; i < userId.length; i++) { 8 | hash = (hash << 5) - hash + userId.charCodeAt(i); 9 | hash |= 0; // Convert to 32bit integer 10 | } 11 | 12 | // 将整数转换为0-255范围内的三个值,分别对应RGB的R、G、B 13 | let r: number | string = (hash & 0xff0000) >> 16; 14 | let g: number | string = (hash & 0x00ff00) >> 8; 15 | let b: number | string = hash & 0x0000ff; 16 | 17 | // 将每个通道的颜色值转换为十六进制形式 18 | r = ('0' + r.toString(16)).slice(-2); 19 | g = ('0' + g.toString(16)).slice(-2); 20 | b = ('0' + b.toString(16)).slice(-2); 21 | 22 | // 返回完整的六位十六进制颜色代码 23 | return '#' + r + g + b; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/ui/input/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react'; 3 | 4 | import { Input, InputProps } from './index'; 5 | 6 | export default { 7 | title: 'Components/Input', 8 | component: Input, 9 | } as Meta; 10 | 11 | const Template: StoryFn = (args) => ; 12 | 13 | export const Default = Template.bind({}); 14 | Default.args = { 15 | type: 'text', 16 | placeholder: 'Enter text...', 17 | }; 18 | 19 | export const Disabled = Template.bind({}); 20 | Disabled.args = { 21 | type: 'text', 22 | placeholder: 'Disabled input...', 23 | disabled: true, 24 | }; 25 | 26 | export const WithDefaultValue = Template.bind({}); 27 | WithDefaultValue.args = { 28 | type: 'text', 29 | defaultValue: 'Default value', 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/ui/accordion/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Meta, StoryFn } from '@storybook/react'; 3 | 4 | import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './index'; 5 | export default { 6 | title: 'Components/Accordion', 7 | component: Accordion, 8 | subcomponents: { AccordionItem, AccordionTrigger, AccordionContent }, 9 | } as Meta; 10 | 11 | const Template: StoryFn = () => ( 12 | 13 | 14 | Accordion Trigger 15 | 16 |
Accordion Content
17 |
18 |
19 |
20 | ); 21 | 22 | export const Default = Template.bind({}); 23 | -------------------------------------------------------------------------------- /src/components/ui/label/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as LabelPrimitive from '@radix-ui/react-label'; 5 | import { cva, type VariantProps } from 'class-variance-authority'; 6 | 7 | import { cn } from '@/utils'; 8 | 9 | const labelVariants = cva( 10 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & VariantProps 16 | >(({ className, ...props }, ref) => ( 17 | 18 | )); 19 | Label.displayName = LabelPrimitive.Root.displayName; 20 | 21 | export { Label }; 22 | -------------------------------------------------------------------------------- /public/images/fileIcon/eslint.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/resize-handle/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { PanelResizeHandle } from 'react-resizable-panels'; 3 | 4 | import { cn } from '@/utils'; 5 | 6 | interface ResizeHandleType { 7 | direction?: 'vertical' | 'horizontal'; 8 | className?: string; 9 | } 10 | 11 | const ResizeHandle: FC = (props) => { 12 | const { direction = 'horizontal', className } = props; 13 | const heightClass = direction === 'horizontal' ? 'h-full' : 'h-[5px]'; 14 | const widthClass = direction === 'vertical' ? 'w-full' : 'w-[5px]'; 15 | 16 | return ( 17 | 23 | ); 24 | }; 25 | 26 | export default ResizeHandle; 27 | -------------------------------------------------------------------------------- /src/assets/image/fileIcon/eslint.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ai/chat/data.tsx: -------------------------------------------------------------------------------- 1 | export const userData: UserData[] = [ 2 | { 3 | id: 1, 4 | avatar: 'https://github.com/shadcn.png', 5 | messages: [], 6 | name: '通义千问', 7 | }, 8 | ]; 9 | 10 | export type UserData = { 11 | id: number; 12 | avatar: string; 13 | messages: Message[]; 14 | name: string; 15 | }; 16 | 17 | export const loggedInUserData = { 18 | id: 5, 19 | avatar: 'https://github.com/shadcn.png', 20 | name: '通义千问', 21 | sessionId: Date.now(), 22 | }; 23 | 24 | export type LoggedInUserData = typeof loggedInUserData; 25 | 26 | export interface Message { 27 | id: number; 28 | avatar: string; 29 | name: string; 30 | message: string; 31 | sessionId: number; 32 | } 33 | 34 | export interface User { 35 | id: number; 36 | avatar: string; 37 | messages: Message[]; 38 | name: string; 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ai/chat/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from './utils'; 4 | 5 | export interface TextareaProps extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 | 80 | 81 | 82 | 83 | 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/components/home/container-scroll-animation/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useRef, useEffect, useState } from 'react'; 4 | import { motion, useScroll, useTransform, MotionValue } from 'framer-motion'; 5 | 6 | interface HeaderProps { 7 | translate: MotionValue; 8 | titleComponent: React.ReactNode; 9 | } 10 | 11 | export function Header({ translate, titleComponent }: HeaderProps) { 12 | return ( 13 | 14 | {titleComponent} 15 | 16 | ); 17 | } 18 | 19 | interface CardProps { 20 | rotate: MotionValue; 21 | scale: MotionValue; 22 | translate: MotionValue; 23 | content: React.ReactNode; 24 | } 25 | 26 | export function Card({ rotate, scale, translate, content }: CardProps) { 27 | return ( 28 | 35 | 43 | {content} 44 | 45 | 46 | ); 47 | } 48 | 49 | interface ContainerScrollProps { 50 | titleComponent: string | React.ReactNode; 51 | content: React.ReactNode; 52 | } 53 | 54 | export function ContainerScroll({ titleComponent, content }: ContainerScrollProps) { 55 | const containerRef = useRef(null); 56 | 57 | const { scrollYProgress } = useScroll({ target: containerRef }); 58 | 59 | const [isMobile, setIsMobile] = useState(false); 60 | 61 | useEffect(() => { 62 | const checkMobile = () => { 63 | setIsMobile(window.innerWidth <= 768); 64 | }; 65 | 66 | checkMobile(); 67 | window.addEventListener('resize', checkMobile); 68 | 69 | return () => { 70 | window.removeEventListener('resize', checkMobile); 71 | }; 72 | }, []); 73 | 74 | const scaleDimensions = () => { 75 | return isMobile ? [0.7, 1] : [1.05, 1]; 76 | }; 77 | 78 | const rotate = useTransform(scrollYProgress, [0, 1], [20, 0]); 79 | const scale = useTransform(scrollYProgress, [0, 1], scaleDimensions()); 80 | const translate = useTransform(scrollYProgress, [0, 1], [0, -100]); 81 | 82 | return ( 83 |
87 |
88 |
89 | 90 |
91 |
92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /src/components/ai/chat/chat-list.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { AnimatePresence, motion } from 'framer-motion'; 3 | 4 | import { Message, UserData } from './data'; 5 | import { cn } from './utils'; 6 | import ChatBottombar from './chat-bottombar'; 7 | 8 | import { Avatar, AvatarImage } from '@/components/ui/avatar/index'; 9 | 10 | interface ChatListProps { 11 | messages?: Message[]; 12 | selectedUser: UserData; 13 | sendMessage: (newMessage: Message) => void; 14 | } 15 | 16 | export function ChatList({ messages, selectedUser, sendMessage }: ChatListProps) { 17 | const messagesContainerRef = useRef(null); 18 | 19 | React.useEffect(() => { 20 | if (messagesContainerRef.current) { 21 | messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight; 22 | } 23 | }, [messages]); 24 | 25 | return ( 26 |
27 |
31 | 32 | {messages?.map((message, index) => ( 33 | 56 |
57 | {message.name !== selectedUser.name && ( 58 | 59 | 60 | 61 | )} 62 | {message.message} 63 | {message.name === selectedUser.name && ( 64 | 65 | 66 | 67 | )} 68 |
69 |
70 | ))} 71 |
72 |
73 | 74 |
75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/edit/header/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { VscLiveShare } from 'react-icons/vsc'; 4 | import { FaGithub, FaRegSave } from 'react-icons/fa'; 5 | import { GoRepoForked } from 'react-icons/go'; 6 | import localforage from 'localforage'; 7 | 8 | import { Avatar } from '@/components/common/Avatar'; 9 | import { Button } from '@/components/ui/button'; 10 | import AvatarPopover from '@/components/avatarPopover'; 11 | import WebContainerProvider from '@/components/webContainerProvider'; 12 | 13 | interface UserInfo { 14 | [key: string]: any; 15 | } 16 | interface HeaderProps { 17 | userInfo: UserInfo; 18 | projectId: string; 19 | } 20 | 21 | export const Header: React.FC = ({ projectId }) => { 22 | const [projectName, setProjectName] = useState(''); 23 | useEffect(() => { 24 | const fetchProjectData = async () => { 25 | const projectData = await localforage.getItem(projectId); 26 | 27 | if (projectData) { 28 | const parsedData = JSON.parse(projectData as string); 29 | setProjectName(parsedData.name); 30 | } 31 | }; 32 | 33 | fetchProjectData(); 34 | }, []); 35 | 36 | return ( 37 |
38 | 39 | 46 | 47 | 48 | 49 |
50 | 54 | 58 | 62 |
63 |
64 | {projectName} 65 |
66 |
67 | 68 | 69 | 70 |
71 |
72 | 73 | 74 | 75 |
76 | 77 |
78 | ); 79 | }; 80 | -------------------------------------------------------------------------------- /src/components/preview/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, memo, useState, ChangeEvent, useEffect } from 'react'; 2 | import { FaUndoAlt, FaLock } from 'react-icons/fa'; 3 | import { RxPinRight, RxPinLeft, RxOpenInNewWindow } from 'react-icons/rx'; 4 | import { Slot } from '@radix-ui/react-slot'; 5 | import { v4 as uuidv4 } from 'uuid'; 6 | 7 | import BootingWebContainer from './booting'; 8 | 9 | import { useWebContainerStore } from '@/store/webContainerStore'; 10 | 11 | // 自定义输入框组件 12 | const CustomInput: FC<{ 13 | value: string; 14 | onChange: (event: ChangeEvent) => void; 15 | placeholder?: string; 16 | }> = ({ value, onChange, placeholder }) => { 17 | return ( 18 |
22 | 23 | 24 | 25 | 32 |
33 | ); 34 | }; 35 | 36 | export const Preview: FC = memo(function Preview() { 37 | const uuid = uuidv4(); 38 | const { url, isInitialized } = useWebContainerStore(); 39 | 40 | const [iframeUrl, setIframeUrl] = useState(''); 41 | const [id, setId] = useState(uuid); 42 | 43 | useEffect(() => { 44 | setIframeUrl(url); 45 | 46 | return () => { 47 | setIframeUrl(''); 48 | }; 49 | }, [url]); 50 | 51 | const handleInputChange = (event: ChangeEvent) => { 52 | setIframeUrl(event.target.value); 53 | }; 54 | 55 | return ( 56 |
57 |
58 | setId(uuidv4())} 61 | /> 62 |
63 | 64 |
65 | 66 | {true ? ( 67 | 68 | ) : ( 69 | 70 | )} 71 |
72 |
73 | {isInitialized ? ( 74 | 81 | ) : ( 82 | 83 | )} 84 |
85 |
86 | ); 87 | }); 88 | -------------------------------------------------------------------------------- /src/components/modals/create-coopertaion-modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { usePathname, useRouter } from 'next/navigation'; 3 | 4 | import { Button } from '@/components/ui/button'; 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogFooter, 9 | DialogHeader, 10 | DialogTitle, 11 | } from '@/components/ui/dialog'; 12 | import { Input } from '@/components/ui/input'; 13 | import { Label } from '@/components/ui/label'; 14 | import { useModal } from '@/hooks/useModal'; 15 | import { useAuthStore } from '@/store/authStore'; 16 | import { PATHS, STORAGE_KEY_AUTH } from '@/utils'; 17 | 18 | export function CreateCoopertaionModal() { 19 | const { setAuth, getAuth } = useAuthStore(); 20 | const router = useRouter(); 21 | const pathname = usePathname(); 22 | const [docName, setDocName] = useState(''); 23 | const { isOpen, onClose, type } = useModal(); 24 | const isModalOpen = isOpen && type === 'createCooperation'; 25 | 26 | const handleCreate = async () => { 27 | let auth = getAuth(); 28 | 29 | if (!auth.access_token) { 30 | const storagedAuth = localStorage.getItem(STORAGE_KEY_AUTH); 31 | 32 | if (storagedAuth) { 33 | auth = JSON.parse(storagedAuth); 34 | 35 | setAuth(auth); 36 | } 37 | } 38 | 39 | if (!auth.access_token) { 40 | router.push(`${PATHS.LOGIN}?redirect=${pathname}`); 41 | 42 | return; 43 | } 44 | 45 | const response = await fetch(`${process.env.NEXT_PUBLIC_SERVER_URL}/api/v1/document/create`, { 46 | method: 'POST', 47 | headers: { 48 | 'Content-Type': 'application/json', 49 | }, 50 | body: JSON.stringify({ 51 | docName, 52 | }), 53 | }).then((response) => response.json()); 54 | 55 | if (response.message === 'Created') { 56 | router.push(`/cooperation/${response.data._id}`); 57 | } else { 58 | } 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 65 | 创建文档 66 | 67 |
68 |
69 | 72 | setDocName(e.target.value)} 76 | className="col-span-3 h-8 bg-[#343740] w-full rounded-md border border-white/20 px-3 py-1 text-sm shadow-sm focus-within:ring-white focus-visible:outline-none focus-visible:ring-1 pr-[72px]" 77 | /> 78 |
79 |
80 | 81 | 88 | 89 |
90 |
91 | ); 92 | } 93 | -------------------------------------------------------------------------------- /src/components/file/fileTree/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useMemo } from 'react'; 4 | 5 | import { Tree, TreeViewElement, File, Folder } from '@/components/extension/tree-view-api'; 6 | import { PendingFileItem } from '@/components/file/pendingFileItem'; 7 | import { FileItem } from '@/components/file/fileItem'; 8 | 9 | type TOCProps = { 10 | toc: TreeViewElement[]; 11 | }; 12 | 13 | function sortToc(toc: TreeViewElement[]): TreeViewElement[] { 14 | const tempToc = [...toc]; 15 | 16 | tempToc 17 | .filter((element) => element.kind === 'directory' && element.children?.length) 18 | .forEach((element) => { 19 | element.children = sortToc(element.children || []); 20 | }); 21 | 22 | return tempToc.sort((a, b) => { 23 | if (a.kind === 'directory' && b.kind === 'file') { 24 | return -1; 25 | } 26 | 27 | if (a.kind === 'file' && b.kind === 'directory') { 28 | return 1; 29 | } 30 | 31 | if (a.filename < b.filename) { 32 | return -1; 33 | } 34 | 35 | return 0; 36 | }); 37 | } 38 | 39 | const TOC = ({ toc }: TOCProps) => { 40 | const sortedToc = useMemo(() => { 41 | return sortToc(toc); 42 | }, [toc]); 43 | 44 | return ( 45 | 46 | {sortedToc.map((element) => ( 47 | 48 | ))} 49 | 50 | ); 51 | }; 52 | 53 | type TreeItemProps = { 54 | elements: TreeViewElement[]; 55 | }; 56 | 57 | export const TreeItem = ({ elements }: TreeItemProps) => { 58 | return ( 59 |
    60 | {elements.map((element) => ( 61 |
  • 62 | {element.status === 'pending' ? ( 63 | 69 | ) : (element.children && element.children?.length > 0) || element.kind === 'directory' ? ( 70 | 79 | 84 | 85 | ) : ( 86 | 87 | 88 | 89 | )} 90 |
  • 91 | ))} 92 |
93 | ); 94 | }; 95 | 96 | const FileTree = ({ data }: { data: TreeViewElement[] }) => { 97 | return ; 98 | }; 99 | 100 | export default FileTree; 101 | -------------------------------------------------------------------------------- /src/components/home/card-hover-effect/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { AnimatePresence, motion } from 'framer-motion'; 4 | import Link from 'next/link'; 5 | import { useState } from 'react'; 6 | import type { ReactNode } from 'react'; 7 | 8 | import { cn } from '@/utils/cn'; 9 | 10 | export function Card({ className, children }: { className?: string; children: React.ReactNode }) { 11 | return ( 12 |
18 |
19 |
{children}
20 |
21 |
22 | ); 23 | } 24 | 25 | export function CardTitle({ 26 | className, 27 | children, 28 | }: { 29 | className?: string; 30 | children: React.ReactNode; 31 | }) { 32 | return

{children}

; 33 | } 34 | 35 | export function CardDescription({ 36 | className, 37 | children, 38 | }: { 39 | className?: string; 40 | children: React.ReactNode; 41 | }) { 42 | return ( 43 |

44 | {children} 45 |

46 | ); 47 | } 48 | 49 | export function HoverEffect({ 50 | items, 51 | className, 52 | }: { 53 | items: { 54 | title: ReactNode; 55 | description: ReactNode; 56 | icon: string; 57 | link: string; 58 | }[]; 59 | className?: string; 60 | }) { 61 | const [hoveredIndex, setHoveredIndex] = useState(null); 62 | 63 | return ( 64 |
65 | {items.map((item, idx) => ( 66 | setHoveredIndex(idx)} 71 | onMouseLeave={() => setHoveredIndex(null)} 72 | > 73 | 74 | {hoveredIndex === idx && ( 75 | 88 | )} 89 | 90 | 91 | 92 |
93 | {item.title} 94 | {item.description} 95 |
96 |
97 | 98 | ))} 99 |
100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /src/components/fileSearch/includeAndExclude/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ChangeEvent, FC } from 'react'; 4 | import { VscBook, VscEllipsis, VscExclude } from 'react-icons/vsc'; 5 | 6 | // import { TreeViewElement } from '@/components/extension/tree-view-api'; 7 | import ToolBtn from '@/components/fileSearch/ToolBtn'; 8 | import InputComp from '@/components/fileSearch/input'; 9 | import { useFileFilter } from '@/store/fileSearchStore'; 10 | import debounce from '@/utils/debounce'; 11 | // import { matchFilesByKey } from '@/utils/match'; 12 | // import { useUploadFileDataStore } from '@/store/uploadFileDataStore'; 13 | // import { useModelsStore } from '@/store/editorStore'; 14 | // import { TreeViewElement } from '@/components/extension/tree-view-api'; 15 | // import { openedFileIds } from '@/utils/matchHelper'; 16 | 17 | const IncludeAndExclude: FC = () => { 18 | // const { fileData } = useUploadFileDataStore(); 19 | // let data: TreeViewElement[] = fileData as unknown as TreeViewElement[]; 20 | 21 | // const { models } = useModelsStore(); 22 | // const openedIds = openedFileIds(models); 23 | 24 | const { 25 | filterSearchOptions, 26 | setFilterSearchOptionItem, 27 | setIncludeFileInpVal, 28 | setExcludeFileInpVal, 29 | } = useFileFilter(); 30 | 31 | const excludeInpOnchange = debounce((e: ChangeEvent) => { 32 | setExcludeFileInpVal(e.target.value); 33 | }); 34 | const includeInpOnchange = debounce((e: ChangeEvent) => { 35 | setIncludeFileInpVal(e.target.value); 36 | }); 37 | 38 | return ( 39 |
40 |
41 | setFilterSearchOptionItem('isUsed')} 45 | active={filterSearchOptions.isUsed} 46 | > 47 | 48 | 49 |
50 | {filterSearchOptions.isUsed && ( 51 |
52 |
53 |
包含的文件
54 | setFilterSearchOptionItem('isOnlyOpened')} 60 | > 61 | 62 | 63 | } 64 | placeholder="例如:*.ts,src/**/include" 65 | onchange={includeInpOnchange} 66 | > 67 |
68 |
69 |
排除的文件
70 | setFilterSearchOptionItem('isUseGitignoreFile')} 76 | > 77 | 78 | 79 | } 80 | placeholder="例如:*.ts,src/**/exclude" 81 | onchange={excludeInpOnchange} 82 | > 83 |
84 |
85 | )} 86 |
87 | ); 88 | }; 89 | 90 | export default IncludeAndExclude; 91 | --------------------------------------------------------------------------------