diff --git a/common/themes.ts b/common/themes.ts index d031de5..64d7e76 100644 --- a/common/themes.ts +++ b/common/themes.ts @@ -1,8 +1,8 @@ import { Extension } from '@uiw/react-codemirror' -import { catppuccin } from 'codemirror-theme-catppuccin' +import { vscodeDark, vscodeLight } from '@uiw/codemirror-theme-vscode' -const darkCodeMirrorTheme = catppuccin('macchiato') -const lightCodeMirrorTheme = catppuccin('latte') +const lightCodeMirrorTheme = vscodeLight +const darkCodeMirrorTheme = vscodeDark export const themeMap = { light: lightCodeMirrorTheme, diff --git a/components/CollaborativeEditor.tsx b/components/CollaborativeEditor.tsx index 28a8fd5..1a84acd 100644 --- a/components/CollaborativeEditor.tsx +++ b/components/CollaborativeEditor.tsx @@ -231,7 +231,7 @@ export default function CollaborativeEditor({ />

- You are User {userId} in Room {roomId}. + You are User {userId} in Room {roomId}.{' '} {isReadOnly ? `User ${editingUser} is currently editing.` : 'You can edit now. Press Enter to switch control.'} diff --git a/package.json b/package.json index 5790c67..6daca78 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "@codemirror/lang-php": "^6.0.1", "@codemirror/language": "^6.10.8", "@codemirror/legacy-modes": "^6.4.2", + "@uiw/codemirror-theme-vscode": "^4.23.8", "@uiw/react-codemirror": "^4.23.7", "clsx": "^2.1.1", "codemirror-lang-elixir": "^4.0.0", - "codemirror-theme-catppuccin": "^0.3.0", "next": "15.1.2", "next-themes": "^0.4.4", "react": "^19.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9551019..c29d077 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@codemirror/legacy-modes': specifier: ^6.4.2 version: 6.4.2 + '@uiw/codemirror-theme-vscode': + specifier: ^4.23.8 + version: 4.23.8(@codemirror/language@6.10.8)(@codemirror/state@6.5.0)(@codemirror/view@6.36.1) '@uiw/react-codemirror': specifier: ^4.23.7 version: 4.23.7(@babel/runtime@7.26.0)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.0)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.1)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -29,9 +32,6 @@ importers: codemirror-lang-elixir: specifier: ^4.0.0 version: 4.0.0 - codemirror-theme-catppuccin: - specifier: ^0.3.0 - version: 0.3.0 next: specifier: 15.1.2 version: 15.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -110,9 +110,6 @@ packages: resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} - '@catppuccin/palette@0.2.0': - resolution: {integrity: sha512-PoAMQMEbL8fBEO80eusB+XQg+1i2kxQHw6COwBRC4bVWxWuRroZjm3K6bOdqPHNHKikDIV7wLi9hSG4NoD1FEQ==} - '@codemirror/autocomplete@6.18.4': resolution: {integrity: sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==} @@ -156,10 +153,6 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@ctrl/tinycolor@4.1.0': - resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==} - engines: {node: '>=14'} - '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} @@ -559,6 +552,16 @@ packages: '@codemirror/state': '>=6.0.0' '@codemirror/view': '>=6.0.0' + '@uiw/codemirror-theme-vscode@4.23.8': + resolution: {integrity: sha512-Gxa98stfYFWGgy3OW4KUUuLI13TSBEp7fY/SSxf6nYzIfwPSBdWP24mizrVgEucsbMw99IvqCP9Uja62Sn2jSw==} + + '@uiw/codemirror-themes@4.23.8': + resolution: {integrity: sha512-PZmJBZxWMuZ48p/2D5aRPl8zTlBq1d/+NeRqyyH6P6k6yWDF6h71m0Dt+fjslgPE7KmWXux2hbejXXXoRLZO9Q==} + peerDependencies: + '@codemirror/language': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + '@uiw/react-codemirror@4.23.7': resolution: {integrity: sha512-Nh/0P6W+kWta+ARp9YpnKPD9ick5teEnwmtNoPQnyd6NPv0EQP3Ui4YmRVNj1nkUEo+QjrAUaEfcejJ2up/HZA==} peerDependencies: @@ -743,9 +746,6 @@ packages: codemirror-lang-elixir@4.0.0: resolution: {integrity: sha512-mzFesxo/t6KOxwnkqVd34R/q7yk+sMtHh6vUKGAvjwHmpL7bERHB+vQAsmU/nqrndkwVeJEHWGw/z/ybfdiudA==} - codemirror-theme-catppuccin@0.3.0: - resolution: {integrity: sha512-pnMMusmL+m8WaLUe+/ap5GjdxeWCtXLrQrU1THqVEEJOgkIXdv6dTNdxAU2orGlq5LjGwPPUq4233vCQyOpKxg==} - codemirror@6.0.1: resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} @@ -2146,8 +2146,6 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 - '@catppuccin/palette@0.2.0': {} - '@codemirror/autocomplete@6.18.4': dependencies: '@codemirror/language': 6.10.8 @@ -2246,8 +2244,6 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@ctrl/tinycolor@4.1.0': {} - '@emnapi/runtime@1.3.1': dependencies: tslib: 2.8.1 @@ -2629,6 +2625,20 @@ snapshots: '@codemirror/state': 6.5.0 '@codemirror/view': 6.36.1 + '@uiw/codemirror-theme-vscode@4.23.8(@codemirror/language@6.10.8)(@codemirror/state@6.5.0)(@codemirror/view@6.36.1)': + dependencies: + '@uiw/codemirror-themes': 4.23.8(@codemirror/language@6.10.8)(@codemirror/state@6.5.0)(@codemirror/view@6.36.1) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + + '@uiw/codemirror-themes@4.23.8(@codemirror/language@6.10.8)(@codemirror/state@6.5.0)(@codemirror/view@6.36.1)': + dependencies: + '@codemirror/language': 6.10.8 + '@codemirror/state': 6.5.0 + '@codemirror/view': 6.36.1 + '@uiw/react-codemirror@4.23.7(@babel/runtime@7.26.0)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.0)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.1)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/runtime': 7.26.0 @@ -2841,15 +2851,6 @@ snapshots: '@codemirror/language': 6.10.8 lezer-elixir: 1.1.2 - codemirror-theme-catppuccin@0.3.0: - dependencies: - '@catppuccin/palette': 0.2.0 - '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.1 - '@ctrl/tinycolor': 4.1.0 - '@lezer/highlight': 1.2.1 - codemirror@6.0.1: dependencies: '@codemirror/autocomplete': 6.18.4 @@ -3182,7 +3183,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0(jiti@1.21.7)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.17.0(jiti@1.21.7)))(eslint@9.17.0(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: @@ -3204,7 +3205,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.17.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.17.0(jiti@1.21.7)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.17.0(jiti@1.21.7)))(eslint@9.17.0(jiti@1.21.7)) hasown: 2.0.2 is-core-module: 2.16.0 is-glob: 4.0.3 diff --git a/server.ts b/server.ts index 4be43f6..0c43c00 100644 --- a/server.ts +++ b/server.ts @@ -51,6 +51,7 @@ interface Room { editingUser: number | null lastEditPosition: number settings: Settings + lastSwitchTime: number } const rooms = new Map() @@ -90,6 +91,7 @@ io.on('connection', (socket) => { switchTrigger: '[ \n]', cursorAtEnd: true, }, + lastSwitchTime: Date.now(), // 初始化切换时间戳 }) } @@ -116,26 +118,20 @@ io.on('connection', (socket) => { socket.on( 'contentChange', - ({ - roomId, - content, - cursorPosition, - newChar, - }: { - roomId: string - content: string - cursorPosition: number - newChar: string | null - }) => { - console.log('contentChange', roomId, content, cursorPosition) + ({ roomId, content, cursorPosition, newChar }) => { const room = rooms.get(roomId) if (room && room.editingUser === userId) { - // const oldContent = room.content room.content = content socket.to(roomId).emit('contentChange', content) - const switchTriggerRe = new RegExp(room.settings.switchTrigger) - if (newChar && switchTriggerRe.test(newChar)) { + const now = Date.now() + // 确保距离上次切换至少200毫秒 + if ( + newChar && + now - room.lastSwitchTime >= 200 && + new RegExp(room.settings.switchTrigger).test(newChar) + ) { + room.lastSwitchTime = now // 更新切换时间戳 room.editingUser = arrNext(room.users, userId) io.to(roomId).emit('setEditingUser', room.editingUser) }