- Update users/actions.ts with new server actions
- Update users/data-access.ts with new data access functions
- Update users/components/admin-users-view.tsx
- Update layout/config/navigation.ts
- Add announcement-card test for component testing
- Add is-announcement-visible test and schema test for logic testing
- Add announcement-list-skeleton for loading states
- Add announcement-pagination for list pagination
- Add announcements-service-context and default-announcements-service for service layer
- Add message-draft-list and message-attachments for draft and attachment management
- Add message-group-compose for group messaging
- Add message-template-picker for message templates
- Add message-report-block for reporting and blocking users
- Add message-list-section and message-list-skeleton for list rendering
- Add lib and services directories for messaging utilities and service layer
- Add attendance-grade-correlation-card and data-access-correlation, correlation-compute
- Add attendance-trend-chart and trend-compute for trend analysis
- Add attendance-warnings-card and warning-compute for attendance warnings
- Add attendance-report-print for printable reports
- Add class-comparison-card for class attendance comparison
- Add notifications and services directory
- Add leave-requests module for staff and student leave request management
- Add invitation-codes module for class invitation code generation and redemption
- Add standards module for curriculum standards management
- Add add-ai-provider-ollama for Ollama AI provider setup
- Add seed-grade5-chinese for grade 5 Chinese content seeding
- Add temp-i18n-zh-fix for Chinese i18n fixes
- Add _l9_deps module dependency analysis
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.
Reverts the following commits that broke composite question handling:
- 85661a5 auto-detect composite sub-questions from text patterns
- 064b3cf use slice to preserve full content when wrapping selections
- 2562de7 remove isolating to allow nested question blocks
Restores the working state from ccf6c03 where:
- wrapInQuestion fails gracefully in isolating nodes
- selected text is preserved when creating sub-questions
- composite question structure is intact
Root cause: When users paste reading comprehension content into a
composite question block without manually marking each sub-question
as a questionBlock, the sub-questions were treated as plain text and
merged into the question stem. This caused:
1. Sub-questions not showing in the preview's sub-question area
2. Text from different paragraphs being concatenated without newlines
3. "A." appearing because parseOptions misidentified list items
Fix:
1. extractText now inserts newlines between paragraphs/listItems,
preserving text structure so pattern detection can work
2. Added detectSubQuestionsFromText: for composite questions without
explicit questionBlock children, auto-detect sub-questions from
text patterns:
- Numbered lines: "1.xxx", "2.xxx", "(1)xxx", "①xxx"
- Lines with score markers: "xxx(3分)"
- If numbered sub-questions are found, check preceding lines for
un-numbered sub-questions (e.g., the first sub-question that
lacks a number but has a score marker)
3. extractMaterialText removes detected sub-question text from the
question stem, keeping only the reading material/passage
This allows users to paste reading comprehension content directly
into a composite question block and have sub-questions automatically
detected, without needing to manually mark each one.
Root cause: wrapIn (Tiptap's built-in command) can fail or lose content
when the selection spans multiple paragraphs or partial paragraphs. When
users selected a long reading passage and clicked "复合" (composite),
the content was destroyed — only fragments remained.
Fix: replace wrapIn with a manual slice-based approach:
1. Use doc.slice(from, to) to get the complete node structure of the
selection (preserves paragraphs, lists, images, etc.)
2. deleteRange to remove the original selection
3. insertContentAt to insert a new questionBlock/groupBlock/sectionBlock
containing the sliced content
This is more reliable than wrapIn because it doesn't depend on
ProseMirror's wrapping logic, which has edge cases with multi-paragraph
selections. The slice API captures the exact node structure, so no
content is lost.
Applied to all three wrapping operations:
- insertQuestion (questionBlock)
- insertGroup (groupBlock)
- insertSection (sectionBlock)
Root cause: questionBlock/groupBlock/sectionBlock had `isolating: true`,
which prevents ProseMirror operations (wrapIn, insertContentAt) from
inserting nodes inside these blocks. When users selected text inside a
composite question and clicked "填空/简答" to create a sub-question,
the new questionBlock was inserted OUTSIDE the composite block instead
of inside it, so buildQuestion couldn't detect it as a subQuestion.
Fix: remove `isolating: true` from all three block extensions. The
`defining: true` property is sufficient to maintain node boundaries
during normal editing operations.
This allows wrapInQuestion to work correctly inside composite questions,
creating properly nested sub-question blocks that are detected by
buildQuestion's recursive parsing.
When wrapIn fails inside isolating nodes (e.g. composite question block),
the previous fallback used insertContent which replaced the entire
selection with an empty questionBlock, causing other sub-questions to
disappear and content to be cleared.
New approach: when wrapIn fails, extract the selected text, delete the
selection, then insert a new node (questionBlock/groupBlock/sectionBlock)
containing the selected text as paragraphs. This preserves the content
and converts it into the desired structure.
Applied to all three wrap operations:
- insertQuestion (questionBlock)
- insertGroup (groupBlock)
- insertSection (sectionBlock)