diff --git a/review-session/review-session.js b/review-session/review-session.js
index 840df68..5f2b9c3 100644
--- a/review-session/review-session.js
+++ b/review-session/review-session.js
@@ -37,6 +37,7 @@ Page({
currentClozeResults: [],
showClozeFeedback: false,
clozeSegments: [],
+ activeClozeBlankIndex: 0,
},
submitting: false,
judged: false,
@@ -65,32 +66,63 @@ Page({
buildClozeSegments(clozeQuestion) {
if (!clozeQuestion || !Array.isArray(clozeQuestion.blanks)) return []
const prompt = clozeQuestion.prompt_text || ''
+ const blanks = clozeQuestion.blanks || []
const segments = []
- let blankIndex = 0
- if (prompt.includes('____')) {
- prompt.split('____').forEach((text, index, parts) => {
+ const useUnderscorePrompt = prompt.includes('____')
+ const workingPrompt = useUnderscorePrompt ? prompt : (clozeQuestion.original_text || prompt)
+
+ const pushWordBlank = (blankIndex, blank) => {
+ const answerText = String(blank?.answer || '')
+ const answerLength = Math.max(1, answerText.length || Number(blank?.hint?.length || 1))
+ segments.push({
+ type: 'word-blank',
+ blankIndex,
+ index: `blank-${blankIndex}`,
+ width: `${Math.max(56, Math.min(260, 26 + answerLength * 24))}rpx`,
+ minWidth: `${Math.max(56, Math.min(260, 26 + answerLength * 24))}rpx`,
+ maxLength: Math.max(1, answerLength),
+ })
+ }
+
+ if (useUnderscorePrompt) {
+ let blankIndex = 0
+ workingPrompt.split('____').forEach((text, index, parts) => {
if (text) segments.push({ type: 'text', text, index: `text-${index}` })
if (index < parts.length - 1) {
- segments.push({ type: 'blank', blankIndex, index: `blank-${blankIndex}` })
+ pushWordBlank(blankIndex, blanks[blankIndex])
blankIndex += 1
}
})
return segments
}
- const blanks = clozeQuestion.blanks || []
+ if (blanks.length === 1 && workingPrompt === (clozeQuestion.original_text || prompt)) {
+ const blank = blanks[0]
+ const answerText = String(blank?.answer || '')
+ const letterSegments = answerText.split('').map((char, idx) => ({
+ type: 'letter-cell',
+ blankIndex: 0,
+ letterIndex: idx,
+ index: `blank-0-letter-${idx}`,
+ width: '42rpx',
+ minWidth: '42rpx',
+ maxLength: 1,
+ }))
+ return letterSegments.length ? letterSegments : [{ type: 'word-blank', blankIndex: 0, index: 'blank-0', width: '80rpx', minWidth: '80rpx', maxLength: 1 }]
+ }
+
let cursor = 0
blanks.forEach((blank, idx) => {
const start = Math.max(0, Number(blank.start || 0))
const end = Math.max(start, Number(blank.end || start + 1))
if (start > cursor) {
- segments.push({ type: 'text', text: prompt.slice(cursor, start), index: `text-${idx}` })
+ segments.push({ type: 'text', text: workingPrompt.slice(cursor, start), index: `text-${idx}` })
}
- segments.push({ type: 'blank', blankIndex: idx, index: `blank-${idx}` })
+ pushWordBlank(idx, blank)
cursor = end
})
- if (cursor < prompt.length) {
- segments.push({ type: 'text', text: prompt.slice(cursor), index: `text-tail` })
+ if (cursor < workingPrompt.length) {
+ segments.push({ type: 'text', text: workingPrompt.slice(cursor), index: `text-tail` })
}
return segments
},
@@ -159,6 +191,7 @@ Page({
meaning: isCloze ? (current?.answer_hint || '') : '',
answer: session.currentAnswer || '',
clozeAnswers: isCloze ? (Array.isArray(session.currentAnswers) ? session.currentAnswers : Array.from({ length: currentClozeQuestion?.blanks?.length || 0 }, () => '')) : [],
+ activeClozeBlankIndex: 0,
feedback: '',
revealAnswer: '',
feedbackType: '',
@@ -179,20 +212,105 @@ Page({
app.globalData.reviewSession.currentAnswer = answer
this.setData({ answer, showFeedback: false, revealAnswer: '', feedback: '', feedbackType: '', judged: false, showClozeFeedback: false })
},
- onClozeAnswerInput(e) {
- const { index } = e.currentTarget.dataset
- const value = e.detail.value
+ onClozeLetterInput(e) {
+ const { index, letterIndex } = e.currentTarget.dataset
+ const rawValue = e.detail.value || ''
+ const cleaned = rawValue.slice(-1)
const answers = [...(app.globalData.reviewSession.currentAnswers || [])]
- answers[index] = value
+ const current = answers[index] || ''
+ const chars = current.split('')
+ chars[letterIndex] = cleaned
+ answers[index] = chars.join('').slice(0, Math.max(chars.length, letterIndex + 1))
app.globalData.reviewSession.currentAnswers = answers
this.setData({
- [`clozeAnswers[${index}]`]: value,
+ [`clozeAnswers[${index}]`]: answers[index],
showFeedback: false,
revealAnswer: '',
feedback: '',
feedbackType: '',
judged: false,
showClozeFeedback: false,
+ }, () => {
+ if (cleaned) this.focusNextLetterCell(Number(index), Number(letterIndex) + 1)
+ })
+ },
+ focusNextLetterCell(blankIndex, letterIndex) {
+ const selector = `#cloze-input-${blankIndex}-${letterIndex}`
+ wx.nextTick(() => {
+ const query = wx.createSelectorQuery().in(this)
+ query.select(selector).fields({ node: true }).exec((res) => {
+ if (!res || !res[0]) return
+ const node = res[0].node
+ if (node && typeof node.focus === 'function') node.focus()
+ })
+ })
+ },
+ handleLetterKeydown(e) {
+ const key = e?.detail?.key || e?.detail?.value || ''
+ const { index, letterIndex } = e.currentTarget.dataset
+ if (key === 'Backspace' || key === 'Delete') {
+ const answers = [...(app.globalData.reviewSession.currentAnswers || [])]
+ const current = answers[index] || ''
+ const chars = current.split('')
+ const pos = Number(letterIndex)
+ if (chars[pos]) {
+ chars[pos] = ''
+ answers[index] = chars.join('')
+ app.globalData.reviewSession.currentAnswers = answers
+ this.setData({ [`clozeAnswers[${index}]`]: answers[index] })
+ return
+ }
+ const prevIndex = Number(letterIndex) - 1
+ if (prevIndex >= 0) this.focusNextLetterCell(Number(index), prevIndex)
+ return
+ }
+ if (key === 'ArrowLeft') {
+ const prevIndex = Number(letterIndex) - 1
+ if (prevIndex >= 0) this.focusNextLetterCell(Number(index), prevIndex)
+ return
+ }
+ if (key === 'ArrowRight' || key === ' ' || key === 'Space') {
+ this.focusNextLetterCell(Number(index), Number(letterIndex) + 1)
+ }
+ },
+ onClozeAnswerInput(e) {
+ const { index } = e.currentTarget.dataset
+ const rawValue = e.detail.value || ''
+ const clozeQuestion = this.data.currentClozeQuestion
+ const blank = clozeQuestion?.blanks?.[index]
+ const maxLength = Math.max(1, Number(blank?.answer?.length || 1))
+ const normalizedValue = rawValue.slice(-maxLength).slice(0, maxLength)
+ const answers = [...(app.globalData.reviewSession.currentAnswers || [])]
+ answers[index] = normalizedValue
+ app.globalData.reviewSession.currentAnswers = answers
+ const nextIndex = Number(index) + (normalizedValue.length >= maxLength ? 1 : 0)
+ const prevIndex = Number(index) - 1
+ this.setData({
+ [`clozeAnswers[${index}]`]: normalizedValue,
+ activeClozeBlankIndex: nextIndex,
+ showFeedback: false,
+ revealAnswer: '',
+ feedback: '',
+ feedbackType: '',
+ judged: false,
+ showClozeFeedback: false,
+ }, () => {
+ if (!normalizedValue && this.data.isCloze && rawValue === '') {
+ this.focusNextClozeInput(Math.max(0, prevIndex))
+ return
+ }
+ if (normalizedValue.length >= maxLength && this.data.isCloze) this.focusNextClozeInput(nextIndex)
+ })
+ },
+ focusNextClozeInput(nextIndex) {
+ const selector = `#cloze-input-${nextIndex}`
+ wx.nextTick(() => {
+ const query = wx.createSelectorQuery().in(this)
+ query.select(selector).fields({ node: true, size: true }).exec((res) => {
+ if (!res || !res[0]) return
+ const node = res[0].node
+ if (node && typeof node.focus === 'function') node.focus()
+ })
})
},
submitAnswer(showExplanation = false, advanceAfterSubmit = false) {
diff --git a/review-session/review-session.wxml b/review-session/review-session.wxml
index 12e9e5e..6988575 100644
--- a/review-session/review-session.wxml
+++ b/review-session/review-session.wxml
@@ -26,7 +26,13 @@
{{segment.text}}
-
+
+
+
+
+
+
+
{{word}}
diff --git a/review-session/review-session.wxss b/review-session/review-session.wxss
index 74b4cd8..aba7fa0 100644
--- a/review-session/review-session.wxss
+++ b/review-session/review-session.wxss
@@ -1 +1 @@
-page{background:#0c0e12;color:#f6f6fc}.page{min-height:100vh;background:#0c0e12}.topbar{display:flex;justify-content:space-between;align-items:center;padding:24rpx 32rpx;background:rgba(12,14,18,.8);border-bottom:1rpx solid rgba(255,255,255,.08);backdrop-filter:blur(24px);box-shadow:0 1rpx 20rpx rgba(0,0,0,.08)}.topbar-left{display:flex;align-items:center;gap:18rpx}.avatar-shell{width:80rpx;height:80rpx;border-radius:9999rpx;overflow:hidden;border:2rpx solid rgba(193,156,255,.2);flex-shrink:0}.avatar-img{width:100%;height:100%}.brand-text{font-size:38rpx;font-weight:800;color:#c19cff}.topbar-icon{width:72rpx;height:72rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);display:flex;align-items:center;justify-content:center}.content{height:calc(100vh - 120rpx);padding:32rpx;box-sizing:border-box}.progress-box{margin-bottom:28rpx}.progress-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:16rpx}.progress-label{font-size:24rpx;color:#aaabb0}.progress-count{font-size:28rpx;font-weight:700;color:#00e3fd}.progress-total{color:#aaabb0;font-size:22rpx;font-weight:400}.progress-bar{height:12rpx;background:#23262c;border-radius:9999rpx;overflow:hidden}.ghost-border{border:1rpx solid rgba(255,255,255,.1)}.progress-fill{height:100%;background:linear-gradient(90deg,#c19cff,#00e3fd)}.glass-card{background:rgba(29,32,37,.4);backdrop-filter:blur(24rpx)}.question-card{position:relative;overflow:hidden;border-radius:32rpx;padding:40rpx 32rpx;text-align:center;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);border:1rpx solid rgba(255,255,255,.1)}.ambient-glow{position:absolute;inset:0;background:linear-gradient(to bottom,rgba(193,156,255,.05),transparent);pointer-events:none}.mode-tag-wrap{display:flex;justify-content:center;margin-bottom:20rpx}.mode-tag{display:inline-flex;align-items:center;gap:10rpx;padding:10rpx 22rpx;border-radius:9999rpx;background:rgba(193,156,255,.12);border:1rpx solid rgba(193,156,255,.18);color:#d8c7ff;font-size:22rpx;letter-spacing:2rpx}.meaning-wrap{margin-bottom:28rpx}.meaning{display:block;color:#aaabb0;font-size:26rpx;letter-spacing:2rpx}.cloze-wrap,.word-wrap{margin:14rpx 0 10rpx}.cloze,.word{display:block;font-size:58rpx;font-weight:800;letter-spacing:2rpx;line-height:1.25}.cloze{color:#f6f6fc}.word{color:#f6f6fc}.entry-type-hint{margin-top:14rpx;color:#8f9198;font-size:22rpx;line-height:1.6}.inline-cloze{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:12rpx;font-size:44rpx;font-weight:800;line-height:1.5;color:#f6f6fc}.cloze-segment-text{display:inline;white-space:pre-wrap}.inline-cloze-answer{display:inline-flex;width:180rpx;min-height:64rpx;padding:8rpx 14rpx;border-radius:16rpx;background:rgba(12,14,18,.45);border:1rpx solid rgba(193,156,255,.28);font-size:28rpx}.cloze-input-list{margin-top:28rpx;display:flex;flex-direction:column;gap:20rpx}.cloze-input-item{padding:20rpx 18rpx;border-radius:24rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.08);text-align:left}.cloze-input-label{display:block;margin-bottom:12rpx;color:#d8c7ff;font-size:22rpx;font-weight:700;letter-spacing:1rpx}.cloze-hint{margin-bottom:12rpx;color:#8f9198;font-size:20rpx;line-height:1.5}.input-shell{margin:26rpx auto 0;max-width:520rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.08);border-radius:9999rpx}.answer{padding:22rpx 28rpx;text-align:center;color:#f6f6fc;font-size:32rpx;caret-color:#c19cff}.cloze-answer{max-width:100%;background:rgba(12,14,18,.35);border-radius:18rpx;border:1rpx solid rgba(255,255,255,.08);text-align:left;padding:22rpx 24rpx;margin:0 4rpx}.answer::placeholder{color:rgba(170,171,176,.72)}.answer::-webkit-input-placeholder{color:rgba(170,171,176,.72)}.answer::-moz-placeholder{color:rgba(170,171,176,.72)}.answer:-ms-input-placeholder{color:rgba(170,171,176,.72)}.feedback-box{margin-top:22rpx;padding:20rpx 24rpx;border-radius:24rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06)}.feedback{display:block;color:#00e3fd}.reveal-answer{display:block;margin-top:10rpx;color:#f6f6fc;font-weight:700}.cloze-feedback-list{margin-top:18rpx;display:flex;flex-direction:column;gap:14rpx}.cloze-feedback-item{padding:18rpx 16rpx;border-radius:18rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06)}.cloze-feedback-title{display:block;margin-bottom:8rpx;font-size:22rpx;font-weight:700;color:#c19cff}.cloze-feedback-text{display:block;font-size:22rpx;line-height:1.55;color:#e8e9ee}.actions-cluster{display:flex;flex-direction:row;gap:18rpx;justify-content:center;align-items:center;margin-top:24rpx}.secondary-btn,.primary-btn{flex:1;border-radius:9999rpx;padding:24rpx 0;font-weight:700}.secondary-btn{background:#23262c;color:#f6f6fc}.primary-btn{background:linear-gradient(90deg,#c19cff,#9146ff);color:#fff}.master-action{margin-top:34rpx;text-align:center}.master-text{color:#aaabb0;font-size:22rpx;border-bottom:1rpx solid transparent}.master-text.active{color:#00e5fd;border-color:#00e5fd}
+page{background:#0c0e12;color:#f6f6fc}.page{min-height:100vh;background:#0c0e12}.topbar{display:flex;justify-content:space-between;align-items:center;padding:24rpx 32rpx;background:rgba(12,14,18,.8);border-bottom:1rpx solid rgba(255,255,255,.08);backdrop-filter:blur(24px);box-shadow:0 1rpx 20rpx rgba(0,0,0,.08)}.topbar-left{display:flex;align-items:center;gap:18rpx}.avatar-shell{width:80rpx;height:80rpx;border-radius:9999rpx;overflow:hidden;border:2rpx solid rgba(193,156,255,.2);flex-shrink:0}.avatar-img{width:100%;height:100%}.brand-text{font-size:38rpx;font-weight:800;color:#c19cff}.topbar-icon{width:72rpx;height:72rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);display:flex;align-items:center;justify-content:center}.content{height:calc(100vh - 120rpx);padding:32rpx;box-sizing:border-box}.progress-box{margin-bottom:28rpx}.progress-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:16rpx}.progress-label{font-size:24rpx;color:#aaabb0}.progress-count{font-size:28rpx;font-weight:700;color:#00e3fd}.progress-total{color:#aaabb0;font-size:22rpx;font-weight:400}.progress-bar{height:12rpx;background:#23262c;border-radius:9999rpx;overflow:hidden}.ghost-border{border:1rpx solid rgba(255,255,255,.1)}.progress-fill{height:100%;background:linear-gradient(90deg,#c19cff,#00e3fd)}.glass-card{background:rgba(29,32,37,.4);backdrop-filter:blur(24rpx)}.question-card{position:relative;overflow:hidden;border-radius:32rpx;padding:40rpx 32rpx;text-align:center;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);border:1rpx solid rgba(255,255,255,.1)}.ambient-glow{position:absolute;inset:0;background:linear-gradient(to bottom,rgba(193,156,255,.05),transparent);pointer-events:none}.mode-tag-wrap{display:flex;justify-content:center;margin-bottom:20rpx}.mode-tag{display:inline-flex;align-items:center;gap:10rpx;padding:10rpx 22rpx;border-radius:9999rpx;background:rgba(193,156,255,.12);border:1rpx solid rgba(193,156,255,.18);color:#d8c7ff;font-size:22rpx;letter-spacing:2rpx}.meaning-wrap{margin-bottom:28rpx}.meaning{display:block;color:#aaabb0;font-size:26rpx;letter-spacing:2rpx}.cloze-wrap,.word-wrap{margin:14rpx 0 10rpx}.cloze,.word{display:block;font-size:58rpx;font-weight:800;letter-spacing:2rpx;line-height:1.25}.cloze{color:#f6f6fc}.word{color:#f6f6fc}.entry-type-hint{margin-top:14rpx;color:#8f9198;font-size:22rpx;line-height:1.6}.inline-cloze{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:center;gap:10rpx;font-size:68rpx;font-weight:900;line-height:1.2;color:#f6f6fc;letter-spacing:1rpx}.cloze-segment-text{display:inline;white-space:pre-wrap;line-height:1.2}.cloze-blank-wrap{display:inline-flex;align-items:flex-end;justify-content:center;vertical-align:baseline;position:relative;top:2rpx}.cloze-letter-cell{display:inline-flex;flex-direction:column;align-items:center;justify-content:flex-end;vertical-align:baseline}.inline-cloze-answer{display:block;width:100%;min-width:100%;padding:0 0 4rpx;background:transparent;border:none;border-bottom:5rpx solid rgba(193,156,255,.98);border-radius:0;font-size:68rpx;font-weight:900;color:#f6f6fc;text-align:center;line-height:1.05;caret-color:#c19cff;box-sizing:border-box}.letter-cell-input{font-size:68rpx}.cloze-letter-underline{width:100%;height:0}.inline-cloze-answer::placeholder{color:transparent}.cloze-input-list{margin-top:28rpx;display:flex;flex-direction:column;gap:20rpx}.cloze-input-item{padding:20rpx 18rpx;border-radius:24rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.08);text-align:left}.cloze-input-label{display:block;margin-bottom:12rpx;color:#d8c7ff;font-size:22rpx;font-weight:700;letter-spacing:1rpx}.cloze-hint{margin-bottom:12rpx;color:#8f9198;font-size:20rpx;line-height:1.5}.input-shell{margin:26rpx auto 0;max-width:520rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.08);border-radius:9999rpx}.answer{padding:22rpx 28rpx;text-align:center;color:#f6f6fc;font-size:32rpx;caret-color:#c19cff}.cloze-answer{max-width:100%;background:rgba(12,14,18,.35);border-radius:18rpx;border:1rpx solid rgba(255,255,255,.08);text-align:left;padding:22rpx 24rpx;margin:0 4rpx}.answer::placeholder{color:rgba(170,171,176,.72)}.answer::-webkit-input-placeholder{color:rgba(170,171,176,.72)}.answer::-moz-placeholder{color:rgba(170,171,176,.72)}.answer:-ms-input-placeholder{color:rgba(170,171,176,.72)}.feedback-box{margin-top:22rpx;padding:20rpx 24rpx;border-radius:24rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06)}.feedback{display:block;color:#00e3fd}.reveal-answer{display:block;margin-top:10rpx;color:#f6f6fc;font-weight:700}.cloze-feedback-list{margin-top:18rpx;display:flex;flex-direction:column;gap:14rpx}.cloze-feedback-item{padding:18rpx 16rpx;border-radius:18rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06)}.cloze-feedback-title{display:block;margin-bottom:8rpx;font-size:22rpx;font-weight:700;color:#c19cff}.cloze-feedback-text{display:block;font-size:22rpx;line-height:1.55;color:#e8e9ee}.actions-cluster{display:flex;flex-direction:row;gap:18rpx;justify-content:center;align-items:center;margin-top:24rpx}.secondary-btn,.primary-btn{flex:1;border-radius:9999rpx;padding:24rpx 0;font-weight:700}.secondary-btn{background:#23262c;color:#f6f6fc}.primary-btn{background:linear-gradient(90deg,#c19cff,#9146ff);color:#fff}.master-action{margin-top:34rpx;text-align:center}.master-text{color:#aaabb0;font-size:22rpx;border-bottom:1rpx solid transparent}.master-text.active{color:#00e5fd;border-color:#00e5fd}