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:
@@ -51,9 +51,15 @@ const collectImages = (
|
|||||||
return imgs
|
return imgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 选择题类型:只有这些类型才会把列表解析为选项 */
|
||||||
|
const CHOICE_TYPES = new Set(["single_choice", "multiple_choice", "judgment"])
|
||||||
|
|
||||||
const parseOptions = (
|
const parseOptions = (
|
||||||
nodes: JSONContent[]
|
nodes: JSONContent[],
|
||||||
|
questionType: RichQuestionType
|
||||||
): Array<{ id: string; text: string; isCorrect?: boolean }> => {
|
): Array<{ id: string; text: string; isCorrect?: boolean }> => {
|
||||||
|
// 只有选择题才解析选项,填空/简答/复合题的列表保持为普通文本
|
||||||
|
if (!CHOICE_TYPES.has(questionType)) return []
|
||||||
const options: Array<{ id: string; text: string; isCorrect?: boolean }> = []
|
const options: Array<{ id: string; text: string; isCorrect?: boolean }> = []
|
||||||
const seenIds = new Set<string>()
|
const seenIds = new Set<string>()
|
||||||
for (const n of nodes) {
|
for (const n of nodes) {
|
||||||
@@ -106,16 +112,19 @@ const buildQuestion = (qb: JSONContent): EditorQuestion => {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
const text = extractText({
|
// 选择题:过滤掉列表(列表作为选项单独处理)
|
||||||
type: "doc",
|
// 非选择题:保留列表内容在文本中(列表是题干的一部分)
|
||||||
content: nonQuestionBlocks.filter(
|
const isChoice = CHOICE_TYPES.has(type)
|
||||||
|
const textNodes = isChoice
|
||||||
|
? nonQuestionBlocks.filter(
|
||||||
(n) =>
|
(n) =>
|
||||||
n.type !== "orderedList" &&
|
n.type !== "orderedList" &&
|
||||||
n.type !== "bulletList" &&
|
n.type !== "bulletList" &&
|
||||||
n.type !== "image"
|
n.type !== "image"
|
||||||
),
|
)
|
||||||
})
|
: nonQuestionBlocks.filter((n) => n.type !== "image")
|
||||||
const options = parseOptions(nonQuestionBlocks)
|
const text = extractText({ type: "doc", content: textNodes })
|
||||||
|
const options = parseOptions(nonQuestionBlocks, type)
|
||||||
const blanks = collectBlanks(nonQuestionBlocks)
|
const blanks = collectBlanks(nonQuestionBlocks)
|
||||||
const images = collectImages(nonQuestionBlocks)
|
const images = collectImages(nonQuestionBlocks)
|
||||||
const content: RichQuestionContent = { text: text.trim() }
|
const content: RichQuestionContent = { text: text.trim() }
|
||||||
|
|||||||
@@ -128,6 +128,14 @@ export function SelectionToolbar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
editor.on("selectionUpdate", updatePosition)
|
editor.on("selectionUpdate", updatePosition)
|
||||||
|
|
||||||
|
// 鼠标释放后立即更新位置(选区拖动结束时)
|
||||||
|
const handlePointerUp = () => {
|
||||||
|
// 微延迟确保选区已更新
|
||||||
|
requestAnimationFrame(updatePosition)
|
||||||
|
}
|
||||||
|
document.addEventListener("pointerup", handlePointerUp)
|
||||||
|
|
||||||
editor.on("blur", () => {
|
editor.on("blur", () => {
|
||||||
// 延迟以允许点击工具栏按钮
|
// 延迟以允许点击工具栏按钮
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -140,6 +148,7 @@ export function SelectionToolbar({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
editor.off("selectionUpdate", updatePosition)
|
editor.off("selectionUpdate", updatePosition)
|
||||||
|
document.removeEventListener("pointerup", handlePointerUp)
|
||||||
}
|
}
|
||||||
}, [editor])
|
}, [editor])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user