fix(exams): fix toolbar tracking and prevent list-to-options for non-choice questions

Two fixes:

1. Selection toolbar tracking: add pointerup listener so the toolbar
   updates its position immediately after the user finishes dragging
   the selection, not just on selectionUpdate events which can lag.

2. List-to-options conversion: parseOptions now only converts
   orderedList/bulletList to A/B/C options for choice question types
   (single_choice, multiple_choice, judgment). For text/composite
   questions, list content is preserved as part of the question stem
   text, preventing unwanted 'A.' prefixes.
This commit is contained in:
SpecialX
2026-06-24 15:13:49 +08:00
parent e9429935b9
commit 90f7d395f2
2 changed files with 29 additions and 11 deletions

View File

@@ -51,9 +51,15 @@ const collectImages = (
return imgs
}
/** 选择题类型:只有这些类型才会把列表解析为选项 */
const CHOICE_TYPES = new Set(["single_choice", "multiple_choice", "judgment"])
const parseOptions = (
nodes: JSONContent[]
nodes: JSONContent[],
questionType: RichQuestionType
): Array<{ id: string; text: string; isCorrect?: boolean }> => {
// 只有选择题才解析选项,填空/简答/复合题的列表保持为普通文本
if (!CHOICE_TYPES.has(questionType)) return []
const options: Array<{ id: string; text: string; isCorrect?: boolean }> = []
const seenIds = new Set<string>()
for (const n of nodes) {
@@ -106,16 +112,19 @@ const buildQuestion = (qb: JSONContent): EditorQuestion => {
return true
})
const text = extractText({
type: "doc",
content: nonQuestionBlocks.filter(
(n) =>
n.type !== "orderedList" &&
n.type !== "bulletList" &&
n.type !== "image"
),
})
const options = parseOptions(nonQuestionBlocks)
// 选择题:过滤掉列表(列表作为选项单独处理)
// 非选择题:保留列表内容在文本中(列表是题干的一部分)
const isChoice = CHOICE_TYPES.has(type)
const textNodes = isChoice
? nonQuestionBlocks.filter(
(n) =>
n.type !== "orderedList" &&
n.type !== "bulletList" &&
n.type !== "image"
)
: nonQuestionBlocks.filter((n) => n.type !== "image")
const text = extractText({ type: "doc", content: textNodes })
const options = parseOptions(nonQuestionBlocks, type)
const blanks = collectBlanks(nonQuestionBlocks)
const images = collectImages(nonQuestionBlocks)
const content: RichQuestionContent = { text: text.trim() }

View File

@@ -128,6 +128,14 @@ export function SelectionToolbar({
}
editor.on("selectionUpdate", updatePosition)
// 鼠标释放后立即更新位置(选区拖动结束时)
const handlePointerUp = () => {
// 微延迟确保选区已更新
requestAnimationFrame(updatePosition)
}
document.addEventListener("pointerup", handlePointerUp)
editor.on("blur", () => {
// 延迟以允许点击工具栏按钮
setTimeout(() => {
@@ -140,6 +148,7 @@ export function SelectionToolbar({
return () => {
editor.off("selectionUpdate", updatePosition)
document.removeEventListener("pointerup", handlePointerUp)
}
}, [editor])