教师端的协作编辑器只有高亮和括号匹配,没装 autocompletion —— 老师替学生 写代码时没有下拉补全,只能盲敲。而且语言写死 cpp(),学生写 Python 时老师 看到的是 C 的高亮。 - 求助协议带上语言:help_request 记到 HelpRequest,accept 时写进 Room, room_open 发给两端;认不出的语言一律当 C(老客户端不带这个字段,而它 以前就是写死 C 的) - 新增 help_language / room_language:学生在排队或协作期间切语言,只更新 语言,不动队列位置和房间;已开房就把新语言推给老师,弹框的高亮和补全 实时跟着换 - CollabModal 装上 autocompletion,和学生端用同一套 enhanceCompletion + completeAnyWord;语言→高亮扩展的映射抽到 shared/extensions/language.ts 两端共用,免得再分叉 - 学生端求助按钮合并状态提示:原来按钮旁边还挂一个 n-tag,一行工具栏在 1280 的机房屏上放不下,改成状态全进 label(已求助 · 待接入 / 已求助 · 前面 N 人 / xxx 老师帮你中) 扩展数组变了 vue-codemirror 会整体 reconfigure,但 CM6 对已存在的 compartment 取 `compartments.get() || ext.inner`,collabDoc 那个装 yCollab 的 compartment 内容会被沿用,切语言、切主题都不会把协作弄断 —— 实跑验证过 双向同步、补全下拉、协作中切语言三条路径。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015APGLsyaCUa7XFMYAaCho4
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
getRoom,
|
getRoom,
|
||||||
hasTeacherOnline,
|
hasTeacherOnline,
|
||||||
listRequests,
|
listRequests,
|
||||||
|
normalizeLanguage,
|
||||||
openRoom,
|
openRoom,
|
||||||
queueAheadOf,
|
queueAheadOf,
|
||||||
removeRequest,
|
removeRequest,
|
||||||
@@ -179,7 +180,12 @@ export function handleCollabClose(ws: CollabSocket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCollabMessage(ws: CollabSocket, raw: string) {
|
export async function handleCollabMessage(ws: CollabSocket, raw: string) {
|
||||||
let message: { type?: unknown; problemId?: unknown; studentId?: unknown }
|
let message: {
|
||||||
|
type?: unknown
|
||||||
|
problemId?: unknown
|
||||||
|
studentId?: unknown
|
||||||
|
language?: unknown
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
message = JSON.parse(raw) as typeof message
|
message = JSON.parse(raw) as typeof message
|
||||||
} catch {
|
} catch {
|
||||||
@@ -201,7 +207,10 @@ export async function handleCollabMessage(ws: CollabSocket, raw: string) {
|
|||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case "help_request":
|
case "help_request":
|
||||||
await handleHelpRequest(ws, message.problemId)
|
await handleHelpRequest(ws, message.problemId, message.language)
|
||||||
|
return
|
||||||
|
case "help_language":
|
||||||
|
handleHelpLanguage(ws, message.language)
|
||||||
return
|
return
|
||||||
case "help_cancel":
|
case "help_cancel":
|
||||||
handleHelpCancel(ws)
|
handleHelpCancel(ws)
|
||||||
@@ -220,7 +229,11 @@ export async function handleCollabMessage(ws: CollabSocket, raw: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleHelpRequest(ws: CollabSocket, problemId: unknown) {
|
async function handleHelpRequest(
|
||||||
|
ws: CollabSocket,
|
||||||
|
problemId: unknown,
|
||||||
|
language: unknown,
|
||||||
|
) {
|
||||||
if (typeof problemId !== "string" || !problemId) {
|
if (typeof problemId !== "string" || !problemId) {
|
||||||
ws.send(JSON.stringify({ type: "error", message: "Invalid problemId" }))
|
ws.send(JSON.stringify({ type: "error", message: "Invalid problemId" }))
|
||||||
return
|
return
|
||||||
@@ -266,6 +279,7 @@ async function handleHelpRequest(ws: CollabSocket, problemId: unknown) {
|
|||||||
className: student?.className ?? null,
|
className: student?.className ?? null,
|
||||||
problemId,
|
problemId,
|
||||||
problemTitle: problem.title,
|
problemTitle: problem.title,
|
||||||
|
language: normalizeLanguage(language),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
status: "pending",
|
status: "pending",
|
||||||
socket: ws,
|
socket: ws,
|
||||||
@@ -274,6 +288,29 @@ async function handleHelpRequest(ws: CollabSocket, problemId: unknown) {
|
|||||||
broadcastRequests()
|
broadcastRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学生在求助期间换了语言。
|
||||||
|
*
|
||||||
|
* 只更新语言,不动队列位置、不动房间 —— 换语言不该让他重新排队。已经在协作中的
|
||||||
|
* 话再推一条 room_language,老师端的高亮和补全立刻跟着换;不推的话老师会拿着
|
||||||
|
* 建房那一刻的语言,对着一套错的补全替学生写代码。
|
||||||
|
*/
|
||||||
|
function handleHelpLanguage(ws: CollabSocket, language: unknown) {
|
||||||
|
const request = getRequest(ws.data.userId)
|
||||||
|
// 比对 socket 归属:同账号的另一个标签页停在别的题上切语言,不该改这条求助
|
||||||
|
if (!request || request.socket !== ws) return
|
||||||
|
const next = normalizeLanguage(language)
|
||||||
|
if (request.language === next) return
|
||||||
|
request.language = next
|
||||||
|
|
||||||
|
const room = getRoom(ws.data.userId)
|
||||||
|
if (!room) return
|
||||||
|
room.language = next
|
||||||
|
room.teacherSocket.send(
|
||||||
|
JSON.stringify({ type: "room_language", language: next }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function handleHelpCancel(ws: CollabSocket) {
|
function handleHelpCancel(ws: CollabSocket) {
|
||||||
const request = getRequest(ws.data.userId)
|
const request = getRequest(ws.data.userId)
|
||||||
// 不比对 socket 归属:取消的是这个学生自己的求助,不管从他哪个标签页发起都
|
// 不比对 socket 归属:取消的是这个学生自己的求助,不管从他哪个标签页发起都
|
||||||
@@ -341,6 +378,7 @@ async function handleAccept(ws: CollabSocket, studentId: unknown) {
|
|||||||
studentSocket: request.socket,
|
studentSocket: request.socket,
|
||||||
teacherSocket: ws,
|
teacherSocket: ws,
|
||||||
problemId: request.problemId,
|
problemId: request.problemId,
|
||||||
|
language: request.language,
|
||||||
})
|
})
|
||||||
|
|
||||||
const openFrame = (peerName: string, peerRole: "student" | "teacher") =>
|
const openFrame = (peerName: string, peerRole: "student" | "teacher") =>
|
||||||
@@ -348,6 +386,7 @@ async function handleAccept(ws: CollabSocket, studentId: unknown) {
|
|||||||
type: "room_open",
|
type: "room_open",
|
||||||
peer: { name: peerName, role: peerRole },
|
peer: { name: peerName, role: peerRole },
|
||||||
problemId: request.problemId,
|
problemId: request.problemId,
|
||||||
|
language: request.language,
|
||||||
})
|
})
|
||||||
request.socket.send(openFrame(request.teacherName, "teacher"))
|
request.socket.send(openFrame(request.teacherName, "teacher"))
|
||||||
ws.send(openFrame(request.studentName, "student"))
|
ws.send(openFrame(request.studentName, "student"))
|
||||||
|
|||||||
@@ -8,6 +8,30 @@
|
|||||||
|
|
||||||
export type CollabSocket = Bun.ServerWebSocket<import("../websocket").SubmissionSocketData>
|
export type CollabSocket = Bun.ServerWebSocket<import("../websocket").SubmissionSocketData>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 协作支持的语言。和前端 utils/types.ts 里的 LANGUAGE 对齐,去掉 Flowchart ——
|
||||||
|
* 流程图题没有代码编辑器,求助入口本身就是隐藏的。
|
||||||
|
*/
|
||||||
|
export const COLLAB_LANGUAGES = [
|
||||||
|
"C",
|
||||||
|
"C++",
|
||||||
|
"Python2",
|
||||||
|
"Python3",
|
||||||
|
"Java",
|
||||||
|
"JavaScript",
|
||||||
|
"Golang",
|
||||||
|
"SQL",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type CollabLanguage = (typeof COLLAB_LANGUAGES)[number]
|
||||||
|
|
||||||
|
/** 认不出来的一律当 C:老客户端不带这个字段,而它以前就是写死 C 的 */
|
||||||
|
export function normalizeLanguage(value: unknown): CollabLanguage {
|
||||||
|
return (COLLAB_LANGUAGES as readonly string[]).includes(value as string)
|
||||||
|
? (value as CollabLanguage)
|
||||||
|
: "C"
|
||||||
|
}
|
||||||
|
|
||||||
export interface HelpRequest {
|
export interface HelpRequest {
|
||||||
studentId: number
|
studentId: number
|
||||||
studentName: string
|
studentName: string
|
||||||
@@ -15,6 +39,11 @@ export interface HelpRequest {
|
|||||||
/** 题目的展示号(problem._id 列,前端一路用的都是它),不是自增主键 */
|
/** 题目的展示号(problem._id 列,前端一路用的都是它),不是自增主键 */
|
||||||
problemId: string
|
problemId: string
|
||||||
problemTitle: string
|
problemTitle: string
|
||||||
|
/**
|
||||||
|
* 学生编辑器当前的语言。决定教师端弹框用哪套高亮和补全 —— 求助时带上,
|
||||||
|
* 协作期间学生切语言会用 help_language 更新这里
|
||||||
|
*/
|
||||||
|
language: CollabLanguage
|
||||||
createdAt: number
|
createdAt: number
|
||||||
status: "pending" | "active"
|
status: "pending" | "active"
|
||||||
teacherId?: number
|
teacherId?: number
|
||||||
@@ -83,6 +112,8 @@ export interface Room {
|
|||||||
studentSocket: CollabSocket
|
studentSocket: CollabSocket
|
||||||
teacherSocket: CollabSocket
|
teacherSocket: CollabSocket
|
||||||
problemId: string
|
problemId: string
|
||||||
|
/** 建房那一刻学生的语言,之后跟着 help_language 走 */
|
||||||
|
language: CollabLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
const rooms = new Map<number, Room>()
|
const rooms = new Map<number, Room>()
|
||||||
|
|||||||
@@ -64,16 +64,39 @@ const showHelpButton = computed(
|
|||||||
!isContestMode.value,
|
!isContestMode.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态全塞进按钮本身。原来旁边还挂一个 n-tag 说明排队情况,一行工具栏
|
||||||
|
* (语言 / 提交 / 提交信息 / 课堂统计 / 更多操作 / 求助)在 1280 的机房屏上放不下。
|
||||||
|
*/
|
||||||
const helpButtonText = computed(() => {
|
const helpButtonText = computed(() => {
|
||||||
if (collabStore.helpStatus === "active") return "老师正在帮你"
|
if (collabStore.helpStatus === "active") {
|
||||||
if (collabStore.helpStatus === "pending") return "取消求助"
|
const name = collabStore.teacherName
|
||||||
|
return name ? `${name} 老师帮你中` : "老师正在帮你"
|
||||||
|
}
|
||||||
|
if (collabStore.helpStatus === "pending") {
|
||||||
|
return collabStore.queueAhead > 0
|
||||||
|
? `已求助 · 前面 ${collabStore.queueAhead} 人`
|
||||||
|
: "已求助 · 待接入"
|
||||||
|
}
|
||||||
return "求助"
|
return "求助"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const helpButtonType = computed(() => {
|
||||||
|
if (collabStore.helpStatus === "active") return "success"
|
||||||
|
if (collabStore.helpStatus === "pending") return "warning"
|
||||||
|
return "default"
|
||||||
|
})
|
||||||
|
|
||||||
|
// 排队中点一下就是取消,这句话再挤进 label 就太长了,挂在原生 title 上。
|
||||||
|
// active 时按钮是 disabled,浏览器不会给 disabled 元素显示 title,所以不放
|
||||||
|
const helpButtonTitle = computed(() =>
|
||||||
|
collabStore.helpStatus === "pending" ? "点击取消求助" : undefined,
|
||||||
|
)
|
||||||
|
|
||||||
const toggleHelp = () => {
|
const toggleHelp = () => {
|
||||||
if (collabStore.helpStatus === "pending") collabStore.cancelHelp()
|
if (collabStore.helpStatus === "pending") collabStore.cancelHelp()
|
||||||
else if (collabStore.helpStatus === "idle")
|
else if (collabStore.helpStatus === "idle")
|
||||||
collabStore.requestHelp(problem.value!._id)
|
collabStore.requestHelp(problem.value!._id, codeStore.code.language)
|
||||||
}
|
}
|
||||||
|
|
||||||
const showGoSubmissionButton = computed(() => {
|
const showGoSubmissionButton = computed(() => {
|
||||||
@@ -251,23 +274,16 @@ onMounted(() => {
|
|||||||
<n-button :size="buttonSize">更多操作</n-button>
|
<n-button :size="buttonSize">更多操作</n-button>
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
|
|
||||||
<template v-if="showHelpButton">
|
<n-button
|
||||||
<n-button
|
v-if="showHelpButton"
|
||||||
:size="buttonSize"
|
:size="buttonSize"
|
||||||
:type="collabStore.helpStatus === 'idle' ? 'default' : 'warning'"
|
:type="helpButtonType"
|
||||||
:disabled="collabStore.helpStatus === 'active'"
|
:disabled="collabStore.helpStatus === 'active'"
|
||||||
@click="toggleHelp"
|
:title="helpButtonTitle"
|
||||||
>
|
@click="toggleHelp"
|
||||||
{{ helpButtonText }}
|
>
|
||||||
</n-button>
|
{{ helpButtonText }}
|
||||||
|
</n-button>
|
||||||
<n-tag v-if="collabStore.helpStatus === 'pending'" type="info">
|
|
||||||
已求助{{ collabStore.queueAhead > 0 ? `,前面还有 ${collabStore.queueAhead} 人` : ",等待老师接入" }}
|
|
||||||
</n-tag>
|
|
||||||
<n-tag v-else-if="collabStore.helpStatus === 'active'" type="success">
|
|
||||||
{{ collabStore.teacherName }} 老师正在帮你
|
|
||||||
</n-tag>
|
|
||||||
</template>
|
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
<n-modal
|
<n-modal
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cpp } from "@codemirror/lang-cpp"
|
|
||||||
import { bracketMatching } from "@codemirror/language"
|
import { bracketMatching } from "@codemirror/language"
|
||||||
import { closeBrackets } from "@codemirror/autocomplete"
|
import {
|
||||||
|
autocompletion,
|
||||||
|
closeBrackets,
|
||||||
|
completeAnyWord,
|
||||||
|
} from "@codemirror/autocomplete"
|
||||||
import type { EditorView } from "@codemirror/view"
|
import type { EditorView } from "@codemirror/view"
|
||||||
import { Codemirror } from "vue-codemirror"
|
import { Codemirror } from "vue-codemirror"
|
||||||
|
import type { LANGUAGE } from "utils/types"
|
||||||
import { oneDark } from "../themes/oneDark"
|
import { oneDark } from "../themes/oneDark"
|
||||||
import { smoothy } from "../themes/smoothy"
|
import { smoothy } from "../themes/smoothy"
|
||||||
import { styleTheme } from "shared/extensions/baseTheme"
|
import { styleTheme } from "shared/extensions/baseTheme"
|
||||||
|
import { enhanceCompletion } from "shared/extensions/autocompletion"
|
||||||
|
import { languageExtension } from "shared/extensions/language"
|
||||||
import { useCollabDoc } from "../composables/collabDoc"
|
import { useCollabDoc } from "../composables/collabDoc"
|
||||||
import { useCollabStore } from "shared/store/collab"
|
import { useCollabStore } from "shared/store/collab"
|
||||||
|
|
||||||
@@ -26,12 +32,25 @@ const show = computed({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言跟着学生走:room_open 带过来,学生协作期间切语言会再来一条 room_language。
|
||||||
|
* 取不到时按 C —— 和这里原来写死 cpp() 的表现一致。
|
||||||
|
*/
|
||||||
|
const language = computed<LANGUAGE>(() => collabStore.room?.language ?? "C")
|
||||||
|
|
||||||
const extensions = computed(() => [
|
const extensions = computed(() => [
|
||||||
styleTheme,
|
styleTheme,
|
||||||
cpp(),
|
languageExtension(language.value),
|
||||||
bracketMatching(),
|
bracketMatching(),
|
||||||
closeBrackets(),
|
closeBrackets(),
|
||||||
isDark.value ? oneDark : smoothy,
|
isDark.value ? oneDark : smoothy,
|
||||||
|
// 学生端有的补全这里必须也有:老师是在替学生写代码,缺了下拉菜单只能盲敲。
|
||||||
|
// 数组一变 vue-codemirror 会整体 reconfigure,但 collabDoc 那个 compartment
|
||||||
|
// 的内容会被 CM6 沿用(@codemirror/state 的 flatten 里 `compartments.get() ||
|
||||||
|
// ext.inner`),所以切语言、切主题都不会把 yCollab 弄掉
|
||||||
|
autocompletion({
|
||||||
|
override: [enhanceCompletion(language.value), completeAnyWord],
|
||||||
|
}),
|
||||||
getInitialExtension(),
|
getInitialExtension(),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -80,7 +99,7 @@ onUnmounted(() => {
|
|||||||
v-model:show="show"
|
v-model:show="show"
|
||||||
preset="card"
|
preset="card"
|
||||||
:style="{ width: '80vw', maxWidth: '1100px' }"
|
:style="{ width: '80vw', maxWidth: '1100px' }"
|
||||||
:title="`正在帮 ${collabStore.room?.peerName ?? ''} · ${collabStore.room?.problemId ?? ''}`"
|
:title="`正在帮 ${collabStore.room?.peerName ?? ''} · ${collabStore.room?.problemId ?? ''} · ${language}`"
|
||||||
>
|
>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-button
|
<n-button
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { cpp } from "@codemirror/lang-cpp"
|
|
||||||
import { python } from "@codemirror/lang-python"
|
|
||||||
import { sql, SQLite } from "@codemirror/lang-sql"
|
|
||||||
import { bracketMatching } from "@codemirror/language"
|
import { bracketMatching } from "@codemirror/language"
|
||||||
import { Codemirror } from "vue-codemirror"
|
import { Codemirror } from "vue-codemirror"
|
||||||
import {
|
import {
|
||||||
@@ -9,13 +6,13 @@ import {
|
|||||||
closeBrackets,
|
closeBrackets,
|
||||||
completeAnyWord,
|
completeAnyWord,
|
||||||
} from "@codemirror/autocomplete"
|
} from "@codemirror/autocomplete"
|
||||||
import type { Extension } from "@codemirror/state"
|
|
||||||
import type { EditorView } from "@codemirror/view"
|
import type { EditorView } from "@codemirror/view"
|
||||||
import type { LANGUAGE } from "utils/types"
|
import type { LANGUAGE } from "utils/types"
|
||||||
import { oneDark } from "../themes/oneDark"
|
import { oneDark } from "../themes/oneDark"
|
||||||
import { smoothy } from "../themes/smoothy"
|
import { smoothy } from "../themes/smoothy"
|
||||||
import { styleTheme } from "shared/extensions/baseTheme"
|
import { styleTheme } from "shared/extensions/baseTheme"
|
||||||
import { enhanceCompletion } from "shared/extensions/autocompletion"
|
import { enhanceCompletion } from "shared/extensions/autocompletion"
|
||||||
|
import { languageExtension } from "shared/extensions/language"
|
||||||
import { useCollabDoc } from "../composables/collabDoc"
|
import { useCollabDoc } from "../composables/collabDoc"
|
||||||
import { useCollabStore } from "shared/store/collab"
|
import { useCollabStore } from "shared/store/collab"
|
||||||
|
|
||||||
@@ -40,11 +37,7 @@ const {
|
|||||||
} = defineProps<Props>()
|
} = defineProps<Props>()
|
||||||
const code = defineModel<string>("value")
|
const code = defineModel<string>("value")
|
||||||
|
|
||||||
const langExtension = computed((): Extension => {
|
const langExtension = computed(() => languageExtension(language))
|
||||||
if (language === "SQL")
|
|
||||||
return sql({ dialect: SQLite, upperCaseKeywords: true })
|
|
||||||
return ["Python2", "Python3"].includes(language) ? python() : cpp()
|
|
||||||
})
|
|
||||||
|
|
||||||
const extensions = computed(() => [
|
const extensions = computed(() => [
|
||||||
styleTheme,
|
styleTheme,
|
||||||
@@ -96,6 +89,15 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 求助期间切了语言:同步给老师,他那边的高亮和补全跟着换。
|
||||||
|
// 教师端不发 —— 这个组件在教师自己的题目页上也挂着,他的语言和房间无关
|
||||||
|
watch(
|
||||||
|
() => language,
|
||||||
|
(lang) => {
|
||||||
|
if (!collabStore.isTeacher) collabStore.updateLanguage(lang)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
stop()
|
stop()
|
||||||
// 这个组件卸载意味着编辑器没了(切走页面、或者把语言切成流程图),
|
// 这个组件卸载意味着编辑器没了(切走页面、或者把语言切成流程图),
|
||||||
|
|||||||
@@ -726,6 +726,8 @@ export interface CollabMessage extends WebSocketMessage {
|
|||||||
| "requests"
|
| "requests"
|
||||||
| "help_status"
|
| "help_status"
|
||||||
| "room_open"
|
| "room_open"
|
||||||
|
// 协作期间学生换了语言,只发给教师端
|
||||||
|
| "room_language"
|
||||||
| "room_closed"
|
| "room_closed"
|
||||||
| "error"
|
| "error"
|
||||||
}
|
}
|
||||||
|
|||||||
16
apps/web/src/shared/extensions/language.ts
Normal file
16
apps/web/src/shared/extensions/language.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { cpp } from "@codemirror/lang-cpp"
|
||||||
|
import { python } from "@codemirror/lang-python"
|
||||||
|
import { sql, SQLite } from "@codemirror/lang-sql"
|
||||||
|
import type { Extension } from "@codemirror/state"
|
||||||
|
import type { LANGUAGE } from "utils/types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言对应的高亮扩展。学生端(SyncCodeEditor)和教师端(CollabModal)共用同一份 ——
|
||||||
|
* 两边各选各的,就是老师看到的高亮和补全跟学生手里那份对不上。
|
||||||
|
*
|
||||||
|
* Java / Golang / JavaScript 没有单独的包,落到 cpp(),是既有行为,不是遗漏。
|
||||||
|
*/
|
||||||
|
export function languageExtension(language: LANGUAGE): Extension {
|
||||||
|
if (language === "SQL") return sql({ dialect: SQLite, upperCaseKeywords: true })
|
||||||
|
return ["Python2", "Python3"].includes(language) ? python() : cpp()
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
type CollabRequestItem,
|
type CollabRequestItem,
|
||||||
} from "shared/composables/websocket"
|
} from "shared/composables/websocket"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
import type { LANGUAGE } from "utils/types"
|
||||||
|
|
||||||
export type HelpStatus = "idle" | "pending" | "active"
|
export type HelpStatus = "idle" | "pending" | "active"
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ export interface RoomInfo {
|
|||||||
peerName: string
|
peerName: string
|
||||||
peerRole: "student" | "teacher"
|
peerRole: "student" | "teacher"
|
||||||
problemId: string
|
problemId: string
|
||||||
|
/** 学生编辑器的语言。教师端的弹框按它选高亮和补全 */
|
||||||
|
language: LANGUAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,8 +101,13 @@ export const useCollabStore = defineStore("collab", () => {
|
|||||||
peerName: String(data.peer?.name ?? ""),
|
peerName: String(data.peer?.name ?? ""),
|
||||||
peerRole: data.peer?.role === "teacher" ? "teacher" : "student",
|
peerRole: data.peer?.role === "teacher" ? "teacher" : "student",
|
||||||
problemId: String(data.problemId ?? ""),
|
problemId: String(data.problemId ?? ""),
|
||||||
|
language: (data.language as LANGUAGE) ?? "C",
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
case "room_language":
|
||||||
|
// 学生在协作期间切了语言。只有教师端收得到这条
|
||||||
|
if (room.value) room.value.language = (data.language as LANGUAGE) ?? "C"
|
||||||
|
return
|
||||||
case "room_closed":
|
case "room_closed":
|
||||||
// 不管 reason 一律先归位到 idle:学生这一侧的 socket 发送失败时,
|
// 不管 reason 一律先归位到 idle:学生这一侧的 socket 发送失败时,
|
||||||
// 服务端把它从请求表里摘掉却**发不出**任何纠正性的 help_status
|
// 服务端把它从请求表里摘掉却**发不出**任何纠正性的 help_status
|
||||||
@@ -149,8 +157,17 @@ export const useCollabStore = defineStore("collab", () => {
|
|||||||
notice.value = ""
|
notice.value = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestHelp(problemId: string) {
|
function requestHelp(problemId: string, language: LANGUAGE) {
|
||||||
ws.send({ type: "help_request", problemId })
|
ws.send({ type: "help_request", problemId, language })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 求助期间换了语言。老师那边的高亮和补全按这个值选,不同步过去他就只能对着
|
||||||
|
* 建房那一刻的语言给学生写代码。idle 时不发 —— 服务端压根没有这条求助记录。
|
||||||
|
*/
|
||||||
|
function updateLanguage(language: LANGUAGE) {
|
||||||
|
if (helpStatus.value === "idle") return
|
||||||
|
ws.send({ type: "help_language", language })
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelHelp() {
|
function cancelHelp() {
|
||||||
@@ -198,6 +215,7 @@ export const useCollabStore = defineStore("collab", () => {
|
|||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
requestHelp,
|
requestHelp,
|
||||||
|
updateLanguage,
|
||||||
cancelHelp,
|
cancelHelp,
|
||||||
accept,
|
accept,
|
||||||
reject,
|
reject,
|
||||||
|
|||||||
Reference in New Issue
Block a user